【Flutter 组件集录】Switch 是怎样炼成的| 8月更文挑战

前言:

这是我参与8月更文挑战的第 3 天,活动详情查看: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 | |


一、 Switch 组件使用详解

可能有人会觉得 Switch 组件非常简单,有什么好说的呢?其实 Switch 组件源码洋洋洒洒 近千行 ,其中关于主题处理平台适配事件处理动画处理绘制处理 都有值得我们学习的地方。那么废话不多说,来一起看看 Switch 是怎么炼成的吧。


1. Switch 最简使用:valueonChanged

Switch 组件的使用中注意:该组件是 StatelessWidget ,表示本身并不维护 开关状态。这也就意味着,我把只能通过 重新构建 Switch组件 来切换 开关状态 。在构建 Switch 时必须传入 valueonChanged 两个参数,其中 value 表示 Switch 开关的状态,onChanged 是状态变化回调函数。

如下,在 _SwitchDemoState 中定义状态 _value 用于表示 Switch 开关的状态,在 _onChanged 回调中改变状态值,并 重新构建 Switch 组件,这样就能达到点击进行开关的效果。

```dart class SwitchDemo extends StatefulWidget { const SwitchDemo({Key? key}) : super(key: key);

@override _SwitchDemoState createState() => _SwitchDemoState(); }

class _SwitchDemoState extends State { bool _value = false;

@override Widget build(BuildContext context) { return Switch( value: _value, onChanged: _onChanged, ); }

void _onChanged(bool value) { setState(() { _value = value; }); } } ```

其实这里可能很让人疑惑 Switch 为什么不自己维护 开关状态,要将改状态交由外界指定呢?既然 SwitchStatelessWidget ,为什么可以执行滑动的动画?还有 onChanged 方法又是何时触发的?带着这些问题我们来逐渐去认识这个属性而陌生的 Switch 组件。


2. Switch 的四个主要颜色

Switch 的构造方法中可以看出,其中定义了非常多的颜色相关属性。

先看前四个颜色属性:

  • inactiveThumbColor 代表关闭时圆圈的颜色。
  • inactiveTrackColor 代表关闭时滑槽的颜色。

  • activeColor 代表打开时圆圈的颜色。
  • inactiveTrackColor 代表打开时滑槽的颜色。

dart Switch( activeColor: Colors.blue, activeTrackColor: Colors.green, inactiveThumbColor: Colors.orange, inactiveTrackColor: Colors.pinkAccent, value: _value, onChanged: _onChanged, );


3. hoverColor 、 mouseCursor 和 splashRadius

前两个属性一般只能在桌面或web 端起作用,hoverColor 顾名思义是鼠标悬浮时,外层的大圈颜色,splashRadius 表示大圈的半径,如果不想要外圈的悬浮效果,可以将半径设为 0 。另外, mouseCursor 代表鼠标的样式,比如下面的小拳头是 SystemMouseCursors.grabbing

dart Switch( activeColor: Colors.blue, activeTrackColor: Colors.green, inactiveThumbColor: Colors.orange, inactiveTrackColor: Colors.pinkAccent, hoverColor: Colors.blue.withOpacity(0.2), mouseCursor: SystemMouseCursors.grabbing, value: _value, onChanged: _onChanged, );

mouseCursor 属性的类型为 MouseCursor ,其中 SystemMouseCursors 中定义了非常多的鼠标指针类型以供使用。下面给出几个效果:

| contextMenu | copy | forbidden | text | | ----------- | ---- | --------- | ---- | | | | | |


5. 指定图片

通过 activeThumbImageinactiveThumbImage 可以指定小圆中开启/关闭 时的图片。另外 onActiveThumbImageErroronInactiveThumbImageError 两个回调用于图片加载错误的监听。

当小圆同时指定 图片颜色 属性时,会显示 图片

