博主打算通过登录功能来总结一下Flutter的相关知识点。为什么选择登陆功能呢?因为登录功能逻辑简单,很好抽象,功能也很普遍,这也是博主以此为突破口写android MVC和MVP探讨的原因。本系列应该不长,初步估计也就三篇左右,通过层层递进,慢慢地会勾勒出一个完整的登陆功能的demo,demo的源码点此可得。俗话说站在巨人的肩膀上成长的更快,当然这个俗话是我瞎篇水字数的,本系列博文的重要参考资料就是alibaba/flutter-go。另外本篇博文借着登录功能来详细解读下Flutter中GlobalKey是个什么玩意,闲言少数,书归正转。
在正式开始阅读本文之前,希望读者阅读下博主的Flutter之BuilderContext和Widget关系浅析和Fultter之Element和Widget对应关系这两篇博文,这是本篇博文的理论知识储备。通过这两篇博文你可以了解到:
1、Widget和Element的对应关系
2、Widget和Element的初始化时机
3、Flutter的BuildContext到底是什么玩意
4、StatefulWidget的state跟StatefulElement之间的联系
Form
和TextFormFiled
的简单使用登录肯定要有输入用户名和密码的输入框,在Flutter
中我们只用Form
表单+TextFormField
的形式加以实现,听起来就像是HTML的Form+input。现在就来讲讲Form
和TextFormField
的简单使用,demo中很low很原始的界面如下:
然后我们在不输入任何字符的情况下点击submit按钮,效果如下所示:
上图布局的代码如下所示:
布局代码很简单,楼主简单的把Form
源码中官方给的例子拿过来直接运行了一下,但是例子简单涉及到的知识点可不简单。看上图代码中的三个红色矩形框锁圈住的部分,我们首先使用GlobalKey
(该对象的继承结构GlobalKey
),其存的value就是一个FormState
。然后我们将此key通过设置为Form
的key,最后再点击Sbumit按钮的时候,通过key.currentState.validate
对输入框TextFormField
进行非空验证。需要注意的是代码中的currentState其类型就是FormState
。
所以本文的核心问题来了:Widget
的key有什么用?GlobalKey
又是什么,其作用是来干什么的!
本篇博文所要讲解的重点之一就是GlobalKey
和Flutter
的State
。
Flutter
中的state
在Flutter中有大致两类的Widget,一个是StatelessWidget,一个就是StatefulWidget;所以咱们这边要谈论的就是StatefulWidget!,先来看看其源码:
abstract class StatefulWidget extends Widget {
//Key是个options的,可以设置也可以不设置
const StatefulWidget({ Key key }) : super(key: key);
@override
StatefulElement createElement() => StatefulElement(this);
@protected
State createState();
}
如上所示StatefulWidget
有一个createState()
方法,该方法在createElement()
调用的时候调用.且我们可以通过构造器传一个key!且StatefulElement
中持有一个State
的变量,该变量就是createState()
方法返回的State对象!(详见Fultter之Element和Widget对应关系),有代码为证:
StatefulElement(StatefulWidget widget)
: _state = widget.createState(),//在构造器里面调用createState
super(widget)
}
@override
Widget build() => state.build(this);
可以看出Element
持有了_state
变量来引用StatefulWidget
所创建的state
。所以问题来了:上文中为什么通过GlobalKey.currentState
就可以获取到FormState
呢?二者是怎么关联起来的呢?现在就来一探究竟。
先来看看GlobalKey
的currentState
方法的具体实现:
T get currentState {
final Element element = _currentElement;
if (element is StatefulElement) {
final StatefulElement statefulElement = element;
final State state = statefulElement.state;
if (state is T)
return state;
}
return null;
}
//从map集合里以GlobalKey为key获取Element对象。
Element get _currentElement => _registry[this];
关于上面这段代码咱们先从第一行解析Element element = _currentElement
,这个_currentElement
是什么呢?回答这个问题之前就需要看看GlobalKey
的整体数据结构了:
abstract class GlobalKey<T extends State<StatefulWidget>> extends Key {
//一个静态的变量
static final Map<GlobalKey, Element> _registry = <GlobalKey, Element>{};
看到没有在GlobalKey
内部有一个静态的的Map
集合,该集合以GlobalKey
为key,以Element
为value;所以说GlobalKey
之所以称之为Global
,完全是因为这个静态map集合的原因。所以什么时候往_registry 集合里填充数据呢?回答这个问题之前,咱们书接上文继续解析currentState
方法!
在拿到Element
对象之后,要对其类型进行判断,因为只有StatefulWidget
或者或者说StatefulElement
,将其转换成StatefulElement
之后就可以获取到其中的state
了。上文说过,这个state对象其实就是StatefulWidget
的createState()
得到的!
现在再来回答GlobalKey
在什么时候往_registry 集合里填充数据?这个问题,通过Fultter之Element和Widget对应关系解析我们知道一个Element
在创建之后会调用mount
方法:
Element inflateWidget(Widget newWidget, dynamic newSlot) {
//省略部分更新Widget的逻辑。
//创建childWidget的具体Element对象
final Element newChild = newWidget.createElement();
//继续调用childElement的mount方法。
newChild.mount(this, newSlot);
return newChild;
}
问题的玄机就在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的作用也可以很清晰的知道了:就是持有当前StatefulWidget
的StatefulElement
对象,我们通过此对象可以获取到当前StatefulWidget
的State
,从而操控State
的方法,比如FormState
的validate()方法进行非空校验。Flutter随着state的改变,确切的说是随着 setState(() { })方法的调用会调用build方法刷新页面,所以我们可以通过GlobalKey拿到最新的state状态。
回到文章开头,我们在点击Submit的时候有_formKey.currentState.validate()
这么一段代码,对TextFormField的内容进行非空校验:
final _formKey = GlobalKey<FormState>();
Widget _createSubmitButton() {
return RaisedButton(
onPressed: () {
if (_formKey.currentState.validate()) {///点击时开始非空验证
}
},
child: Text('Submit'),
);
}
到此为止这个FormState我们就可以推断出是Form的createState方法返回的东东,有码可证:
class Form extends StatefulWidget {
const Form({
Key key,
@required this.child,
}) ;
@override
FormState createState() => FormState();
static FormState of(BuildContext context) {
final _FormScope scope = context.inheritFromWidgetOfExactType(_FormScope);
return scope?._formState;
}
}
注意到没有,博主故意留了一个静态的of(BuildContext context)
方法,通过此方法可以我们也可以拿到FormState
对象啊,那为什么在此处还要使用GlobalKey
呢?直接使用of方法获取FormState
对象就是了!当然可以,详细解析见Flutter之实战InheritedWidget详解