前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Flutter 组件集录】Switch 是怎样炼成的| 8月更文挑战

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

作者头像
张风捷特烈
发布2022-03-12 13:07:39
8270
发布2022-03-12 13:07:39
举报
一、 Switch 组件使用详解

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

1. Switch 最简使用:valueonChanged

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

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

代码语言:javascript
复制
class SwitchDemo extends StatefulWidget {
  const SwitchDemo({Key? key}) : super(key: key);

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

class _SwitchDemoState extends State<SwitchDemo> {
  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 代表打开时滑槽的颜色。
代码语言:javascript
复制
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

代码语言:javascript
复制
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 中定义了非常多的鼠标指针类型以供使用。下面给出几个效果:

5. 指定图片

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

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

代码语言:javascript
复制
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 个元素。

代码语言:javascript
复制
enum MaterialState {
  hovered,
  focused,
  pressed,
  dragged,
  selected,
  disabled,
  error,
}

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

代码语言:javascript
复制
---->[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 会优先显示,优先级如下:

代码语言:javascript
复制
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 键切换焦点的效果:

代码语言:javascript
复制
@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 。下面在源码分析中会详细介绍该属性的作用。

代码语言:javascript
复制
enum MaterialTapTargetSize {
  padded,
  shrinkWrap,
}
二、 挖掘 Switch 源码中的一些细节
1. 类型 _SwitchType

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

代码语言:javascript
复制
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

代码语言:javascript
复制
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 组件的使用方式到这里就完全介绍完毕,那本文到这里就结束了,谢谢观看,明天见~

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/08/03 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、 Switch 组件使用详解
  • 1. Switch 最简使用:value 与 onChanged
  • 2. Switch 的四个主要颜色
  • 3. hoverColor 、 mouseCursor 和 splashRadius
  • 5. 指定图片
  • 6.主题相关属性: thumbColor 和 trackColor
  • 7. Switch 的焦点: focusColor 与 autofocus
  • 8. Switch 的尺寸相关: materialTapTargetSize
  • 二、 挖掘 Switch 源码中的一些细节
    • 1. 类型 _SwitchType
      • 2. 两种风格的 Switch 构建
        • 4. onChanged 方法触发的时机
          • 5.动画的创建与触发
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档