在 Flutter 中 BuildContext 可太常见了,不管是 StatelessWidget 还是 StatefulWidget 的 build() 函数参数都会带有 BuildContext,如同随处可见,就像咱们的一位老朋友,但似乎又对其知之甚少(熟悉的陌生人),今日咱们再来了解一下这位老朋友 BuildContext,看看它在 Flutter 架构中扮演什么角色,咱们该怎么运用它及运用的时候需求留意什么。
BuildContext 是什么
打开 BuildContext 所在的文档的看到的第一句话便是 A handle to the location of a widget in the widget tree. (翻译过来:小部件树中小部件方位的句柄),啥意思呢?
每一个 Widget 都有自己的 BuildContext,而 BuildContext 代表了 Widget 在 Widget Tree 中的方位,常用于在 Widget Tree 中查找和定位 Widget,或许执行任务,例如导航到其他屏幕、显现对话框、访问主题数据等,如 Theme.of(context)、Navigator.of(context)。
BuildContext 提供对 Widget 和资源的访问,以及对当时 Widget 最近的先人Widget的其他数据的访问。 如每个 Widget 的 build() 函数中运用的 BuildContext 参数,便是 Flutter 框架经过 Widget Tree 向下传递的 BuildContext。
假设现在显现一个对话框。即运用 showDialog() 方法创立对话框,但一起 showDialog() 需求传一个 BuildContext 参数。此刻就能够把当时 Widget 的 BuildContext 传递给此方法以显现对话框,如下面代码:
import 'package:flutter/material.dart';
class BuildContextPage extends StatefulWidget {
const BuildContextPage({Key? key}) : super(key: key);
@override
State<BuildContextPage> createState() => _BuildContextPageState();
}
class _BuildContextPageState extends State<BuildContextPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xffffffff),
body: Center(
child: Column(
children: [
TextButton(
child: const Text('ShowAlert'),
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Dialog Title'),
content: const Text('This is the content of the dialog.'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Close'),
),
],
);
},
);
},
),
],
)));
}
}
怎么运用 BuildContext
一般咱们在运用 BuildContext 前会经过 State 的属性 mounted 来判别再运用,这是因为 State 是依附于 Element 创立,Element 的生命周期和 State 是同步的。如果 Element 毁掉了,那此刻的 mounted 则为 false,再去运用 BuildContext 就会报错,为 true 才能够继续运用,代码如下:
TextButton(
onPressed: () async {
await Future.delayed(const Duration(seconds: 3));
if (!mounted) return;
Navigator.of(context).pop();
},
child: const Text('Close'),
)
在逻辑层运用 BuildContext
有时候咱们在 ViewModel 或许 Bloc 异步执行完成一些操作后,再运用 BuildContext 返回页面或许弹出提示框,如下面的代码:
TextButton(
onPressed: () async {
var success = await model.login(success: true);
if (success) {
Navigator.of(context).pushNamed("");
}
},
child: const Text('Close'),
),
而此刻的 ViewModel 或许 Bloc 没有 BuildContext,一起,如上面代码需求在 UI 展现层来处理与功用相关的逻辑,随着 App 的需求和功用的扩展,有或许会在这儿添加更多逻辑,造成视图层和逻辑层代码耦合在一起,欠好维护。那要在 ViewModel 或许 Bloc 运用 BuildContext 该怎么做呢?
-
创立类
NavigationService,并给添加一个GlobalKey属性。class NavigationService { final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); Future<dynamic>? navigateTo(String routeName) { return navigatorKey.currentState?.pushNamed(routeName); } void goBack() { return navigatorKey.currentState?.pop(); } } -
将
NavigationService注册到get_it容器中。GetIt locator = GetIt.instance; void setupLocator() { locator.registerLazySingleton(() => NavigationService()); } -
将
navigatorKey赋值给程序入口Widget的key。class App extends StatelessWidget { @override Widget build(BuildContext context) { return MultiProvider( providers: [ ChangeNotifierProvider(create: (_) { return AppLanguageProvider(); }), ], builder: (BuildContext context, Widget? child) { return MaterialApp( ... key: locator<NavigationService>().navigatorKey, onGenerateRoute: MyRoutes.router.generator, initialRoute: MyRoutes.root, ..., ); }, ); } } ``` -
修正
LoginViewModel中的代码,异步操作完成后跳转页面。class LoginViewModel extends ChangeNotifier { final NavigationService _navigationService = locator<NavigationService>(); Future<bool> login({bool success = true}) async { /// 模拟网络请求 await Future.delayed(const Duration(seconds: 1)); if (success) { _navigationService.navigateTo(""); return true; } return false; } } -
页面UI层调用。
TextButton( onPressed: () async { await model.login(success: true); }, child: const Text('Close'), ),
这样达到了 ViewModel 层处理所有逻辑,视图应该只调用模型上的函数,然后在需求时运用新状态 rebild 或许其它操作,降低了彼此之间的耦合。
需求留意什么?
-
作用域问题,保证运用的
BuildContext在正确的作用域内,即所在的Widget Tree中。防止在Widget Tree之外的地方运用BuildContext,不然或许导致运行时错误. -
生命周期问题,
BuildContext的生命周期与相应的Widget相关联。当Widget被创立时,会创立一个新的BuildContext目标,并在Widget树中传递。当Widget被移除时,相关的BuildContext也会被毁掉。因而,在保存BuildContext时,要保证它的生命周期与所需的操作相匹配,防止呈现空指针反常。 -
尽量防止在
build()函数中使用BuildContext获取MediaQuery的size和padding做大量核算的操作,如下面代码:@override Widget build(BuildContext context) { var size = MediaQuery.of(context).size; var padding = MediaQuery.of(context).padding; var width = size.width / 2; var height = size.width / size.height * (40 - padding.bottom); return Container( color: Colors.amber, width: width, height: height, ); }
上面这种用法或许会导致键盘在弹出的时候,尽管当时页面并没有完全展现,但是也会导致你的控件不断从头核算然后呈现卡顿。这儿参考了文章 Flutter小技巧之优化运用的 BuildContext,里边有比较具体介绍。
好了,今日共享就到这儿,感谢您的阅读,记得重视加点赞哦。

