在Flutter框架中,Widget代表最基础的部分,相当于是应用程序的基石.引用一句话“万物皆是Widget”,就能看出Widget的重要性不言而喻,所以本篇着重讲一下,我在学习Flutter的过程中,对Widget的一些理解和认知.有些基本知识直接摘自Flutter中文网
1.Widget的划分
import 'package:flutter/material.dart'; //安卓类型的风格库
void main() {
runApp(
new Center(
child: new Text(
'Hello, world!',
textDirection: TextDirection.ltr,
),
),
);
}
眼熟吧? HelloWorld,程序员标准入门,上面的例子中,将一个widget传递给runApp函数就能构成一个最简单的Flutter程序.
上面的例子中,该runApp函数接收给定的widget并使其成为widget树的根,这就类似于iOS中,指定window的rootViewController.(我是这样理解的).
常用的Widget:
1.Text:文本格式的widget
2.Row,Column:类似web开发中的盒模型FlexBox,让你可以在水平或者垂直方向上灵活布局.
3.Stack:取代线性布局,Stack允许子widget堆叠,你可以使用Positioned来定位他们相对于Stack的上左下右四条边的位置.
4.Container:它可以让你创建一个矩形元素.它可以被装饰为一个BoxDecoration,如background、一个边框或者一个阴影.它同样具有margins、padding和应用于其大小的约束constraints.
1.1.StatelessWidget和StatefulWidget
statelessWidget:表示无状态的widget,内部没有保存状态,UI界面一经创建不会发生改变.
statefulWidget:有状态的widget,内部有保存状态,当状态发生改变,调用setState()方法,会触发更新UI界面的操作.另外对于自定义继承自StatefulWidget的子类,必须要重写createState()方法.
StatelessWidget示例
import 'package:flutter/material.dart';
void main() => runApp(MyStatelessWidget(text: "Welcome"));
class MyStatelessWidget extends StatelessWidget {
final String text;
MyStatelessWidget({Key key, this.text}) : super(key: key);
@override
Widget build(BuildContext context) {
return Center(
child: Text(
text,
textDirection: TextDirection.ltr,
),
);
}
}
在上面的示例中,使用了MyStatelessWidget类的构造函数传递标记为final的text。这个类继承了StatelessWidget,它包含不可变数据。
statelessWidget的build方法通常只会在以下三种情况调用:
- 将widget插入树中时
- 当widget的父级更改其配置时
- 当它依赖的InheritedWidget发生变化时
StatefulWidget示例
class HomePage extends StatefulWidget {
HomePage({Key key}) : super(key: key);
_HomePageState createState() => _HomePageState();
}
------------------------------------------------------------------------
class _HomePageState extends State<HomePage> {
int count=0;
@override
Widget build(BuildContext context) {
return Container(
child:Column(
children: <Widget>[
Chip(
label: Text("${this.count}")
),
RaisedButton(
child: Text('增加'),
onPressed: (){
setState(() {
this.count++;
});
},
)
],
)
);
}
}
上面的示例中,虚线上面的部分,声明了一个StatefulWidget,它需要一个createState()方法。此方法创建管理widget状态的状态对象_HomePageState 。
2.如何判断使用statelessWidget还是StatefulWidget的条件?
在Flutter中,widget是有状态的还是无状态的 , 取决于是否他们依赖于状态的变化。
1.如果用户交互或数据改变导致widget改变,那么它就是有状态的。
2.如果一个widget是最终的或不可变的,那么它就是无状态。
首先需要判断widget的状态,简单点,没有数据驱动,信息不可变的,都是statelessWidget,Flutter本身也告诉你,优先使用StatelessWidget。
还有一个重要的条件,Flutter并没有实现数据双向绑定,当你使用StatefulWidget时,你在 State.setState((){}) 中写什么代码都不重要,它仅用来标记这个State对象需要重新Build,重新build后根据已变更的数据来创建新的Widget,但是这个build带来的消耗是巨大的,它会触发父类底下每个子widget的build方法。
当某个父widget下,只有部分数据发生改变时,它还是会全局重新刷新,所以这对于个别场景是不适用的。对于网上的一些学习资料中提到的方法,我认为是行之有效的。
1.尽量将需要刷新的statefulWidget放在最小的子节点
2.根布局视图不要使用statefulWidget
3.将会调用到setState((){}) 的代码尽可能的和要更新的视图封装在一个尽可能小的模块里。
4.如果一个Widget需要reBuild,那么它的子节点、兄弟节点、兄弟节点的子节点应该尽可能少
3.Widget的生命周期
在iOS里的ViewController中,每个视图控制器都有自己的生命周期,包含loadView,viewDidLoad等方法一样,Flutter中的widget也有属于自己生命周期。
方法 | 状态 |
---|---|
initState | 插入渲染树时调用,只调用一次 |
didChangeDependencies | state依赖的对象发生变化时调用 |
build | 构建widget时调用 |
setState | 触发视图重建 |
didUpdateWidget | 某个组件状态发生改变时调用,可能会调用多次 |
deactivate | 移除渲染树时调用 |
dispose | 组件即将销毁时调用 |
上面的表格中,罗列了widget生命周期的各个阶段。
initState:类似于iOS中ViewController的ViewDidLoad方法,
可以在此做一些初始化的设置,比如为某些状态变量设置默认值。
didChangeDependencies: 用来专门处理 State 对象依赖关系变化,会在 initState() 调用结束后被调用。
build:构建视图,创建并返回一个widget。
setState:当状态数据发生变化时,通过调用这个方法通知 Flutter 更新重构 Widget。
didUpdateWidget:当 Widget 的配置发生变化时,比如,父 Widget 触发重建(即父 Widget 的状态发生变化时),热重载时,系统会调用这个函数。
deactivate:当组件的可见状态发生变化时,deactivate 函数会被调用,这时 State 会被暂时从视图树中移除。当页面切换时,由于 State 对象在视图树中的位置发生了变化,需要先暂时移除后再重新添加,重新触发组件构建,因为这个函数也会被调用。
dispose:从视图树中销毁时调用。
4.Flutter中视图的层级关系
如上图所示,Flutter的视图层级主要包含三个层级:widget,Element和RenderObject。下面我们就按照这三个层级,依次讲述其中的知识点。
4.1Widget
首先是widget,按我的理解,它在这三者里应该属于组织者的角色,通过下面widget的源码,我们做简单分析。
abstract class Widget extends DiagnosticableTree {
const Widget({ this.key });
final Key key;
/// 创建Widget对应的Element对象,Element对象存储了Widget的配置信息
@protected
Element createElement();
/// 判断是否可以更新Widget
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
}
widget这个class中,主要有以下两个方法:
createElement:该方法主要是通过widget创建一个对应的Element对象。
canUpdate:该方法主要是判断widget是否可更新,通过比较新旧widget的runtimeType和key。说到这里就不不提新旧widget之间的重要判断依据key。
Widget的标识符:Key
Key是所有Widget都拥有的重要属性,但它并不是默认的,构建某些复杂界面时,我们需要设置widget的key来提升性能。通过Key来比较新旧widget Tree。
4.2 Element
在这三者里,属于协调者的角色。Element对象会同时持有widget对象和RenderObject对象,相当于是widget和RenderObject之间的桥梁。
它承载了视图构建的上下文数据,Element与Widget是一对多的关系。由于Element是可变的,所以通过Element将Widget树的变化做了抽象,可以将真正需要修改的部分同步到RenderObject树中,最大程度降低对真实渲染视图的修改,提高渲染效率,而不是销毁整个渲染视图树重建。
我们在代码中最常看见的BuildContext,其实就是抽象的Element对象。
4.3 RenderObject
RenderObject中包含了所有用来渲染实例Widget的逻辑。它负责layout、painting和hit-testing。它的生成十分耗费性能,所以我们应该尽可能的缓存它。我们与它打交道最多的时候就是在调试布局的时候。
RenderObject | 抽象的Widget |
---|---|
layout | Column和Row |
painting | Text和Image |
hit-testing | GestureDetector |
通过上面的叙述,可以总结出三者的基本关系:
视图由一个个独立的Element节点构成。组件最终的Layout、渲染都是通过RenderObejct来完成的,从创建到绘制渲染的大体流程是:根据Widget生成Element,然后创建相应的RenderObejct并关联到Element.renderObject属性上,最后再通过RenderObject来完成布局排列和绘制。
Flutter这块总是写写停停的,层级关系这部分还有许多更深的知识点,以后还是要继续总结学习,回顾旧知识,学习新知识。