Flutter 动画 缩放图片、动画简化

图片放大缩小

scss 复制代码
class ScaleAnimationRoute extends StatefulWidget {
  const ScaleAnimationRoute({Key? key}) : super(key: key);

  @override
  _ScaleAnimationRouteState createState() => _ScaleAnimationRouteState();
}

//需要继承TickerProvider,如果有多个AnimationController,则应该使用TickerProviderStateMixin。
class _ScaleAnimationRouteState extends State<ScaleAnimationRoute>
    with SingleTickerProviderStateMixin {
  late Animation<double> animation;
  late AnimationController controller;

  @override
  initState() {
    super.initState();
    controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    );

    //匀速
    //图片宽高从0变到300
    animation = Tween(begin: 0.0, end: 300.0).animate(controller)
      ..addListener(() {
        setState(() => {});
      });

    controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
          child: GestureDetector(
        onTap: () {
          //启动动画(正向执行)
          controller.reset();
          controller.forward();
        },
        child: Image.asset(
          "assets/imgs/avatar.png",
          width: animation.value,
          height: animation.value,
        ),
      )),
    );
  }

  @override
  dispose() {
    //路由销毁时需要释放动画资源
    controller.dispose();
    super.dispose();
  }
}

addListener()函数调用了setState(),所以每次动画生成一个新的数字时,当前帧被标记为脏(dirty),这会导致widget的build()方法再次被调用,而在build()中,改变Image的宽高,因为它的高度和宽度现在使用的是animation.value ,所以就会逐渐放大。值得注意的是动画完成时要释放控制器(调用dispose()方法)以防止内存泄漏。

实现点击下陷的效果

scss 复制代码
class ScaleAnimationRoute extends StatefulWidget {
  const ScaleAnimationRoute({Key? key}) : super(key: key);

  @override
  _ScaleAnimationRouteState createState() => _ScaleAnimationRouteState();
}

//需要继承TickerProvider,如果有多个AnimationController,则应该使用TickerProviderStateMixin。
class _ScaleAnimationRouteState extends State<ScaleAnimationRoute>
    with SingleTickerProviderStateMixin {
  late Animation<double> animation;
  late AnimationController controller;

  @override
  initState() {
    super.initState();
    controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    );

    //匀速
    //图片宽高从0变到300
    animation = Tween(begin: 0.0, end: 300.0).animate(controller)
      ..addListener(() {
        setState(() => {});
      });

    controller.forward(from: 0.9);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
          child: Container(
              width: 330,
              color: Colors.orange,
              height: 180,
              alignment: Alignment.center,
              child: GestureDetector(
                onTap: () {
                  //启动动画(正向执行)
                  controller.reset();
                  controller.forward(from: 0.9);
                },
                child: Container(
                  color: Colors.blue,
                  width: animation.value,
                  height: animation.value * 0.5,
                ),
              ))),
    );
  }

  @override
  dispose() {
    //路由销毁时需要释放动画资源
    controller.dispose();
    super.dispose();
  }
}

AnimatedWidget简化

通过addListener()setState() 来更新UI这一步其实是通用的,如果每个动画中都加这么一句是比较繁琐的。AnimatedWidget类封装了调用setState()的细节,并允许我们将 widget 分离出来,重构后的代码如下:

scala 复制代码
class ScaleAnimationRoute1 extends StatefulWidget {
  const ScaleAnimationRoute1({Key? key}) : super(key: key);

  @override
  _ScaleAnimationRouteState createState() => _ScaleAnimationRouteState();
}

class _ScaleAnimationRouteState extends State<ScaleAnimationRoute1>
    with SingleTickerProviderStateMixin {
  late Animation<double> animation;
  late AnimationController controller;

  @override
  initState() {
    super.initState();
    controller =
        AnimationController(duration: const Duration(seconds: 2), vsync: this);
    //图片宽高从0变到300
    animation = Tween(begin: 0.0, end: 300.0).animate(controller);
    //启动动画
    controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: GestureDetector(
      onTap: () {
        controller.reset();
        controller.forward();
      },
      child: AnimatedImage(
        animation: animation,
      ),
    ));
  }

  @override
  dispose() {
    //路由销毁时需要释放动画资源
    controller.dispose();
    super.dispose();
  }
}

