在 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,里边有比较具体介绍。
好了,今日共享就到这儿,感谢您的阅读,记得重视加点赞哦。