在Flutter的学习过程中,听到看到最多的一个概念就是Widget
。可以说,在Flutter宇宙中:Everything is widget. 机器翻译一下就是:一切都是小玩意儿。这个小玩意儿到底是个啥?咱们来盘一盘!
官方解释
Describes the configuration for an [Element].
Widget
描述了Element
的配置。
并且!源码中有这么一句注释:Widgets are the central class hierarchy in the Flutter framework.
Widget
是个啥???Flutter框架中的C位担当!!!
Element
同样是一位咖位很高的角色,但是我们暂时先放一放,先来看看这个C位出道的Widget
。
凭什么?
刚开始学习Flutter开发,最先接触到的可能就是Text
、Container
、Row
、Scaffold
、GestureDetector
等。其实这些都是Widget
,只是有各自不同的分工而已。
开发一个App,就是使用一个个相同的、不同的Widget
堆叠起来的一个过程。这也体现了Flutter的一个非常重要的理念 组合 。这些Widget
中,有些是最小的小玩意儿,有些是由几个小玩意儿拼装在一起的大玩意儿。
把开发一款App比作造一辆车。我们只需要使用如下几个现成零部件:轮子,座椅,把手,发动机,连接杆,拼接好就可以,而不用关心发动机是如何工作的,轮胎是怎么能滚动的。
用两个轮子+人力发动机+一根连接杆就能做成一辆自行车。
用四个轮子+汽油发动机+三根连接杆就能做成一辆汽车。
找到老父亲的皮夹克,裁一裁套在坐凳上,坐凳变成了一个新的部件-皮坐凳,那么最后出厂的就是辆豪华真皮自行车了。
再买来一桶红油漆,一通乱刷,法拉利来了。
再装个竹蜻蜓,就是想上天了。。。
是的,嘴炮造车就是这么简单,同样开发Flutter App也是这么简单。Flutter为你提供了一所超级工厂,Widget
就是你车间展柜上的半成品,你不用理解实现细节,就可以收获马斯克式快感。你说这个C位坐的实不实?
阅读源码
Widget
的源码非常简单,把几个关键的摘了出来:
@immutable
abstract class Widget extends DiagnosticableTree {
const Widget({ this.key });
final Key key;
@protected
Element createElement();
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
}
@immutable
注解表明Widget
是不可变的,所有的属性必须为final修饰- 继承自
DiagnosticableTree
表明Flutter中存在一棵Widget
树,而Widget
就是这棵树上的对象。 Key
是区分一棵树上不同Widget
的标志createElement()
生成一个Element
对象canUpdate
这个方法决定该widget
的重建方式,具体区别会在Element
篇中提到。
四大弟子
Widget
主要有四个子类,特点鲜明。Flutter中大部分的Widget都是继承自它们并且用它们加以组合。
RenderObjectWidget
顾名思义,Widget
是一个抽象的配置文件,RenderObjectWidget
就是配置中带一个RenderObject
的文件。
RenderObject
是什么?后续的文章中会详细说明。暂时可以把它当成一个干实事的,细粒度的东西。比如弹簧本簧,螺丝钉本钉,内胎本胎,油漆本漆等等。当一个Widget
需要去做layout
、paint
发挥实际功效时,就需要拥有它。
RenderObjectWidget
就是对其的一个抽象,可以理解为泛指。通过方法RenderObject createRenderObject(BuildContext context)
规定了其指定的一个RenderObject
。注意:这里只是规定,并没有创建RenderObject
,别被方法名误导了。
RenderObjectWidget
下根据其允许接入的child
的个数也产生了三个分支:
- LeafRenderObjectWidget
不能有child
即表示此Widget
只能是Widget Tree
上的叶节点。例如图片控件:
Image(StatefulWidget)
-> RawImage(LeafRenderObjectWidget)
-> 指定RenderImage作为其RenderObject
所以Image
常常存在于dart回调地狱的最底层。
- SingleChildRenderObjectWidget
只规定有一个child
的RenderObjectWidget
,拥有属性final Widget child
,可以从构造器中传入一个Widget
。
大部分RenderObjectWidget
都继承自这个类,例如透明度控件:
Opacity(SingleChildRenderObjectWidget)
-> 指定RenderOpacity作为其RenderObject
- MultiChildRenderObjectWidget
可以有多个child
,拥有属性final List<Widget> children
,可以从构造器中传入多个Widget
。例如:
Row/Column
-> Flex(MultiChildRenderObjectWidget)
-> 指定RenderFlex作为其RenderObject
Stack(MultiChildRenderObjectWidget)
-> 指定RenderStack作为其RenderObject
StatelessWidget
一种状态不可变的Widget
是最简单的Widget
,提供build
方法,整合一个或者多个Widget
并返回一个新的Widget
。
这就是一个组合的过程,使用现有的零件,封装成一个新的零件。
最标志性的控件就是Container
了。不知道你们有没有这种感觉,在Flutter开发过程中,总感觉其无所不能,可以控制各种各样的属性,实现各种各样的效果。Ctrl+Click
一下一目了然。
class Container extends StatelessWidget {
Container({
Key key,
... //很多很多参数
}) : super(key: key);
final ... // 很多很多属性
@override
Widget build(BuildContext context) {
Widget current = child;
if (child == null && (constraints == null || !constraints.isTight)) {
current = LimitedBox(
maxWidth: 0.0,
maxHeight: 0.0,
child: ConstrainedBox(constraints: const BoxConstraints.expand()),
);
}
if (alignment != null)
current = Align(alignment: alignment, child: current);
final EdgeInsetsGeometry effectivePadding = _paddingIncludingDecoration;
if (effectivePadding != null)
current = Padding(padding: effectivePadding, child: current);
if (color != null)
current = ColoredBox(color: color, child: current);
if (decoration != null)
current = DecoratedBox(decoration: decoration, child: current);
if (foregroundDecoration != null) {
current = DecoratedBox(
decoration: foregroundDecoration,
position: DecorationPosition.foreground,
child: current,
);
}
if (constraints != null)
current = ConstrainedBox(constraints: constraints, child: current);
if (margin != null)
current = Padding(padding: margin, child: current);
if (transform != null)
current = Transform(transform: transform, child: current);
if (clipBehavior != Clip.none) {
current = ClipPath(
clipper: _DecorationClipper(
textDirection: Directionality.of(context),
decoration: decoration
),
clipBehavior: clipBehavior,
child: current,
);
}
return current;
}
}
可以看到build
方法中的逻辑,非常简单,根据属性值,将child
用各种各样的Widget
一层一层的包裹起来,最后返回了一个新的Widget
。
就像一块面包,根据配料,加一层肉,就变成了汉堡,加一层西红柿,就变成了营养汉堡,再加两层肉,就变成了三倍快乐堡。
Container
就是这样一个DIY菜单,给你提供在给定范围内自由选择的空间。
利用StatelessWidget
就可以利用一些基础的食材,创造麻辣烫菜单,奶茶菜单等等。(指配料自选,芝芝莓莓,三兄弟,什么都有之类的茶品其实已经是一个比较完善的StatelessWidget
了)
StatefulWidget
StatefulWidget
的区别就在于,他拥有属于自己的局部状态管理机制State
。
State
的生命周期_StateLifecycle
有四种状态:
created
State
被创建,State.initState
初始化方法在此时被调用initialized
State.initState
初始化方法执行完毕,但还没有准备好build
。此时State.didChangeDependencies
方法被调用ready
已经准备好build
。State.dispose
方法未被调用defunct
State.dispose
方法被调用,失去build
能力
State
最重要的一个方法就是setState(){}
,用来重构Widget
,如何重构暂且不提。
看到上述一大堆东西,可能你乱了。但是举个例子你就明白了。
StatelessWidget
就像是一张纸质的菜单,预置了几种可变项之后就不能改变了。
StatefulWidget
就是一块电子屏菜单,可以根据不同的状态,刷新一下变成早餐菜单,午餐菜单,夜宵菜单,或者是夏日菜单和冬季菜单。
State
生命周期就相当于电子屏的网络状态,开机,连接网络,匹配菜单状态更新菜单,断网失去更新能力。
ProxyWidget
当父子控件,需要传递信息时,就需要使用ProxyWidget
了。
主要有两种情况:
- InheritedWidget
官方提供的允许子类访问和修改父类属性的基础控件。但是只允许向下传递,子类访问父类。看个官方栗子:
class FrogColor extends InheritedWidget {
const FrogColor({
Key key,
@required this.color,
@required Widget child,
}) : assert(color != null),
assert(child != null),
super(key: key, child: child);
final Color color;
static FrogColor of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<FrogColor>();
}
}
通过FrogColor.of(BuildContext context)
方法获取到父类对象和属性。
是不是想起了MediaQuery
?
使用这个思想可以实现全局状态的效果,但是实现起来有点复杂,效果也不好,所以诞生很多的替代方案比如Provider,FishRedux等等。
- ParentDataWidget
当父控件需要给子类传递信息时,会将传递的信息装在ParentData
中,传给child
。ParentDataWidget
由此得名。
RenderObject
有一个存储信息的属性parentData
,当Widget
需要申请访问这个数据时,就需要继承ParentDataWidget
通过void applyParentData(RenderObject renderObject) {}
方法去访问这个数据。而ParentData
中到底会存储哪些信息,在RenderObject
篇中会有介绍。
最常见的两个案例就是:
Expanded -> Flexible -> ParentDataWidget<FlexParentData>
Positioned -> ParentDataWidget<StackParentData>
总结
Widget
本身的内容并不多,也比较好理解。我们所接触到的Widget
都是Flutter已经封装好功能的一个个配件,我们只要按照其规则进行组装,搭配,就能得到自己想要效果。
以上均为个人愚见,若有错误和不足之处,欢迎指正!
最后祈祷我身上流淌的贵族血脉不易脱发!