Flutter 组件集录 | 从图标按钮看组件封装

Posted 嘴巴吃糖了

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter 组件集录 | 从图标按钮看组件封装相关的知识,希望对你有一定的参考价值。

1. 封装的目的

虽然 Flutter 中提供的组件众多,但并非所有组件都是复杂的。大部分是 StatelessWidgetStatefulWidget 的派生类,在面对这些组件时,我们要清楚地认识一点:

它们的核心功能是基于 已有组件封装构建逻辑,完成特定的功能,简化使用。

比如下面的 BackButtonIcon 组件,继承自 StatelessWidget 。在 build 方法中封装构建逻辑,其中使用 Icon 组件,根据不同的平台,展示不同的图标,如下所示:

android/linux/windowsios/macOS
---->[BackButtonIcon 源码]----
class BackButtonIcon extends StatelessWidget 

  const BackButtonIcon( super.key );

  static IconData _getIconData(TargetPlatform platform) 
    switch (platform) 
      case TargetPlatform.android:
      case TargetPlatform.fuchsia:
      case TargetPlatform.linux:
      case TargetPlatform.windows:
        return Icons.arrow_back;
      case TargetPlatform.iOS:
      case TargetPlatform.macOS:
        return Icons.arrow_back_ios;
    
  

  @override
  Widget build(BuildContext context) => Icon(_getIconData(Theme.of(context).platform));

可能有人会疑惑 :

为什么这么简单的逻辑,还要通过一个组件来完成。会不会有些小题大做?
这难道不是导致 Flutter 组件数量庞大的 “元凶” 吗?

对于编程者来说,写重复代码是很反感的。试想一下,如果不用 BackButtonIcon 来封装这个构建逻辑。那么 每次 想要实现不同平台展示不同返回按钮时,就需要编程者自己处理构建逻辑。如果需要修改构建逻辑,就要一处处去修改。

所以 封装 的魅力在于:集中逻辑处理,统一使用的形式,便于复用。 如果一个构建逻辑相对固定,有使用的场景,就可以通过 StatelessWidgetStatefulWidget 进行封装,简化使用。反过来,源码中封装的组件,也一定有其目的或使用场景,我们可以通过这个线索来思考组件的源码设计者的考量。


2. BackButton 组件

BackButton 组件继承自 StatelessWidget ,在 build 构建逻辑中使用 IconButton 组件触发点击事件,如果未提供 onPressed 参数,则会触发 Navigator.maybePop 返回。 显示的内容组件为 BackButtonIcon ,说明其会根据平台来决定图标样式。

另外,可以通过 color 入参设置返回按钮的颜色。通过 源码可以知道,本质上这个颜色属性是传入到 IconButton 组件构造方法中的。

class BackButton extends StatelessWidget 
  const BackButton( super.key, this.color, this.onPressed );

  final Color? color;

  final VoidCallback? onPressed;

  @override
  Widget build(BuildContext context) 
    assert(debugCheckHasMaterialLocalizations(context));
    return IconButton(
      icon: const BackButtonIcon(),
      color: color,
      tooltip: MaterialLocalizations.of(context).backButtonTooltip,
      onPressed: () 
        if (onPressed != null) 
          onPressed!();
         else 
          Navigator.maybePop(context);
        
      ,
    );
  

通过已存在的 IconButtonBackButtonIcon 组件,组合拼装成更复杂的,具有某种使用场景的组件。这就是封装后可复用的魅力。如果想对一个组件从根源上进行了解,查看它的构建逻辑即可。从中你可以知其然,知其所以然,当你知道一件事物的构成机理,那它的任何表现都不会脱离你的控制,在使用时就是 “降维打击”


3. IconButton 组件

IconButton 是一个具有圆形水波纹点击效果的组件,必须传入一个子组件 icon 和回调函数 onPressed

说实话,国内的应用软件基本上不喜欢用 material 风格。对我个人来说,水波纹能给用户一个交互的反馈,本身是比较好的,但一个小小的图标按钮有水波纹,感觉怪怪的。这不得不让图标按钮的占位区域扩大,当多个 IconButton 排列时,如下所示,默认情况下,水波纹区域太大,又会显得拥挤:

不过可以通过 splashRadius 来控制水波纹的扩散半径。但在小区域中,当长按时展示水波扩散的动画效果时,手指几乎占据了整个区域,所以整个动画效果呈现的收益并不明显。

IconButton(
  onPressed: (),
  color: Colors.blue ,
  icon: Icon(Icons.add ),
  splashRadius: 20,
),

