在Flutter中Widjet,State,BuildContext,InheritedWidget在整個(gè)小部件里都是非常重要的概念。如何自定義,如何使用都需要對(duì)這幾個(gè)概念十分了解。
BuildContext
BuildContext只不過(guò)是對(duì)構(gòu)建的所有窗口小部件的樹(shù)結(jié)構(gòu)中的窗口小部件的位置的引用。簡(jiǎn)而言之,將BuildContext視為Widgets樹(shù)的一部分,Widget將附加到此樹(shù)。一個(gè)BuildContext只屬于一個(gè)小部件。
如果窗口小部件“A”具有子窗口小部件,則窗口小部件“A”的BuildContext將成為直接子窗口BuildContexts的父BuildContext。很明顯BuildContexts是鏈接的,并且正在組成BuildContexts樹(shù)(父子關(guān)系)。
BuildContext可見(jiàn)性(簡(jiǎn)化語(yǔ)句):
“ Something ”僅在其自己的BuildContext或其父BuildContext的BuildContext中可見(jiàn)。
比如一個(gè)例子是,考慮Scaffold> Center> Column> Text,我希望從最后面的Text找到最頂端的Widjet:
context.ancestorWidgetOfExactType(Scaffold)=>通過(guò)從Text上下文轉(zhuǎn)到樹(shù)結(jié)構(gòu)來(lái)返回第一個(gè)Scaffold。
從父BuildContext,也可以找到一個(gè)后代(=子)Widget,但不建議這樣做
在上文總結(jié)了State,那么State與BuildContext有啥關(guān)系呢?
State和BuildContext之間的關(guān)系
對(duì)于有狀態(tài)窗口小部件,狀態(tài)與BuildContext關(guān)聯(lián)。此關(guān)聯(lián)是永久性的 ,State對(duì)象永遠(yuǎn)不會(huì)更改其BuildContext。即使可以在樹(shù)結(jié)構(gòu)周圍移動(dòng)Widget BuildContext,State仍將與該BuildContext保持關(guān)聯(lián)。當(dāng)State與BuildContext關(guān)聯(lián)時(shí),State被視為已掛載。
由于State對(duì)象與BuildContext相關(guān)聯(lián),這意味著State對(duì)象不能(直接)通過(guò)另一個(gè)BuildContext訪問(wèn)!
State鏈接到一個(gè)BuildContext,BuildContext鏈接到Widget的一個(gè)實(shí)例。
Widget唯一標(biāo)識(shí) - key
在Flutter中,每個(gè)Widget都是唯一標(biāo)識(shí)的。這個(gè)唯一標(biāo)識(shí)由構(gòu)建/渲染時(shí)的框架定義。
此唯一標(biāo)識(shí)對(duì)應(yīng)于可選的Key參數(shù)。如果省略,F(xiàn)lutter將為您生成一個(gè)。
在某些情況下,您可能需要強(qiáng)制使用此密鑰,以便可以通過(guò)其密鑰訪問(wèn)窗口小部件。為此,您可以使用以下幫助程序之一:GlobalKey ,LocalKey,UniqueKey 或ObjectKey。
// GlobalKey確保關(guān)鍵是在整個(gè)應(yīng)用程序唯一的。強(qiáng)制使用Widget的唯一標(biāo)識(shí):
GlobalKey myKey = new GlobalKey();
...
@override
Widget build(BuildContext context){
return new MyWidget(
key: myKey
);
}
有時(shí),父窗口小部件可能需要訪問(wèn)其直接子節(jié)點(diǎn)的狀態(tài)才能執(zhí)行特定任務(wù)。在這種情況下,要訪問(wèn)這些直接子項(xiàng)State,您需要了解它們。我們先看看以下列子:讓我們考慮一個(gè)基本示例,當(dāng)用戶點(diǎn)擊按鈕時(shí)顯示SnackBar。由于SnackBar是Scaffold的子Widget,它不能直接被Scaffold身體的任何其他孩子訪問(wèn)(還記得上下文的概念及其層次結(jié)構(gòu)/樹(shù)結(jié)構(gòu)嗎?)。因此,訪問(wèn)它的唯一方法是通過(guò)ScaffoldState,它公開(kāi)一個(gè)公共方法來(lái)顯示SnackBar。
import 'package:flutter/material.dart';
class SnackBarDemo extends StatefulWidget{
@override
State<StatefulWidget> createState() {
return new SnackState();
}
}
class SnackState extends State<SnackBarDemo>{
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
@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
在說(shuō)這個(gè)部件的時(shí)候,先來(lái)看看這個(gè)一個(gè)需求:如何在子Widget中獲取到另外一個(gè)Widget的state的屬性?如下代碼:
import 'package:flutter/material.dart';
class OneWidget extends StatefulWidget {
OneState one;
@override
State<StatefulWidget> createState() {
one = new OneState();
return one;
}
}
class OneState extends State<OneWidget> {
// 私有 暴露一些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,
);
}
}
雖然實(shí)現(xiàn)了,但子窗口小部件如何知道它何時(shí)需要重建?在這個(gè)解決方案,它不知道。它必須等待重建才能刷新其內(nèi)容,這不是很方便。下面將討論Inherited Widget的概念,它可以解決這個(gè)問(wèn)題。
InheritedWidget允許在窗口小部件樹(shù)中有效地傳播(和共享)信息:InheritedWidget是一個(gè)特殊的Widget,您可以將其作為另一個(gè)子樹(shù)的父級(jí)放在Widgets樹(shù)中。該子樹(shù)的所有小部件都必須能夠與該InheritedWidget公開(kāi)的數(shù)據(jù)進(jìn)行交互。他的用法其實(shí)在很多系統(tǒng)的API中常見(jiàn)比如: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('我是測(cè)試',style: Theme.of(context).textTheme.title,);
}
}
inherited widget就像對(duì)其他的Widget的一個(gè)實(shí)現(xiàn)或者說(shuō)是補(bǔ)充,就像Theme.of(context)返回你能拿到一個(gè)ThemeData,并使用其內(nèi)部自定義的屬性改變你當(dāng)前widget的顯示效果。那么如何體使用?可以看看下面列子:
import 'package:flutter/material.dart';
// 共享的數(shù)據(jù)
class InheritedData{
final String data;
const InheritedData(this.data);
}
// MyInheritedWidget作為所有widget的根旨在“共享”所有小部件(與子樹(shù)的一部分)中的某些數(shù)據(jù)。
class MyInheritedWidget extends InheritedWidget {
InheritedData data; // 共享的數(shù)據(jù)
MyInheritedWidget({
Key key,
@required Widget child,
this.data,
}) : super(key: key, child: child);
// 暴露給外部以便獲得MyInheritedWidget,允許所有子窗口小部件獲取最接近上下文的MyInheritedWidget的實(shí)例
static MyInheritedWidget of(BuildContext context) {
return context.inheritFromWidgetOfExactType(MyInheritedWidget);
}
// 用于告訴InheritedWidget是否必須將通知傳遞給所有子窗口小部件(已注冊(cè)/已訂閱),如果對(duì)數(shù)據(jù)應(yīng)用了修改
@override
bool updateShouldNotify(MyInheritedWidget oldWidget) =>
data != oldWidget.data;
}
class ChildWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 子Widget就可以拿到父Widget的數(shù)據(jù)
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) {
// 構(gòu)造器對(duì)數(shù)據(jù)容器進(jìn)行初始化操作,保證后面拿到的data不是空
data = new InheritedData("來(lái)打我啊");
return new MyInheritedWidget(data: data,child: new ChildWidget());
}
}
上面InheritedWidget其實(shí)可以看出是一個(gè)公共的數(shù)據(jù)共享空間,之前說(shuō)她還有更新Widget功能,可以看下面示例,點(diǎn)擊+和-兩個(gè)按鈕實(shí)現(xiàn)InheritedWidget中數(shù)據(jù)的增加與減少,并且用一個(gè)Widget顯示InheritedWidget中數(shù)據(jù)的增加情況:
import 'package:flutter/material.dart';
/**
* 共享的model
*/
class InheritedModel {
final int count;
InheritedModel(this.count);
}
/**
* 定義InheritedWidget
*/
class TestInheritedWidget extends InheritedWidget {
// 共享的數(shù)據(jù)
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就取決于數(shù)據(jù)是否相同
@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("-")),
);
}
}
/**
* 展示數(shù)據(jù)更新的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(
'當(dāng)前count:${modeData.count}',
style: new TextStyle(fontSize: 20.0),
),
);
}
}
/**
* 因?yàn)樾枰趕tate中改變不同的增減操作,所以選擇StatefulWidget
*/
class InheritedWidgetTestContainer extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return new _InheritedWidgetTestContainerState();
}
}
class _InheritedWidgetTestContainerState
extends State<InheritedWidgetTestContainer> {
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('我是測(cè)試widget更新'),
),
body: new Column(
children: <Widget>[
new IncrementWidget(),
new ShowCountWidget(),
new ReduceWidget(),
],
),
)
);
}
}
上面的代碼中其實(shí)就是以TestInheritedWidget作為數(shù)據(jù)共享的根節(jié)點(diǎn),在其節(jié)點(diǎn)下的所有Widget在數(shù)據(jù)發(fā)現(xiàn)變化的時(shí)候都會(huì)重新更新。那么我們有一個(gè)需求,就是在一個(gè)節(jié)點(diǎn)下有A,B,C三個(gè)Widget,我希望B在A點(diǎn)擊的時(shí)候狀態(tài)會(huì)改變,但是A,C不改變?nèi)绾螌?shí)現(xiàn):
import 'package:flutter/material.dart';
class Item {
String reference;
Item(this.reference);
}
// _MyInherited是一個(gè)InheritedWidget,每次我們通過(guò)點(diǎn)擊“Widget A”按鈕添加一個(gè)Item時(shí)都會(huì)重新創(chuàng)建它
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是永遠(yuǎn)不會(huì)改變的,所以這里需要更新子組件的話直接設(shè)置為true
}
}
// MyInheritedWidget是一個(gè)Widget,其狀態(tài)包含Items列表??梢酝ㄟ^(guò)“(BuildContext context)的靜態(tài)MyInheritedWidgetState”訪問(wèn)此狀態(tài)。
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:返回給定類型的最近祖先小部件,這個(gè)組件創(chuàng)建過(guò)一次就不會(huì)重新創(chuàng)建
}
}
//MyInheritedWidgetState公開(kāi)一個(gè)getter(itemsCount)和一個(gè)方法(addItem),以便它們可以被小部件使用,這是子小部件樹(shù)的一部分
class MyInheritedWidgetState extends State<MyInheritedWidget>{
/// List of Items
List<Item> _items = <Item>[];
/// Getter (number of items)
int get itemsCount => _items.length;
// 這個(gè)方法沒(méi)吊用一次都會(huì)重新執(zhí)行build方法,所以_MyInherited也會(huì)重新執(zhí)行一遍
void addItem(String reference){
setState((){
_items.add(new Item(reference));
});
}
@override
Widget build(BuildContext context){
return new _MyInherited(
data: this,
child: widget.child,
);
}
}
// 每次我們向State添加一個(gè)Item時(shí),MyInheritedWidgetState都會(huì)重建
class MyTree extends StatefulWidget {
@override
_MyTreeState createState() => new _MyTreeState();
}
class _MyTreeState extends State<MyTree> {
@override
Widget build(BuildContext context) {
return new MyInheritedWidget(
child: new Scaffold(
appBar: new AppBar(
title: new Text('Title'),
),
body: new Column(
children: <Widget>[
new WidgetA(),
new Container(
child: new Row(
children: <Widget>[
new Icon(Icons.shopping_cart),
new WidgetB(),
new WidgetC(),
],
),
),
],
),
),
);
}
}
// WidgetA是一個(gè)簡(jiǎn)單的RaisedButton,當(dāng)按下它時(shí),從最近的MyInheritedWidget調(diào)用addItem方法,它本身是不需要重新更新的,所以我們?cè)O(shè)計(jì)讓他不會(huì)重新更新
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是一個(gè)簡(jiǎn)單的文本,顯示最接近的MyInheritedWidget級(jí)別的項(xiàng)目數(shù)
class WidgetB extends StatelessWidget {
@override
Widget build(BuildContext context) {
final MyInheritedWidgetState state = MyInheritedWidget.of(context,true);//這段代碼其實(shí)就相當(dāng)于注冊(cè)訂閱,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');
}
}
上面代碼詳解:說(shuō)白了就是給訂閱的人進(jìn)行Widget的更新
當(dāng)子Widget調(diào)用MyInheritedWidget.of(context)時(shí),它會(huì)調(diào)用MyInheritedWidget的以下方法,并傳遞自己的BuildContext。
static MyInheritedWidgetState of([BuildContext context, bool rebuild = true]){
return (rebuild ? context.inheritFromWidgetOfExactType(_MyInherited) as _MyInherited
: context.ancestorWidgetOfExactType(_MyInherited) as _MyInherited).data;// ancestorWidgetOfExactType:返回給定類型的最近祖先小部件,這個(gè)組件創(chuàng)建過(guò)一次就不會(huì)重新創(chuàng)建
}
在內(nèi)部,除了簡(jiǎn)單地返回MyInheritedWidgetState的實(shí)例之外,它還將消費(fèi)者窗口小部件訂閱到更改通知。
整個(gè)過(guò)程如下:
- 調(diào)用
MyInheritedWidget.of(context,true)這個(gè)方法本質(zhì)返回的是MyInheritedWidgetState類。它還將消費(fèi)者窗口小部件訂閱到更改通知。這里核心是A與B傳入了context,是通過(guò)AB的context獲取到繼承小部件,而C沒(méi)有傳入。 - 調(diào)用MyInheritedWidgetState類中的
void addItem(String reference)方法,因?yàn)?code>setState()方法,所以會(huì)重新執(zhí)行build方法,最后也就是_MyInherited重新創(chuàng)建,也就是updateShouldNotify執(zhí)行了。其實(shí)就是_MyInherited中的data重新了,里面的數(shù)據(jù)也就是最新的。它檢查是否需要“通知”“使用者”(答案為是) - 迭代整個(gè)消費(fèi)者列表(這里是Widget B)并請(qǐng)求他們重建
- 由于Wiget C不是消費(fèi)者,因此不會(huì)重建。因?yàn)镃沒(méi)有傳入context獲取對(duì)應(yīng)的MyInheritedWidgetState,而且MyInheritedWidgetState也沒(méi)有重新創(chuàng)建_MyInherited,所以就算C在MyInheritedWidget組件的節(jié)點(diǎn)下,也不會(huì)接收到更新的通知。
總的來(lái)說(shuō)就是利用StatefulWidget來(lái)存儲(chǔ)需要改變的數(shù)據(jù),然后StatefulWidget的根節(jié)點(diǎn)設(shè)置為InheritedWidget,然后利用StatefulWidget的state的setState方法重新創(chuàng)建生成新的InheritedWidget節(jié)點(diǎn),最后也就InheritedWidget中定義的新的StatefulWidget變量,然后updateShouldNotify重新調(diào)用通知相關(guān)訂閱的Widget更新。
這里其實(shí)就有點(diǎn)像Android中的eventbus訂閱了就能接收到通知類似。在flutter中類似的框架有Redux,這個(gè)后面再總結(jié)。