Flutter 數據監聽Widget 自動更新你的UI

在開發中,我們很有可能會遇見這種需求:

 

 

 

這裏每一個圓形都是同一個數據。

現在這個圓形的數據被修改了,我們要更新這個頁面上所有的數據,是不是很麻煩?

Flutter爲我們考慮到了。

ValueListenableBuilder

看名字我們也就能看出來這個控件是幹嘛的,監聽值的構造器。

那我們照例先看官方文檔:

A widget whose content stays synced with a ValueListenable.

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,它到底是個啥?

點進去看:

// 用於公開值的可偵聽子類的接口。
An interface for subclasses of Listenable that expose a value.

// 這個接口由ValueNotifier和Animation實現,並且允許其他API交替接受這些實現中的任何一個。
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,來確認怎麼使用:

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final ValueNotifier<int> _counter = ValueNotifier<int>(0);
  final Widget goodJob = const Text('Good job!');
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title)
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('You have pushed the button this many times:'),
            ValueListenableBuilder(
              builder: (BuildContext context, int value, Widget child) {
								// 只有在更新計數器時纔會調用此生成器。
                return Row(
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: <Widget>[
                    Text('$value'),
                    child,
                  ],
                );
              },
              valueListenable: _counter,
							// 如果child 的構建成本很高,並且不依賴於通知程序的值,則child參數非常有用。
              child: goodJob,
            )
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.plus_one),
        // 點擊的時候用 ValueNotifier 來更新值
        onPressed: () => _counter.value += 1,
      ),
    );
  }
}
複製代碼

代碼還是比較簡單的,就是在平常的佈局上面添加了一個 ValueListenableBuilder

然後在點擊 FAB 的時候更新值。

我們運行一下程序,看看是什麼樣子:

 

 

 

官方這個例子把該控件所有的信息都寫上去了,但是並不直觀,顯示不出來這個控件的威力。

自定義頁面展示 ValueListenableBuilder

我也寫了一個小Demo:

 

 

 

代碼如下:

class _ValueListenableBuildPageState extends State<ValueListenableBuildPage> {
  
  ValueNotifier<Person> _valueListenable = ValueNotifier<Person>(
      Person(name: 'WAnimal', age: 18, head: 'images/bg.jpg'));
  Widget _contentWidget;
  
  @override
  void initState() {
    super.initState();
    _contentWidget =
        Padding(
          padding: const EdgeInsets.all(10.0),
          child: Text(
            '我是正文我是正文我是正文我是正文我是正文我是正文我是正文我是正文我是正文我是正文我是正文我是正文我是正文我是正文我是正文',
            style: TextStyle(fontSize: 16),
          ),
        );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('ValueListenableBuildPage'),
      ),
      body: ValueListenableBuilder(
        valueListenable: _valueListenable,
        builder: (BuildContext context, Person value, Widget child) {
          return SingleChildScrollView(
            child: Column(
              children: <Widget>[
                Padding(
                  padding: const EdgeInsets.only(top: 18.0),
                  child: ClipOval(
                    child: Image.asset(
                      value.head,
                      fit: BoxFit.cover,
                      width: 100,
                      height: 100,
                    ),
                  ),
                ),
                Padding(
                  padding: const EdgeInsets.symmetric(vertical: 8.0),
                  child: Text(
                    '${value.name}',
                    style: TextStyle(
                      fontSize: 18,
                      fontWeight: FontWeight.bold,
                      color: Colors.black,
                    ),
                  ),
                ),
                Padding(
                  padding: const EdgeInsets.only(bottom: 8.0),
                  child: Text(
                    'age:${value.age}',
                    style: TextStyle(
                      fontSize: 18,
                      fontWeight: FontWeight.bold,
                      color: Colors.black,
                    ),
                  ),
                ),
                ListView.builder(
                  shrinkWrap: true,
                  itemCount: 10,
                  physics: NeverScrollableScrollPhysics(),
                  itemBuilder: (context, index) {
                    return Column(
                      children: <Widget>[
                        Row(
                          children: <Widget>[
                            Padding(
                              padding: const EdgeInsets.all(8.0),
                              child: ClipOval(
                                child: Image.asset(
                                  value.head,
                                  fit: BoxFit.cover,
                                  width: 50,
                                  height: 50,
                                ),
                              ),
                            ),
                            Column(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: <Widget>[
                                Padding(
                                  padding:
                                  const EdgeInsets.symmetric(vertical: 4.0),
                                  child: Text(
                                    '${value.name}',
                                    style: TextStyle(
                                      fontSize: 18,
                                      fontWeight: FontWeight.bold,
                                      color: Colors.black,
                                    ),
                                  ),
                                ),
                                Text(
                                  'age: ${value.age}',
                                  style: TextStyle(
                                    fontSize: 16,
                                    color: Colors.black,
                                  ),
                                ),
                              ],
                            ),
                          ],
                        ),
                        child
                      ],
                    );
                  },
                )
              ],
            ),
          );
        },
        child: _contentWidget,
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          _valueListenable.value = Person(name: '91李先生', age: 24, head: 'images/bg.png');
        },
        child: Icon(Icons.refresh),
      ),
    );
  }
}

複製代碼

按照官方Demo 所說,不需要監聽值的控件我們放在別的地方初始化後,放入 child 參數中。

所以我們在 initState() 方法中初始化了 _contentWidget,來作爲ListView 的 ·正文·。

然後我們在ValueListenableBuilder 中,包裹了一個 最上層的 ·用戶信息· ,還有下面該用戶所發表的文章的用戶信息。

最後在FAB 中更改 Person對象來達到更新信息的目的。

自定義 ValueNotifier

看到這肯定有人會說,我也不可能每次都更新這一個對象啊,我只想更新其中的一個字段就達到這種效果。

沒問題老鐵,這時候就像ValueListenable的文檔中所說,需要用到自己定義 ValueNotifier。

自定義也沒什麼難得,只需要記住一點,在需要更改的地方調用 notifyListeners() 就 ok了。

自定義 PersonNotifier 代碼如下:

class PersonNotifier extends ValueNotifier<Person>{
  PersonNotifier(Person value) : super(value);

  void changePersonName(String name){
    value.name = name;
    notifyListeners();
  }
}
複製代碼

相當簡單的代碼,定義了一個方法來修改名字,調用通知就ok了。

看一下效果:

 

 

 

總結

我們在這裏只是簡單的使用了一下 ValueListenableBuilder 其中的一個ValueNotifier 的功能

還可以使用 Animation,使用方法都差不多,可以自行研究一下。

Flutter 確實爲我們提供了特別多特別方便的控件。


作者:Flutter筆記
鏈接:https://juejin.cn/post/6844903855528869901
來源:掘金
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章