博主打算通过登录功能来总结一下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之间的联系
登录肯定要有输入用户名和密码的输入框,在Flutter中我们只用Form表单+TextFormField的形式加以实现,听起来就像是HTML的Form+input。现在就来讲讲Form和TextFormField的简单使用,demo中很low很原始的界面如下: 然后我们在不输入任何字符的情况下点击submit按钮,效果如下所示: 上图布局的代码如下所示: 布局代码很简单,楼主简单的把Form源码中官方给的例子拿过来直接运行了一下,但是例子简单涉及到的知识点可不简单。看上图代码中的三个红色矩形框锁圈住的部分,我们首先使用GlobalKey (该对象的继承结构GlobalKey<T extends State<StatefulWidget>> extends Key),其存的value就是一个FormState。然后我们将此key通过设置为Form的key,最后再点击Sbumit按钮的时候,通过key.currentState.validate对输入框TextFormField进行非空验证。需要注意的是代码中的currentState其类型就是FormState。
所以本文的核心问题来了:Widget的key有什么用?GlobalKey又是什么,其作用是来干什么的! 本篇博文所要讲解的重点之一就是GlobalKey和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<StatefulWidget> _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详解
