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(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有两个实现
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又分为三种:ProxyElement
、StatefulElement
、StatelessElement
,分别对应ProxyWidget
(InheritedWidget
的父类)、StatefulWidget
、StatelessWidget
。
这三种Widget不负责render,其他负责render的widget都继承自RenderObjectWidget
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的特点:
我们平常碰到最多的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
MaterialApp
BuildContext
中有很多可以获取父节点的方法,都是以ancestor
或者inherit
为前缀,获取子节点的方法只有一个visitChildElements
。child设定之前子节点为null
下面代码执行时,会产生异常
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),
);
}),