Widget简介
在Flutter中, 几乎所有的对象都是一个Widget, 它不仅可以表示UI元素, 也可以表示一些功能性的组件, 如手势检测的GestureDetector、App主题数据传递的Theme等等, 而在原生开发中, 组件通常指的是UI组件。
Widget与Element
官方文档中, Widget的功能是描述一个UI元素的配置数据, 也就是说, Widget只是显示元素的配置数据, 并不是表示手机屏幕的显示元素。
Element才是真正代表屏幕上的显示元素, Widget只是UI元素的配置数据, 一个Widget可以对应多个Element。
Widget是一个抽象类, 其中最核心的就是定义了createElement()接口, 在Flutter开发中,我们一般都不用直接继承Widget类来实现一个新组件,相反,我们通常会通过继承StatelessWidget或StatefulWidget来间接继承Widget类来实现。StatelessWidget和StatefulWidget都是直接继承自Widget类,而这两个类也正是Flutter中非常重要的两个抽象类,它们引入了两种Widget模型,接下来我们将重点介绍一下这两个类。
StatelessWidget
- 继承自Widget类,重写了createElement()方法
- 用于不需要维护状态的场景
- 在build方法中通过嵌套其它Widget来构建UI,在构建过程中会递归的构建其嵌套的Widget
实现一个回显字符串的Echo widget
import 'package:flutter/material.dart';
//main是入口函数
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: new Text("Hello Flutter title"),
),
body: new Center(
child: Echo(text: 'Echo Flutter')
),
),
);
}
}
class Echo extends StatelessWidget {
//属性, Widget的属性应尽可能的被声明为final,防止被意外改变。
final String text;
final Color backgroundColor;
//构造方法
const Echo({
Key key,//这个key属性类似于React/Vue中的key,主要的作用是决定是否在下一次build时复用旧的widget,决定的条件在Widget类中的canUpdate()方法中, 不理解就记住继承Widget时, 第一个参数通常应该是key
@required this.text, //@required标注代表必须要传的参数
this.backgroundColor:Colors.grey,
}):super(key:key);
@override
Widget build(BuildContext context) {
return Center(
child: Container(
color: backgroundColor,
child: Text(text),
),
);
}
}
运行效果:
- Context
build方法有一个context参数,它是BuildContext类的一个实例, 表示当前widget在widget树中的上下文,一个widget都会对应一个context对象(因为每一个widget都是widget树上的一个节点)。
Context怎么用?
可以用context向上遍历widget树或按照widget类型查找父级widget
import 'package:flutter/material.dart';
//main是入口函数
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: new Text("Hello Flutter title"),
),
body: Container(
child: Builder(builder: (context){
//在Widget树中向上查找最近的父级Scaffold
Scaffold scaffold = context.ancestorWidgetOfExactType(Scaffold);
//返回AppBar的title
return (scaffold.appBar as AppBar).title;
}),
)
),
);
}
}
运行效果
StatefulWidget
1.和StatelessWidget一样, StatefuleWidget也继承了Widget类, 并重写了createElement()方法
2.和StatelessWidget不一样的地方就是, 返回的Element对象不同, StatefulWidget类中添加了一个新的接口createState()
3.createState用于创建和StatefuleWidget相关的状态, 在StatefuleWidget整个生命周期中会被多次调用。
State
1.一个StatefuleWidget类会对应一个State类, State类表示与其对应的StatefulWidget要维护的状态
2.StatefuleWidget创建时可以读取State的状态
3.在StatefuleWidget生命周期中, 可以通过手动调用setState()方法通知Flutter framework状态发生改变, Flutter framework收到消息后, 会重新调用其build方法重新构建widget树, 从而达到更新UI的目的
- State有两个重要的属性, widget和context
1.widget表示与该State实例关联的widget实例, 当时这种关联并非是永久的, State实例只会在第一次插入到树中时被创建, 当重新构建时, 如果widget被修改了, Flutter framework会动态设置State.widget为新的实例;
2.context是StatefuleWidget对应的BuildContext, 作用跟上面讲到的StatelessWidget的BuildContext一致。 -
State的生命周期
class CounterWidget extends StatefulWidget{
final int initValue;
const CounterWidget({
Key key,
this.initValue: 0, //初始值为0
});
@override
State<StatefulWidget> createState() {
return _CounterWidgetState();
}
}
class _CounterWidgetState extends State<CounterWidget>{
int _counter;
/*
* 1. 当Widget第一次插入到Widget树时会被调用, 对于每一个State, Flutter framework只会调用一次该方法
* 2. 通常我们在这里做一些一次性的操作, 如状态初始化、时间通知等
* 3. 不能在该回调中调用BuildContext.inheritFromWidgetOfExactType,
* 原因是在初始化完成后,Widget树中的InheritFromWidget也可能会发生变化,
* 所以正确的做法应该在在build()方法或didChangeDependencies()中调用它
*/
@override
void initState() {
super.initState();
_counter = widget.initValue;
print("initState");
}
/*
* 1. 当State对象的依赖发生变化时会被调用
*/
@override
void didChangeDependencies() {
super.didChangeDependencies();
print("didChangeDependencies");
}
/*
* 1. 主要用于构建Widget子树
* 2. 一般在调用initState()或didUpdateWidget()或setState()或didChangeDependencies()后会被调用
* 3. 在State对象从树中的一个位置移除, 又重新调插入到树的其他位置后也会被调用
*/
@override
Widget build(BuildContext context) {
print("build");
return Scaffold(
body: Center(
child: FlatButton(
child: Text('$_counter'),
//点击数字++
onPressed: ()=>{
setState(()=>++_counter)
},
),
),
);
}
/*
* 1. 专门为调试提供的, 热重载后会贝调用, 在Release模式下永远不会被调用
*/
@override
void reassemble() {
super.reassemble();
print("reassemble");
}
/*
* 1. 在Widget重新构建时, Flutter framework会调用Widget.canUpdate来检测Widget树中同一位置的新旧节点,
* 然后决定是否需要更新, 如果widget.canUpdate()返回true则会调用此回调
*/
@override
void didUpdateWidget(CounterWidget oldWidget) {
super.didUpdateWidget(oldWidget);
print("didUpdateWidget");
}
/*
* 1. 当State对象从树中移除时, 会被调用
* 2. 在一些场景下, Flutter framework会将对象重新插入树中, 如果移除后没有重新插入, 会调用dispose方法
*/
@override
void deactivate() {
super.deactivate();
print("deactivate");
}
/*
* 1. State对象从树中被永久移除时调用, 通常在此回调中释放资源
*/
@override
void dispose() {
super.dispose();
print("dispose");
}
}
- 在Widget树中获取State对象
由于StatefuleWidget的具体逻辑都在State中, 所以很多时候, 我们需要获取StatefulWidget对应的State对象来调用一些方法, 有两种方法可以获取。
1.通过Context获取
context对象有一个ancestorStateOfType(TypeMatcher)方法,该方法可以从当前节点沿着widget树向上查找指定类型的StatefulWidget对应的State对象。
import 'package:flutter/material.dart';
//main是入口函数
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: new Text("子树中获取State对象"),
),
body: Center(
child: Builder(builder: (context){
return RaisedButton(
onPressed: (){
ScaffoldState _state = context.ancestorStateOfType(TypeMatcher<ScaffoldState>());
_state.showSnackBar(
SnackBar(content: Text("我是SnackBar"),),
);
},
child: Text("显示SnackBar"),
);
})
),
),
);
}
}
运行效果
- 通过GlobalKey获取State
分两步
第一步, 先给目标StatefuleWidget添加GlobalKey,
//定义一个globalKey, 由于GlobalKey要保持全局唯一性,我们使用静态变量存储
static GlobalKey<ScaffoldState> _globalKey= GlobalKey();
...
Scaffold(
key: _globalKey , //设置key
...
)
第二步, 通过GlobalKey获取State对象
_globalKey.currentState.openDrawer()
GlobalKey是Flutter提供的一种在整个APP中引用element的机制
如果一个Widget设置了GlobalKey
那么我们便可以通过globalKey.currentWidget获取Widget对象
通过globalKey.currentElement来获取Widget对应的element对象,
如果是StatefuleWidget, 则可以通过globalKey.currentState来获取该widget对象的State对象
注: 使用GlobalKey开销较大,如果有其他可选方案,应尽量避免使用它。另外同一个GlobalKey在整个widget树中必须是唯一的,不能重复。