Flutter中Globalkey和Get是如何获取BuildContext的
前言
最近闲下来在回顾知识点的时候,发现用了很久的Get.context却并不知道它的具体实现逻辑,和GlobalKey获取的BuildContext的方式又有什么共同点和差异点。今天就来分析一下它们。
GlobalKey的实现
@optionalTypeArgs
abstract class GlobalKey<T extends State<StatefulWidget>> extends Key {
factory GlobalKey({ String? debugLabel }) => LabeledGlobalKey<T>(debugLabel);
const GlobalKey.constructor() : super.empty();
Element? get _currentElement => WidgetsBinding.instance.buildOwner!._globalKeyRegistry[this];
BuildContext? get currentContext => _currentElement;
Widget? get currentWidget => _currentElement?.widget;
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;
}
}
GlobalKey是一个抽象类,我们创建的GlobalKey实际返回的是LabeledGlobalKey,这里我们不去探究。
GlobalKey内部实现非常简单,提供了三个getter,这里我们要探究的是currentContext。
currentContext从何而来
Element? get _currentElement => WidgetsBinding.instance.buildOwner!._globalKeyRegistry[this];
BuildContext? get currentContext => _currentElement;
可以看到currentContext获取的是_currentElement,_currentElement又是从_globalKeyRegistry中获取的传入当前this对象。
class BuildOwner {
final Map<GlobalKey, Element> _globalKeyRegistry = <GlobalKey, Element>{};
void _registerGlobalKey(GlobalKey key, Element element) {
_globalKeyRegistry[key] = element;
}
void _unregisterGlobalKey(GlobalKey key, Element element) {
if (_globalKeyRegistry[key] == element) {
_globalKeyRegistry.remove(key);
}
}
}
可以看到在BuildOwner关于_globakKeyRegistry的实现也很简单,一个GlobalKey做key,Element做value的map,提供了添加与移除的方法。这两个方法又是何时调用的呢。
GlobalKey添加、取消
我们都知道Globalkey是作为key传入Widget的,我们随便找一个Widget一探究竟。
abstract class StatefulWidget extends Widget {
const StatefulWidget({ super.key });
}
abstract class Widget extends DiagnosticableTree {
const Widget({ this.key });
final Key? key;
}
我们在Widget传入的key最终会赋值给Widget,而Widget只是作为配置项最终创建的是Element,所以我们进入Element查看。
abstract class Element extends DiagnosticableTree implements BuildContext {
void mount(Element? parent, Object? newSlot) {
if (parent != null) {
_owner = parent.owner;
}
final Key? key = widget.key;
if (key is GlobalKey) {
owner!._registerGlobalKey(key, this);
}
}
void unmount() {
final Key? key = _widget?.key;
if (key is GlobalKey) {
owner!._unregisterGlobalKey(key, this);
}
}
去掉无关代码,ELement中添加和移除GlobalKey的地方有两处,mount挂载和unmount取消挂载,只有传入的key为GlobalKey时才会执行。
abstract class GlobalKey<T extends State<StatefulWidget>> extends Key {
Element? get _currentElement => WidgetsBinding.instance.buildOwner!._globalKeyRegistry[this];
BuildContext? get currentContext => _currentElement;
}
再回过头来看GlobalKey内部实现,就能明白GlobakKey是如何拿到当前当前BuildContext的了。
Get.context的实现
extension GetNavigation on GetInterface {
GlobalKey<NavigatorState> get key => _getxController.key;
BuildContext? get context => key.currentContext;
static GetMaterialController _getxController = GetMaterialController();
}
点击查看Get.context源码,我们可以找到上面三行代码,可以看到Get获取context的方式也是用的GlobalKey,它只是对这个方式做了一层封装供我们使用。
这里的key是从GetMaterialController中获取的,接下来我们来看看在GetMaterialController中key是怎么定义的。
Get中GlobalKey是如何创建的
class GetMaterialController extends SuperController {
var _key = GlobalKey<NavigatorState>(debugLabel: 'Key Created by default');
GlobalKey<NavigatorState> get key => _key;
GlobalKey<NavigatorState>? addKey(GlobalKey<NavigatorState> newKey) {
_key = newKey;
return key;
}
}
在GetMaterialController中有一个默认的key,同时也能调用addKey替换默认的key,这个默认的key又是何时生效的呢。
GlobakKey如何起作用
我们把目光放回到main.dart中,再使用了Get我们都会把MaterialApp替换成GetMaterialApp,而Get对MaterialApp对其做一层封装就是为了执行自己的一系列操作逻辑。
class GetMaterialApp extends StatelessWidget {
final GlobalKey<NavigatorState>? navigatorKey;
const GetMaterialApp({
Key? key,
this.navigatorKey,
});
@override
Widget build(BuildContext context) => GetBuilder<GetMaterialController>(
builder: (_) =>
MaterialApp(
navigatorKey: (navigatorKey == null
? Get.key
: Get.addKey(navigatorKey!)),
)
}
可以看到,在Get中如果我们传入了navigatorKey就调用addKey替换它的默认key,如果没有传入就使用Get的默认key。
看到这里我们也就能明白Get.context是如何起作用和获取到了。
总结
GloblKey最终赋值给Widget、在对应的Element中调用BuildOwner进行添加和取消GlobalKey获取到的key是在BuildOwner中添加时传入的当前ElementGet中有默认的GlobalKey,在GetMaterialApp中如果我们传入了navigatorKey,就会替代默认的keyGet.context原理就是GlobalKey,Get只是对这种方式的一种封装
关于BuildOwner的知识如果有不了解的,Flutter框架分析 -- runApp初始化里面有对WidgetBinding和BuildOwner有的介绍。
转载自:https://juejin.cn/post/7221359467618893884