Flutter中GlobalKey的用法

GlobalKey的作用

先说结论,后文会分析此结论的由来: 每个Widget都对应一个Element,我们可以直接对Widget进行操作,但是无法直接操作Widget对应的Element。而GlobalKey就是那把直接访问Element的钥匙。通过GlobalKey可以获取到Widget对应的Element,比如获取StatelessElementStatefulElement. 比如如果获取到了StatefullElement,那么我们就可以获取StatefulElementState对象。
 

下面我们就以Form表单为例来分析GlobalKey为什么可以获取Widget对应的Element

GlobalKey实战举例

登录肯定要有输入用户名和密码的输入框,在Flutter中我们只用Form表单+TextFormField的形式加以实现。现在就来讲讲FormTextFormField的简单使用,demo中登录界面如下:

在这里插入图片描述



然后我们在不输入任何字符的情况下点击submit按钮,效果如下所示:
 

 在这里插入图片描述

 
上图布局的代码如下所示:

import 'package:flutter/material.dart';

//使用GlobalKey来使用Form
class FormUseGlobalKeyDemo extends StatefulWidget {
  @override
  _FormState createState() {
    return _FormState();
  }
}


class _FormState extends State<FormUseGlobalKeyDemo> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("使用GlobalKey的Form表单"),
      ),
      body: _createForm(),
    );
  }

  final _formKey = GlobalKey<FormState>();
  Widget _createForm() {
    return Form(
        key: _formKey,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            TextFormField(
              validator: (value) {///输入字符校验
                if (value.isEmpty) {
                  return '请输入文字';
                }
                return null;
              },
            ),
            _createSubmitButton(),///创建submit按钮
          ],
        ),
    );
  }

  Widget _createSubmitButton() {
    return RaisedButton(
      onPressed: () {
        Form.of(context);
        if (_formKey.currentState.validate()) {///点击时开始非空验证
            print('验证通过');
          }
      },
      child: Text('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呢?二者是怎么关联起来的呢?现在就来一探究竟。

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

  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的作用就是:*持有当前WidgetElement对象,因此通过GlobalKey对象可以获取到当前StatefulWidgetStatefullElement,在通过StatefullElement获取State状态对象,从而操控State的相关方法。比如FormState的validate()方法进行非空校验

事实上我们还可以使用Form.of(context)方法也可以获到FormState对象,然后调用validate方法完成TextFormField的非空校验,其中原理,详细解析见Flutter之实战InheritedWidget详解

完整代码:https://github.com/guoyanqiu/flutter_login

猜你喜欢

转载自blog.csdn.net/jdsjlzx/article/details/123577341