flutter 动画

104 阅读2分钟

animation

  1. Animation: 抽象类 监听动画值的改变 监听动画状态的改变 value status

  2. AnimationController 继承自Animation vsync:同步构造信号, 需要是statefulWidget 混入 SingleTickerProviderStateMixin forward(): reverse():

  3. CurvedAnimation 设置动画执行的速率

  4. Tween: 设置动画执行的value的范围

1. 直接使用 setState 来更新动画

// 动画执行需要调用setState,即需要是StatefulWidget
class MyAnimations extends StatefulWidget {
  
  MyAnimations({super.key});

  @override
  State<MyAnimations> createState() => _MyAnimationsState();
}
// state 需要混入 SingleTickerProviderStateMixin
class _MyAnimationsState extends State<MyAnimations> with SingleTickerProviderStateMixin{
  late AnimationController _controller; // 动画控制器
  late Animation _animation; // 动画执行函数  
  late Animation _animation1;  // 动画执行的数值变化

  @override
  void initState() {
    super.initState();
    // 控制器的创建vsync:固定传this,duration 动画执行时长。
    _controller = AnimationController(vsync: this, duration: Duration(seconds: 1));
    // 动画执行时间函数.parent:传控制器。curve: 时间函数
    _animation = CurvedAnimation(parent: _controller, curve: Curves.linear );
    // Tween动画执行时数值的变化,
    _animation1 = Tween(begin: 50.0,end: 150.0).animate(_controller);
    
    // 动画监听,执行时是使用setState 更新builder
    _controller.addListener(() {
      setState(() {
        
      });
    });
    // 监听动画执行的状态。
    _controller.addStatusListener((AnimationStatus status) {
      if(status == AnimationStatus.completed){
        _controller.reverse();
      }else if(status == AnimationStatus.dismissed){
        _controller.forward();
      }
    });
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('animations'),),
      // 需要执行动画的widget:icon的size,变大变小
      body: Center(child: Icon(Icons.favorite, color: Colors.red, size: _animation1.value,),),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // 开始执行动画
          _controller.forward();
        },
        child: Icon(Icons.play_lesson),
      ),
    );
  }
}

2. AnimatedBuilder实现动画

不需要去写监听更新widget。这样也就不会一直调用builder函数。减少开销 AnimatedBuilder的参数:animation:对应的动画控制器 builder: (context,child){} 函数, 函数返回需要使用动画的widget。 child: 执行动画的widget

class MyAnimations extends StatefulWidget {
  
  MyAnimations({super.key});

  @override
  State<MyAnimations> createState() => _MyAnimationsState();
}

class _MyAnimationsState extends State<MyAnimations> with SingleTickerProviderStateMixin{
  late AnimationController _controller;
  late Animation _animation;  

  late Animation _size;  
  late Animation _color;
  late Animation _opvalue;
  late Animation _rovalue;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this, duration: Duration(seconds: 1));
    _animation = CurvedAnimation(parent: _controller, curve: Curves.linear );

    _size = Tween(begin: 10.0,end: 100.0).animate(_controller);
    _rovalue = Tween(begin: 0.0,end: 2*pi).animate(_controller);
    _color = ColorTween(begin: Colors.red,end: Colors.purple).animate(_controller);
    _opvalue = Tween(begin: 0.0,end: 1.0).animate(_controller);
    // _controller.addListener(() {
    //   setState(() {
        
    //   });
    // });

    _controller.addStatusListener((AnimationStatus status) {
      if(status == AnimationStatus.completed){
        _controller.reverse();
      }else if(status == AnimationStatus.dismissed){
        _controller.forward();
      }
    });
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('animations'),),
      body: Center(
        child: AnimatedBuilder(
          animation: _controller, 
          builder: (context,child){
            return Transform(
              transform: Matrix4.rotationZ(_rovalue.value),
              alignment: Alignment.center,
              child: Opacity(
                opacity: _opvalue.value ,
                child: Container(
                  width: _size.value,
                  height: _size.value,
                  color: _color.value,
                ),
              ),
            ); 
          }
        )
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          _controller.forward();
        },
        child: Icon(Icons.play_lesson),
      ),
    );
  }
}

hero 动画 (飞入飞出的动画)

Hero参数: 必传参数 :tag 是区别执行hero动画的标志。 child: widget。


class MyHeroPage extends StatefulWidget {
  const MyHeroPage({super.key});

  @override
  State<MyHeroPage> createState() => _MyHeroPageState();
}

class _MyHeroPageState extends State<MyHeroPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('hero page'),
        ),
        body: GridView.count(
          crossAxisCount: 2,
          childAspectRatio: 16 / 9,
          mainAxisSpacing: 10,
          crossAxisSpacing: 10,
          children: List.generate(20, (index) {
            final images = "https://picsum.photos/200/300?random=$index";
            return Hero(
                // tag 使用图片的地址
                tag: images,
                child: GestureDetector(
                  onTap: () {
                    Navigator.push(
                        context,
                        // 使用自定义的页面跳转动动画:transitionDuration 动画执行时间,pageBuilder函数携带context 和 两个动画参数,FadeTransition 渐入动画
                        PageRouteBuilder(
                            // transitionDuration: Duration(seconds: 1),
                            pageBuilder:
                                (context, animation, secondaryAnimation) {
                              return FadeTransition(
                                opacity: animation,
                                child: MyImgagView(
                                  imageurl: images,
                                ),
                              );
                            }));
                  },
                  child: Image.network(
                    "https://picsum.photos/200/300?random=$index",
                    fit: BoxFit.cover,
                  ),
                ));
          }),
        ));
  }
}

class MyImgagView extends StatelessWidget {
  final imageurl;
  const MyImgagView({super.key, this.imageurl});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Container(
            width: double.infinity,
            height: double.infinity,
            color: Colors.black,
            child: Hero(
            // 使用传过来的图片地址作为tag, hero 动画需要两个hero。 然后根据tag 作为标识
                tag: imageurl,
                child: Center(
                  child: GestureDetector(
                    onTap: () {
                      Navigator.pop(context);
                    },
                    child: Image.network(
                      imageurl,
                      fit: BoxFit.cover,
                    ),
                  ),
                ))));
  }
}

结果:点击图片,图片会在当前位置飞出放大。显示在黑色背景下

d5c44ea7ce9e91f71d33f656e4525a7.png

e6623747828712217ab2707694b335f.png