【Flutter 组件集录】CupertinoActivityIndicator| 8月更文挑战

前言:

这是我参与8月更文挑战的第 6 天,活动详情查看:8月更文挑战。为应掘金的八月更文挑战,我准备在本月挑选 31 个以前没有介绍过的组件,进行全面分析和属性介绍。这些文章将来会作为 Flutter 组件集录 的重要素材。希望可以坚持下去,你的支持将是我最大的动力~

| 本系列 | 组件文章 | 列表 | | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | | 1.NotificationListener | 2.Dismissible | 3.Switch | | 4.Scrollbar | 5.ClipPath | 6.CupertinoActivityIndicator | | 7.Opacity | 8.FadeTransition | 9. AnimatedOpacity | | 10. FadeInImage | 11. Offstage | 12. TickerMode | | 13. Visibility | 14. Padding | 15. AnimatedContainer | | 16.CircleAvatar | 17.PhysicalShape | 18.Divider | | 19.Flexible、Expanded 和 Spacer | 20.Card | |


一、CupertinoActivityIndicator 的使用

可能看到 CupertinoActivityIndicator 组件,有人会嗤之以鼻:不就是个 iOS 风格的菊花转 吗,用起来这么简单的对象,有什么好说的啊,看来你也要水文章了。 在我心目中 CupertinoActivityIndicator 是一个 教科书 级别的组件,它融汇了非常多组件相关的知识要点,比如动画绘制State 生命周期回调的使用,是非常值得去学习、分析、品味的。


1. CupertinoActivityIndicator 的属性

CupertinoActivityIndicator 的使用确实非常简单,普通构造中只有两个参数:

| 属性名 | 类型 | 默认值 | 用途 | | --------- | ------ | ------ | ---------------- | | animating | bool | true | 表示是否进行动画 | | radius | double | 10 | 表示指示器半径 |


如下是 CupertinoActivityIndicator 两个属性使用的小案例,左侧半径 15,且animating 置为 true,所以在不停旋转,进行 loading 展示。右侧半径 20,且animating 置为 false,则表现为静态。

```dart class CupertinoActivityIndicatorDemo extends StatelessWidget { const CupertinoActivityIndicatorDemo({Key? key}) : super(key: key);

@override Widget build(BuildContext context) { return Wrap( spacing: 40, children:[ CupertinoActivityIndicator( animating: true, radius: 15, ), CupertinoActivityIndicator( animating: false, radius: 20, ), ] ); } } ```


2.CupertinoActivityIndicator 的 partiallyRevealed 构造

除了普通构造外,还有一个 partiallyRevealed 构造 ,从下面的定义中可以看出,属性只有半径 radius 和进度 progress 。而且 animating 固定为 false,表示这个构造是指定进度的 静态 效果。

下面是 progress0 ~ 1 间隔 0.1 个效果:

```dart class CupertinoActivityIndicatorDemo extends StatelessWidget { const CupertinoActivityIndicatorDemo({Key? key}) : super(key: key);

@override Widget build(BuildContext context) { return Wrap( spacing: 20, children: List.generate( 10, (index) => CupertinoActivityIndicator.partiallyRevealed( progress: 0.1 * index, radius: 15, ), ).toList()); } } ```


二、CupertinoActivityIndicator 源码知识点
1. CupertinoActivityIndicator 组件源码介绍

CupertinoActivityIndicator 继承自 StatefulWidget ,表示它有内部状态更新的需求。其中定义了三个成员,用于组件信息配置,这三个属性在上面的使用中也介绍了作用。作为一个 StatefulWidget ,其组件构建的逻辑将交由对应的状态类进行,这里是 _CupertinoActivityIndicatorState


_CupertinoActivityIndicatorState 的类结构中可以看出,组件的构建依赖于 SizedBoxCustomPaint 。并覆写了三个 State 生命周期的回调方法。


2. 动画的处理

CupertinoActivityIndicator 既然可以进行 loading 旋转,那必然需要进行动画处理。如下, _CupertinoActivityIndicatorState 混入 SingleTickerProviderStateMixin,在 initState 中实例化 AnimationController ,这里可以看出当 widget.animatingtrue ,动画器控制器会立刻 repeat 重复执行,周期为 1s

dispose 回调中将 _controller 释放。

dart @override void dispose() { _controller.dispose(); super.dispose(); }


3. didUpdateWidget 回调

