flutter项目中必不可少一部分,就是widget,今天就来简单的聊一下。
通俗的说,widget框架是flutter的UI方案,widget描述了他们的视图在给定其当前配置和状态时应该看起来像什么。当widget的状态发生变化时,widget会重新构建UI,Flutter会对比前后变化的不同, 以确定底层渲染树从一个状态转换到下一个状态所需的最小更改,类似于React/Vue中虚拟DOM的diff算法。
如果接触过Android开发的同学一定对widget不会陌生,在flutter中,widget与Android的定义是一致的,学名可以叫:窗口小部件。
widget类的官方说明文档: https://api.flutter.dev/flutter/widgets/Widget-class.html
widget库的官方文档地址:https://api.flutter.dev/flutter/widgets/widgets-library.html
widget类是widget库中的所有组件的基类,所以如果我们制作自定义UI组件的话,一定也会继承widget,当然多数时候你不是直接继承widget,而是继承自widget的子类,比如StatelessWidget
StatefulWidget
。
widget的主要工作是实现一个build
函数,用以构建自身。一个widget通常由一些较低级别widget组成。Flutter框架将依次构建这些widget,直到构建到最底层的子widget时,这些最低层的widget通常为RenderObject
,它会计算并描述widget的几何形状。
我想大家肯定对这样的树形结构都不陌生,几乎所有框架的UI结构都是类似的结构
而刚刚提到的StatelessWidget与StatefulWidget也是一组重要的基类
- StatefulWidget 具有可变状态( state)的Widget(窗口小部件).
- StatelessWidget 不需要可变状态的小部件
状态( state) 是可以在构建Widget时同步读取时 和 在Widget的生命周期期间可能改变的信息
Widget实现者的责任就是 在状态改变时通过 State.setState. 立即通知状态
我想接触过react 小程序 或者vue的同学,应该可以很好的理解他们。动态内容制作时,需要使用StatefulWidget,通过 State.setState改变数据来修改展示。
Flutter有一套丰富、强大的基础widget,其中以下是很常用的:
Text
:该 widget 可让创建一个带格式的文本。Row
、Column
: 这些具有弹性空间的布局类Widget可让您在水平(Row)和垂直(Column)方向上创建灵活的布局。其设计是基于web开发中的Flexbox布局模型。Stack
: 取代线性布局 (与Android中的LinearLayout相似,而html中默认是线性布局),Stack
允许子 widget 堆叠, 你可以使用Positioned
来定位他们相对于Stack
的上下左右四条边的位置。Stacks是基于Web开发中的绝度定位(absolute positioning )布局模型设计的。Container
:Container
可让您创建矩形视觉元素。container 可以装饰为一个BoxDecoration
, 如 background、一个边框、或者一个阴影。Container
也可以具有边距(margins)、填充(padding)和应用于其大小的约束(constraints)。另外,Container
可以使用矩阵在三维空间中对其进行变换。
以下是一些简单的Widget,它们可以组合出其它的Widget:
import 'package:flutter/material.dart';
class MyAppBar extends StatelessWidget {
MyAppBar({this.title});
// Widget子类中的字段往往都会定义为"final"
final Widget title;
@override
Widget build(BuildContext context) {
return new Container(
height: 56.0, // 单位是逻辑上的像素(并非真实的像素,类似于浏览器中的像素)
padding: const EdgeInsets.symmetric(horizontal: 8.0),
decoration: new BoxDecoration(color: Colors.blue[500]),
// Row 是水平方向的线性布局(linear layout)
child: new Row(
//列表项的类型是 <Widget>
children: <Widget>[
new IconButton(
icon: new Icon(Icons.menu),
tooltip: 'Navigation menu',
onPressed: null, // null 会禁用 button
),
// Expanded expands its child to fill the available space.
new Expanded(
child: title,
),
new IconButton(
icon: new Icon(Icons.search),
tooltip: 'Search',
onPressed: null,
),
],
),
);
}
}
class MyScaffold extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Material 是UI呈现的“一张纸”
return new Material(
// Column is 垂直方向的线性布局.
child: new Column(
children: <Widget>[
new MyAppBar(
title: new Text(
'Example title',
style: Theme.of(context).primaryTextTheme.title,
),
),
new Expanded(
child: new Center(
child: new Text('Hello, world!'),
),
),
],
),
);
}
}
void main() {
runApp(new MaterialApp(
title: 'My app', // used by the OS task switcher
home: new MyScaffold(),
));
}
看到这里,大家会发现,build方法中传入了一个BuildContext,那么这个buildcontext是什么呢?
下面就是重点内容,flutter的widget与element。先看一张图,表明他们的继承关系
在上面我们已经说了,Widget是flutter的UI方案。但实际上,Widget并不是真正要显示在屏幕上的东西,只是一个配置信息,它永远是immutable的,并且可以在多处重复使用。那真正显示在屏幕上的视图树是什么呢?Element Tree!
abstract class Element extends DiagnosticableTree implements BuildContext
可以看到Element 实现了BuildContext。我们再来看官方对于BuildContext的解释:
BuildContextobjects are actually Element objects. The BuildContextinterface is used to discourage direct manipulation of Element objects.
BuildContext对象实际上就是Element对象,BuildContext 接口用于阻止对 Element 对象的直接操作。
也就是说,build中传入的BuildContext其实是一个element.
视图树装载过程
StatelessWidget
- 首先它会调用StatelessWidget的 createElement 方法,并根据这个widget生成StatelesseElement对象。
- 将这个StatelesseElement对象挂载到element树上。
- StatelesseElement对象调用widget的build方法,并将element自身作为BuildContext传入。
StatefulWidget
- 首先同样也是调用StatefulWidget的 createElement方法,并根据这个widget生成StatefulElement对象,并保留widget引用。
- 将这个StatefulElement挂载到Element树上。
- 根据widget的 createState 方法创建State。
- StatefulElement对象调用state的build方法,并将element自身作为BuildContext传入。
所以我们在build函数中所使用的context,正是当前widget所创建的Element对象。
好了,有了这个基础之后,我们可以理解build方法与widget框架的结构了。总结起来
*树形结构,节点嵌套
*build方法传入widget创建的Element,返回这个widget所代表的用户界面的描述
最后我们再看看widget的生命周期
class MyStatefulWidget extends StatefulWidget {
MyStatefulWidget({
Key key,
this.parameter,
}): super(key: key);
final parameter;
@override
_MyStatefulWidgetState createState() => new _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
@override
void initState(){
super.initState();
// Additional initialization of the State
}
@override
void didChangeDependencies(){
super.didChangeDependencies();
// Additional code
}
@override
void dispose(){
// Additional disposal code
super.dispose();
}
@override
Widget build(BuildContext context){
return new ...
}
}
留一个小问题,我们可以确定父节点的构造方法比子节点的构造方法先执行,那么后续的生命周期方法,先后顺序是怎样的呢?