dart Switch( activeColor: Colors.blue, activeThumbImage: AssetImage('assets/images/icon_head.png'), inactiveThumbImage: AssetImage('assets/images/icon_8.jpg'), activeTrackColor: Colors.green, inactiveThumbColor: Colors.orange, inactiveTrackColor: Colors.pinkAccent, hoverColor: Colors.blue.withOpacity(0.2), mouseCursor: SystemMouseCursors.move, splashRadius: 15, value: _value, onChanged: _onChanged, );


6.主题相关属性: thumbColor 和 trackColor

一些具有交互性的 Material 组件会通过有 MaterialState 枚举定义交互行为,有如下 7 个元素。

dart enum MaterialState { hovered, focused, pressed, dragged, selected, disabled, error, }

可以看出这两个成员都是 MaterialStateProperty 类型,那这种类型的对象如何创建,又有什么特点呢?

dart ---->[Switch 成员声明]---- final MaterialStateProperty<Color?>? thumbColor; final MaterialStateProperty<Color?>? trackColor;


简单来说通过 MaterialStateProperty.resolveWith 方法,传入一个函数返回对应泛型数据。如下回调函数为 getThumbColor ,回调参数为 Set<MaterialState> 。也仅仅说,会根据 MaterialState 集合,来返回泛型数据。从 thumbColor 属性源码注释中可以看出,Switch 有如下四种 MaterialState

getThumbColor 中根据 states 的情况,分别对几种状态返回不同颜色,这样 Switch 在不同的状态下,就会自动使用对应颜色。比如下面的 onChanged: null 代表 Switch 不可用,在 getThumbColor 中当为 disabled ,会返回红色。

thumbColor 代表小圆颜色,trackColor 代表滑槽颜色,使用方式是一样的。这里可能有人会问:有三个属性可以设置小圆,那它们同时存在,优先级怎么样?结果测试发现,inactiveThumbImage 会优先显示,优先级如下:

dart inactiveThumbImage > thumbColor > inactiveThumbColor > 默认 Switch 主题


上面提到了 默认 Switch 主题 ,这里就来说一下 SwitchTheme ,它是一个 InheritedWidget,维护 SwitchThemeData 类型数据,具体内容如下:

我们可以通过在上层嵌套 SwitchTheme 来为子树中的 Switch 指定默认样式,由于 MaterialApp 内部继承了 SwitchTheme组件,我们可以在 theme 中指定 Switch 的主题样式。这样在指定 Switch 的相关颜色属性,就会使用默认的主题样式:


7. Switch 的焦点: focusColor 与 autofocus

Switch 组件是拥有焦点的,焦点相关的处理被封装在组件内部。focusColor 表示聚焦时的颜色,可被聚焦的组件有个特点:在桌面或 web 平台中可以通过 Tab 键,切换焦点。如下是六个 Switch 通过 Tab 键切换焦点的效果:

dart @override Widget build(BuildContext context) { return Wrap( children: List.generate(6, (index) => Switch( value: _value, focusColor: Colors.blue.withOpacity(0.1), onChanged: _onChanged, )) ); }


8. Switch 的尺寸相关: materialTapTargetSize

MaterialTapTargetSize 是一个枚举类型,有两个元素。该属性可以影响 Switch 的大小,如下分布是 paddedshrinkWrap 的效果。通过调试可知,默认是 padded 。下面在源码分析中会详细介绍该属性的作用。

dart enum MaterialTapTargetSize { padded, shrinkWrap, }


二、 挖掘 Switch 源码中的一些细节
1. 类型 _SwitchType

Switch 类中有一个 _SwitchType 类型成员,该成员完全被封装在 Switch 内部,我们是无法直接操作的。 _SwitchType 是只有两个元素的枚举类。

```dart enum _SwitchType { material, adaptive }

---->[Switch 成员声明]---- final _SwitchType _switchType; ```

既然是成员变量,必然会在类内部被初始化,一般来说对 成员变量 初始化的地方在 构造方法 中。如下,Switch 的普通构造 中,会将 _switchType 设为 _SwitchType.material


