Day14 - Flutter - 动画

简介: Day14 - Flutter - 动画

概述


  • 动画API认识
  • 动画案例练习
  • 其它动画补充


一、动画API认识



动画实际上是我们通过某些方式(某种对象,Animation对象)给Flutter引擎提供不同的值,而Flutter可以根据我们提供的值,给对应的小部件添加顺滑的动画效果。


  • 1.1、Animation在Flutter中,实现动画的核心类是动画,小部件可以直接将这些动画合并到自己的构建方法中来读取它们的当前值或监听其状态变化。我们一起来看一下Animation这个类,它是一个抽象类
  • addListener方法(监听动画值的概念)
  • 建立动画的状态值发生变化时,动画都会通知所有通过addListener添加的监听器。
  • 通常,一个正在监听的动画的state对象会调用自身的setState方法,将自身本身作为这些监听器的插入函数来通知小部件,系统需要根据新状态值进行重新生成。
  • addStatusListener(监听动画状态的改变)
  • 当动画的状态发生变化时,会通知所有通过addStatusListener添加的监听器。
  • 通常情况下,动画会从dismissed状态开始,表示它处于变化区间的开始点。
    举例来说,从0.0到1.0的动画在dismissed状态时的值应该是0.0。
  • 动画进行的下一状态可能是forward(例如从0.0到1.0)或者reverse(例如从1.0到0.0)。
  • 最终,如果动画到达其区间的结束点(例如1.0),则动画会变成completed状态。


abstractclass Animation<T> extends Listenable implements ValueListenable<T> {
    const Animation();
    // 添加动画监听器
    @override
    void addListener(VoidCallback listener);
    // 移除动画监听器
    @override
    void removeListener(VoidCallback listener);
    // 添加动画状态监听器
    void addStatusListener(AnimationStatusListener listener);
    // 移除动画状态监听器
    void removeStatusListener(AnimationStatusListener listener);
    // 获取动画当前状态
    AnimationStatus get status;
    // 获取动画当前的值
    @override
    T get value;
}
  • 1.2、AnimationControllerAnimation是一个抽象类,并不能直接创建对象实现动画的使用。AnimationController是Animation的一个子类,实现动画通常我们需要创建AnimationController对象。
  • AnimationController会生成一系列的值,交替情况下值是0.0到1.0区间的值;
  • 除了上面的监听器,获取动画的状态,值之外,AnimationController还提供了对动画的控制:
  • forward:向前执行动画
  • 反向:方向播放动画
  • stop:停止动画
  • AnimationController的源码:


class AnimationController extends Animation<double>  with AnimationEagerListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin {
   AnimationController({
       // 初始化值
       double value,
       // 动画执行的时间
       this.duration,
       // 反向动画执行的时间
       this.reverseDuration,
       // 最小值
       this.lowerBound = 0.0,
       // 最大值
       this.upperBound = 1.0,
       // 刷新率ticker的回调(看下面详细解析)
       @required TickerProvider vsync,
   })
}
  • AnimationController有一个必传的参数vsync,它是什么呢?
  • Flutter的渲染闭环,Flutter每次渲染一帧画面之前都需要等待一个vsync信号。
  • 这里也是为了监听vsync信号,当Flutter开发的应用程序不再接受同步信号时(比如锁屏或退到后台),那么继续执行动画会消耗性能。
  • 这个时候我们设置了Ticker,就不会再出发动画了。
  • 开发中比较常见的是将 SingleTickerProviderStateMixin 混入到State的定义中。


  • 1.3、CurvedAnimation(设置动画执行的速率-速率曲线)CurvedAnimation也是Animation的一个实现类,它的目的是为了给AnimationController增加动画曲线:CurvedAnimation可以将AnimationControllerCurve结合起来,生成一个新的Animation对象
class CurvedAnimation extends Animation<double> with AnimationWithParentMixin<double> {
    CurvedAnimation({
        // 通常传入一个AnimationController
        @requiredthis.parent,
        // Curve类型的对象
        @requiredthis.curve,
        this.reverseCurve,
    });
}
  • Curve类型的对象的有一些常量Curves(和Color类型有一些Colors是一样的),可以供我们直接使用:
  • 官方也发表了自己的定义Curse的一个示例


import'dart:math';
class ShakeCurve extends Curve {
    @override
    double transform(double t) => sin(t * pi * 2);
}
  • 1.4、Tween
    默认情况下,AnimationController动画生成的值所在区间是0.0到1.0
    如果希望使用这个以外的值,或者其他的数据类型,就需要使用Tween
    Tween的源码:源码非常简单,预设两个值即可,可以定义一个范围。


