继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

Flutter 数据监听Widget

Flutter笔记
关注TA
已关注
手记 49
粉丝 30
获赞 45

在开发中,我们很有可能会遇见这种需求:
图片描述

这里每一个圆形都是同一个数据。
现在这个圆形的数据被修改了,我们要更新这个页面上所有的数据,是不是很麻烦?
Flutter为我们考虑到了。

ValueListenableBuilder

看名字我们也就能看出来这个控件是干嘛的,监听值的构造器。
那我们照例先看官方文档:

1.  `A widget whose content stays synced with a ValueListenable.`
    
2.    
    
3.  `Given a ValueListenable<T>  and a builder which builds widgets from concrete values of T,  this  class will automatically register itself as a listener of the ValueListenable  and call the builder with updated values when the value changes.`

使内容 和 ValueListenable 保持一致的控件。
给定ValueListenable 一个泛型和一个构建器,它从泛型的具体值构建小部件,这个类将自动注册为ValueListenable 的侦听器,并在值更改时用更新的值调用构建器。
说了这么多 ValueListenable,它到底是个啥?
点进去看:

1.  `// 用于公开值的可侦听子类的接口。`
    
2.  `An  interface  for subclasses of Listenable that expose a value.`
    
3.    
    
4.  `// 这个接口由ValueNotifier和Animation实现,并且允许其他API交替接受这些实现中的任何一个。`
    
5.  `This  interface  is implemented by  ValueNotifier<T>  and  Animation<T>,  and allows other APIs to accept either of those implementations interchangeably.`

那也就是说,这个类被ValueNotifier和Animation实现,从名字我们也能理解他们是干嘛的。
一个是值,一个是动画。

官方 Demo

再来看一下官方Demo,来确认怎么使用:

1.  `class  MyHomePage  extends  StatefulWidget  {`
    
2.   `MyHomePage({Key key,  this.title})  :  super(key: key);`
    
3.   `final  String title;`
    
4.    
    
5.   `@override`
    
6.   `_MyHomePageState createState()  =>  _MyHomePageState();`
    
7.  `}`
    
8.    
    
9.  `class  _MyHomePageState  extends  State<MyHomePage>  {`
    
10.   `final  ValueNotifier<int> _counter =  ValueNotifier<int>(0);`
    
11.   `final  Widget goodJob =  const  Text('Good job!');`
    
12.   `@override`
    
13.   `Widget build(BuildContext context)  {`
    
14.   `return  Scaffold(`
    
15.   `appBar:  AppBar(`
    
16.   `title:  Text(widget.title)`
    
17.   `),`
    
18.   `body:  Center(`
    
19.   `child:  Column(`
    
20.   `mainAxisAlignment:  MainAxisAlignment.center,`
    
21.   `children:  <Widget>[`
    
22.   `Text('You have pushed the button this many times:'),`
    
23.   `ValueListenableBuilder(`
    
24.   `builder:  (BuildContext context,  int value,  Widget child)  {`
    
25.   `// 只有在更新计数器时才会调用此生成器。`
    
26.   `return  Row(`
    
27.   `mainAxisAlignment:  MainAxisAlignment.spaceEvenly,`
    
28.   `children:  <Widget>[`
    
29.   `Text('$value'),`
    
30.   `child,`
    
31.   `],`
    
32.   `);`
    
33.   `},`
    
34.   `valueListenable: _counter,`
    
35.   `// 如果child 的构建成本很高,并且不依赖于通知程序的值,则child参数非常有用。`
    
36.   `child: goodJob,`
    
37.   `)`
    
38.   `],`
    
39.   `),`
    
40.   `),`
    
41.   `floatingActionButton:  FloatingActionButton(`
    
42.   `child:  Icon(Icons.plus_one),`
    
43.   `// 点击的时候用 ValueNotifier 来更新值`
    
44.   `onPressed:  ()  => _counter.value +=  1,`
    
45.   `),`
    
46.   `);`
    
47.   `}`
    
48.  `}`

代码还是比较简单的,就是在平常的布局上面添加了一个 ValueListenableBuilder
然后在点击 FAB 的时候更新值。
我们运行一下程序,看看是什么样子:

官方这个例子把该控件所有的信息都写上去了,但是并不直观,显示不出来这个控件的威力。

自定义页面展示 ValueListenableBuilder

自定义一个小Demo:

代码如下:

1.  `class  _ValueListenableBuildPageState  extends  State<ValueListenableBuildPage>  {`
    
2.    
    
3.   `ValueNotifier<Person> _valueListenable =  ValueNotifier<Person>(`
    
4.   `Person(name:  'WAnimal', age:  18, head:  'images/bg.jpg'));`
    
5.   `Widget _contentWidget;`
    
6.    
    
7.   `@override`
    
8.   `void initState()  {`
    
9.   `super.initState();`
    
10.   `_contentWidget =`
    
11.   `Padding(`
    
12.   `padding:  const  EdgeInsets.all(10.0),`
    
13.   `child:  Text(`
    
14.   `'我是正文我是正文我是正文我是正文我是正文我是正文我是正文我是正文我是正文我是正文我是正文我是正文我是正文我是正文我是正文',`
    
15.   `style:  TextStyle(fontSize:  16),`
    
16.   `),`
    
17.   `);`
    
18.   `}`
    
19.    
    
20.   `@override`
    
21.   `Widget build(BuildContext context)  {`
    
22.   `return  Scaffold(`
    
23.   `appBar:  AppBar(`
    
24.   `title:  Text('ValueListenableBuildPage'),`
    
25.   `),`
    