一般来说,枚举对象就是为了分类处理,在 Switch#build 方法中,会根据 _switchType 的值进行不同的构建逻辑,如果是 material ,则所有的平台都使用Material风格的 Switch 。 如果是 adaptive 会根据平台的不同,使用不同的风格的 Switch 。在 androidfuchsialinuxwindows 中会使用 Material 风格;在 iOSmacOS 中会使用 Cupertino 风格。

到这里,可能有人会问, _SwitchType 成员完全被封装在 Switch 内部,那如何设置 adaptive 类型呢?仔细查看源码可以看出 Switch 还有一个 adaptive 构造,此处会将 _switchType 设为 _SwitchType.adaptive


2. 两种风格的 Switch 构建

_buildCupertinoSwitch 是当模式为 adaptive 时,用于构建 iOSmacOS 平台 Switch 组件构建,可以看出其内部是通过 CupertinoSwitch 进行构建,效果如下:


_buildMaterialSwitch 用于构建 Material 风格的 Switch 组件构建,可见其内部通过 _MaterialSwitch 组件进行构建。到这里我们就可以回答:既然 SwitchStatelessWidget ,为什么可以执行滑动的动画?因为 _MaterialSwitch 组件是 StatefulWidget ,它可以在内部改变组件状态。


3.Switch 尺寸的确定

从上面可以看出,两种风格的 Switch 都是通过 _getSwitchSize 获取 Size 尺寸的。如下代码中,可以看出,尺寸是通过 MaterialTapTargetSize 对象控制的。如果未指定 materialTapTargetSize 则会通过主题获取,调试可以看出,主题中 materialTapTargetSize 默认是 padded

dart Size _getSwitchSize(ThemeData theme) { final MaterialTapTargetSize effectiveMaterialTapTargetSize = materialTapTargetSize ?? theme.switchTheme.materialTapTargetSize ?? theme.materialTapTargetSize; switch (effectiveMaterialTapTargetSize) { case MaterialTapTargetSize.padded: return const Size(_kSwitchWidth, _kSwitchHeight); case MaterialTapTargetSize.shrinkWrap: return const Size(_kSwitchWidth, _kSwitchHeightCollapsed); } }

下面分别是 paddedshrinkWrap 的调试信息,可以很清楚地看出尺寸情况。


到这里 Switch 组件的源码就已经面面俱到了,我们可以发现,它作为一个 StatelessWidget 并不能做太多的事,只是定义了很多属性,并通过别的组件进行构建。也就是说,它本身起到平台差异的统筹、封装的作用,目的就是方便用户使用。


4. onChanged 方法触发的时机

通过调试可以发现,onChanged 方法 的触发是 ToggleableStateMixin#_handleTap 中触发的。如下是 buildToggleable 的源码,可以看出其中通过 GestureDetector 监听点击事件。

_MaterialSwitchState.build 方法中,可以看到其中通过 GestureDetector 监听了水平拖拽事件,这也是为什么 Switch 可以支持拖动的原因,同时 child 属性是 buildToggleable ,也就是上面的组件,支持点击事件。这是一个很好的多事件监听的案例。


5.动画的创建与触发

仔细看一下滑动的过程,可以看出其中有 位移动画透明度渐变动画。 首先来说一下动画的来源:


这些动画器都定义在 ToggleableStateMixin 中。而 _MaterialSwitchState 混入了 ToggleableStateMixin


和隐式动画一样, _MaterialSwitchState 中的动画触发也是通过重构组件,执行 didUpdateWidget 。如果你了解隐式动画,就不难理解 Switch 的动画触发机制。

最后,绘制是通过 _SwitchPainter 画出来的,这个画板是比较复杂的,这里就不展开了,有兴趣的可以自己研究一下。

Switch 组件的使用方式到这里就完全介绍完毕,那本文到这里就结束了,谢谢观看,明天见~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值