在正式开始阅读本文之前,希望读者阅读下博主的Flutter之BuilderContext和Widget关系浅析和Fultter之Element和Widget对应关系这两篇博文,这是本篇博文的理论知识储备。通过这两篇博文你可以了解到:
1、Widget和Element的对应关系
2、Widget和Element的初始化时机
3、Flutter的BuildContext到底是什么玩意
4、StatefulWidget的state跟StatefulElement之间的联系
本文的的涉及到的代码demo,点此查看
GlobalKey详解
GlobalKey的作用
先说结论,后文会分析此结论的由来: 每个Widget
都对应一个Element
,我们可以直接对Widget
进行操作,但是无法直接操作Widget
对应的Element
。而GlobalKey
就是那把直接访问Element
的钥匙。通过GlobalKey
可以获取到Widget
对应的Element
,比如获取StatelessElement
和StatefulElement
. 比如如果获取到了StatefullElement
,那么我们就可以获取StatefulElement
的State
对象,拿到后我们就可以进行一些相应的操作。
/// An [Element] that uses a [StatefulWidget] as its configuration.
class StatefulElement extends ComponentElement {
@override
Widget build() => state.build(this);
State<StatefulWidget> get state => _state!;
State<StatefulWidget>? _state;
}
下面我们就以Form
表单为例来分析GlobalKey
为什么可以获取Widget
对应的Element
。
GlobalKey实战举例
登录肯定要有输入用户名和密码的输入框,在Flutter
中我们只用Form
表单+TextFormField
的形式加以实现。现在就来讲讲Form
和TextFormField
的简单使用,demo中登录界面如下:
然后我们在不输入任何字符的情况下点击submit按钮,效果如下所示:
上图布局的代码如下所示:
如上所示首先初始化GlobalKey
对象。然后将此对象设置为Form
的key,最后再点击Submit按钮的时候,我们没有直接操作TextFormField
,而是通过_formKey.currentState.validate
对输入框TextFormField
的内容进行非空验证。代码中的_formKey.currentState
其类型是FormState
:
class Form extends StatefulWidget {
const Form({
Key key,
@required this.child,
}) ;
@override
FormState createState() => FormState();
//调用Form.of(context)也可以获取FormState对象
//详情请看【Flutter之实战InheritedWidget详解】
static FormState of(BuildContext context) {
final _FormScope scope = context.inheritFromWidgetOfExactType(_FormScope);
return scope?._formState;
}
}
GlobalKey获取Element的原理
abstract class GlobalKey<T extends State<StatefulWidget>> extends Key {
//一个静态的变量map集合
static final Map<GlobalKey, Element> _registry = <GlobalKey, Element>{};
}
,从 GlobalKey<T extends State<StatefulWidget>>
的类结构可以看出,GlobalKey
主要用来存储状态信息 State<StatefulWidget>
,State
指的是StatefulWidget
的状态类,通过StatefulWidget
的createState
方法创建:
abstract class StatefulWidget extends Widget {
//Key是个options的,可以设置也可以不设置
const StatefulWidget({ Key key }) : super(key: key);
@protected
State createState();
}
上文中为什么通过GlobalKey.currentState
就可以获取到FormState
呢?二者是怎么关联起来的呢?现在就来一探究竟。
先来看看GlobalKey
的currentState
方法的具体实现:
T get currentState {
//当前的Element对象
final Element element = _currentElement;
//检测是否是SatefulElement对象
if (element is StatefulElement) {
final StatefulElement statefulElement = element;
//获取StatefulElement对象的State对象
final State state = statefulElement.state;
//如果状态匹配,则返回对应的T
if (state is T)
return state;
}
return null;
}
//_currentElement是一个map集合Map<GlobalKey, Element>
//该集合以GlobalKeyweight对象,其值保存的是Element。
Element get _currentElement => _registry[this];
在GlobalKey
内部有一个静态的的_registry Map
集合,该集合以GlobalKey
为key,以Element
为value;其提供的currentState
方法就是以GlobalKey
对象为Key
获取对应的StatefulElement
对象,然后从StatefulElement.state
里获取具体的值FormState
,那么什么时候往_registry 集合里填充数据呢?通过Fultter之Element和Widget对应关系解析我们知道一个Element
在创建之后会调用mount
方法:
void mount(Element parent, dynamic newSlot) {
///省略部分代码
if (widget.key is GlobalKey) {
final GlobalKey key = widget.key;
//将Element对象注册进来
key._register(this);
}
}
//GlobalKey的_register方法。
void _register(Element element) {
_registry[this] = element;
}
可以发现在mount方法将我们创建的Element
注入到GlobalKey
的静态map集合中去!所以GlobalKey
的作用就是:*持有当前Widget
的Element
对象,因此通过GlobalKey
对象可以获取到当前StatefulWidget
的StatefullElement
,在通过StatefullElement
获取State
状态对象,从而操控State
的相关方法。比如FormState
的validate()方法进行非空校验。
事实上我们还可以使用Form.of(context)
方法也可以获到FormState
对象,然后调用validate方法完成TextFormField
的非空校验,其中原理,详细解析见Flutter之实战InheritedWidget详解
本篇博文更新于2021年12月7号。