当前位置: 首页 > news >正文

Flutter 中的 Animations(二)

官方文档

AnimatedWidget

还记得 上一节 里面是怎么更新 widget 的状态的吗?我们上次的步骤是:首先创建动画,然后给动画添加监听 addListener(...), 在 addListener(...) 方法中我们干了件 很重要 的事儿:setState((){}),因为只有调用这个,才会让 widget 重绘。

这一次我们使用 AnimatedWidget 来实现动画,使用它就不需要给动画 addListener(...)setState((){}) 了,AnimatedWidget 自己会使用当前 Animationvalue 来绘制自己。当然,这里 Animation 我们是以构造参数的方式传递进去的。

看代码:

class AnimatedContainer extends AnimatedWidget {

  AnimatedContainer({Key key, Animation<double> animation})
      : super (key: key, listenable: animation);

  @override
  Widget build(BuildContext context) {
    final Animation<double> animation = listenable;
    return Center(
      child: Container(
        decoration: BoxDecoration(
            color: Colors.redAccent
        ),
        margin: EdgeInsets.symmetric(vertical: 10.0),
        height: animation.value,
        width: animation.value,
      ),
    );
  }
}
复制代码

上述代码中,我们定义了一个 AnimatedContainer 继承了 AnimatedWidget,然后定义了一个构造方法,注意,构造方法中我们定义了一个 Animation 然后把这个 Animation 传到父类(super)中去了,我们可以看看 listenable: animation 这个参数,是一个 Listenable 类型,如下:

/// The [Listenable] to which this widget is listening.
///
/// Commonly an [Animation] or a [ChangeNotifier].
final Listenable listenable;
复制代码

然后再看看 Animation 类:

abstract class Animation<T> extends Listenable implements ValueListenable<T> {
...
}
复制代码

可以看到 AnimationListenable 的子类,所以在我们自定义的 AnimatedContainer 类中可以传一个 Animation 类型的的参数作为父类中 listenable 的值。

使用我们上面定义的 AnimatedContainer 也很简单,直接作为 widget 使用就好,部分代码如下:

@override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'AnimatedWidgetDemo',
      theme: ThemeData(
          primaryColor: Colors.redAccent
      ),
      home: Scaffold(
          appBar: AppBar(
            title: Text('AnimatedWidgetDemo'),
          ),
          body: AnimatedContainer(animation: animation,)
      ),
    );``
  }
复制代码

可以看出我们在实例化 AnimatedContainer 的时候传入了一个 Animation 对象。

AnimatedWidgetDemo.dart

效果如下:

AnimatedBuilder

我们先看看如何使用 AnimatedBuilder 做一个上面一样的效果

部分代码如下:

@override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'AnimatedBuilderExample',
      theme: ThemeData(primaryColor: Colors.redAccent),
      home: Scaffold(
        appBar: AppBar(
          title: Text('animatedBuilderExample'),
        ),
        body: Center(
          child: AnimatedBuilder(
            animation: _animation,
            child: Container(
              decoration: BoxDecoration(color: Colors.redAccent),
            ),
            builder: (context, child) {
              return Container(
                width: _animation.value,
                height: _animation.value,
                child: child,
              );
            },
          ),
        ),
      ),
    );
  }
复制代码

因为 AnimatedBuilder 是继承于 AnimatedWidget 的,

class AnimatedBuilder extends AnimatedWidget { ... }
复制代码

所以可以直接把 AnimatedBuilder 当作 widget 使用

上述代码关键部分如下:

body: Center(
  child: AnimatedBuilder(
    animation: _animation,
    child: Container(
      decoration: BoxDecoration(color: Colors.redAccent),
    ),
    builder: (context, child) {
      return Container(
        width: _animation.value,
        height: _animation.value,
        child: child,
      );
    },
  ),
),
复制代码

效果如下:

AnimatedBuilderExample_2.dart

builder 这个匿名类是每次动画值改变的时候就会被调用

AnimatedBuilder 使用简化后的结构如下:

AnimatedBuilder(
    animateion: ... ,
    child: ... ,
    builder: (context, child) {
        return Container(
            width: ... ,
            height: ... ,
            child: child
        )
    }
)
复制代码

上述代码看着可能会有迷糊的地方,里面的 child 好像被指定了两次,外面一个,里面一个。其实,外面的 child 是传给 AnimatedBuilder 的,而 AnimatedBuilder 又将这个 child 作为参数传递给了里面的匿名类(builder)。

我们可以验证上述说明,就是给外面的 child 指定一个 key,然后在 builder 里面打印出参数 childkey

body: Center(
  child: AnimatedBuilder(
    animation: _animation,
    child: Container(
      decoration: BoxDecoration(color: Colors.redAccent),
    key: Key("android"),
    ),
    builder: (context, child) {
      print("child.key: ${child.key}");
      return Container(
        width: _animation.value,
        height: _animation.value,
        child: child,
      );
    },
  ),
),
复制代码

我们在外面的 child 里面的添加了一个 key 值,然后在 builder 里面打印出参数 childkey

输出如下:

flutter: child.key: [<'android'>]
复制代码

区别

我们来看看 AnimatedBuilder AnimatedWidget 和添加 addListener{}监听并在里面触发 setState(...) 这三种方式做动画有什么区别。

为了更直观的看出它们的区别,这里使用一个第三方控件:RandomContainer,这个控件会在屏幕每次重绘的时候改变自身的颜色。

首先在pubspec.yaml中添加依赖 random_pk: any,如下:

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^0.1.2
  # RandomContainer
  random_pk: any
复制代码

先写一个小例子来看看 RandomContainer 这个控件每次在屏幕重绘的时候自身颜色改变的情况。

在屏幕上绘制一个宽高都为200.0RandomContainer 然后给 RandomContainer 添加点击事件,点击事件里面做的就是调用 setState(...)widget 重绘,部分代码如下:

body: Center(
  child: GestureDetector(
    onTap: _changeColor,
    child: RandomContainer(
      width: 200.0,
      height: 200.0,
    ),
  ),
),
复制代码

使用 RandomContainer 的时候需要引入 import 'package:random_pk/random_pk.dart';

点击事件代码如下:

void _changeColor() {
    setState(() {});
  }
复制代码

AnimatedBuilderExample_1.dart

效果如下:

接下来我们使用三种方式实现同一种效果来看看有什么不同:

AnimatedWidget

_controller =
        AnimationController(vsync: this, duration: Duration(seconds: 5))
          ..repeat();
复制代码
@override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'AnimatedBuilder',
      theme: ThemeData(primaryColor: Colors.redAccent),
      home: Scaffold(
        appBar: AppBar(
          title: Text('AnimatedBuilder'),
        ),
        body: Center(
          child: RandomContainer(
            width: 200.0,
            height: 200.0,
            child: AnimatedWidgetDemo( // new
              animation: _controller,
            ),
          ),
        ),
      ),
    );
  }
复制代码

效果如下:

AnimatedBuilder

_controller =
        AnimationController(vsync: this, duration: Duration(seconds: 5))
          ..repeat();
复制代码
@override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'AnimatedBuilder',
      theme: ThemeData(primaryColor: Colors.redAccent),
      home: Scaffold(
        appBar: AppBar(
          title: Text('AnimatedBuilder'),
        ),
        body: Center(
          child: RandomContainer(
            width: 200.0,
            height: 200.0,
            child: AnimatedBuilderDemo( // new
              child: getContainer(),
              animation: _controller,
            ),
          ),
        ),
      ),
    );
  }
复制代码

AnimatedBuilder 的效果和 AnimatedWidget 的效果是一样的。

接下来我们看看在 addListener{} 里面调用 setState(...) 的效果,也就是我们在上一节中实现动画的方式

_controller =
        AnimationController(vsync: this, duration: Duration(seconds: 5))
          ..repeat()
          ..addListener(() {
            setState(() {});
          });