class AnimatedImage extends AnimatedWidget {
  const AnimatedImage({
    Key? key,
    required Animation<double> animation,
  }) : super(key: key, listenable: animation);

  @override
  Widget build(BuildContext context) {
    final animation = listenable as Animation<double>;
    return Center(
      child: Image.asset(
        "assets/imgs/avatar.png",
        width: animation.value,
        height: animation.value,
      ),
    );
  }
}

使用AnimatedBuilder

scala 复制代码
class ScaleAnimationRoute1 extends StatefulWidget {
  const ScaleAnimationRoute1({Key? key}) : super(key: key);

  @override
  _ScaleAnimationRouteState createState() => _ScaleAnimationRouteState();
}

class _ScaleAnimationRouteState extends State<ScaleAnimationRoute1>
    with SingleTickerProviderStateMixin {
  late Animation<double> animation;
  late AnimationController controller;

  @override
  initState() {
    super.initState();
    controller =
        AnimationController(duration: const Duration(seconds: 2), vsync: this);
    //图片宽高从0变到300
    animation = Tween(begin: 0.0, end: 300.0).animate(controller);
    //启动动画
    controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: GestureDetector(
            onTap: () {
              controller.reset();
              controller.forward();
            },
            child: GrowTransition(
              animation: animation,
              child: Image.asset("assets/imgs/avatar.png"),
            )));
  }

  @override
  dispose() {
    //路由销毁时需要释放动画资源
    controller.dispose();
    super.dispose();
  }
}

class GrowTransition extends StatelessWidget {
  const GrowTransition({
    Key? key,
    required this.animation,
    this.child,
  }) : super(key: key);

  final Widget? child;
  final Animation<double> animation;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: AnimatedBuilder(
        animation: animation,
        builder: (BuildContext context, child) {
          return SizedBox(
            height: animation.value,
            width: animation.value,
            child: child,
          );
        },
        child: child,
      ),
    );
  }
}

用AnimatedWidget 可以从动画中分离出 widget,而动画的渲染过程(即设置宽高)仍然在AnimatedWidget 中,假设如果我们再添加一个 widget 透明度变化的动画,那么我们需要再实现一个AnimatedWidget,这样不是很优雅,如果我们能把渲染过程也抽象出来,那就会好很多,而AnimatedBuilder正是将渲染逻辑分离出来。

上面的代码中有一个迷惑的问题是,child看起来像被指定了两次。但实际发生的事情是:将外部引用child传递给AnimatedBuilder后,AnimatedBuilder再将其传递给匿名构造器, 然后将该对象用作其子对象。最终的结果是AnimatedBuilder返回的对象插入到 widget 树中。

其实它会带来三个好处:

  1. 不用显式的去添加帧监听器,然后再调用setState() 了,这个好处和AnimatedWidget是一样的。
  2. 更好的性能:因为动画每一帧需要构建的 widget 的范围缩小了,如果没有buildersetState()将会在父组件上下文中调用,这将会导致父组件的build方法重新调用;而有了builder之后,只会导致动画widget自身的build重新调用,避免不必要的rebuild。
  3. 通过AnimatedBuilder可以封装常见的过渡效果来复用动画。通过封装一个GrowTransition来说明。

FadeTransition 实现透明度变化效果

Flutter中正是通过这种方式封装了很多动画,如:FadeTransition、ScaleTransition、SizeTransition等,很多时候都可以复用这些预置的过渡类。

scala 复制代码
class FadeTranstionScreen extends StatefulWidget {
  FadeTranstionScreen({Key? key}) : super(key: key);

  @override
  _FadeTranstionScreenState createState() => _FadeTranstionScreenState();
}

