【Flutter 组件】001-关于 Widget 的一切
一、概述
1、Widget 基本概述
Flutter 中的 Widget 相当于 Android 里的 View,iOS 里的 UIView。
- Flutter 中几乎所有的对象都是一个 **widget **;* Widget 不仅可以表示 UI 元素,还可以表示一些功能性的组件,如用于手势检测的
GestureDetector
、用于APP主题数据传递的Theme
等等;* 我们将 Widget 统称为组件。> 界面刷新机制,类似 React
当 Widget 状态发生变化,需要更新界面时,框架会先计算从上一个状态转换到下一个状态所需的最小更改,然后再去刷新界面。
2、Flutter Framework 里的 Widget
架构图
说明
Framework 里面有一层是 Widgets,在 Widgets 层下面,有:
- Rendering(渲染层)
- Animation、Painting、Gestures(动画、绘制、手势)
- Foundation(基础库层)
Widgets 层下面平常使用较少,也比较复杂,常用 Widgets 层上面的 Material 和 Cupertino。
摘录:Material & Cupertino 指的 Widget 的风格是 Material 或 Cupertino 。Flutter 为了减轻开发人员的工作量,实现了两种不同风格的组件:Material 和 Cupertino 。**Material 用于 Android,Cupertino 用于 iOS。**有了这些组件,开发人员不需要再做额外的工作,就可以让 Flutter 的 UI 风格适应不同的平台,让 Flutter UI 获得和 Native UI 一样的使用体验。
3、根 Widget
我们常用的 MaterialApp 就是根(Root)Widget ,Flutter会默认把 根Widget 充满屏幕。
根 Widget 只能是下面三个:
- WidgetsAppWidgetsApp 是可以自定义风格的 根Widget。* MaterialAppMaterialApp 是在 WidgetsApp 上添加了很多 material-design 的功能,是 Material Design 风格的 根Widget。* CupertinoAppCupertinoApp 也是基于 WidgetsApp 实现的 iOS 风格的 根Widget。MaterialApp 是最常用的,也是功能最完善的,且经常与 Scaffold 一起使用。
二、Widget 类
1、Widget 的功能
描述一个UI元素的配置信息。
就是说, Widget 其实并不是表示最终绘制在设备屏幕上的显示元素,所谓的配置信息就是 Widget 接收的参数,比如对于 Text 来讲,文本的内容、对齐方式、文本样式都是它的配置信息。
Widget 描述了他们的视图在给定其当前配置和状态时应该看起来像什么。
2、Widget 类
源码
@immutable
abstract class Widget extends DiagnosticableTree {const Widget({ this.key });final Key? key;@protected@factoryElement createElement();@overrideString toStringShort() {final String type = objectRuntimeType(this, 'Widget');return key == null ? type : '$type-$key';}@overridevoid debugFillProperties(DiagnosticPropertiesBuilder properties) {super.debugFillProperties(properties);properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;}@override@nonVirtualbool operator ==(Object other) => super == other;@override@nonVirtualint get hashCode => super.hashCode;static bool canUpdate(Widget oldWidget, Widget newWidget) {return oldWidget.runtimeType == newWidget.runtimeType&& oldWidget.key == newWidget.key;}static int _debugConcreteSubtype(Widget widget) {return widget is StatefulWidget ? 1 : widget is StatelessWidget ? 2 : 0;}
}
说明
Widget
类本身是一个抽象类,其中最核心的就是定义了createElement()
接口。我们在开发中一般不直接继承 Widget 而是 Widget 的子类:StatelessWidget
或StatefulWidget
来间接继承widget
类。
@immutable
:代表 Widget 是不可变的,这会限制 Widget 中定义的属性(即配置信息)必须是不可变的(final)。为什么?因为 Flutter 中的属性发生变化会导致重新构建 Widget 树,即重新创建新的 Widget 实例来替换旧的 Widget 实例,所以允许 Widget 的属性变化是没有意义的,因为一旦 Widget 自己的属性变了自己就会被替换。*widget
类继承自DiagnosticableTree
,DiagnosticableTree
即“诊断树”,主要作用是提供调试信息。*Key
: 这个key
属性类似于 React/Vue 中的key
,主要的作用是决定是否在下一次build
时复用旧的 widget ,决定的条件在canUpdate()
方法中。*createElement()
:正如前文所述“一个 widget 可以对应多个Element
”;Flutter 框架在构建UI树时,会先调用此方法生成对应节点的Element
对象。 此方法是 Flutter 框架隐式调用的,在我们开发过程中基本不会调用到。*debugFillProperties(...)
复写父类的方法,主要是设置诊断树的一些特性。*canUpdate(...)
是一个静态方法,它主要用于在 widget 树重新build
时复用旧的 widget ,其实具体来说,应该是:是否用新的 widget 对象去更新旧 UI 树上所对应的Element
对象的配置;通过其源码我们可以看到,只要newWidget
与oldWidget
的runtimeType
和key
同时相等时就会用new widget
去更新Element
对象的配置,否则就会创建新的Element
。### Widget 的标识符:Key
diff 简介: 因为 Flutter 采用的是 react-style 的框架,每次刷新 UI 的时候,都会重新构建新的 Widget树,然后和之前的 Widget树 进行对比,计算出变化的部分,这个计算过程叫做 diff 。
标识符: 在 diff 过程中,如果能提前知道哪些 Widget 没有变化,无疑会提高 diff 的性能,这时候就需要使用到标识符。
为了在 diff 过程中,知道 Widget 有没有变化,就需要给 Widget 添加一个唯一的标识符,然后在 Widget树 的 diff 过程中,查看刷新前后的 Widget树 有没有相同标识符的 Widget,如果标识符相同,则说明 Widget 没有变化,否则说明 Widget 有变化。
假设 UI 刷新前,Widget树 是 A,在 A 里有一个标识符为 a 的 Widget,在 UI 刷新后,重建的 Widget树 是 B,如果 B 里还有标识符为 a 的 Widget,则说明这个 Widget 没变,但是如果 B 里没有标识符为 a 的 Widget,那么说明这个 Widget 发生了变化。
这个标识符在 Flutter 中就是 Key,所有 Widget 都有 Key 这一个属性。
Flutter 中如何在 diff 过程中判断哪些 Widget 没有变化
稍微有些复杂,有两种情况:
- 默认情况下( Widget 没有设置 Key)当没有给 Widget 设置 Key 时,Flutter 会根据 Widget 的 runtimeType 和显示顺序是否相同来判断 Widget 是否有变化。runtimeType 是 Widget 的类型,例如 Text 和 RaisedButton 就是不同的类型。* Widget 有 Key当给 Widget 设置了 Key 时,Flutter 是根据 Key 和 runtimeType 是否相同来判断 Widget 是否有变化。### Key 的分类
Key 总共分为两类:
1.Local Key(局部Key)
2.Global Key(全局Key)
1. Local Key(局部Key)
在有相同父级的 Widget 中,Key 必须是唯一的,这样的 Key 叫做 局部 Key。
局部Key 在 Flutter 中对应的抽象类是 LocalKey。LocalKey 有不同的实现,主要的区别就是使用什么值来作为 Key 的值:
- ObjectKey将对象作为 Key 的值。* ValueKey使用特定类型的值来作为 Key 的值。* UniqueKey使用 UniqueKey 自己的对象作为 Key 的值,所以只与自身相等