首页 > 其他分享 >BuildContext 是什么

BuildContext 是什么

时间:2024-08-28 09:57:34浏览次数:11  
标签:Widget return 什么 BuildContext context child const

在 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 层处理所有逻辑,视图应该只调用模型上的函数,然后在需要时使用新状态 rebuild 或者其它操作,降低了彼此之间的耦合。

需要注意什么?

  1. 作用域问题,确保使用的 BuildContext 在正确的作用域内,即所在的 Widget Tree 中。避免在 Widget Tree 之外的地方使用 BuildContext,否则可能导致运行时错误。
  2. 生命周期问题,BuildContext 的生命周期与相应的 Widget 相关联。当 Widget 被创建时,会创建一个新的 BuildContext 对象,并在 Widget 树中传递。当 Widget 被移除时,相关的 BuildContext 也会被销毁。因此,在保存BuildContext 时,要确保它的生命周期与所需的操作相匹配,避免出现空指针异常
  3. 尽量避免在 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技术实践,感谢您的阅读,记得关注加点赞哦。

标签:Widget,return,什么,BuildContext,context,child,const
From: https://www.cnblogs.com/xiongwei/p/18383982

相关文章

  • 文献翻译什么软件好?推荐5个文献翻译软件给你
    在学术研究的海洋中,文献翻译无疑是探索知识宝库的一把钥匙。面对海量的外文资料,不少学者和研究人员都渴望拥有一款既精准又便捷的翻译工具。然而市场上,虽然翻译软件众多,但真正免费且高效的却屈指可数。今天,就让我们一起探讨文献翻译软件推荐免费有哪些吧,让它们帮助我们快速跨......
  • Kafka Topic 中明明有可拉取的消息,为什么 poll 不到
    开心一刻今天小学女同学给我发消息她:你现在是毕业了吗我:嗯,今年刚毕业她给我发了一张照片,怀里抱着一只大橘猫她:我的眯眯长这么大了,好看吗我:你把猫挪开点,它挡住了,我看不到她:你是sb吗,滚我解释道:你说的是猫呀可消息刚发出,就出现了红色感叹号,并提示:消息已发出,但被对方拒收了kafka......
  • 模拟版图设计工程师要学些什么?从入门到入行,你想知道的都在这里了
    IC模拟版图设计是门槛最低的IC设计方向,最低专科学历即可,其他IC设计大多要求本科以上,研究生学历,0基础小白经过几个月的学习也可以入行。那么,待遇还不低的模拟版图设计工程师入行都要学一些什么?下面我们来聊一聊 版图学习最好有一些工艺的基础,了解MOS的基本工作原理,比如PN结......
  • 11. HashSet的内部实现原理是什么?它如何保证元素不重复?
    HashSet是Java集合框架中的一个实现了Set接口的类,它用于存储不重复的元素。HashSet的内部实际上是基于HashMap来实现的。下面是HashSet的内部实现原理和它如何保证元素不重复的细节。1.HashSet的底层数据结构HashSet内部使用一个HashMap实例来存储元素。在HashSet中,每个添......
  • stm32f103c8t6 程序编译后的 Program Size: Code=xxx RO-data=xxx RW-data=xxx ZI-dat
            之前在裸机跑一些简单的项目内存完全够用,就不会涉及到内存方面的问题。最近在学FreeRTOS时,将大容量的stm32f103rct6代码移植到小容量的stm32f103c8t6上时,就遇到了内存不足的问题,所以才注意到这些东西。    那么在我们编译后看到的这些东西到底......
  • 生动形象的解释下为什么需要进行四次挥手
    四次挥手是TCP(传输控制协议)中用于终止一个已经建立的连接的过程。为什么需要四次挥手呢?让我们通过一个生活中的例子来解释。假设你正在打电话,当通话结束时,你不能直接挂断电话,因为对方可能还有话要说。所以,你会先说“我说完了”,这就相当于TCP中的第一次挥手,告诉对方你已经发......
  • 关于为什么监听effect时开启immediate第一次会返回undefined
    说白了就是源码这样写的,这里再科普下WatchEffectWatchEffect和Watch不同之处:进入页面马上就回调用一下有许多变体,WatchEffectSync等,都是WatchEffectOption下面的属性flush?:'pre'|'post'|'sync';当然可以帮你理解watchEffect的用法和场景。场景描述你提到的场景涉......
  • 当构造与析构的函数体为空,会发生什么?
    析构函数、构造函数用来进行数据的销毁和初始化。那么系统默认生成的构造和析构有什么特点呢?构造函数对于自定义类型,会调用对应的默认构造,内置类型不做处理。当显式定义了无参的默认构造,初始化列表和函数体都为空时,也会在初始化列表阶段调用自定义类的默认构造(所有成员变量......
  • 《JavaEE进阶》----1.<JavaEE进阶可以学到什么>
    本篇博客会讲到一、JavaEE进阶学习内容:1.框架的学习:Spring、SpringBoot、SpringMVC、MyBatis2.大项目实践3.源码阅读二、JavaEE简介B/S架构web开发流程web前端开发(了解)web后端开发(重点)三、什么是框架四、学习编程思维方式(重点:学习建议)学完JavaEE你的收获会持续......
  • 什么领域/方向的产品经理既有发展前景又能做的长久
    前几天我在知乎上回答了一道“目前什么领域的产品经理比较有发展前景?有推荐的课程吗?”的问题,讲得还比较实在,于是在这里也顺便分享一下。TOP1.商业产品经理之所以这个方向能排在第1,我认为有3点理由。第1点:任何互联网企业或者产品,都需要考虑商业变现也就是盈利的问题,即便傲娇......