Flutter 資料監聽Widget 自動更新你的UI

愛小麗0427發表於2019-05-28

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

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 的時候更新值。

我們執行一下程式,看看是什麼樣子:

Flutter 資料監聽Widget 自動更新你的UI

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

自定義頁面展示 ValueListenableBuilder

我也寫了一個小Demo:

Flutter 資料監聽Widget 自動更新你的UI

程式碼如下:

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了。

看一下效果:

Flutter 資料監聽Widget 自動更新你的UI

總結

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

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

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

關注我,每天更新 Flutter & Dart 知識?。

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

Flutter 資料監聽Widget 自動更新你的UI

相關文章