Flutter进阶之实现动画效果(一)

上一篇文章我们了解了Flutter的动画基础,这一篇文章我们就来实现一个图表的动画效果。

首先,我们需要创建一个新项目myapp,然后把main.dart的内容替换成下面的代码

import 'package:flutter/material.dart';
import 'dart:math';

void main() {
 runApp(new MyApp());
}

class MyApp extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
 return new MaterialApp(
 title: 'Flutter Demo',
 home: new MyHomePage(),
 );
 }
}

class MyHomePage extends StatefulWidget {
 @override
 _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
 // Random([int seed ]):创建一个随机数生成器
 final random = new Random();
 int dataSet;

 void changeData() {
 setState(() {
 dataSet = random.nextInt(100);
 });
 }

 @override
 Widget build(BuildContext context) {
 return new Scaffold(
 body: new Center(
 child: new Text('数据集:$dataSet'),
 ),
 floatingActionButton: new FloatingActionButton(
 onPressed: changeData,
 child: new Icon(Icons.refresh),
 ),
 );
 }
}

启动项目后,应用程序会显示一个居中的文本标签,显示“数据集:null”和浮动按钮来刷新数据。

我们的应用程序生成的树结构如下图所示,您可以看到,虽然控件概念相当广泛,但每个具体的控件类型通常具有非常重要的责任。

通过定义用户界面的不可变的控件树,修改用户界面的唯一方法是重建树,当下一帧到期时告诉Flutter一个子树所依赖的一些状态已经改变了。这种状态依赖的子树的根必须是StatefulWidget,一个StatefulWidget不是可变的,但是它的子树是由State对象构建的。Flutter在构建期间通过树重建保留State对象并将其附加到新树中的各自的控件,然后,它们确定该控件的子树是如何构建的。在我们的应用程序中,MyHomePage是以_MyHomePageState为其状态的StatefulWidget,每当用户按下按钮时,我们执行一些代码来更改_MyHomePageState。我们已经用setState划分了这个变化,以便Flutter可以进行内部管理,并调度控件树进行重建。当发生这种情况时,_MyHomePageState将构建一个稍微不同的子树,这个子树以新的MyHomePage实例为根。

不可变的控件和状态依赖的子树是Flutter提供的主要工具,用于处理响应异步事件(比如按钮、定时器刻度或输入数据)的复杂用户界面中的状态管理的复杂性。

我们的应用程序将保持简单的控件结构,但我们会做一些动画定制图形,第一步是用一个非常简单的图表替换每个数据集的文本显示。由于数据集当前仅有一个在0~100之间数字,所以图表将是一个带有单个条形的条形图,其高度由该数字确定,我们将使用初始值50来避免高度为null。

import 'package:flutter/material.dart';
import 'dart:math';

void main() {
 runApp(new MyApp());
}

class MyApp extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
 return new MaterialApp(
 title: 'Flutter Demo',
 home: new MyHomePage(),
 );
 }
}

class MyHomePage extends StatefulWidget {
 @override
 _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
 // Random([int seed ]):创建一个随机数生成器
 final random = new Random();
 int dataSet = 50;

 void changeData() {
 setState(() {
 dataSet = random.nextInt(100);
 });
 }

 @override
 Widget build(BuildContext context) {
 return new Scaffold(
 body: new Center(
 child: new CustomPaint(
 size: new Size(200.0, 100.0),
 painter: new BarChartPainter(dataSet.toDouble())
 )
 ),
 floatingActionButton: new FloatingActionButton(
 onPressed: changeData,
 child: new Icon(Icons.refresh),
 ),
 );
 }
}

// CustomPaint:是将绘画委托给CustomPainter策略的控件
class BarChartPainter extends CustomPainter {
 static const barWidth = 10.0;

 BarChartPainter(this.barHeight);
 final double barHeight;