可能很多人不是很清楚这个回调的作用。当组件重建时,状态类不会重新初始化的,而是会回调 didUpdateWidget 来对比新旧两个 Widget 的配置信息进行响应逻辑处理。明面上使 组件重建 的方式非常多,比如 setStateValueListenableBuilderFutrueBuilder 等,本质上基本都是 setState

dart @override void didUpdateWidget(CupertinoActivityIndicator oldWidget) { super.didUpdateWidget(oldWidget); if (widget.animating != oldWidget.animating) { if (widget.animating) _controller.repeat(); else _controller.stop(); } }

如果不处理 didUpdateWidget 会有什么后果?比如通过 Switch 来开关 CupertinoActivityIndicatoranimating 属性, CupertinoActivityIndicator 重建时,如果没有 didUpdateWidget 处理,状态类是无法感知 widget 配置信息变化的,也就无法完成是否动画的切换。


4. 绘制的处理

build 方法中,使用 SizedBox 组件进行尺寸的限定,通过 _CupertinoActivityIndicatorPainter 进行绘制。

在很久以前,对于那时还只会 setState 触发画板重绘,我一直对这种方式有疑问,因为 setState 更新画板会让画板对象重新创建,这对于绘制动画来说是很不友好的,因为触发的频率非常高。直到我看懂 CupertinoActivityIndicator 的源码,才对画板重绘有了全新的认知。这也为 《Flutter 绘制指南 - 妙笔生花》扫清了最后障碍。

都是看到 CupertinoActivityIndicator 并没有使用 setState ,却可以执行动画来更新内部状态,这是让人很兴奋的。经过一点点测试发现秘密在于 super(repaint: position) 。画板可以通过一个 Listenable 对象触发重绘,而不会触发任何组件的构建。至于其更深层的实现原理,在 《Flutter 绘制探索》专栏中有详细的源码分析。


具体的绘制逻辑也很简单,就是遍历旋转绘制圆角矩形而已。


4. CupertinoActivityIndicator 的颜色

从源码中可以看出 CupertinoActivityIndicator 的颜色是固定的,用户无法直接设置。但在 暗/亮 模式下,颜色会有差异,如下:

对于 activeColor 会根据 暗/亮 模式进行处理。如下,在暗色模式下,会略显白色。如果我们想要自己定义的组件支持 暗/亮 模式,也可以效仿一下,进行处理。


三、CupertinoActivityIndicator 的注意点

有一个注意点。比如,我通过 Wrap 包裹 CupertinoActivityIndicator 和另一个 CustomPaint ,通过 BoxPainter 画一个方块。

```dart class CupertinoActivityIndicatorDemo extends StatelessWidget { const CupertinoActivityIndicatorDemo({Key? key}) : super(key: key);

@override Widget build(BuildContext context) { return Wrap( spacing: 20, children: [ CupertinoActivityIndicator( animating: true, radius: 15, ), CustomPaint( size: Size(50, 50), painter: BoxPainter(), ) ], ); } }

class BoxPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { print('-------BoxPainter----------'); canvas.drawRect(Offset.zero & size, Paint()); }

@override bool shouldRepaint(covariant CustomPainter oldDelegate) { return false; } } ```


通过日志可以发现 BoxPainter 会随着 CupertinoActivityIndicator 的动画进行重绘。也能有人会非常疑惑,明明 BoxPainter 不需要重绘,为什么会一直绘制, CupertinoActivityIndicator 太垃圾了。

这也算不上什么异常,本质就是 RepaintBoundary 机制,通过 debugDumpRenderTree() 方法查看渲染树,可以看出:这两者在同一渲染区域内,如下它们都在 up7。在同一片渲染区域内的一个节点重绘,会连带这片区域的所有渲染节点重绘。像 WrapColumnRowSingleChildScrollViewStack 这样可以有多个子组件,对应的渲染对象会在同一层。


我们可以通过 RepaintBoundary,将 CupertinoActivityIndicator 对应的渲染对象隔开,这样就不会影响其他节点。注意,这并不是 CupertinoActivityIndicator 自身的问题,是 RepaintBoundary 机制使然。

dart Wrap( spacing: 20, children: [ RepaintBoundary( //<---- child: CupertinoActivityIndicator( animating: false, radius: 15, ), ), CustomPaint( size: Size(50, 50), painter: BoxPainter(), ), ], ),


CupertinoActivityIndicator 组件的使用方式到这里就介绍完毕,虽然是个简单的小组件,但麻雀虽小五脏俱全。是很值得去研究和学习的。那本文到这里就结束了,谢谢观看,明天见~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值