class Tween<T extends dynamic> extends Animatable<T> {
   // begin 开始值,end 结束值
   Tween({ this.begin, this.end });
}
  • Tween也有一些子类,比如ColorTween、BorderTween,可以针对动画或者边框来设置动画的值。
    Tween.animate
    要使用Tween对象,需要调用Tween的animate()方法,传入一个Animation对象。


二、动画案例练习



  • 2.1. 动画的基本使用(不可取,优缺点)我们来完成一个案例:
  • 点击案例后执行一个心跳动画,可以反复执行
  • 再次点击可以暂停和重新开始动画


image.png


import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
     return MaterialApp(
         title: 'Flutter Demo',
         theme: ThemeData(
             primarySwatch: Colors.blue, splashColor: Colors.transparent),
             home: HYHomePage(),
         );
     }
}
class HYHomePage extends StatefulWidget {
   @override
    _HYHomePageState createState() => _HYHomePageState();
}
class _HYHomePageState extends State<HYHomePage>  with SingleTickerProviderStateMixin {
    // 创建AnimationController
    AnimationController _controller;
    Animation _animation;
    Animation _sizeAnim;
    @override
    void initState() {
        super.initState();
        // 1.创建AnimationController
        _controller = AnimationController(
             vsync: this,
             duration: Duration(seconds: 2)
        );
        // 2.动画添加Curve效果
        _animation = CurvedAnimation(parent: _controller, curve: Curves.linear);
        // 3.Tween 设置值的范围
        _sizeAnim = Tween(begin: 50.0, end: 150.0).animate(_animation);
        // 4.监听动画值的改变
        _controller.addListener(() {
           setState(() {});
        });
        // 5.监听动画的状态改变
        _controller.addStatusListener((status) {
           if (status == AnimationStatus.completed) {
                _controller.reverse();
           } else if (status == AnimationStatus.dismissed) {
                _controller.forward();
           }
        });
    }
    @override
    Widget build(BuildContext context) {
       print("执行_HYHomePageState的build方法");
       return Scaffold(
          appBar: AppBar(
             title: Text("首页"),
          ),
          body: return Center(
             child: Icon(Icons.favorite, color: Colors.red, size: _sizeAnim.value,),
          );
          floatingActionButton: FloatingActionButton(
            child: Icon(Icons.play_arrow),
            onPressed: () {
                if (_controller.isAnimating) {
                    _controller.stop();
                    print(_controller.status);
                } else if (_controller.status == AnimationStatus.forward) {
                    _controller.forward();
                } else if (_controller.status == AnimationStatus.reverse) {
                    _controller.reverse();
                } else {
                    _controller.forward();
                }
            },
          ),
       );
    }
    @override
    void dispose() {
       _controller.dispose();
       super.dispose();
    }
}
  • 2.2、AnimatedWidget(不可取,优缺点)在上面的代码中,我们必须监听动画值的改变,并且改变后需要调用setState(也就是上面的第4步),这会带来两个问题:
  • 1.执行动画必须包含这部分代码,代码比较冗余
  • 2.调用setState意味着整个State类中的build方法就会被重新build
  • 如何可以优化上面的操作:创建一个Widget继承自AnimatedWidget:


class IconAnimation extends AnimatedWidget {
   IconAnimation(Animation animation): super(listenable: animation);
   @override
   Widget build(BuildContext context) {
       Animation animation = listenable;
       return Icon(Icons.favorite, color: Colors.red, size: animation.value,);
   }
}
  • 那么2.1中的 的 第四步就可以去掉了,在Icon调用的地方直接:IconAnimation(_animation)
  • 缺点是:1、每次都需要创建一个类,类里面的build也会打印;2、如果创建的Widget有子类,那么子类依然会重复的build
  • 2.3、AnimatedBuilder(优解)
    AnimatedBuilder 可以解决上面 AnimatedWidget 产生的两个问题,代码如下


class _HYHomePageState extends State<HYHomePage> with SingleTickerProviderStateMixin {
   // 创建AnimationController
   AnimationController _controller;
   Animation _animation;
   @override
   void initState() {
       super.initState();
       // 1.创建AnimationController
       _controller = AnimationController(
           vsync: this,
           duration: Duration(seconds: 2)
       );
       // 2.设置Curve的值
       _animation = CurvedAnimation(parent: _controller, curve: Curves.linear);
       // 3.Tween
       _animation = Tween(begin: 50.0, end: 150.0).animate(_animation);
       // 监听动画的状态改变
       _controller.addStatusListener((status) {
          if (status == AnimationStatus.completed) {
                _controller.reverse();
          } else if (status == AnimationStatus.dismissed) {
                _controller.forward();
          }
       });
    }
    @override
     Widget build(BuildContext context) {
        print("执行_HYHomePageState的build方法");
        return Scaffold(
            appBar: AppBar(
                title: Text("首页"),
            ),
            body: Center(
                child: AnimatedBuilder(
                   animation: _controller,
                   builder: (ctx, child) {
                       return Icon(Icons.favorite, color: Colors.red, size: _animation.value,);
                   },
                ),
            ),
            floatingActionButton: FloatingActionButton(
                child: Icon(Icons.play_arrow),
                onPressed: () {
                    if (_controller.isAnimating) {
                        _controller.stop();
                        print(_controller.status);
                    } else if (_controller.status == AnimationStatus.forward) {
                        _controller.forward();
                    } else if (_controller.status == AnimationStatus.reverse) {
                        _controller.reverse();
                    } else {
                        _controller.forward();
                    }
                },
             ),
        );
     }
     @override
     void dispose() {
        _controller.dispose();
        super.dispose();
     }
}


三、其它动画补充



  • 3.1、交织动画(多个动画同时执行)
    案例说明:点击floatingActionButton执行动画
    动画集合了透明度变化大小变化颜色变化旋转动画等;
    我们这里是通过多个Tween生成了多个Animation对象;
    代码如下


class HYHomePage extends StatefulWidget {
   @override
   _HYHomePageState createState() => _HYHomePageState();
}
class _HYHomePageState extends State<HYHomePage> with SingleTickerProviderStateMixin {
   // 创建AnimationController
   AnimationController _controller;
   Animation _animation;
   // 大小
   Animation<double> _sizeAnim;
   // 颜色
   Animation _colorAnim;
   // 透明度
   Animation<double> _opactiyAnim;
   // 角度
   Animation<double> _radiansAnim;
   @override
   void initState() {
     super.initState();
     // 1.创建AnimationController
     _controller = AnimationController(
        vsync: this,
        duration: Duration(seconds: 2)
     );
     // 2.设置Curve的值
     _animation = CurvedAnimation(parent: _controller, curve: Curves.linear);
     // 3.Tween
     _sizeAnim = Tween(begin: 10.0, end: 150.0).animate(_controller);
     _colorAnim = ColorTween(begin: Colors.brown, end: Colors.green).animate(_controller);
     _opactiyAnim = Tween(begin: 0.0, end: 1.0).animate(_controller);
     _radiansAnim = Tween(begin: 0.0, end: 2 * pi).animate(_controller);
     // 监听动画的状态改变
     _controller.addStatusListener((status) {
          if (status == AnimationStatus.completed) {
             _controller.reverse();
          } else if (status == AnimationStatus.dismissed) {
             _controller.forward();
          }
     });
    }
    @override
    Widget build(BuildContext context) {
       print("执行_HYHomePageState的build方法");
       return Scaffold(
           appBar: AppBar(
              title: Text("首页"),
           ),
           body: Center(
              child: AnimatedBuilder(
                 animation: _controller,
                 builder: (ctx, child) {
                    return Opacity(
                      opacity: _opactiyAnim.value,
                      child: Transform(
                          transform: Matrix4.rotationZ(_radiansAnim.value),
                          alignment: Alignment.center,
                          child: Container(
                              width: _sizeAnim.value,
                              height: _sizeAnim.value,
                              color: _colorAnim.value,
                          ),
                      ),
                   );
                 },
              )
           ),
           floatingActionButton: FloatingActionButton(
              child: Icon(Icons.play_arrow),
              onPressed: () {
                   if (_controller.isAnimating) {
                       _controller.stop();
                       print(_controller.status);
                   } else if (_controller.status == AnimationStatus.forward) {
                       _controller.forward();
                   } else if (_controller.status == AnimationStatus.reverse) {
                       _controller.reverse();
                   } else {
                       _controller.forward();
                   }
              },
           ),
       );
    }
    @override
    void dispose() {
       _controller.dispose();
       super.dispose();
    }
}


  • 3.2、Hero动画移动端开发会经常遇到类似这样的需求:
  • 点击一个头像,显示头像的大图,并且从原来图像的Rect到大图的Rect
  • 点击一个商品的图片,可以展示商品的大图,并且从原来图像的Rect到大图的Rect
    这种跨页面共享的动画被称之为享元动画(Shared Element Transition)
  • 在Flutter中,有一个专门的Widget可以来实现这种动画效果:Hero
    实现Hero动画,需要如下步骤:
  • 1.在第一个Page1中,定义一个起始的Hero Widget,被称之为source hero,并且绑定一个tag;
  • 2.在第二个Page2中,定义一个终点的Hero Widget,被称之为 destination hero,并且绑定相同的tag;
  • 3.可以通过Navigator来实现第一个页面Page1到第二个页面Page2的跳转过程;
  • Flutter会设置Tween来界定Hero从起点到终端的大小和位置,并且在图层上执行动画效果。
    首页Page的核心代码:


GridView(
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
    crossAxisSpacing: 6,
    mainAxisSpacing: 6,
    childAspectRatio: 16/9
  ),
  children: List.generate(20, (index) {
    String imageURL = "https://picsum.photos/200/300?random=$index";
    return GestureDetector(
      onTap: () {
        Navigator.of(context).push(PageRouteBuilder(
          pageBuilder: (ctx, animation1, animation2) {
            return FadeTransition(
              opacity: animation1,
              child: JKImageDeyail(imageURL),
            );
          },
        ));
      },
      child: Hero(tag: imageURL, child: Image.network(imageURL, fit: BoxFit.cover,)),
    );
  }),
),

提示:外层包裹了一个:手势 GestureDetector,跳转用的带动画的 PageRouteBuilder,对于跳转的页面包裹了渐变 FadeTransition

对于展示的 Image 我们包裹了一个 Hero ,对于 Hero 下个页面也要有 Hero,并且和当前的 Hero 的 tag 保持一致


  • 图片展示Page


import 'package:flutter/material.dart';
class JKImageDeyail extends StatelessWidget {
    final String _imageUrl;
    JKImageDeyail(this._imageUrl);
    @override
    Widget build(BuildContext context) {
       return Scaffold(
          backgroundColor: Colors.black,
          appBar: AppBar(
             title: Text('图片详情'),
          ),
          body: Center(
             child: GestureDetector(
                onTap: () {
                  Navigator.of(context).pop();
                },
               child: Hero(
                 tag: _imageUrl,
                 child: Image.network(
                    _imageUrl,
                    width: double.infinity,
                    fit: BoxFit.cover,
                 ),
               ),
             ),
          ),
      );
   }
}


目录
相关文章
|
15天前
|
开发工具 UED 容器
Flutter&鸿蒙next 实现长按录音按钮及动画特效
本文介绍了如何在 Flutter 中实现一个带有动画效果的长按录音按钮。通过使用 `GestureDetector` 监听长按手势,结合 `AnimatedContainer` 和 `AnimationController` 实现按钮的动画效果,以及 `flutter_sound` 插件完成录音功能。文章详细讲解了功能需求、实现思路和代码实现,帮助读者逐步掌握这一实用功能的开发方法。
92 5
|
17天前
|
前端开发 开发者
深入探索 Flutter 鸿蒙版的画笔使用与高级自定义动画
本文深入探讨了 Flutter 中的绘图功能,重点介绍了 CustomPainter 和 Canvas 的使用方法。通过示例代码,详细讲解了如何绘制自定义图形、设置 Paint 对象的属性以及实现高级自定义动画。内容涵盖基本绘图、动画基础、渐变动画和路径动画,帮助读者掌握 Flutter 绘图与动画的核心技巧。
65 1
|
23天前
动画控制器在 Flutter 中的工作原理
【10月更文挑战第18天】总的来说,动画控制器 `AnimationController` 在 Flutter 中起着关键的作用,它通过控制动画的数值、速度、节奏和状态,实现了丰富多彩的动画效果。理解它的工作原理对于我们在 Flutter 中创建各种精彩的动画是非常重要的。
|
1月前
|
UED
flutter:动画&状态管理 (十三)
本文档介绍了Flutter中`animatedList`的使用方法和状态管理的示例。`animatedList`用于创建带有动画效果的列表,示例代码展示了如何添加、删除列表项,并执行相应的动画效果。状态管理部分通过一个简单的点击切换颜色的示例,演示了如何在Flutter中管理组件的状态。
|
3月前
|
前端开发
Flutter快速实现自定义折线图,支持数据改变过渡动画
Flutter快速实现自定义折线图,支持数据改变过渡动画
93 4
Flutter快速实现自定义折线图,支持数据改变过渡动画
|
4月前
Flutter-实现头像叠加动画效果
Flutter-实现头像叠加动画效果
64 0
|
4月前
Flutter-加载中动画
Flutter-加载中动画
42 0
|
4月前
Flutter-自定义表情雨下落动画
Flutter-自定义表情雨下落动画
39 0
|
4月前
Flutter-数字切换动画
Flutter-数字切换动画
29 0
|
4月前
|
开发者
Flutter 动画学习
Flutter 动画学习