class _FadeTranstionScreenState extends State<FadeTranstionScreen>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller =
        AnimationController(vsync: this, duration: const Duration(seconds: 2));
    _animation = Tween(begin: 0.0, end: 1.0).animate(_controller);
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    _controller.forward();
    return Scaffold(
        appBar: AppBar(title: const Text("FadeTranstion")),
        body: GestureDetector(
          onTap: () {
            _controller.reset();
            _controller.forward();
          },
          child: Container(
            alignment: Alignment.center,
            child: FadeTransition(
              opacity: _animation,
              child: Container(
                width: 100,
                height: 100,
                color: Colors.blue,
              ),
            ),
          ),
        ));
  }
}

ScaleTransition 实现缩小放大效果

scala 复制代码
class FadeTranstionScreen extends StatefulWidget {
  FadeTranstionScreen({Key? key}) : super(key: key);

  @override
  _FadeTranstionScreenState createState() => _FadeTranstionScreenState();
}

class _FadeTranstionScreenState extends State<FadeTranstionScreen>
    with SingleTickerProviderStateMixin {

  late final AnimationController _controller = AnimationController(
    duration: const Duration(seconds: 2),
    vsync: this,
  )..repeat(reverse: true);

  late final Animation<double> _animation = CurvedAnimation(
    parent: _controller,
    curve: Curves.fastOutSlowIn,
  );

  @override
  void dispose() {
    super.dispose();
    _controller.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
          child: GestureDetector(
        onTap: () {
          _controller.reset();
          _controller.forward();
        },
        child: ScaleTransition(
          scale: _animation,
          child: const Padding(
            padding: EdgeInsets.all(8.0),
            child: FlutterLogo(size: 150.0),
          ),
        ),
      )),
    );
  }
}

SizeTransition 实现文字横向飞入效果

scala 复制代码
class DemoSizeTransition extends StatefulWidget {
  const DemoSizeTransition({super.key});

  @override
  _DemoSizeTransitionState createState() => _DemoSizeTransitionState();
}

class _DemoSizeTransitionState extends State<DemoSizeTransition>
    with SingleTickerProviderStateMixin {
  late AnimationController _animationController;

  @override
  void initState() {
    super.initState();

    _animationController =
        AnimationController(vsync: this, duration: const Duration(seconds: 1));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("SizeTransition")),
      body: SizeTransition(
        //偏移量
        axisAlignment: 0.0,
        //动画控制
        sizeFactor: _animationController,
        axis: Axis.horizontal,
        child: buildContainer(),
      ),
      floatingActionButton: FloatingActionButton(
        child: const Text("开始"),
        onPressed: () {
          _animationController.forward(from: 0);
        },
      ),
    );
  }

  Container buildContainer() {
    return Container(
      //子Widget 对齐方式
      alignment: Alignment.center,
      width: 300,
      //高度
      height: 200,
      //背景颜色
      color: Colors.blue,
      child: const Text(
        "此处文字需要有一些动画效果",
        style: TextStyle(color: Colors.white),
      ),
    );
  }
}

案例 切换到分支flutter_animation

相关推荐
我要最优解3 小时前
关于在mac中配置Java系统环境变量
java·flutter·macos
江上清风山间明月2 天前
Flutter开发的应用页面非常多时如何高效管理路由
android·flutter·路由·页面管理·routes·ongenerateroute
Zsnoin能2 天前
flutter国际化、主题配置、视频播放器UI、扫码功能、水波纹问题
flutter
早起的年轻人2 天前
Flutter CupertinoNavigationBar iOS 风格导航栏的组件
flutter·ios
HappyAcmen2 天前
关于Flutter前端面试题及其答案解析
前端·flutter
coooliang2 天前
Flutter 中的单例模式
javascript·flutter·单例模式
coooliang2 天前
Flutter项目中设置安卓启动页
android·flutter
JIngles1232 天前
flutter将utf-8编码的字节序列转换为中英文字符串
java·javascript·flutter
B.-2 天前
在 Flutter 中实现文件读写
开发语言·学习·flutter·android studio·xcode
freflying11193 天前
使用jenkins构建Android+Flutter项目依赖自动升级带来兼容性问题及Jenkins构建速度慢问题解决
android·flutter·jenkins