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

在開發中,咱們頗有可能會碰見這種需求:git

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

如今這個圓形的數據被修改了,咱們要更新這個頁面上全部的數據,是否是很麻煩?bash

Flutter爲咱們考慮到了。app

ValueListenableBuilder

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

那咱們照例先看官方文檔:佈局

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 的偵聽器,並在值更改時用更新的值調用構建器。ui

說了這麼多 ValueListenable,它究竟是個啥?this

點進去看:spa

// 用於公開值的可偵聽子類的接口。
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 & Dart 知識🌝。

完整代碼已經傳至GitHub:github.com/wanglu1209/…

相關文章
相關標籤/搜索