最常見的場景是我們在一個介面執行了某種操作,需要在另一個介面的某個地方(例如一個變數)產生更新等效果,就需要進行介面之間的通訊
本文將介紹四種方法來完成這種事
- 使用scoped_model
- 使用InheritedWidget
- 使用key進行訪問
- 父widget建立state後儲存供子widget訪問
一 使用scoped_model
什麼是scoped_model?
Scoped_model是一個dart第三方庫,提供了讓您能夠輕鬆地將資料模型從父Widget傳遞到它的後代的功能。此外,它還會在模型更新時重新渲染使用該模型的所有子項。,而且無論該widget是不是有狀態的都可以進行更新,再一次build
實現原理
Scoped model使用了觀察者模式,將資料模型放在父代,後代通過找到父代的model進行資料渲染,最後資料改變時將資料傳回,父代再通知所有用到了該model的子代去更新狀態。
用途
可以進行全域性公共資料共享,除了使用scoped_model也可以使用shared_preferences、或者新建一個類將共享資料設定成static類的成員。
- 使用sp的缺點是效率低,需要讀寫資料庫,同時不可以在狀態改變後主動發起更新
- 使用static類成員的缺點也是不可以主動發起類更新
- 使用scoped_model的速度上更快,並且可以監聽變換後自動發起setState變化
具體使用
1 新增依賴
scoped_model: ^0.3.0複製程式碼
2 、建立Model
import 'package:scoped_model/scoped_model.dart';
class CountModel extends Model{
int _count = 0;
get count => _count;
void increment(){
_count++;
notifyListeners();
}
}複製程式碼
3、將model放在頂層
//建立頂層狀態
CountModel countModel = CountModel();
@override
Widget build(BuildContext context) {
return ScopedModel<CountModel>(
model: countModel,
child: new MaterialApp(
home: TopScreen(),
),
);
}複製程式碼
4、在子介面中獲取Model
@override
Widget build(BuildContext context) {
return ScopedModelDescendant<CountModel>(
builder: (context,child,model){
return Scaffold(
body: Center(
child: Text(
model.count.toString(),
style: TextStyle(fontSize: 48.0),
),
),
);
},
);
}複製程式碼
5 一個簡單的demo可以直接執行
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
class CountModel extends Model{
int _count = 0;
get count => _count;
void increment()
{
_count++;
notifyListeners();
}
}
void main()
{
CountModel countModel = CountModel();
runApp(new ScopedModel(model: countModel, child: MaterialApp(
home: PageA(),
)));
}
class PageA extends StatefulWidget{
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return new _PageA();
}
}
class _PageA extends State<PageA>
{
@override
Widget build(BuildContext context) {
// TODO: implement build
return ScopedModelDescendant<CountModel>(
builder: (context,child,model)
{
return Scaffold(
appBar: AppBar(
title: Text(model.count.toString()),
),
body: RaisedButton(
onPressed: (){model.increment();},
),
);
},
);
}
}複製程式碼
二 使用InhertedWidget
什麼是InheritedWidget?
InheritedWidget 是一個特殊的 Widget,它將作為另一個子樹的父節點放置在 Widget 樹中。該子樹的所有 widget 都必須能夠與該 InheritedWidget 暴露的資料進行互動。
為了更好理解概念,我們看一個例子
class MyInheritedWidget extends InheritedWidget {
MyInheritedWidget({
Key key,
@required Widget child,
this.data,
}): super(key: key, child: child);
final data;
static MyInheritedWidget of(BuildContext context) {
return context.inheritFromWidgetOfExactType(MyInheritedWidget);
}
@override
bool updateShouldNotify(MyInheritedWidget oldWidget) => data != oldWidget.data;
}
複製程式碼
以上程式碼定義了一個名為 “MyInheritedWidget” 的 Widget,目的在於為子樹中的所有 widget 提供某些『共享』資料。
如前所述,為了能夠傳播/共享某些資料,需要將 InheritedWidget 放置在 widget 樹的頂部,這解釋了傳遞給 InheritedWidget 基礎建構函式的 @required Widget child 引數。
static MyInheritedWidget of(BuildContext context) 方法允許所有子 widget 通過包含的 context 獲得最近的 MyInheritedWidget 例項(參見後面的內容)。
最後重寫 updateShouldNotify 方法用來告訴 InheritedWidget 如果對資料進行了修改,是否必須將通知傳遞給所有子 widget(已註冊/已訂閱)。
因此,使用時我們需要把它放在父節點
class MyParentWidget... {
...
@override
Widget build(BuildContext context){
return new MyInheritedWidget(
data: counter,
child: new Row(
children: <Widget>[
...
],
),
);
}
}複製程式碼
子結點如何訪問InhertedWidget中的資料呢?
class MyChildWidget... {
...
@override
Widget build(BuildContext context){
final MyInheritedWidget inheritedWidget = MyInheritedWidget.of(context);
///
/// 此刻,該 widget 可使用 MyInheritedWidget 暴露的資料
/// 通過呼叫:inheritedWidget.data
///
return new Container(
color: inheritedWidget.data.color,
);
}
}複製程式碼
InhertedWidget的高階用法
上文講到的用法有一個明顯的缺點是無法實現類似於Scoped_model的自動更新,為此,我們將inhertedWidget依託於一個statefulWidget實現更新
場景如下:
為了說明互動方式,我們做以下假設:
- ‘Widget A’ 是一個將專案新增到購物車裡的按鈕;
- ‘Widget B’ 是一個顯示購物車中商品數量的文字;
- ‘Widget C’ 位於 Widget B 旁邊,是一個內建任意文字的文字;
- 我們希望 ‘Widget A’ 在按下時 ‘Widget B’ 能夠自動在購物車中顯示正確數量的專案,但我們不希望重建 ‘Widget C’
示例程式碼如下
class Item {
String reference;
Item(this.reference);
}
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;
}
}
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){
return (context.inheritFromWidgetOfExactType(_MyInherited) as _MyInherited).data;
}
}
class MyInheritedWidgetState extends State<MyInheritedWidget>{
/// List of Items
List<Item> _items = <Item>[];
/// Getter (number of items)
int get itemsCount => _items.length;
/// Helper method to add an Item
void addItem(String reference){
setState((){
_items.add(new Item(reference));
});
}
@override
Widget build(BuildContext context){
return new _MyInherited(
data: this,
child: widget.child,
);
}
}
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(),
],
),
),
],
),
),
);
}
}
class WidgetA extends StatelessWidget {
@override
Widget build(BuildContext context) {
final MyInheritedWidgetState state = MyInheritedWidget.of(context);
return new Container(
child: new RaisedButton(
child: new Text('Add Item'),
onPressed: () {
state.addItem('new item');
},
),
);
}
}
class WidgetB extends StatelessWidget {
@override
Widget build(BuildContext context) {
final MyInheritedWidgetState state = MyInheritedWidget.of(context);
return new Text('${state.itemsCount}');
}
}
class WidgetC extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Text('I am Widget C');
}
}
複製程式碼
說明
在這個非常基本的例子中:
_MyInherited
是一個 InheritedWidget,每次我們通過 ‘Widget A’ 按鈕新增一個專案時它都會重新建立- MyInheritedWidget 是一個 State 包含了專案列表的 Widget。可以通過 static MyInheritedWidgetState of(BuildContext context) 訪問該 State
- MyInheritedWidgetState 暴露了一個獲取 itemsCount 的 getter 方法 和一個 addItem 方法,以便它們可以被 widget 使用,這是子 widget 樹的一部分
- 每次我們將專案新增到 State 時,MyInheritedWidgetState 都會重建
- MyTree 類僅構建了一個 widget 樹,並將 MyInheritedWidget 作為樹的根節點
- WidgetA 是一個簡單的 RaisedButton,當按下它時,它將從最近的 MyInheritedWidget 例項中呼叫 addItem 方法
- WidgetB 是一個簡單的 Text,用來顯示最近 級別 MyInheritedWidget 的專案數
三 使用key進行訪問
什麼是key?
在 Fultter 中,每一個 Widget 都是被唯一標識的。這個唯一標識在 build/rendering 階段由框架定義。
該唯一標識對應於可選的 Key 引數。如果省略該引數,Flutter 將會為你生成一個。
在某些情況下,你可能需要強制使用此 key,以便可以通過其 key 訪問 widget。
為此,你可以使用以下方法中的任何一個:GlobalKey、LocalKey、UniqueKey 或 ObjectKey。
GlobalKey 確保生成的 key 在整個應用中是唯一的。
強制使用GlobalKey的方法
GlobalKey myKey = new GlobalKey();
...
@override
Widget build(BuildContext context){
return new MyWidget(
key: myKey
);
}
複製程式碼
使用GlobalKey訪問State
class _MyScreenState extends State<MyScreen> {
/// the unique identity of the Scaffold
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
@override
Widget build(BuildContext context){
return new Scaffold(
key: _scaffoldKey,
appBar: new AppBar(
title: new Text('My Screen'),
),
body: new Center(
new RaiseButton(
child: new Text('Hit me'),
onPressed: (){
_scaffoldKey.currentState.showSnackBar(
new SnackBar(
content: new Text('This is the Snackbar...'),
)
);
}
),
),
);
}
}
複製程式碼
四 父widget建立state後儲存供子widget訪問
假設你有一個屬於另一個 Widget 的子樹的 Widget,如下圖所示。
使用步驟如下
1. 『帶有 State 的 Widget』(紅色)需要暴露其 State
為了暴露 其 State,Widget 需要在建立時記錄它,如下所示:
class MyExposingWidget extends StatefulWidget {
MyExposingWidgetState myState;
@override
MyExposingWidgetState createState(){
myState = new MyExposingWidgetState();
return myState;
}
}
複製程式碼複製程式碼
2. “Widget State” 需要暴露一些 getter/setter 方法
為了讓“其他類” 設定/獲取 State 中的屬性,Widget State 需要通過以下方式授權訪問:
- 公共屬性(不推薦)
- getter / setter
例子:
class MyExposingWidgetState extends State<MyExposingWidget>{
Color _color;
Color get color => _color;
...
}
複製程式碼複製程式碼
3. “對獲取 State 感興趣的 Widget”(藍色)需要得到 State 的引用
class MyChildWidget extends StatelessWidget {
@override
Widget build(BuildContext context){
final MyExposingWidget widget = context.ancestorWidgetOfExactType(MyExposingWidget);
final MyExposingWidgetState state = widget?.myState;
return new Container(
color: state == null ? Colors.blue : state.color,
);
}
}複製程式碼