26.   `body:  ValueListenableBuilder(`
    
27.   `valueListenable: _valueListenable,`
    
28.   `builder:  (BuildContext context,  Person value,  Widget child)  {`
    
29.   `return  SingleChildScrollView(`
    
30.   `child:  Column(`
    
31.   `children:  <Widget>[`
    
32.   `Padding(`
    
33.   `padding:  const  EdgeInsets.only(top:  18.0),`
    
34.   `child:  ClipOval(`
    
35.   `child:  Image.asset(`
    
36.   `value.head,`
    
37.   `fit:  BoxFit.cover,`
    
38.   `width:  100,`
    
39.   `height:  100,`
    
40.   `),`
    
41.   `),`
    
42.   `),`
    
43.   `Padding(`
    
44.   `padding:  const  EdgeInsets.symmetric(vertical:  8.0),`
    
45.   `child:  Text(`
    
46.   `'${value.name}',`
    
47.   `style:  TextStyle(`
    
48.   `fontSize:  18,`
    
49.   `fontWeight:  FontWeight.bold,`
    
50.   `color:  Colors.black,`
    
51.   `),`
    
52.   `),`
    
53.   `),`
    
54.   `Padding(`
    
55.   `padding:  const  EdgeInsets.only(bottom:  8.0),`
    
56.   `child:  Text(`
    
57.   `'age:${value.age}',`
    
58.   `style:  TextStyle(`
    
59.   `fontSize:  18,`
    
60.   `fontWeight:  FontWeight.bold,`
    
61.   `color:  Colors.black,`
    
62.   `),`
    
63.   `),`
    
64.   `),`
    
65.   `ListView.builder(`
    
66.   `shrinkWrap:  true,`
    
67.   `itemCount:  10,`
    
68.   `physics:  NeverScrollableScrollPhysics(),`
    
69.   `itemBuilder:  (context, index)  {`
    
70.   `return  Column(`
    
71.   `children:  <Widget>[`
    
72.   `Row(`
    
73.   `children:  <Widget>[`
    
74.   `Padding(`
    
75.   `padding:  const  EdgeInsets.all(8.0),`
    
76.   `child:  ClipOval(`
    
77.   `child:  Image.asset(`
    
78.   `value.head,`
    
79.   `fit:  BoxFit.cover,`
    
80.   `width:  50,`
    
81.   `height:  50,`
    
82.   `),`
    
83.   `),`
    
84.   `),`
    
85.   `Column(`
    
86.   `crossAxisAlignment:  CrossAxisAlignment.start,`
    
87.   `children:  <Widget>[`
    
88.   `Padding(`
    
89.   `padding:`
    
90.   `const  EdgeInsets.symmetric(vertical:  4.0),`
    
91.   `child:  Text(`
    
92.   `'${value.name}',`
    
93.   `style:  TextStyle(`
    
94.   `fontSize:  18,`
    
95.   `fontWeight:  FontWeight.bold,`
    
96.   `color:  Colors.black,`
    
97.   `),`
    
98.   `),`
    
99.   `),`
    
100.   `Text(`
    
101.   `'age: ${value.age}',`
    
102.   `style:  TextStyle(`
    
103.   `fontSize:  16,`
    
104.   `color:  Colors.black,`
    
105.   `),`
    
106.   `),`
    
107.   `],`
    
108.   `),`
    
109.   `],`
    
110.   `),`
    
111.   `child`
    
112.   `],`
    
113.   `);`
    
114.   `},`
    
115.   `)`
    
116.   `],`
    
117.   `),`
    
118.   `);`
    
119.   `},`
    
120.   `child: _contentWidget,`
    
121.   `),`
    
122.   `floatingActionButton:  FloatingActionButton(`
    
123.   `onPressed:  ()  {`
    
124.   `_valueListenable.value =  Person(name:  '91李先生', age:  24, head:  'images/bg.png');`
    
125.   `},`
    
126.   `child:  Icon(Icons.refresh),`
    
127.   `),`
    
128.   `);`
    
129.   `}`
    
130.  `}`

按照官方Demo 所说,不需要监听值的控件我们放在别的地方初始化后,放入 child 参数中。
所以我们在 initState() 方法中初始化了 _contentWidget,来作为ListView 的 ·正文·。
然后我们在ValueListenableBuilder 中,包裹了一个 最上层的 ·用户信息· ,还有下面该用户所发表的文章的用户信息。
最后在FAB 中更改 Person对象来达到更新信息的目的。

自定义 ValueNotifier

看到这肯定有人会说,我也不可能每次都更新这一个对象啊,我只想更新其中的一个字段就达到这种效果。
没问题老铁,这时候就像 ValueListenable的文档中所说,需要用到自己定义 ValueNotifier。
自定义也没什么难得,只需要记住一点,在需要更改的地方调用 notifyListeners() 就 ok了。
自定义 PersonNotifier 代码如下:

1.  `class  PersonNotifier  extends  ValueNotifier<Person>{`
    
2.   `PersonNotifier(Person value)  :  super(value);`
    
3.    
    
4.   `void changePersonName(String name){`
    
5.   `value.name = name;`
    
6.   `notifyListeners();`
    
7.   `}`
    
8.  `}`

相当简单的代码,定义了一个方法来修改名字,调用通知就ok了。
看一下效果:

总结

我们在这里只是简单的使用了一下 ValueListenableBuilder 其中的一个ValueNotifier 的功能
还可以使用 Animation,使用方法都差不多,可以自行研究一下。
Flutter 确实为我们提供了特别多特别方便的控件。

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP