Android Flutter实现GIF动画效果的方法详解
作者:岛上码农 发布时间:2023-02-06 02:46:37
前言
我们之前介绍了不少有关动画的篇章。前面介绍的动画都是只有一个动画效果,那如果我们想对某个组件实现一组动效,比如下面的效果,该怎么办?
staggered animation
这个时候我们需要用到组合动效, Flutter 提供了交错动画(Staggered Animation)的方式实现。对于多个 Anmation
对象,可以共用一个 AnimationController
,然后在不同的时间段执行动画效果。这就有点像 GIF 图片一样,一帧帧图像播放实现连续的动画。
交错动画机制
交错动画的实现基于以下几个要点:
所有的 animation对象使用同一个
AnimationController
驱动;不管实际动画持续的时间长度多长,动画控制器
controller
的值必须在0-1之间;每个动画对象都有一个0-1范围内的间隔(
Interval
);在间隔时间内,
Tween
对象从起始值过渡到结束值。由
AnimationController
统一管理这些Tween 产生的Animation
对象。
听起来有点抽象,我们以一张图来表述就清晰多了,假设我们有4个动画对象,分别控制组件的透明度(Opacity
),宽度(Width
),高度(Height
)和颜色(Color
),交错动画过程如下:
时序示意图
controller
是一个从0到1的归一化的动画控制,其实对应的就是动画时长的归一化。然后 Opacity
透明度动效占据了0-0.25区间;Width
占据了0.25-0.5区间;Height
占据了0.5-0.75区间;最后是 Color
占据了0.75-1.0的区间。区间对应就是动画的时间间隔,只是每个区间内的Tween
动画对象的取值范围都是0-1以控制从起始值到结束值。我们可以理解为是 AnimationController
将多个 Animation
对象按序(也可以重合)拼接起来形成复合形式的动画。
代码实现
看上面的说明是不是觉得还有些难以理解,我们来一段示例代码就很容易明白了。下面的代码我们定义了一个共用的_controller
,然后四段动画对象_opaticy
,_width
,_height
和_color
。其中关键的实现是使用了 Tween
对象的 animate
方法,并指定了一个 CurvedAnimation
对象作为 其parent
参数。而这个CurvedAnimation
实际使用 Interval
来切分_controller
的动画时间,从而可以将多个 Animation
对象组合起来。
import?'package:flutter/material.dart';
class?StaggeredAnimationDemo?extends?StatefulWidget?{
??StaggeredAnimationDemo({Key??key})?:?super(key:?key);
??@override
??_StaggeredAnimationDemoState?createState()?=>?_StaggeredAnimationDemoState();
}
class?_StaggeredAnimationDemoState?extends?State<StaggeredAnimationDemo>
????with?SingleTickerProviderStateMixin?{
??late?AnimationController?_controller;
??late?Animation<double>?_opacity;
??late?Animation<double>?_width;
??late?Animation<double>?_height;
??late?Animation<Color?>?_color;
??@override
??void?initState()?{
????_controller?=
????????AnimationController(duration:?Duration(seconds:?2),?vsync:?this)
??????????..addListener(()?{
????????????setState(()?{});
??????????});
????_opacity?=?Tween<double>(begin:?0.5,?end:?1.0).animate(
??????CurvedAnimation(
????????parent:?_controller,
????????curve:?Interval(
??????????0.0,
??????????0.25,
??????????curve:?Curves.easeIn,
????????),
??????),
????);
????_width?=?Tween<double>(begin:?0.0,?end:?2.0).animate(
??????CurvedAnimation(
????????parent:?_controller,
????????curve:?Interval(
??????????0.25,
??????????0.5,
??????????curve:?Curves.easeIn,
????????),
??????),
????);
????_height?=?Tween<double>(begin:?0.0,?end:?2.0).animate(
??????CurvedAnimation(
????????parent:?_controller,
????????curve:?Interval(
??????????0.5,
??????????0.75,
??????????curve:?Curves.easeIn,
????????),
??????),
????);
????_color?=?ColorTween(begin:?Colors.green,?end:?Colors.blue).animate(
??????CurvedAnimation(
????????parent:?_controller,
????????curve:?Interval(
??????????0.75,
??????????1.0,
??????????curve:?Curves.easeIn,
????????),
??????),
????);
????super.initState();
??}
??@override
??Widget?build(BuildContext?context)?{
????return?Scaffold(
??????appBar:?AppBar(
????????title:?const?Text('交错动画'),
??????),
??????body:?Center(
????????child:?Opacity(
??????????opacity:?_opacity.value,
??????????child:?Container(
????????????width:?100?+?100?*?_width.value,
????????????height:?100?+?100?*?_height.value,
????????????color:?_color.value,
??????????),
????????),
??????),
??????floatingActionButton:?FloatingActionButton(
????????child:?Icon(Icons.play_arrow),
????????onPressed:?()?{
??????????if?(_controller.isCompleted)?{
??????????????_controller.reverse();
????????????}?else?if?(!_controller.isAnimating)?{
??????????????_controller.forward();
??????????}
????????},
??????),
????);
??}
}
我们来看一下运行效果,可以看到运行的动画过程其实就是4段动画效果拼接来的,先是透明度改变,然后是宽度改变,再之后是高度改变,最后是颜色的改变。
运行效果
Interval 介绍
我们来看一下关键的 Interval
类的介绍。
A curve that is 0.0 until [begin], then curved (according to [curve]) from 0.0 at [begin] to 1.0 at [end], then remains 1.0 past [end].
Interval
类继承自 Curve
,所不同的是,在 begin
之前曲线的值一直保持为0.0,而在 end
之后一直保持为1.0。所以可以理解为,在 AnimationController
启动动画后,Interval
曲线其实也已经在绘制,只是有效的取值区间只在 begin
到 end
之间,下面就是 Interval
的一种示例曲线图。
image.png
从 Interval
的源码也能看出来,其中 clamp
方法限制了取值范围,当 t <= begin
的时候取值就是0,当 t >= end
的时候,取值就是1.0。
@override
double?transformInternal(double?t)?{
??assert(begin?>=?0.0);
??assert(begin?<=?1.0);
??assert(end?>=?0.0);
??assert(end?<=?1.0);
??assert(end?>=?begin);
??t?=?((t?-?begin)?/?(end?-?begin)).clamp(0.0,?1.0);
??if?(t?==?0.0?||?t?==?1.0)
????return?t;
??return?curve.transform(t);
}
来源:https://mp.weixin.qq.com/s/Q_Sn-RgpKMzhRl9FKmca1w
猜你喜欢
- 一、TCP/IP简介TCP/IP协议族是互联网使用的协议,也可以用在独立的专用网络中。TCP/IP协议族包括了IP协议、TCP协议和UDP协
- 项目需要对接外部接口,将图片文件流发送到外部接口,下面代码就是HttpsURLConnection如何上传文件流:/** *
- yml文件参数的读取附上一个较为常见的application.yml文件示例server: port: 9999 u
- 常用的类:@ConditionalOnProperty(name = "use.redis.session.store"
- 项目中需要判断传入的日期是否在未来的一年以内,百度了一下网上没有找到好的方式,写了,方便自己和他人:int datecompareAfter
- 在 C# 中,new 关键字可用作运算符、修饰符或约束。new 运算符 用于创建对象和调用构造函数。new 修饰符 用于向基类成员隐藏继承成
- 一、引言之前小编讲了MP从入门到核心功能的使用,接下来这几天小编会把MP在实际项目中,一些常用的高级功能给记录一下。高级功能分为:逻辑删除、
- 池塘里养:Object;一、设计与原理1、基础案例首先看一个基于common-pool2对象池组件的应用案例,主要有工厂类、对象池、对象三个
- 今天来给大家介绍一个非常有用的Studio Tips,有些时候我们在一个方法内部写了过多的代码,然后想要把一些代码提取出来再放在一个单独的方
- 一、前言序列化:将对象转换为二进制序列在网络中传输或保存到磁盘反序列化:从网络或磁盘中将二进制序列转换为对象注意:对象必须实现Seriali
- 本文实例讲述了Java正则验证正整数的方法。分享给大家供大家参考,具体如下:package des;import java.util.reg
- 本文介绍一些Java初学者常问的问题,可以用%除以一个小数吗? a += b 和 a = a + b 的效果有区别吗? 声明一个数组为什么需
- 在进行详解之前,我想先声明一下,本次我们进行讲解说明的是 Kafka 消息存储的信息文件内容,不是所谓的 Kafka 服务器运行产生的日志文
- DateTime dt = DateTime.Now;Label1.Text = dt.ToString();//2005-11-5 13:
- 背景出现了一次生产事故,事情是这样的,我们有一个项目,Java访问数据库的框架使用的是MyBatis。然后一个业务员在系统中查询了一个订单,
- MapperScan添加动态配置(占位符)在对Mybatis自动扫描配置中,使用注解配置时,@MapperScan中的配置,通常配置如下:@
- 上次简单的说了一下CoordinatorLayout的基本用法(android特性之CoordinatorLayout用法探析实例)。其中C
- Java空字符串与null的区别:1、类型 null表示的是一个对象的值,而并不是一个字符串。例如声明一个对象的引用,String a =
- 问题在项目过程中使用MyBatis-Puls的saveBatch一次性添加大量数据时很慢原因MyBatis-Puls的saveBatch默认
- Author:jeffreyDate:2019-04-08一、开发环境:1、mysql - 5.72、navicat(mysql客户端管理工