推荐文章:
View Tree直接通信
父View如何拿到子View的状态
-
在这里假设我们子View需要拿到父View的数据,父View与子View的关系图层如下所示。
层级关系
处理方式
方案一
- 在父View中创建State时候把变量引用给保存下来。
class ParentWidgetView extends StatefulWidget {
ParentStateView myState;
@override
ParentStateView createState(){
myState = new ParentStateView();
return myState;
}
}
//提供一个color给子View使用
class ParentStateView extends State<ParentStateView>{
Color _color;
Color get color => _color;
...
}
- 在子View中通过BuildContext关系获取父View的State
class ChildrenView extends StatelessWidget {
@override
Widget build(BuildContext context){
final ParentWidgetView widget = context.ancestorWidgetOfExactType(ParentWidgetView);
final ParentStateView state = widget?.myState;
return new Container(
//拿到对应的state,state里面的所有属性变量自然能获取到了
color: state == null ? Colors.blue : state.color,
);
}
}
缺点:该方案当父View刷新时候,子View怎么知道何时应该刷新呢?唯有子View重建的时候才能达到刷新效果,所以不是很方便。下面方案二将讨论“InheritedWidget”的用法,该类可以解决此问题。
方案二
- InheritedWidget是一个特殊的Widget,您将它作为另一个子树的父级放入Widgets树中。该子树的所有子View都必须具有与该InheritedWidget公开的数据进行交互的能力。所以首先声明一个InheritedWidget。
class MyInheritedWidget extends InheritedWidget {
MyInheritedWidget({
Key key,
@required Widget child,
this.data,
}): super(key: key, child: child);
final data;
static ParentWidgetView of(BuildContext context) {
return context.inheritFromWidgetOfExactType(MyInheritedWidget);
}
//updateShouldNotify”重写方法用于告诉InheritedWidget如果对数据进行了修改,是否必须将通知传递给所有子小部件(已注册/已预订)
@override
bool updateShouldNotify(MyInheritedWidget oldWidget) => data != oldWidget.data;
}
- 在ParentWidgetView时候构建时候,我们用MyInheritedWidget作为最外层的View。让其在ParentWidgetView的子View能获取到对应数据
class ParentWidgetView... {
...
@override
Widget build(BuildContext context){
return new MyInheritedWidget(
//关键关联数据
data: widget.mState,
child: new Row(
children: <Widget>[
...
],
),
);
}
}
- 子View如何从父View中获取数据呢?生成子对象时,后者将获得对InheritedWidget的引用,如下
class ChildrenView... {
...
@override
Widget build(BuildContext context){
//通过静态方法获取对应MyInheritedWidget句柄
final MyInheritedWidget inheritedWidget = MyInheritedWidget.of(context);
return new Container(
//通过句柄的data访问父View的数据,当父View数据更新后,data也会相应更新
color: inheritedWidget.data.color,
);
}
}
子View和子View如何能拿到对方数据
-
关系树形图如下
关系树形图
- 为了说明互动的类型,我们假设以下内容:
- A的Widget是将商品添加到购物车的按钮;
- B的Widget是显示购物车中商品数量的文本;
- C的Widget 位于小部件B旁边,是一个文本,其中包含任何文本;
- 我们希望“B”能够在按下“A”后,自动在购物车中自动显示正确数量的商品,但我们不希望重新构建“小部件C”
- 代码如下
MyTree类作为View Tree的入口,以MyInheritedWidget作为树的父级。然后各自A、B通过
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(
//将数据直接指向自己,供其子View获取state里面的方法
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');
}
}
问题
- 当MyInheritedWidget的数据改变后,A的widget也会被重建,但是我们的A其实不需要重建的,那么怎样阻止某些小部件在能访问继承的小部件的同时,不进行重建呢?
解决办法
防止这种自动订阅同时仍允许Widget A访问 MyInherited WidgetState的解决方案是如下更改MyInheritedWidget的静态方法:
static MyInheritedWidgetState of([BuildContext context, bool rebuild = true]){
return (rebuild ? context.inheritFromWidgetOfExactType(_MyInherited) as _MyInherited
: context.ancestorWidgetOfExactType(_MyInherited) as _MyInherited).data;
}
通过增加布尔值,让其是否自动添加到订阅者队列中
- 如果“ rebuild”参数为true(默认情况下),我们将使用常规方法(并且Widget将被添加到订户列表中)
- 如果“ rebuild”参数为false,则仍然可以访问数据,但无需使用InheritedWidget的内部实现
因此,要完成该解决方案,我们还需要按如下所示稍微更新小部件A的代码(我们添加了false额外参数)
class WidgetA extends StatelessWidget {
@override
Widget build(BuildContext context) {
final MyInheritedWidgetState state = MyInheritedWidget.of(context, false);
return new Container(
child: new RaisedButton(
child: new Text('Add Item'),
onPressed: () {
state.addItem('new item');
},
),
);
}
}
总结
- 参考View Tree直接通信。
- 多动手试试,这次通过state拿到引用后是否能拿到对应的context的位置信息。
网友评论