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,
                 ),
               ),
             ),
          ),
      );
   }
}


目录
相关文章
|
18天前
|
开发框架 API 开发者
Flutter的动画:实现方式与动画库的技术探索
【4月更文挑战第26天】探索Flutter动画机制与库:基础动画、自定义动画、物理动画及Lottie、AnimatedWidgets、EasyAnimations等库的应用,助开发者实现丰富动画效果,提升用户体验。同时,了解性能优化技巧,如避免重绘、利用离屏渲染和GPU加速,确保动画流畅。 Flutter为移动应用开发带来强大动画支持。
|
4月前
|
前端开发
Flutter笔记:光影动画按钮、滚动图标卡片组等
Flutter笔记:光影动画按钮、滚动图标卡片组等
40 0
|
5月前
|
UED
Flutter之自定义路由切换动画
Flutter之自定义路由切换动画 在Flutter中,我们可以通过Navigator来实现路由管理,包括路由的跳转和返回等。默认情况下,Flutter提供了一些简单的路由切换动画,但是有时候我们需要自定义一些特殊的动画效果来提高用户体验。本文将介绍如何在Flutter中实现自定义的路由切换动画。
|
5月前
|
开发框架
Flutter 工程化框架选择——搞定 Flutter 动画
Flutter 工程化框架选择——搞定 Flutter 动画 Flutter 是 Google 推出的跨平台移动应用开发框架,它具有快速开发、高性能、美观等优点。但是,在实际开发中,为了更好地维护和扩展代码,我们需要选择一个合适的工程化框架来协助我们进行开发。本文将介绍几种常用的 Flutter 工程化框架,并重点介绍一个搞定 Flutter 动画的方法。
|
12月前
|
设计模式 算法 vr&ar
Flutter 基础 | 动画框架分析及其中的设计模式
Flutter 基础 | 动画框架分析及其中的设计模式
122 0
flutter系列之:做一个下载按钮的动画
我们在app的开发过程中经常会用到一些表示进度类的动画效果,比如一个下载按钮,我们希望按钮能够动态显示下载的进度,这样可以给用户一些直观的印象,那么在flutter中一个下载按钮的动画应该如何制作呢? 一起来看看吧。
|
12月前
|
Java Spring
flutter系列之:使用AnimationController来控制动画效果
之前我们提到了flutter提供了比较简单好用的AnimatedContainer和SlideTransition来进行一些简单的动画效果,但是要完全实现自定义的复杂的动画效果,还是要使用AnimationController。 今天我们来尝试使用AnimationController来实现一个拖拽图片,然后返回原点的动画。
|
存储 容器
flutter系列之:做一个修改组件属性的动画
什么是动画呢?动画实际上就是不同的图片连续起来形成的。flutter为我们提供了一个AnimationController来对动画进行详尽的控制,不过直接是用AnimationController是比较复杂的,如果只是对一个widget的属性进行修改,可以做成动画吗? 答案是肯定的,一起来看看吧。
|
存储 监控
flutter系列之:如何自定义动画路由
flutter中有默认的Route组件,叫做MaterialPageRoute,一般情况下我们在flutter中进行跳转的话,只需要向Navigator中传入一个MaterialPageRoute就可以了。 但是MaterialPageRoute太普通了,如果我们想要做点不同的跳转特效应该如何处理呢? 一起来看看吧。
|
开发者
Flutter小球弹跳动画
Flutter 的动画系统可以帮助开发者实现生动的游戏效果,例如物理效果、平移动画、旋转动画等等。以下是一个使用 Flutter 动画系统实现小球弹跳的示例代码
Flutter小球弹跳动画