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

相关推荐
火柴就是我14 小时前
flutter 之真手势冲突处理
android·flutter
Speed12314 小时前
`mockito` 的核心“打桩”规则
flutter·dart
法的空间14 小时前
Flutter JsonToDart 支持 JsonSchema
android·flutter·ios
恋猫de小郭14 小时前
Android 将强制应用使用主题图标,你怎么看?
android·前端·flutter
玲珑Felone15 小时前
从flutter源码看其渲染机制
android·flutter
ALLIN1 天前
Flutter 三种方式实现页面切换后保持原页面状态
flutter
Dabei1 天前
Flutter 国际化
flutter
Dabei1 天前
Flutter MQTT 通信文档
flutter
Dabei2 天前
Flutter 中实现 TCP 通信
flutter
孤鸿玉2 天前
ios flutter_echarts 不在当前屏幕 白屏修复
flutter