最常见的场景是我们在一个界面执行了某种操作,需要在另一个界面的某个地方(例如一个变量)产生更新等效果,就需要进行界面之间的通信
本文将介绍四种方法来完成这种事
- 使用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,
);
}
}