 /*
 void paint(
 Canvas canvas,
 Size size
 )
 当对象需要绘制时调用,它给出Canvas的坐标空间,使得原点位于框的左上角,
 框的面积是size参数的大小
 */
 @override
 void paint(Canvas canvas, Size size) {
 final paint = new Paint()
 ..color = Colors.blue[400]
 ..style = PaintingStyle.fill;
 // drawRect:使用给定的Paint绘制一个矩形,是否填充或描边(或两者)是由Paint.style控制
 canvas.drawRect(
 // Rect.fromLTWH(double left, double top, double width, double height):
 // 从左上角和上边缘构造一个矩形,并设置其宽度和高度
 new Rect.fromLTWH(
 size.width-barWidth/2.0,
 size.height-barHeight,
 barWidth,
 barHeight
 ),
 paint
 );
 }

 /*
 bool shouldRepaint(
 CustomPainter,
 oldDelegate
 )
 当定制绘画委托类的新实例被提供给RenderCustomPaint对象时,
 或任何时候使用自定义绘画委托类的新实例创建新的CustomPaint对象
 (这相当于同一件事,因为后者是以前者实施)
 */
 @override
 bool shouldRepaint(BarChartPainter old) => barHeight != old.barHeight;
}

下一步是添加动画,每当数据集发生变化时,我们希望该栏可以平滑而不是突然地改变高度。Flutter有一个AnimationController的概念,用于编排动画,通过注册一个监听器,我们被告知当动画值(0.0~1.0)改变时。每当发生这种情况,我们可以像以前一样调用setState并更新_MyHomePageState。

import 'package:flutter/material.dart';
import 'package:flutter/animation.dart';
import 'dart:math';
import 'dart:ui' show lerpDouble;

void main() {
 runApp(new MyApp());
}

class MyApp extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
 return new MaterialApp(
 title: 'Flutter Demo',
 home: new MyHomePage(),
 );
 }
}

class MyHomePage extends StatefulWidget {
 @override
 _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
 // Random([int seed ]):创建一个随机数生成器
 final random = new Random();
 int dataSet = 50;
 AnimationController animation;
 double startHeight;
 double currentHeight;
 double endHeight;

 /*
 @protected
 @mustCallSuper
 void initState()
 将此对象插入树中时调用
 该框架将为其创建的每个State对象精确地调用此方法一次
 */
 @override
 void initState() {
 super.initState();
 /*
 AnimationController({
 double value,
 Duration duration,
 String debugLabel,
 double lowerBound: 0.0,
 double upperBound: 1.0,
 TickerProvider vsync
 })
 创建动画控制器
 */
 animation = new AnimationController(
 // 这个动画应该持续的时间长短
 duration: const Duration(milliseconds: 300),
 vsync: this
 )
 /*
 void addListener(
 VoidCallback listener
 )
 每次动画值更改时调用监听器
 可以使用removeListener删除监听器
 */
 ..addListener((){
 setState((){
 /*
 double lerpDouble(
 num a,
 num b,
 double t
 )
 在两个数字之间进行线性内插
 return a + (b - a) * t;
 */
 currentHeight = lerpDouble(
 startHeight,
 endHeight,
 animation.value
 );
 });
 });
 startHeight = 0.0;
 currentHeight = 0.0;
 endHeight = dataSet.toDouble();
 // 开始向前运行这个动画(朝向最后)
 animation.forward();
 }

 /*
 @override
 void dispose()
 当该对象永久从树中删除时调用
 当该State对象永远不会再次构建时,该框架调用此方法
 框架调用dispose后,该State对象被视为已卸载,并且mounted属性为false,此时调用setState是一个错误
 生命周期的这个阶段是终点:没有办法重新安装dispose的State对象
 */
 @override
 void dispose() {
 animation.dispose();
 super.dispose();
 }

 void changeData() {
 setState(() {
 startHeight = currentHeight;
 dataSet = random.nextInt(100);
 endHeight = dataSet.toDouble();
 animation.forward(from: 0.0);
 });
 }

 @override
 Widget build(BuildContext context) {
 return new Scaffold(
 body: new Center(
 child: new CustomPaint(
 size: new Size(200.0, 100.0),
 painter: new BarChartPainter(currentHeight)
 )
 ),
 floatingActionButton: new FloatingActionButton(
 onPressed: changeData,
 child: new Icon(Icons.refresh),
 ),
 );
 }
}

