iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 22
0
自我挑戰組

從零開始的Flutter世界系列 第 22

Day22 Flutter 的狀態管理 Provider (一)

  • 分享至 

  • xImage
  •  

在講解Provider之前呢,因為Provider 會大量使用到 InheritedWidget來實現資料共享的功能,所以我們來先為各位介紹InheritedWidget

InheritedWidget

官方文件

前言:

首先Flutter 用分層設計來實現畫面的渲染,像很多文章會提到Render Tree,可參考,內容只要WidgetElementRenderObject

https://ithelp.ithome.com.tw/upload/images/20201007/20118479s6bQtA0yDr.png

  • Widget → Describes the configuration for an Element.

    為我們的控件,用來描述對應的Element的描述或配置

  • Element → An instantiation of a Widget at a particular location in the tree.

    element組成了element tree,Element的主要功能就是維護這棵樹,節點的增加,刪除,更新,樹的遍歷都在這裡完成,Element都是從Widget中生成的,每個Widget也都會對應一個Element

  • RenderObject → An object in the render tree

    負責具體佈局,繪製

InheritedWidget:

Flutter中非常重要的一個功能型Widget,讓資料可以在Widget Tree 中向下傳遞、共享,提供子控件使用,例如:Flutter 的SDK 正是透過InheritedWidget來共享主題( Theme ) 和當前語言環境( Locale ) 資訊的

可以理解成InheritedWidget是一個能高效的在Widget 樹中傳遞共享資料的基本類別

用法

  1. 繼承InheritedWidget

  2. 覆寫updateShouldNotify方法,來決定什麼時候要通知說資料有變化

  3. 提供了靜態方法 of來獲取實例

    核心在使用BuildContext.dependOnInheritedWidgetOfExactType獲取指定類型的 InheritedWidget 的實例

    • 尋找父節點中類型匹配的最近節點並返回
    • 將調用dependOnInheritedWidgetOfExactTypeBuildContext註冊到對應的InheritedWidget並監聽
    • BuildContext即調用者Widget 本身所對應的Element 實例
    • 調用dependOnInheritedWidgetOfExactType,即建立了兩個Widget 的依賴關係

範例一:

class FrogColor extends InheritedWidget {
  const FrogColor({
    Key key,
    @required this.color,
    @required Widget child,
  }) : assert(color != null),
       assert(child != null),
       super(key: key, child: child);

  final Color color;

  static FrogColor of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<FrogColor>();
  }

  @override
  bool updateShouldNotify(FrogColor old) => color != old.color;
}

範例二:

Theme.of使用BuildContext.dependOnInheritedWidgetOfExactType來查找InheritedWidget並返回 ThemeData

class Theme extends StatelessWidget {
...
  static ThemeData of(BuildContext context, { bool shadowThemeOnly = false }) {
    final _InheritedTheme? inheritedTheme = context.dependOnInheritedWidgetOfExactType<_InheritedTheme>();
    if (shadowThemeOnly) {
      if (inheritedTheme == null || inheritedTheme.theme.isMaterialAppTheme)
        return null;
      return inheritedTheme.theme.data;
    }
...
  }
}
class _InheritedTheme extends InheritedWidget {
...
}

didChangeDependencies

之前提到的StatefulWidget,其內部所管理的狀態有一個didChangeDependencies方法,它會在依賴發生變化時被Flutter Framework調用,而這個依賴指的就是是否使用了父widget中InheritedWidget的數據,如果使用了,則代表有依賴,如果沒有使用則代表沒有依賴,這樣的模式可以使子widget在所依賴的InheritedWidget變化時來更新內容,例如:Theme、Locale 等發生變化時,依賴其的子widget的didChangeDependencies方法將會被調用

範例:

counter_inherited_widget.dart

import 'package:flutter/material.dart';

class CounterInheritedWidget extends InheritedWidget {
  CounterInheritedWidget({@required this.count, Widget child})
      : super(child: child);

  // 共享的資料,計數的值
  final int count;

  // 獲取CounterInheritedWidget實例的方法, 方便widget樹中的子widget獲取共享的資料
  // 必須在State中調用才會有效
  static CounterInheritedWidget of(BuildContext context) {
    // 調用共享數據的子widget將不會回調didChangeDependencies方法,即子widget將不會更新
    return context.dependOnInheritedWidgetOfExactType<CounterInheritedWidget>();
  }

  // 決定是否通知樹中有依賴共享數據的子widget (true 就通知)
  @override
  bool updateShouldNotify(CounterInheritedWidget oldWidget) {
    return oldWidget.count != count;
  }
}

counter_text.dart

import 'package:flutter/material.dart';

import 'counter_inherited_widget.dart';

class CounterText extends StatefulWidget {
  @override
  _CounterTextState createState() {
    return _CounterTextState();
  }
}

class _CounterTextState extends State<CounterText> {
  @override
  Widget build(BuildContext context) {
    //使用InheritedWidget中的共享資料
    return Text(
        "count: ${CounterInheritedWidget.of(context).count.toString()}");
  }

  //父或祖先widget中的InheritedWidget改變(updateShouldNotify返回true)時會被調用
  //如果在build中沒有依賴InheritedWidget,則此回調就不會被調用
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print("didChangeDependencies");
  }
}

counter_widget.dart

import 'package:flutter/material.dart';

import 'counter_inherited_widget.dart';
import 'counter_text.dart';

class CounterWidget extends StatefulWidget {
  @override
  _CounterWidgetState createState() {
    return _CounterWidgetState();
  }
}

class _CounterWidgetState extends State<CounterWidget> {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Count App',
      theme: new ThemeData(primarySwatch: Colors.blue),
      home: Scaffold(
        appBar: AppBar(
          title: Text("Count"),
        ),
        body: Center(
          child: CounterInheritedWidget(
            count: count,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                CounterText(),
                //每點擊一次,將count值遞增,然後重新build,CounterInheritedWidget的data將被更新
                RaisedButton(
                  onPressed: () => setState(() => count++),
                  child: Text("Increment"),
                )
              ],
            ),
          ),
        ),
      ),
    );
  }
}

main.dart

import 'package:flutter/material.dart';

import 'counter_widget.dart';

void main() {
  runApp(CounterWidget());
}

就能完成我們InheritedWidget的範例囉

介紹完了基礎的InheritedWidget用法,下一篇將正式進入Provider 的內容


上一篇
Day21 Flutter 的狀態管理 BLoC (五) Firebase Login
下一篇
Day23 Flutter 的狀態管理 Provider (二)
系列文
從零開始的Flutter世界30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言