服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Android - Flutter进阶之实现动画效果(一)

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

2022-07-19 10:46吉原拉面 Android

这篇文章主要为大家详细介绍了Flutter实现动画效果的第一篇,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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进阶之实现动画效果(一)

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

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
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。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
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函数比较难理解,代入参数之后计算结果如下图。

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

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

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

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

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

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

原文链接:https://blog.csdn.net/yumi0629/article/details/81775805

延伸 · 阅读

精彩推荐
  • AndroidAndroid MPAndroidChart开源图表库之饼状图的代码

    Android MPAndroidChart开源图表库之饼状图的代码

    MPAndroidChart是一款基于Android的开源图表库,MPAndroidChart不仅可以在Android设备上绘制各种统计图表,而且可以对图表进行拖动和缩放操作,应用起来非常灵活...

    shineflowers11532022-02-21
  • AndroidAndroid添加(创建)、删除及判断是否存在桌面快捷方式的方法

    Android添加(创建)、删除及判断是否存在桌面快捷方式的方法

    这篇文章主要介绍了Android添加(创建)、删除及判断是否存在桌面快捷方式的方法,涉及Android针对桌面快捷方式的相关操作技巧,需要的朋友可以参考下...

    3H5122021-03-23
  • AndroidAndroid实现屏幕旋转方法总结

    Android实现屏幕旋转方法总结

    这篇文章主要介绍了Android实现屏幕旋转方法,实例总结了屏幕旋转的相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下...

    Trent5272021-03-17
  • AndroidAndroid指纹识别功能

    Android指纹识别功能

    这篇文章主要为大家详细介绍了Android指纹识别功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    qq_283774239112022-03-10
  • AndroidAndroid使用WebView播放flash的方法

    Android使用WebView播放flash的方法

    这篇文章主要介绍了Android使用WebView播放flash及判断是否安装flash插件的方法,以实例形式详细讲述了从布局、逻辑判断到功能最终实现播放Flash的方法,是An...

    Android开发网11402021-03-11
  • Androidandroid textview 显示html方法解析

    android textview 显示html方法解析

    现在网络的繁盛时代,光文字是不能满足人们的胃口的,图片,flash,音频,视频就成为浏览网页的主流显示,在手机上也一样,本文将详细介绍此功能的...

    Android教程网2492020-12-18
  • Android理解Android中Activity的方法回调

    理解Android中Activity的方法回调

    这篇文章主要介绍了理解Android中Activity的方法回调,本文用一个完整实例模拟了Activity方法回调的过程,从而加深理解Activity的方法回调思想,需要的朋友可以参...

    Android开发网9612021-03-18
  • AndroidAndroid开发技巧之我的菜单我做主(自定义菜单)

    Android开发技巧之我的菜单我做主(自定义菜单)

    Android SDK本身提供了一种默认创建菜单的机制,虽然功能上还不错,但是界面的美观度不是很理想,本结介绍一种实现方法:就是通过onKeyDown事件方法和Pop...

    Android开发网11002021-01-05