// CustomPaint:是将绘画委托给CustomPainter策略的控件
class BarChartPainter extends CustomPainter {
 static const barWidth = 10.0;

 BarChartPainter(this.barHeight);
 final double barHeight;

 /*
 void paint(
 Canvas canvas,
 Size size
 )
 当对象需要绘制时调用,它给出Canvas的坐标空间,使得原点位于框的左上角,
 框的面积是size参数的大小
 */
 @override
 void paint(Canvas canvas, Size size) {
 final paint = new Paint()
 ..color = Colors.blue[400]
 ..style = PaintingStyle.fill;
 // drawRect:使用给定的Paint绘制一个矩形,是否填充或描边(或两者)是由Paint.style控制
 canvas.drawRect(
 // Rect.fromLTWH(double left, double top, double width, double height):
 // 从左上角和上边缘构造一个矩形,并设置其宽度和高度
 new Rect.fromLTWH(
 size.width-barWidth/2.0,
 size.height-barHeight,
 barWidth,
 barHeight
 ),
 paint
 );
 }

 /*
 bool shouldRepaint(
 CustomPainter,
 oldDelegate
 )
 当定制绘画委托类的新实例被提供给RenderCustomPaint对象时,
 或任何时候使用自定义绘画委托类的新实例创建新的CustomPaint对象
 (这相当于同一件事,因为后者是以前者实施)
 */
 @override
 bool shouldRepaint(BarChartPainter old) => barHeight != old.barHeight;
}

上面代码中的lerpDouble函数比较难理解,代入参数之后计算结果如下图。

数据从一开始的0.0到达50.0时,花费了10个时间点。再到达52时,则花费了16个时间点。因此大约得出的结论时,在我们的应用程序中,数据变化越小,花费的时间点越多。

现在程序已经变得复杂性,我们的数据集仍然只是一个数字,设置动画控制所需的代码是一个小问题,因为当我们获得更多的图表数据时,它不会被分解。真正的问题是变量startHeight、currentHeight和endHeight,反映了对数据集和动画值所做的更改,并在三个不同的地方更新。

