赵走x博客
网站访问量:151887
首页
书籍
软件
工具
古诗词
搜索
登录
75、插件开发:iOS端API实现
64、文件操作
85、图片加载原理与缓存
84、Flutter运行机制-从启动到显示
83、RenderObject和RenderBox
82、Element与BuildContext
81、Flutter UI系统
80、国际化常见问题
79、使用Intl包
78、实现Localizations
77、让App支持多语言
76、Texture和PlatformView
73、开发Flutter插件
72、插件开发:平台通道简介
71、开发Package
70、Json转Dart Model类
69、使用Socket API
68、使用WebSockets
67、实例:Http分块下载
66、Http请求-Dio http库
65、通过HttpClient发起HTTP请求
63、自绘实例:圆形背景渐变进度条
62、自绘组件 (CustomPaint与Canvas)
61、组合实例:TurnBox
60、组合现有组件
59、自定义组件方法简介
58、动画过渡组件
57、通用"切换动画"组件(AnimatedSwitcher)
56、交织动画
55、Hero动画
54、自定义路由切换动画
53、动画基本结构及状态监听
51、动画
50、通知(Notification)
49、全局事件总线
48、手势识别
47、原始指针事件处理
46、事件处理与通知
45、对话框详解
44、异步UI更新(FutureBuilder、StreamBuilder)
43、颜色和主题
42、跨组件状态共享(Provider)
41、数据共享(InheritedWidget)
40、导航返回拦截(WillPopScope)
39、功能型Widget简介
38、滚动监听及控制
37、CustomScrollView
36、GridView
35、ListView
34、SingleChildScrollView
33、可滚动组件简介
32、剪裁(Clip)
31、Scaffold、TabBar、底部导航
30、Container
29、变换(Transform)
28、装饰容器DecoratedBox
27、尺寸限制类容器
26、填充(Padding)
25、容器类Widget
24、对齐与相对定位(Align)
23、层叠布局 Stack、Positioned
22、流式布局
21、弹性布局(Flex)
20、线性布局(Row和Column)
19、布局类组件简介
18、进度指示器
17、输入框及表单
16、单选开关和复选框
15、图片及ICON
14、按钮
13、文本、字体样式
12、状态管理
11、Widget简介
10、Flutter异常捕获
9、调试Flutter应用
8、资源管理
7、包管理
6、路由管理
5、第一个Flutter应该:计数器应用示例
4、Dart语言简介
3、搭建Flutter开发环境
2、初识Flutter
1、移动开发技术简介
61、组合实例:TurnBox
资源编号:76213
Flutter实战
书籍
热度:88
我们之前已经介绍过RotatedBox,它可以旋转子组件,但是它有两个缺点:一是只能将其子节点以90度的倍数旋转;二是当旋转的角度发生变化时,旋转角度更新过程没有动画。 本节我们将实现一个TurnBox组件,它不仅可以以任意角度来旋转其子节点,而且可以在角度发生变化时执行一个动画以过渡到新状态,同时,我们可以手动指定动画速度。
我们之前已经介绍过RotatedBox,它可以旋转子组件,但是它有两个缺点:一是只能将其子节点以90度的倍数旋转;二是当旋转的角度发生变化时,旋转角度更新过程没有动画。 本节我们将实现一个TurnBox组件,它不仅可以以任意角度来旋转其子节点,而且可以在角度发生变化时执行一个动画以过渡到新状态,同时,我们可以手动指定动画速度。 TurnBox的完整代码如下: ``` import 'package:flutter/widgets.dart'; class TurnBox extends StatefulWidget { const TurnBox({ Key key, this.turns = .0, //旋转的“圈”数,一圈为360度,如0.25圈即90度 this.speed = 200, //过渡动画执行的总时长 this.child }) :super(key: key); final double turns; final int speed; final Widget child; @override _TurnBoxState createState() => new _TurnBoxState(); } class _TurnBoxState extends State
with SingleTickerProviderStateMixin { AnimationController _controller; @override void initState() { super.initState(); _controller = new AnimationController( vsync: this, lowerBound: -double.infinity, upperBound: double.infinity ); _controller.value = widget.turns; } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return RotationTransition( turns: _controller, child: widget.child, ); } @override void didUpdateWidget(TurnBox oldWidget) { super.didUpdateWidget(oldWidget); //旋转角度发生变化时执行过渡动画 if (oldWidget.turns != widget.turns) { _controller.animateTo( widget.turns, duration: Duration(milliseconds: widget.speed??200), curve: Curves.easeOut, ); } } } ``` 上面代码中: * 我们是通过组合RotationTransition和child来实现的旋转效果。 * 在didUpdateWidget中,我们判断要旋转的角度是否发生了变化,如果变了,则执行一个过渡动画。 下面我们测试一下TurnBox的功能,测试代码如下: ``` import 'package:flutter/material.dart'; import '../widgets/index.dart'; class TurnBoxRoute extends StatefulWidget { @override _TurnBoxRouteState createState() => new _TurnBoxRouteState(); } class _TurnBoxRouteState extends State
{ double _turns = .0; @override Widget build(BuildContext context) { return Center( child: Column( children:
[ TurnBox( turns: _turns, speed: 500, child: Icon(Icons.refresh, size: 50,), ), TurnBox( turns: _turns, speed: 1000, child: Icon(Icons.refresh, size: 150.0,), ), RaisedButton( child: Text("顺时针旋转1/5圈"), onPressed: () { setState(() { _turns += .2; }); }, ), RaisedButton( child: Text("逆时针旋转1/5圈"), onPressed: () { setState(() { _turns -= .2; }); }, ) ], ), ); } } ``` 测试代码运行后效果如图10-2所示:  当我们点击旋转按钮时,两个图标的旋转都会旋转1/5圈,但旋转的速度是不同的,读者可以自己运行一下示例看看效果。 实际上本示例只组合了RotationTransition一个组件,它是一个最简的组合类组件示例。另外,如果我们封装的是StatefulWidget,那么一定要注意在组件更新时是否需要同步状态。比如我们要封装一个富文本展示组件MyRichText ,它可以自动处理url链接,定义如下: ``` class MyRichText extends StatefulWidget { MyRichText({ Key key, this.text, // 文本字符串 this.linkStyle, // url链接样式 }) : super(key: key); final String text; final TextStyle linkStyle; @override _MyRichTextState createState() => _MyRichTextState(); } ``` 接下来我们在_MyRichTextState中要实现的功能有两个: * 解析文本字符串“text”,生成TextSpan缓存起来; * 在build中返回最终的富文本样式; _MyRichTextState 实现的代码大致如下: ``` class _MyRichTextState extends State
{ TextSpan _textSpan; @override Widget build(BuildContext context) { return RichText( text: _textSpan, ); } TextSpan parseText(String text) { // 耗时操作:解析文本字符串,构建出TextSpan。 // 省略具体实现。 } @override void initState() { _textSpan = parseText(widget.text) super.initState(); } } ``` 由于解析文本字符串,构建出TextSpan是一个耗时操作,为了不在每次build的时候都解析一次,所以我们在initState中对解析的结果进行了缓存,然后再build中直接使用解析的结果_textSpan。这看起来很不错,但是上面的代码有一个严重的问题,就是父组件传入的text发生变化时(组件树结构不变),那么MyRichText显示的内容不会更新,原因就是initState只会在State创建时被调用,所以在text发生变化时,parseText没有重新执行,导致_textSpan任然是旧的解析值。要解决这个问题也很简单,我们只需添加一个didUpdateWidget回调,然后再里面重新调用parseText即可: ``` @override void didUpdateWidget(MyRichText oldWidget) { if (widget.text != oldWidget.text) { _textSpan = parseText(widget.text); } super.didUpdateWidget(oldWidget); } ``` 有些读者可能会觉得这个点也很简单,是的,的确很简单,之所以要在这里反复强调是因为这个点在实际开发中很容易被忽略,它虽然简单,但却很重要。总之,当我们在State中会缓存某些依赖Widget参数的数据时,一定要注意在组件更新时是否需要同步状态。