复制代码
@override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'AnimatedBuilder',
      theme: ThemeData(primaryColor: Colors.redAccent),
      home: Scaffold(
        appBar: AppBar(
          title: Text('AnimatedBuilder'),
        ),
        body: Center(
          child: RandomContainer(
            width: 200.0,
            height: 200.0,
            child: Transform.rotate( // new
              child: getContainer(),
              angle: _controller.value * 2.0 * pi,
            ),
          ),
        ),
      ),
    );
  }
复制代码

效果如下

看出来了吧。。。

AnimatedBuilderExample_3.dart

RandomContainer 参考

如有错误,还请指出,谢谢!

相关文章:

  • Spring Cloud Commons 普通抽象
  • zabbix中文问题汇总
  • join
  • 华为S5300系列交换机V200R001SPH027升级补丁
  • 正则表达式小结
  • sql查询语句
  • [转] 梦里Babel知多少(一)
  • 性能测试 tps持续走低,响应时间持续增加,瓶颈分析
  • BZOJ1497 最大获利
  • 探秘varian:优雅的发布部署程序
  • 论“小猪佩奇如何从营销到吸金一路开挂前行”!
  • 使用mysqldump 备份 恢复从库报错解决方案(ERROR 1872)
  • Jquery mobiscroll 移动设备(手机)wap日期时间选择插件以及滑动、滚动插件
  • 动画小记——点击头像逐渐放大
  • Tuxera NTFS for Mac 拼团仅需¥99!再见原价¥298!
  • const let
  • Cumulo 的 ClojureScript 模块已经成型
  • Dubbo 整合 Pinpoint 做分布式服务请求跟踪
  • IDEA 插件开发入门教程
  • jdbc就是这么简单
  • SegmentFault 2015 Top Rank
  • 关键词挖掘技术哪家强(一)基于node.js技术开发一个关键字查询工具
  • 盘点那些不知名却常用的 Git 操作
  • 如何编写一个可升级的智能合约
  • 算法---两个栈实现一个队列
  • 异步
  • 找一份好的前端工作,起点很重要
  • 字符串匹配基础上
  • 机器人开始自主学习,是人类福祉,还是定时炸弹? ...
  • #【QT 5 调试软件后,发布相关:软件生成exe文件 + 文件打包】
  • #微信小程序:微信小程序常见的配置传值
  • #周末课堂# 【Linux + JVM + Mysql高级性能优化班】(火热报名中~~~)
  • (笔试题)合法字符串
  • (附源码)spring boot网络空间安全实验教学示范中心网站 毕业设计 111454
  • (力扣题库)跳跃游戏II(c++)
  • (使用vite搭建vue3项目(vite + vue3 + vue router + pinia + element plus))
  • (四)docker:为mysql和java jar运行环境创建同一网络,容器互联
  • (五)大数据实战——使用模板虚拟机实现hadoop集群虚拟机克隆及网络相关配置
  • (转)Spring4.2.5+Hibernate4.3.11+Struts1.3.8集成方案一
  • (转)全文检索技术学习(三)——Lucene支持中文分词
  • (转)如何上传第三方jar包至Maven私服让maven项目可以使用第三方jar包
  • .desktop 桌面快捷_Linux桌面环境那么多,这几款优秀的任你选
  • .Net 4.0并行库实用性演练
  • .NET Core 控制台程序读 appsettings.json 、注依赖、配日志、设 IOptions
  • .Net Redis的秒杀Dome和异步执行
  • .netcore 获取appsettings
  • .NET简谈互操作(五:基础知识之Dynamic平台调用)
  • .Net转前端开发-启航篇,如何定制博客园主题
  • @data注解_一枚 架构师 也不会用的Lombok注解,相见恨晚
  • @require_PUTNameError: name ‘require_PUT‘ is not defined 解决方法
  • @Tag和@Operation标签失效问题。SpringDoc 2.2.0(OpenApi 3)和Spring Boot 3.1.1集成
  • [ C++ ] STL---string类的模拟实现
  • [ vulhub漏洞复现篇 ] Celery <4.0 Redis未授权访问+Pickle反序列化利用
  • [AIGC] MySQL存储引擎详解
  • [APIO2015]巴厘岛的雕塑