Flutter的BuildContext介绍

BuildContxt


BuildContext objects are actually Element objects. The BuildContext interface is used to discourage direct manipulation of Element objects.

简单来说,BuildContext就是Element。Flutter不鼓励直接操作Element,所以以BuildContext接口的姿态对外暴露


Element


那Element是什么呢?

 Element(Widget widget)
    : assert(widget != null),
      _widget = widget;

通过构造函数可以知道Element持有对widget的引用,所以一个BuildContext对应一个widget。

Element的官方定义如下:

An instantiation of a Widget at a particular location in the tree.

我们知道widget以树形结构组织UI,element就是widget在树上的真是代表。widget可以复用客户重建,widget树是虚的、短暂的,Element树是实实在在的,widget树的变化会反应到element树上

Element有两个实现

  1. ComponentElement
  2. RenderObjectElement

ComponentElement

An Element that composes other Elements.
Rather than creating a RenderObject directly, a ComponentElement creates RenderObjects indirectly by creating other Elements.

ComponentElement用来组装其他element,不直接生成RenderObject

ComponentElement又分为三种:ProxyElementStatefulElementStatelessElement,分别对应ProxyWidgetInheritedWidget的父类)、StatefulWidgetStatelessWidget

这三种Widget不负责render,其他负责render的widget都继承自RenderObjectWidget

RenderObjectElement

An Element that uses a RenderObjectWidget as its configuration.
RenderObjectElement objects have an associated RenderObject widget in the render tree, which handles concrete operations like laying out, painting, and hit testing.

RenderObjectElement负责视图树的渲染,对应RenderObjectWidget

以上,总结起来Element的特点:

  • 和Widget一样组成树形结构
  • 一个Element对应一个widget
  • 分为ComponentElement和RenderObjectElement
  • RenderObjectElement会生成RenderObject

build方法中的BuildContext


我们平常碰到最多的BuildContext的场景就是build方法

Each widget has its own BuildContext, which becomes the parent of the widget returned by the StatelessWidget.build or State.build function. (And similarly, the parent of any children for RenderObjectWidgets.)

简单来说,StatelessWidget或者State的build方法中返回的BuildContext,就是当前实现build方法的Widget的element,自然也就是build方法中返回的所有widget的父element。

通过以下代码debug可以看到父子关系:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp( // ⑤
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) { // ④
    return Scaffold( // ③
      body: Center( // ②
        child: Text( // ① 
          '$_counter',
          style: Theme.of(context).textTheme.display1,
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}
  • ①的Text(=StatelessWidget)的build方法的BuildContext中持有的widget是Text('0'),①的父widget是②的Center
  • ②的Center(=RenderObjectWidget)的createRenderObject方法中BuildContext的父是MediaQauery,向上遍历可以找到③的Scaffold
  • ④的BuildContext向上遍历后找到⑤的MaterialApp

BuildContext中有很多可以获取父节点的方法,都是以ancestor或者inherit为前缀,获取子节点的方法只有一个visitChildElements。child设定之前子节点为null


误用Context的错误


下面代码执行时,会产生异常

class _MyHomePageState extends State<MyHomePage> {

  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Text(
          '$_counter',
          style: Theme.of(context).textTheme.display1,
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          Scaffold.of(context).showSnackBar(SnackBar(content: Text('message')));
        },
        child: Icon(Icons.add),
      ),
    );
  }

showSnackBar是Scaffold的方法,通过_MyHomePageState传入的context向上遍历自然找不到Scaffold所以会出错。这是因为没有理清context的父子关系,错用了不正确的context

有两个解决,第一个是为Scaffold指定id(key),并通过其获取currentState

  final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();

   @override
   Widget build(BuildContext context) {
    return Scaffold(
      key: _scaffoldKey,
      body: Center(
        child: Text(
          '$_counter',
          style: Theme.of(context).textTheme.display1,
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
         // Scaffold.of(context).showSnackBar(SnackBar(content: Text('message')));
          _scaffoldKey.currentState.showSnackBar(SnackBar(content: Text('message')));
            _incrementCounter();
        },
        child: Icon(Icons.add),
      ),
    );
  }

第二个是使用Builder,增加一层widget,此时的context中可以找到Scaffold了

@override
   Widget build(BuildContext context) {
    return Scaffold(
      body: ・・・ ,
      floatingActionButton: Builder(builder: (context) {
        return FloatingActionButton(
          onPressed: () {
            Scaffold.of(context).showSnackBar(SnackBar(content: Text('message')));
            _incrementCounter();
          },
          child: Icon(Icons.add),
        );
      }),
发布了116 篇原创文章 · 获赞 15 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/vitaviva/article/details/105475460
今日推荐