如下,是启用 Material3 的效果,感觉这种水波纹要比 Material2 的好看一些,对于 IconButton 而言,会根据图标颜色显示背景色,长按时也不再是扩散的水波纹,而是背景色的变化。

MaterialApp(
   theme: ThemeData(
     primarySwatch: Colors.blue,
     useMaterial3: true
   ),
   home: const MyHomePage(),
 );

下面来简单看一下 IconButton 组件的源码实现,首先,它继承自 StatelessWidget ,表明它是基于已有组件封装构建逻辑,从而形成的新组件。 从构造方法中可以到有大量的可配置属性:


如下是非 useMaterial3 时的主要构建逻辑,主题部分使用 ConstrainedBoxPaddingSizedBoxAlignIconTheme 组合构建:

tooltip 非空时会包裹 Tooltip 组件实现长按提示信息的功能,事件响应通过 InkResponse 组件实现。


最后说一下 useMaterial3 的处理, 在 IconButton#build 方法中,通过 Theme 数据的 useMaterial3 属性校验是否启用 Material3

通过启用 Material3 ,会返回 _SelectableIconButton 完成构建逻辑。如下所示,像 selectedIconisSelectedstyleminSizemaxWidth是为 Material3 风格新加的属性。

---->[IconButton#build]----
if (theme.useMaterial3) 
  final Size? minSize = constraints == null
      ? null
      : Size(constraints!.minWidth, constraints!.minHeight);
  final Size? maxSize = constraints == null
      ? null
      : Size(constraints!.maxWidth, constraints!.maxHeight);
 // 略...
  if (style != null) 
    adjustedStyle = style!.merge(adjustedStyle);
  
  Widget effectiveIcon = icon;
  if ((isSelected ?? false) && selectedIcon != null) 
    effectiveIcon = selectedIcon!;
  
  Widget iconButton = IconTheme.merge(
    data: IconThemeData(
      size: effectiveIconSize,
    ),
    child: effectiveIcon,
  );
 // 略...
  return _SelectableIconButton( // tag1
    style: adjustedStyle,
    onPressed: onPressed,
    autofocus: autofocus,
    focusNode: focusNode,
    isSelected: isSelected,
    child: iconButton,
  );

从这里可以看出,通过 IconButton 组件封装构建逻辑,也有利于功能拓展。想要适应新的主题,只要优化构建逻辑即可,这对于使用者是没有任何影响的,且可以通过 Theme 决定启用的主题。组件封装的是 构建流程,它更是一种对 功能的允诺,通过它你能完成什么样的表现,是一个组件最重要的事。


4、FloatingActionButton 组件

FloatingActionButton 一般来说是使用在 ScaffoldfloatingActionButton 属性中。因为 Scaffold 在构建逻辑中有一些和 FloatingActionButton 联动的效果,比如浮动按钮方位、动画等。不过 FloatingActionButton 本身只不过是一个圆形样式的 RawMaterialButton 而已。

它有如下四个构造,用来创建不同类型的浮动按钮,构造中主要为私有的 _FloatingActionButtonType 成员赋值:

enum _FloatingActionButtonType 
  regular,
  small,
  large,
  extended,

regularsmalllarge

其中 extendedmaterial3 中的风格,是圆角按钮,可以在官网的 extended-fab 中查看详情,也可以在该网站中看一下其他 material3 的风格:


它继承自 StatelessWidget ,表明它是基于已有组件封装构建逻辑,从而形成的新组件。 如下是 FloatingActionButton 的构造方法,其中的属性虽然挺多的,但基本上都是常规属性。其中比较特殊的是 heroTag 属性,默认值是一个 const 常量 _DefaultHeroTag :

class _DefaultHeroTag 
  const _DefaultHeroTag();
  @override
  String toString() => '<default FloatingActionButton tag>';

如果 heroTag 非空,会在构建逻辑中套上 Hero 组件:

这就有一个问题:在界面跳转时同一界面中不能有两个相同 tagHero。这也是一个界面中使用两个 FloatingActionButton 常出现的问题,解决方案也很简单,将手动指定 heroTag 参数即可。


最终,FloatingActionButton 是依赖 RawMaterialButton 组件完成展示效果的:


从这四个相对简单的 StatelessWidget 组件可以看出,它们本质上是依赖其他已有组件,完成构建逻辑的封装,来满足一些特殊的使用场景。并且通过组件类成员属性的配置,让组件在表现上可以更加灵活。这个就是将构建逻辑分离成组件进行封装的主要优势。

可能有人会疑惑,使用函数不是也能封装组件吗,通过函数参数也能控制构建的表现,它和分离组件有什么区别呢?其实两者在本质上并没有什么区别,目的是一致的:封装特点创建中的构建逻辑。 这个问题等价于在问: 类封装和函数封装的区别

类中可以定义成员变量和成员方法,封装能力更强,更像一个独立的 个体 ,通过类封装相当于加入了 Widget 家族的正规军;通过函数封装,会显得比较零散,不利于分离和管理,但形式的比较灵活,相当于 游击队。至于使用函数还是使用类封装构建逻辑,并没有严格的标准,自己结合场景考量。一般有 复用价值 或后期需要 拓展、修改的构建逻辑,可以独立封装为类,一些临时构建的逻辑,可以通过函数来处理。


更多 Flutter 内置组件介绍,欢迎关注 《Flutter 组件集录》 专栏。

作者:张风捷特烈
链接:https://juejin.cn/post/7155644433948475406

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。

相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

一、架构师筑基必备技能

1、深入理解Java泛型
2、注解深入浅出
3、并发编程
4、数据传输与序列化
5、Java虚拟机原理
6、高效IO
……

二、Android百大框架源码解析

1.Retrofit 2.0源码解析
2.Okhttp3源码解析
3.ButterKnife源码解析
4.MPAndroidChart 源码解析
5.Glide源码解析
6.Leakcanary 源码解析
7.Universal-lmage-Loader源码解析
8.EventBus 3.0源码解析
9.zxing源码分析
10.Picasso源码解析
11.LottieAndroid使用详解及源码解析
12.Fresco 源码分析——图片加载流程

三、Android性能优化实战解析

  • 腾讯Bugly:对字符串匹配算法的一点理解
  • 爱奇艺:安卓APP崩溃捕获方案——xCrash
  • 字节跳动:深入理解Gradle框架之一:Plugin, Extension, buildSrc
  • 百度APP技术:Android H5首屏优化实践
  • 支付宝客户端架构解析:Android 客户端启动速度优化之「垃圾回收」
  • 携程:从智行 Android 项目看组件化架构实践
  • 网易新闻构建优化:如何让你的构建速度“势如闪电”?

四、高级kotlin强化实战

1、Kotlin入门教程
2、Kotlin 实战避坑指南
3、项目实战《Kotlin Jetpack 实战》

  • 从一个膜拜大神的 Demo 开始

  • Kotlin 写 Gradle 脚本是一种什么体验?

  • Kotlin 编程的三重境界

  • Kotlin 高阶函数

  • Kotlin 泛型

  • Kotlin 扩展

  • Kotlin 委托

  • 协程“不为人知”的调试技巧

  • 图解协程:suspend

五、Android高级UI开源框架进阶解密

1.SmartRefreshLayout的使用
2.Android之PullToRefresh控件源码解析
3.Android-PullToRefresh下拉刷新库基本用法
4.LoadSir-高效易用的加载反馈页管理框架
5.Android通用LoadingView加载框架详解
6.MPAndroidChart实现LineChart(折线图)
7.hellocharts-android使用指南
8.SmartTable使用指南
9.开源项目android-uitableview介绍
10.ExcelPanel 使用指南
11.Android开源项目SlidingMenu深切解析
12.MaterialDrawer使用指南

六、NDK模块开发

1、NDK 模块开发
2、JNI 模块
3、Native 开发工具
4、Linux 编程
5、底层图片处理
6、音视频开发
7、机器学习

七、Flutter技术进阶

1、Flutter跨平台开发概述
2、Windows中Flutter开发环境搭建
3、编写你的第一个Flutter APP
4、Flutter开发环境搭建和调试
5、Dart语法篇之基础语法(一)
6、Dart语法篇之集合的使用与源码解析(二)
7、Dart语法篇之集合操作符函数与源码分析(三)

八、微信小程序开发

1、小程序概述及入门
2、小程序UI开发
3、API操作
4、购物商场项目实战……

全套视频资料:

一、面试合集

二、源码解析合集


三、开源框架合集


欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证微信卡片免费领取↓↓↓

以上是关于Flutter 组件集录 | 从图标按钮看组件封装的主要内容,如果未能解决你的问题,请参考以下文章

Flutter 按钮组件 底部导航 浮动按钮 Swiper 自定义Dialog

Flutter中常用的按钮组件-IconButton(可点击的Icon)

Flutter中常用的按钮组件-IconButton(可点击的Icon)

Flutter 自定义弹窗组件

Flutter——AppBar组件(顶部导航组件)

Flutter 自定义渐变按钮 GradientButton