我们需要一个概念来处理这个混乱的情况。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Flutter进阶之实现动画效果(二)

    在上一篇文章:Flutter进阶-实现动画效果(一)的最后,我们说到需要一个处理程序混乱的概念.在这一篇文章中,我们会引入补间,它是构建动画代码的一个非常简单的概念,主要作用是用面向对象的方法替代之前面向过程的方法.tween是一个值,它描述了其他值的空间中的两个点之间的路径,比如条形图的动画值从0运行到1. 补间在Dart中表示类型为Tween的对象 abstract class Tween<T> { final T begin; final T end; Tween(this.begin,

  • Flutter ListView 上拉加载更多下拉刷新功能实现方法

    先上图 下拉刷新 跟原生开发一样,下拉刷新在flutter里提供的有组件实现 RefreshIndicator 一直不明白为啥组件中都提供下拉刷新,但就是没有上拉加载!! 我这请求接口数据用的是 http 库,是个第三方的是需要安装的 https://pub.dev/packages/http 用法如下 class MyHomePage extends StatefulWidget { MyHomePage({Key key}) : super(key: key); @override MyHo

  • Flutter进阶之实现动画效果(四)

    在上一篇文章:Flutter进阶-实现动画效果(三)中,实现了一个随机高度.颜色的条形.这一篇文章我们会实现多个条形,同样是随机高度.颜色. 首先在bar.dart中创建BarChart类,并使用固定长度的Bar实例列表.我们将使用5个条形,表示一周的5个工作日.然后,我们需要将创建空白和随机实例的责任从Bar转移到BarChart. import 'package:flutter/material.dart'; import 'package:flutter/animation.dart';

  • Flutter Image实现图片加载

    Image 简介 Android ios 原生中使用 ImageView 来加载显示图片. 在flutter 中通过Image来加载并显示图片. 所有的widget并不是直接绘制图片的,而是控制的图片的主要属性的容器,负责绘制的是RenderObject,他们中间是通过ElementTree来联系起来.有了这个基础后,所有的widget都不会提供画布(canvas)来直接绘制image RawImage 这是一个最基础图片容器Widget. Image 这是一个通用包装类,它包装了RawImag

  • Flutter进阶之实现动画效果(三)

    在上一篇文章:Flutter进阶-实现动画效果(二)的最后,我们实现了一个控件,其中包含各种布局和状态处理控件.以及使用自定义的动画感知绘图代码绘制单个Bar的控件.还有一个浮动按钮控件,用于启动条形图高度的动画变化. 现在开始向我们的单个条形添加颜色,在Bar类的height字段下添加一个color字段,并且更新Bar.lerp以使其两者兼容.在上一篇文章中,介绍过"lerp"是"线性内插"或"线性插值"的一种简短形式. class Bar {

  • Flutter中网络图片加载和缓存的实现

    前言 应用开发中经常会碰到网络图片的加载,通常我们会对图片进行缓存,以便下次加载同一张图片时不用再重新下载,在包含有大量图片的应用中,会大幅提高图片展现速度.提升用户体验且为用户节省流量.Flutter本身提供的Image Widget已经实现了加载网络图片的功能,且具备内存缓存的机制,接下来一起看一下Image的网络图片加载的实现. 重温小部件Image 常用小部件Image中实现了几种构造函数,已经足够我们日常开发中各种场景下创建Image对象使用了. 有参构造函数: Image(Key k

  • 如何使用Flutter实现58同城中的加载动画详解

    前言 在应用中执行耗时操作时,为了避免界面长时间等待造成假死的现象,往往会添加一个加载中的动画来提醒用户,在58同城中也不例外,而且我们并没有使用系统默认的加载动画,而是制作了一个具有58特色的加载动画. 在本篇文章中,给大家分享下笔者使用Flutter实现58同城中加载动画的过程.先看一下加载动画的效果: 动画效果乍看比较复杂,难以看出端倪,其实我们可以先调慢动画的速度,这样能够比较清晰地分析出动画的流程. 动画的流程 动画由两个圆弧的动效组成,两个圆弧的起始点角度和扫过的弧度随着时间规律变化

  • Flutter进阶之实现动画效果(十)

    前面的两篇文章[动画效果(八).动画效果(九)]中,我们只需要统计产品和地区,如果现在增加一个统计项目--销售渠道,那么使用之前的堆叠条形图和分组条形图都不适合.我们可以将两者结合,使用分组+堆叠条形图,实际效果如下图所示: 如上图,我们使用同一种颜色的不同透明度表示不同的销售渠道,为了实现不同的透明度,我们需要先更新一下color_palette.dart文件的代码: import 'package:flutter/material.dart'; import 'dart:math'; cla

  • flutter 轮播图动态加载网络图片的方法

    Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面. Flutter可以与现有的代码一起工作.在全世界,Flutter正在被越来越多的开发者和组织使用,并且Flutter是完全免费.开源的. Swiper,网上很多例子只是加载固定的几张图,并且页面只有一个轮播图,在实际应用中,可能会遇到类似ins这种,加载列表,并且都是多图模式的情况. 需要添加依赖包 flukit: ^1.0.0 引用 import 'package:flukit/flukit.da

  • Flutter进阶之实现动画效果(五)

    在本篇文章开始前,我们先来回顾一下之前我们都做了哪些事情.在第一篇文章中,我们在动画值更改时调用double lerpDouble(num a, num b, double t)重新绘制条形.在第二篇文章中,我们首先用Tween类帮助我们管理动画值,并重新绘制条形,然后把绘制条形动画相关的类提取到bar.dart文件.在第三篇文章中,我们首先在Bar类中增加颜色的字段,再新建color_palette.dart文件,用于获取颜色值,同时用工厂构造函数Bar.empty和Bar.random分别创

随机推荐