文章目录
Flutter Widget采用现代响应式框架构建,这是从 React 中获得的灵感,中心思想是用widget构建你的UI。
Widget描述了他们的视图在给定其当前配置和状态时应该看起来像什么。当widget的状态发生变化时,widget会重新构建UI,Flutter会对比前后变化的不同, 以确定底层渲染树从一个状态转换到下一个状态所需的最小更改(译者语:类似于React/Vue中虚拟DOM的diff算法)。
runApp函数接受给定的Widget并使其成为widget树的根。
在编写应用程序时,通常会创建新的widget,这些widget是无状态的StatelessWidget或者是有状态的StatefulWidget, 具体的选择取决于您的widget是否需要管理一些状态。widget的主要工作是实现一个build函数,用以构建自身。一个widget通常由一些较低级别widget组成。Flutter框架将依次构建这些widget,直到构建到最底层的子widget时,这些最低层的widget通常为RenderObject,它会计算并描述widget的几何形状。
一、Widget、Element、State
1、widget
(1)概念
widget 的主要工作是通过实现 build 函数 来构建自身。一个 widget 通常由一些低级别的 widget 组成,flutter 框架依次的构建这些低级别的 widget,直到构建到最底层的子 widget 时,它会计算并描述 widget 的几何形状
(2)分类
StatelessWidget:无状态,比如标题栏中的标题
StatefulWidget:有状态,创建时需要指定一个 State ,在需要更新 UI时调用 setState(VoidCallbackfn),并在 VoidCallback 中改变一些些变量数值等,组件会重新 build 以达到数显状态/UI的效果。
/**
* @des StatefulWidget 使用实例 - 计数器
* @author liyongli 20190409
* */
class Conunter extends StatefulWidget{
// 快捷写法
@override
_CounterState createState() => new _CounterState();
/**
* // 原始写法
* @override
* State<StatefulWidget> createState() {
* return new _CounterState();
* }
* */
}
/**
* 计数器操作模块
* */
class _CounterState extends State<Conunter>{
// 计数器
int number = 0;
// 按钮点击事件监听
void _numberAdd(){
setState(() {
number += 1;
});
}
@override
Widget build(BuildContext context) {
return new Row(
children: <Widget>[
new RaisedButton(
onPressed: _numberAdd,
child: new Text("ADD"),
),
new Text("更新数值: $number次")
],
);
}
}
管理状态的常见方法:
- widget 管理自己的 state
- 父 widget 管理子 widget 状态
- 混合管理
widget 管理自己的 state
/**
* @des 管理自身状态
* @author liyongli 20190410
* */
class TapboxA extends StatefulWidget{
TapboxA({Key key}):super(key:key);
@override
_TapboxAState createState() => new _TapboxAState();
}
/**
* 颜色变化测试模块
* */
class _TapboxAState extends State<TapboxA>{
bool _active = false;
void _handleTap(){
setState(() {
_active = !_active;
});
}
@override
Widget build(BuildContext context) {
return new MaterialApp(
home: new GestureDetector(
onTap: _handleTap,
child: new Container(
child: new Center(
child: new Text(
_active ? "Activie" : "Inactive",
style: new TextStyle(fontSize: 32.0,color: Colors.white),
),
),
width: 200.0,
height: 200.0,
decoration: new BoxDecoration(
color: _active ? Colors.lightGreen[700] : Colors.grey[600]
),
),
),
);
}
}
父 widget 管理子 widget 状态
/**
* @des 父 widget 管理子 widget 状态
* @author liyongli 20190410
* */
class TapboxBParentWidget extends StatefulWidget {
@override
_TapboxBParentState createState() => new _TapboxBParentState();
}
/**
* 父 widget
* */
class _TapboxBParentState extends State<TapboxBParentWidget>{
bool _active = false;
void _handleTapboxChanged(bool newValue) {
setState(() {
_active = newValue;
});
}
@override
Widget build(BuildContext context) {
return new Container(
child: new TapboxB(
active: _active,
onChanged: _handleTapboxChanged
),
);
}
}
/**
* 子 widget
* */
class TapboxB extends StatelessWidget{
TapboxB({Key key, this.active: true, @required this.onChanged})
: super(key: key);
ValueChanged<bool> onChanged ;
bool active;
void _handleTap(){
onChanged(!active);
}
Widget build(BuildContext context) {
return new GestureDetector(
onTap: _handleTap,
child: new Container(
child: new Center(
child: new Text(
active ? 'Active' : 'Inactive',
style: new TextStyle(fontSize: 32.0, color: Colors.white),
),
),
width: 200.0,
height: 200.0,
decoration: new BoxDecoration(
color: active ? Colors.lightGreen[700] : Colors.grey[600],
),
),
);
}
}
混合管理
/**
* @des 混合管理
* @author liyongli 20190410
* */
class ParentTapboxCWidget extends StatefulWidget{
@override
State<StatefulWidget> createState() => new ParentTapboxCState();
}
class ParentTapboxCState extends State<ParentTapboxCWidget>{
bool _active = false;
void _handleTapboxChanged(bool newValue){
setState(() {
_active = newValue;
});
}
@override
Widget build(BuildContext context) {
return new Container(
child: new TapboxCWidget(
active: _active,
onChanged: _handleTapboxChanged,
),
);
}
}
class TapboxCWidget extends StatefulWidget{
TapboxCWidget({Key key, this.active:false, @required this.onChanged}):super(key:key);
final bool active;
final ValueChanged<bool> onChanged;
@override
State<StatefulWidget> createState() => new TapboxCState();
}
class TapboxCState extends State<TapboxCWidget>{
bool _highlight = false;
void _handleTapDown(TapDownDetails details){
setState(() {
_highlight = true;
});
}
void _handleTapUp(TapUpDetails details){
setState(() {
_highlight = false;
});
}
void _handleCancel(){
setState(() {
_highlight = false;
});
}
void _handleTap(){
widget.onChanged(!widget.active);
}
@override
Widget build(BuildContext context) {
return new GestureDetector(
onTapDown: _handleTapDown,
onTapUp: _handleTapUp,
onTap: _handleTap,
onTapCancel: _handleCancel,
child: new Container(
child: new Center(
child: new Text(
widget.active ? "Active" : "Inactive",
style: new TextStyle(fontSize: :32.0,color: Colors.white),),
),
width: 200.0,
height: 200.0,
decoration: new BoxDecoration(
color: widget.active ? Colors.lightGreen[700] : Colors.grey[600],
border: _highlight
? new Border.all(
color: Colors.teal[700],
width: 10.0,
)
: null,
),
),
);
}
}
决定状态管理的原则:
- 有关用户数据由父 widget 管理
- 有关界面效果由 widget 本身管理
- 状态被不同 widget 共享,由他们共同的父 widget 管理
(3)Widget支持库
flutter 提供了一套丰富、强大的基础 widget ,在此基础上还提供了Android 默认风格库: Material 与 IOS 风格库:Cupertino。
导入相应的依赖即可使用:
import 'package:flutter/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
(4)基础Widget
- Text:文本
- Row:水平布局,基于 web Flexbox 布局模型。
- Column:垂直布局,基于 web Flexbox 布局模型。
- Stack:取代线性布局,与 Android 中 FrameLayout相似,允许子 widget 堆叠,使用 positioned 定位它们相对于上下左右四条边的位置。基于 web absolute positioning(绝对定位) 布局模型
- Container:矩形元素,可以装饰 BoxDecoration,如 background、边框、阴影,它可以具有边距(margins)、填充(padding)和应用与其大小的约束(constraints)。Container 可以使用矩阵在三维空间中对其进行变换
Material
遵循 Material Design 规则。以 MaterialAppWidget 开始,包含有 Scaffold、AppBar、FlatButton 等。
使用前需要先引入依赖:
import 'package:flutter/material.dart';
Material 库中有一些 widget 可以根据实际运行平台切换风格,如 MaterialPageRoute,在路由切换时,切换动画会随平台不同而变化
Cupertino
遵循 IOS 应用风格,目前还没有 Material 库丰富。
使用前需要先引入依赖:
import 'package:flutter/cupertino.dart';
由于 Material 和 Cupertino 都是在基础 widget 库之上的,所以如果你的应用中引入了这两者之一,则不需要再引入 flutter/widgets.dart 了,因为它们内部已经引入过了。
2.Element
widget 中主要包含了组件的配置数据,但它并不代表最终绘制在屏幕上的显示元素,真正代表屏幕上显示元素的是 element,widget 是 element 的配置数据,一个 widget 可同时对应多个 element
3.State
每一个 StatefulWidget 类都会对应一个 State 类,State 表示与其对应的 StatefulWidget 要维护的状态,保存的状态信息可以在 build 时被获取,同时,在 widget 生命周期中可以被改变,改变发生时,可以调用其 setState() 方法通知 framework 发生改变,framework 会重新调用 build 方法重构 widget 树,最终完成更新 UI 的目的。
state 中包含两个常用属性:widget 和 context。widget 属性表示当前正在关联的 widget 实例,但关联关系可能会在 widget 重构时发生变化(framework 会动态设置 widget 属性为最新的widget 对象)。context 属性是 buildContext 类的实例,表示构建 widget 的上下文,每个 widget 都有一个自己的 context 对象,它包含了查找、遍历当前 widget 树的方法。
State 的生命周期
1. 在StatefulWidget调用createState之后,框架将新的状态对象插入树中,然后调用状态对象的initState。 子类化State可以重写initState,以完成仅需要执行一次的工作。 例如,您可以重写initState以配置动画或订阅platform services。initState的实现中需要调用super.initState。
2. 当一个状态对象不再需要时,框架调用状态对象的dispose。 您可以覆盖该dispose方法来执行清理工作。例如,您可以覆盖dispose取消定时器或取消订阅platform services。 dispose典型的实现是直接调用super.dispose。
- initState:当前 widget 对象插入 widget树中时调用
- didChangeDependencies:当前 State 对象的依赖项发生变化时调用
- build:绘制当前界面布局时调用
- reassemble:使用热重载时调用
- didUpdateWidget:widget 配置内容有变动重构时调用
- deactivate:当前 widget 对象从 widget 树中移出时调用
- dispose:当前 widget 对象从 widget 树中永久删除时调用
/**
* @des StatefulWidget 使用实例 - 计数器
* @author liyongli 20190409
* */
class Conunter extends StatefulWidget{
// 快捷写法
@override
_CounterState createState() => new _CounterState();
/**
* // 原始写法
* @override
* State<StatefulWidget> createState() {
* return new _CounterState();
* }
* */
}
/**
* 计数器操作模块
* */
class _CounterState extends State<Conunter>{
// 计数器
int number = 0;
// 按钮点击事件监听
void _numberAdd(){
setState(() {
number += 1;
});
}
@override
Widget build(BuildContext context) {
print("widget 绘制 - build");
return new Row(
children: <Widget>[
new RaisedButton(
onPressed: _numberAdd,
child: new Text("ADD"),
),
new Text("更新数值: $number次")
],
);
}
@override
void initState() {
super.initState();
print("State 创建 - initState");
}
@override
void didUpdateWidget(Conunter oldWidget) {
super.didUpdateWidget(oldWidget);
print("widget 重构 - didUpdateWidget");
}
@override
void deactivate() {
super.deactivate();
print("State 移出 - deactivate");
}
@override
void dispose() {
super.dispose();
print("State 删除 - dispose");
}
@override
void reassemble() {
super.reassemble();
print("热重载 - reassemble");
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
print("State 更改 - didChangeDependencies");
}
}
首次运行时
I/flutter (28866): State 创建 - initState
I/flutter (28866): State 更改 - didChangeDependencies
I/flutter (28866): widget 绘制 - build
使用热重载时
I/flutter (28866): 热重载 - reassemble
I/flutter (28866): widget 重构 - didUpdateWidget
I/flutter (28866): widget 绘制 - build
更改路由(移除当前 widget)后使用热重载时
I/flutter (28866): 热重载 - reassemble
I/flutter (28866): State 移出 - deactivate
I/flutter (28866): State 删除 - dispose
二.Material组件
主要文章: Widgets 总览 - Material 组件
Flutter提供了许多widgets,可帮助您构建遵循Material Design的应用程序。Material应用程序以MaterialApp widget开始, 该widget在应用程序的根部创建了一些有用的widget,其中包括一个Navigator, 它管理由字符串标识的Widget栈(即页面路由栈)。Navigator可以让您的应用程序在页面之间的平滑的过渡。 是否使用MaterialApp完全是可选的,但是使用它是一个很好的做法。
三.处理手势
class MyButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new GestureDetector(
onTap: () {
print('MyButton was tapped!');
},
child: new Container(
height: 36.0,
padding: const EdgeInsets.all(8.0),
margin: const EdgeInsets.symmetric(horizontal: 8.0),
decoration: new BoxDecoration(
borderRadius: new BorderRadius.circular(5.0),
color: Colors.lightGreen[500],
),
child: new Center(
child: new Text('Engage'),
),
),
);
}
}
该GestureDetector widget并不具有显示效果,而是检测由用户做出的手势。 当用户点击Container时, GestureDetector会调用它的onTap回调, 在回调中,将消息打印到控制台。您可以使用GestureDetector来检测各种输入手势,包括点击、拖动和缩放。
许多widget都会使用一个GestureDetector为其他widget提供可选的回调。 例如,IconButton、 RaisedButton、 和FloatingActionButton ,它们都有一个onPressed回调,它会在用户点击该widget时被触发。
四.StatelessWidget StatefulWidget State
1、简述
在Flutter中,这两种类型的对象具有不同的生命周期: Widget是临时对象,用于构建当前状态下的应用程序,而State对象在多次调用build()之间保持不变,允许它们记住信息(状态)。
2、关于状态的demo,仔细体会:
(1)两个无状态的Widget 和 一个有状态的Widget的清晰的分离了,显示和更改的逻辑,将复杂性逻辑封装在各个widget中,同时保持父项的简单性。
(2)事件流是“向上”传递的,而状态流是“向下”传递的
ps:函数的生命和调用分离,实现了回调;体会无状态,和有状态部件的区别。
class CounterDisplay extends StatelessWidget {
CounterDisplay({this.count});
final int count;
@override
Widget build(BuildContext context) {
return new Text('Count: $count');
}
}
class CounterIncrementor extends StatelessWidget {
CounterIncrementor({this.onPressed});
final VoidCallback onPressed;
@override
Widget build(BuildContext context) {
return new RaisedButton(
onPressed: onPressed,
child: new Text('Increment'),
);
}
}
class Counter extends StatefulWidget {
@override
_CounterState createState() => new _CounterState();
}
class _CounterState extends State<Counter> {
int _counter = 0;
void _increment() {
setState(() {
++_counter;
});
}
@override
Widget build(BuildContext context) {
return new Row(children: <Widget>[
new CounterIncrementor(onPressed: _increment),
new CounterDisplay(count: _counter),
]);
}
}
五.key
(1)什么是key
我们知道 Widget 可以有 Stateful 和 Stateless 两种。Key 能够帮助开发者在 Widget tree 中保存状态
A Key is an identifier for Widgets, Elements and SemanticsNodes.
A new widget will only be used to update an existing element if its key is the same as the key of the current widget associated with the element.
Keys must be unique amongst the Elements with the same parent.
Subclasses of Key should either subclass LocalKey or GlobalKey.
See also the discussion at Widget.key.
Widget的源码:
@immutable
abstract class Widget extends DiagnosticableTree {
const Widget({ this.key });
final Key key;
···
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
}
**我们知道 Widget 只是一个配置且无法修改,而 Element 才是真正被使用的对象,并可以修改。**当新的 Widget 到来时将会调用 canUpdate 方法,来确定这个 Element是否需要更新。
canUpdate 对两个(新老) Widget 的 runtimeType 和 key 进行比较,从而判断出当前的 Element 是否需要更新。
StatelessContainer 比较过程:
在 StatelessContainer 中,我们并没有传入 key ,所以只比较它们的 runtimeType。我们将 color 属性定义在了 Widget 中,这将导致他们具有不同的 runtimeType。所以在 StatelessContainer 这个例子中,Flutter能够正确的交换它们的位置。
StatefulContainer 比较过程:
而在 StatefulContainer 的例子中,我们将 color 的定义放在了 State 中,Widget 并不保存 State,真正 hold State 的引用的是 Stateful Element。当我们没有给 Widget 任何 key 的时候,将会只比较这两个 Widget 的 runtimeType 。由于两个 Widget 的属性和方法都相同,canUpdate 方法将会返回 false,在 Flutter 看来,并没有发生变化。所以这两个 Element 将不会交换位置。而我们给 Widget 一个 key 之后,canUpdate 方法将会比较两个 Widget 的 runtimeType 以及 key。并返回 true,现在 Flutter 就可以正确的感知到两个 Widget 交换了顺序了。 (这里 runtimeType 相同,key 不同)
比较范围:
···
class _ScreenState extends State<Screen> {
List<Widget> widgets = [
Padding(
padding: const EdgeInsets.all(8.0),
child: StatefulContainer(key: UniqueKey(),),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: StatefulContainer(key: UniqueKey(),),
),
];
···
分析:
我们分析一下这次的Widget Tree 和 Element Tree,当我们交换元素后,Flutter element-to-widget matching algorithm,(元素-组件匹配算法),开始进行对比,算法每次只对比一层,即Padding这一层。显然,Padding并没有发生本质的变化。
于是开始进行第二层对比,在对比时Flutter发现元素与组件的Key并不匹配,于是,把它设置成不可用状态,但是这里所使用的Key只是本地Key(Local Key),Flutter并不能找到另一层里面的Key(即另外一个Padding Widget中的Key)所以,Flutter就创建了一个新的Widget,而这个Widget的颜色就成了我们看到的『随机色』。
(2)什么时候使用key?
当使用Stateless Widget时,我们并不需要使用key,当使用Stateful Widget时,集合内有数据移动和改变并且需要展示到界面时才需要key。
(3)Key应该用到哪?
我们的Key要设置到组件树的顶层,而这一层在改变时,才能复用或者更新状态。
(4)用哪一种Key?
Flutter中有很多Key,但是总体分为两种 Local Key和Global Key两种。
上面的例子,因为没有数据,所以使用了UniqueKey,在真实的开发中,我们可以用Model中的id作为ObjectKey。
GlobalKey其实是对应于LocalKey,上面我们说Padding中的就是LocalKey,Global即可以在多个页面或者层级复用,比如两个页面也可也同时保持一个状态。