Flutter之GlobalKey详解

博主打算通过登录功能来总结一下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之间的联系


FormTextFormFiled的简单使用

登录肯定要有输入用户名和密码的输入框,在Flutter中我们只用Form表单+TextFormField的形式加以实现,听起来就像是HTML的Form+input。现在就来讲讲FormTextFormField的简单使用,demo中很low很原始的界面如下:
Flutter之GlobalKey详解_第1张图片
然后我们在不输入任何字符的情况下点击submit按钮,效果如下所示:
Flutter之GlobalKey详解_第2张图片
上图布局的代码如下所示:
Flutter之GlobalKey详解_第3张图片
布局代码很简单,楼主简单的把Form源码中官方给的例子拿过来直接运行了一下,但是例子简单涉及到的知识点可不简单。看上图代码中的三个红色矩形框锁圈住的部分,我们首先使用GlobalKey (该对象的继承结构GlobalKey> extends Key),其存的value就是一个FormState。然后我们将此key通过设置为Form的key,最后再点击Sbumit按钮的时候,通过key.currentState.validate对输入框TextFormField进行非空验证。需要注意的是代码中的currentState其类型就是FormState

所以本文的核心问题来了:Widget的key有什么用?GlobalKey又是什么,其作用是来干什么的!
本篇博文所要讲解的重点之一就是GlobalKeyFlutterState


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 _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呢?二者是怎么关联起来的呢?现在就来一探究竟。

先来看看GlobalKeycurrentState方法的具体实现:

  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对象其实就是StatefulWidgetcreateState()得到的!

现在再来回答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的作用也可以很清晰的知道了:就是持有当前StatefulWidgetStatefulElement对象,我们通过此对象可以获取到当前StatefulWidgetState,从而操控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详解

你可能感兴趣的:(Flutter实战)