在Flutter中Widjet,State,BuildContext,InheritedWidget在整个小部件里都是非常重要的概念。如何自定义,如何使用都需要对这几个概念十分了解。
BuildContext
BuildContext只不过是对构建的所有窗口小部件的树结构中的窗口小部件的位置的引用。简而言之,将BuildContext视为Widgets树的一部分,Widget将附加到此树。一个BuildContext只属于一个小部件。
如果窗口小部件“A”具有子窗口小部件,则窗口小部件“A”的BuildContext将成为直接子窗口BuildContexts的父BuildContext。很明显BuildContexts是链接的,并且正在组成BuildContexts树(父子关系)。
BuildContext可见性(简化语句):
“ Something ”仅在其自己的BuildContext或其父BuildContext的BuildContext中可见。
比如一个例子是,考虑Scaffold> Center> Column> Text,我希望从最后面的Text找到最顶端的Widjet:
context.ancestorWidgetOfExactType(Scaffold)=>通过从Text上下文转到树结构来返回第一个Scaffold。
从父BuildContext,也可以找到一个后代(=子)Widget,但不建议这样做
在上文总结了State,那么State与BuildContext有啥关系呢?
State和BuildContext之间的关系
对于有状态窗口小部件,状态与BuildContext关联。此关联是永久性的 ,State对象永远不会更改其BuildContext。即使可以在树结构周围移动Widget BuildContext,State仍将与该BuildContext保持关联。当State与BuildContext关联时,State被视为已挂载。
由于State对象与BuildContext相关联,这意味着State对象不能(直接)通过另一个BuildContext访问!
State链接到一个BuildContext,BuildContext链接到Widget的一个实例。
Widget唯一标识 - key
在Flutter中,每个Widget都是唯一标识的。这个唯一标识由构建/渲染时的框架定义。
此唯一标识对应于可选的Key参数。如果省略,Flutter将为您生成一个。
在某些情况下,您可能需要强制使用此密钥,以便可以通过其密钥访问窗口小部件。为此,您可以使用以下帮助程序之一:GlobalKey ,LocalKey,UniqueKey 或ObjectKey。
// GlobalKey确保关键是在整个应用程序唯一的。强制使用Widget的唯一标识:
GlobalKey myKey = new GlobalKey();
...
@override
Widget build(BuildContext context){
return new MyWidget(
key: myKey
);
}
有时,父窗口小部件可能需要访问其直接子节点的状态才能执行特定任务。在这种情况下,要访问这些直接子项State,您需要了解它们。我们先看看以下列子:让我们考虑一个基本示例,当用户点击按钮时显示SnackBar。由于SnackBar是Scaffold的子Widget,它不能直接被Scaffold身体的任何其他孩子访问(还记得上下文的概念及其层次结构/树结构吗?)。因此,访问它的唯一方法是通过ScaffoldState,它公开一个公共方法来显示SnackBar。
import 'package:flutter/material.dart';
class SnackBarDemo extends StatefulWidget{
@override
State createState() {
return new SnackState();
}
}
class SnackState extends State{
final GlobalKey _scaffoldKey = new GlobalKey();
@override
Widget build(BuildContext context){
return new Scaffold(
key: _scaffoldKey,
appBar: new AppBar(
title: new Text('SnackBarDemo'),
),
body: new Center(
child: new RaisedButton(
child: new Text('Hit me'),
onPressed: (){
_scaffoldKey.currentState.showSnackBar(
new SnackBar(
content: new Text('This is the Snackbar...'),
)
);
}
),
),
);
}
}
InheritedWidget
在说这个部件的时候,先来看看这个一个需求:如何在子Widget中获取到另外一个Widget的state的属性?如下代码:
import 'package:flutter/material.dart';
class OneWidget extends StatefulWidget {
OneState one;
@override
State createState() {
one = new OneState();
return one;
}
}
class OneState extends State {
// 私有 暴露一些getter / setter
Color _color = Colors.red;
Color get color => _color;
@override
Widget build(BuildContext context) {
return new Center(
child: new Tow(),
);
}
}
class Tow extends StatelessWidget {
@override
Widget build(BuildContext context) {
final OneWidget widget = context.ancestorWidgetOfExactType(OneWidget);
final OneState state = widget?.one;
return new Container(
color: state == null ? Colors.blue : state.color,
);
}
}
虽然实现了,但子窗口小部件如何知道它何时需要重建?在这个解决方案,它不知道。它必须等待重建才能刷新其内容,这不是很方便。下面将讨论Inherited Widget的概念,它可以解决这个问题。
InheritedWidget允许在窗口小部件树中有效地传播(和共享)信息:InheritedWidget是一个特殊的Widget,您可以将其作为另一个子树的父级放在Widgets树中。该子树的所有小部件都必须能够与该InheritedWidget公开的数据进行交互。他的用法其实在很多系统的API中常见比如:Theme.of(context).textTheme,MediaQuery.of(context).size
class ChildWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final MyInheritedWidget inheritedWidget = MyInheritedWidget.of(context);
return new Text('我是测试',style: Theme.of(context).textTheme.title,);
}
}
inherited widget就像对其他的Widget的一个实现或者说是补充,就像Theme.of(context)返回你能拿到一个ThemeData,并使用其内部自定义的属性改变你当前widget的显示效果。那么如何体使用?可以看看下面列子:
import 'package:flutter/material.dart';
// 共享的数据
class InheritedData{
final String data;
const InheritedData(this.data);
}
// MyInheritedWidget作为所有widget的根旨在“共享”所有小部件(与子树的一部分)中的某些数据。
class MyInheritedWidget extends InheritedWidget {
InheritedData data; // 共享的数据
MyInheritedWidget({
Key key,
@required Widget child,
this.data,
}) : super(key: key, child: child);
// 暴露给外部以便获得MyInheritedWidget,允许所有子窗口小部件获取最接近上下文的MyInheritedWidget的实例
static MyInheritedWidget of(BuildContext context) {
return context.inheritFromWidgetOfExactType(MyInheritedWidget);
}
// 用于告诉InheritedWidget是否必须将通知传递给所有子窗口小部件(已注册/已订阅),如果对数据应用了修改
@override
bool updateShouldNotify(MyInheritedWidget oldWidget) =>
data != oldWidget.data;
}
class ChildWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 子Widget就可以拿到父Widget的数据
final MyInheritedWidget inheritedWidget = MyInheritedWidget.of(context);
final InheritedData data = inheritedWidget.data;
print("data的值为:"+data.data);
return new Text(data.data);
}
}
// 父容器
class ParentWidget extends StatelessWidget {
InheritedData data;
@override
Widget build(BuildContext context) {
// 构造器对数据容器进行初始化操作,保证后面拿到的data不是空
data = new InheritedData("来打我啊");
return new MyInheritedWidget(data: data,child: new ChildWidget());
}
}
上面InheritedWidget其实可以看出是一个公共的数据共享空间,之前说她还有更新Widget功能,可以看下面示例,点击+和-两个按钮实现InheritedWidget中数据的增加与减少,并且用一个Widget显示InheritedWidget中数据的增加情况:
import 'package:flutter/material.dart';
/**
* 共享的model
*/
class InheritedModel {
final int count;
InheritedModel(this.count);
}
/**
* 定义InheritedWidget
*/
class TestInheritedWidget extends InheritedWidget {
// 共享的数据
final InheritedModel mode;
// 自增的方法
final Function() increment;
// 自减的方法
final Function() reduce;
TestInheritedWidget({
Key key,
@required this.mode,
@required this.increment,
@required this.reduce,
@required Widget child,
}) : super(key: key, child: child);
//是否重建widget就取决于数据是否相同
@override
bool updateShouldNotify(TestInheritedWidget oldWidget) =>
mode != oldWidget.mode;
static TestInheritedWidget of(BuildContext context) =>
context.inheritFromWidgetOfExactType(TestInheritedWidget);
}
/**
* 自增的widget
*/
class IncrementWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final testInheritedWidget = TestInheritedWidget.of(context);
final mode = testInheritedWidget.mode;
print('IncrementWidget中的count:${mode.count}');
return new Padding(
padding: const EdgeInsets.only(left: 10.0, top: 10.0, right: 10.0),
child: new RaisedButton(
onPressed: testInheritedWidget.increment,
textColor: Colors.red,
child: new Text("+")),
);
}
}
/**
* 自减的widget
*/
class ReduceWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final testInheritedWidget = TestInheritedWidget.of(context);
final mode = testInheritedWidget.mode;
print('ReduceWidget中的count:${mode.count}');
return new Padding(
padding: const EdgeInsets.only(left: 10.0, top: 10.0, right: 10.0),
child: new RaisedButton(
onPressed: testInheritedWidget.reduce,
textColor: Colors.green,
child: new Text("-")),
);
}
}
/**
* 展示数据更新的Widget
*/
class ShowCountWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final testInheritedWidget = TestInheritedWidget.of(context);
final modeData = testInheritedWidget.mode;
print('ShowCountWidget中的count:${modeData.count}');
return new Padding(
padding: const EdgeInsets.only(left: 10.0, top: 10.0, right: 10.0),
child: new Text(
'当前count:${modeData.count}',
style: new TextStyle(fontSize: 20.0),
),
);
}
}
/**
* 因为需要在state中改变不同的增减操作,所以选择StatefulWidget
*/
class InheritedWidgetTestContainer extends StatefulWidget {
@override
State createState() {
return new _InheritedWidgetTestContainerState();
}
}
class _InheritedWidgetTestContainerState
extends State {
InheritedModel model;
_initData() {
model = new InheritedModel(0);
}
@override
void initState() {
_initData();
super.initState();
}
_incrementCount() {
setState(() {
model = new InheritedModel(model.count + 1);
});
}
_reduceCount() {
setState(() {
model = new InheritedModel(model.count - 1);
});
}
@override
Widget build(BuildContext context) {
return new TestInheritedWidget(
mode: model,
increment: _incrementCount,
reduce: _reduceCount,
child: new Scaffold(
appBar: new AppBar(
title: new Text('我是测试widget更新'),
),
body: new Column(
children: [
new IncrementWidget(),
new ShowCountWidget(),
new ReduceWidget(),
],
),
)
);
}
}
上面的代码中其实就是以TestInheritedWidget作为数据共享的根节点,在其节点下的所有Widget在数据发现变化的时候都会重新更新。那么我们有一个需求,就是在一个节点下有A,B,C三个Widget,我希望B在A点击的时候状态会改变,但是A,C不改变如何实现:
import 'package:flutter/material.dart';
class Item {
String reference;
Item(this.reference);
}
// _MyInherited是一个InheritedWidget,每次我们通过点击“Widget A”按钮添加一个Item时都会重新创建它
class _MyInherited extends InheritedWidget {
_MyInherited({
Key key,
@required Widget child,
@required this.data,
}) : super(key: key, child: child);
final MyInheritedWidgetState data;
@override
bool updateShouldNotify(_MyInherited oldWidget) {
return true;// 由于data是永远不会改变的,所以这里需要更新子组件的话直接设置为true
}
}
// MyInheritedWidget是一个Widget,其状态包含Items列表。可以通过“(BuildContext context)的静态MyInheritedWidgetState”访问此状态。
class MyInheritedWidget extends StatefulWidget {
MyInheritedWidget({
Key key,
this.child,
}): super(key: key);
final Widget child;
@override
MyInheritedWidgetState createState() => new MyInheritedWidgetState();
static MyInheritedWidgetState of([BuildContext context, bool rebuild = true]){
return (rebuild ? context.inheritFromWidgetOfExactType(_MyInherited) as _MyInherited
: context.ancestorWidgetOfExactType(_MyInherited) as _MyInherited).data;// ancestorWidgetOfExactType:返回给定类型的最近祖先小部件,这个组件创建过一次就不会重新创建
}
}
//MyInheritedWidgetState公开一个getter(itemsCount)和一个方法(addItem),以便它们可以被小部件使用,这是子小部件树的一部分
class MyInheritedWidgetState extends State{
/// List of Items
List- _items =
- [];
/// Getter (number of items)
int get itemsCount => _items.length;
// 这个方法没吊用一次都会重新执行build方法,所以_MyInherited也会重新执行一遍
void addItem(String reference){
setState((){
_items.add(new Item(reference));
});
}
@override
Widget build(BuildContext context){
return new _MyInherited(
data: this,
child: widget.child,
);
}
}
// 每次我们向State添加一个Item时,MyInheritedWidgetState都会重建
class MyTree extends StatefulWidget {
@override
_MyTreeState createState() => new _MyTreeState();
}
class _MyTreeState extends State
{
@override
Widget build(BuildContext context) {
return new MyInheritedWidget(
child: new Scaffold(
appBar: new AppBar(
title: new Text('Title'),
),
body: new Column(
children: [
new WidgetA(),
new Container(
child: new Row(
children: [
new Icon(Icons.shopping_cart),
new WidgetB(),
new WidgetC(),
],
),
),
],
),
),
);
}
}
// WidgetA是一个简单的RaisedButton,当按下它时,从最近的MyInheritedWidget调用addItem方法,它本身是不需要重新更新的,所以我们设计让他不会重新更新
class WidgetA extends StatelessWidget {
@override
Widget build(BuildContext context) {
final MyInheritedWidgetState state = MyInheritedWidget.of(context,false);
print("WidgetA重新更新了~~~");
return new Container(
child: new RaisedButton(
child: new Text('Add Item'),
onPressed: () {
state.addItem('new item');
},
),
);
}
}
// WidgetB是一个简单的文本,显示最接近的MyInheritedWidget级别的项目数
class WidgetB extends StatelessWidget {
@override
Widget build(BuildContext context) {
final MyInheritedWidgetState state = MyInheritedWidget.of(context,true);//这段代码其实就相当于注册订阅,true表示需要接受更新
print("WidgetB重新更新了~~~");
return new Text('${state.itemsCount}');
}
}
class WidgetC extends StatelessWidget {
@override
Widget build(BuildContext context) {
print("WidgetC重新更新了~~~");
return new Text('I am Widget C');
}
}
上面代码详解:说白了就是给订阅的人进行Widget的更新
当子Widget调用MyInheritedWidget.of(context)时,它会调用MyInheritedWidget的以下方法,并传递自己的BuildContext。
static MyInheritedWidgetState of([BuildContext context, bool rebuild = true]){
return (rebuild ? context.inheritFromWidgetOfExactType(_MyInherited) as _MyInherited
: context.ancestorWidgetOfExactType(_MyInherited) as _MyInherited).data;// ancestorWidgetOfExactType:返回给定类型的最近祖先小部件,这个组件创建过一次就不会重新创建
}
在内部,除了简单地返回MyInheritedWidgetState的实例之外,它还将消费者窗口小部件订阅到更改通知。
整个过程如下:
- 调用
MyInheritedWidget.of(context,true)
这个方法本质返回的是MyInheritedWidgetState类。它还将消费者窗口小部件订阅到更改通知。这里核心是A与B传入了context,是通过AB的context获取到继承小部件,而C没有传入。 - 调用MyInheritedWidgetState类中的
void addItem(String reference)
方法,因为setState()
方法,所以会重新执行build方法,最后也就是_MyInherited重新创建,也就是updateShouldNotify执行了。其实就是_MyInherited中的data重新了,里面的数据也就是最新的。它检查是否需要“通知”“使用者”(答案为是) - 迭代整个消费者列表(这里是Widget B)并请求他们重建
- 由于Wiget C不是消费者,因此不会重建。因为C没有传入context获取对应的MyInheritedWidgetState,而且MyInheritedWidgetState也没有重新创建_MyInherited,所以就算C在MyInheritedWidget组件的节点下,也不会接收到更新的通知。
总的来说就是利用StatefulWidget来存储需要改变的数据,然后StatefulWidget的根节点设置为InheritedWidget,然后利用StatefulWidget的state的setState方法重新创建生成新的InheritedWidget节点,最后也就InheritedWidget中定义的新的StatefulWidget变量,然后updateShouldNotify重新调用通知相关订阅的Widget更新。
这里其实就有点像Android中的eventbus订阅了就能接收到通知类似。在flutter中类似的框架有Redux,这个后面再总结。