天天看點

Flutter移動應用:狀态管理

原文連結https://siques.cn/p/41

這個章節我們會先介紹一下 Flutter 的 StatelessWidget … 還有 StatefulWidget …

然後會用一個非常簡單的例子來了解一下 Flutter 應用的狀态管理 … 狀态就是小部件裡的資料 … 小部件可以自己管理需要的資料 … 這些資料也可以通過小部件的構造函數從它的父輩那裡傳遞過來 …

我們還會學一下,使用 InheritedWidget … 還有 ScopedModel,更有效的去把資料傳遞給需要的小部件 …

小部件

StatelessWidget:無變化狀态的小部件

StatelessWidget,這種小部件裡面不包含可以變化的狀态,State 指的就是狀态,狀态可以想成是小部件裡的資料 … 也就是 StatelessWidget 小部件一但被建立以後 … 它裡面的狀态一般不會有什麼變化 …

Flutter移動應用:狀态管理

下面再把 count 的值輸出到控制台上檢查一下 … 打開調試控制台 …

按一下界面上的這個漂浮按鈕 … 你會發現,每次按這個按鈕的時候,count 的值都會加上 1 … 不過在界面上這個 Chip 裡面顯示的 count 的值沒有發生改變 …

我們建立的小部件的狀态會根據使用者行為發生變化 … 這個時候我們需要考慮使用另一種小部件,StatefulWidget …

StatefulWidget:帶變化狀态的小部件

StatefulWidget,這種類型的小部件裡面可以有一些能夠變化的狀态 … 也就是 StaefulWidget 建構的使用者界面可以動态的發生變化 … 比如在我們這個例子裡面 … 點了這個漂浮按鈕,界面上顯示的這個數字要動态的發生變化 … 這種情況我們就可以使用一個 StaefulWidget …

下面可以把我們這個小部件改造成一個 StatefulWidget … 讓它繼承一下 StatefulWidget … 要注意的是 StaefulWidget 小部件本身也是 immutable ,不可改變的 …

小部件需要的可以變化的那些狀态要單獨放在一個 State 對象裡面 … 這個 State 對象可以使用小部件裡的 createState 這個方法來建立 .

Flutter移動應用:狀态管理

然後到模拟器上再試一下 … 按一下這個動作按鈕 … count 的值會加上 1,這個變化是在 setState 方法裡面完成的,是以每次有變化,小部件都會使用新的狀态被重建 … 重建以後,界面上會顯示小部件變化之後的狀态 …

狀态管理(由父輩管理狀态)

在我們這個 StateManagementDemo 小部件裡面 … 小部件的狀态是它自己管理的 … 它需要的所有的狀态都在這個小部件自己這裡 … 有時候這個狀态可能在小部件的父輩那裡 … 把 State 從父輩那裡傳遞過來,可以通過小部件的構造函數 …

Flutter移動應用:狀态管理

使用這個 Counter 的時候,現在需要提供一個 count 屬性 … 這個屬性的值可以使用上面定義的這個 _count …

現在 Counter 小部件裡面需要的資料是從它爸爸,也就是 StateManagementDemo 那裡傳遞過來的 …

從父輩那裡傳遞個回調

下面我們再改造一下這個 Counter 小部件 … 把這個 Chip 換成一個 ActionChip ,這樣可以給它一個 onPressed 屬性,設定一下點按這個 Chip 要執行的動作 … 我想在按它的時候也可以讓 _count 的值加上 1 …

如果在這個 Counter 小部件裡面設定它的這個 count ,界面上顯示的數字是不會有什麼變化的 … 因為這個數字其實是 StateManagementDemo 裡面的 _count 的的值 …

Flutter移動應用:狀态管理

按一下界面上的這個 ActionChip … 執行的就是它爸爸傳遞過來的一個回調 … 做的事兒就是讓小部件的 _count 的值加上 1 …

再按一下漂浮動作按鈕 … 同樣可以增加 _count 的值 …

狀态樹

Flutter移動應用:狀态管理

現在,在 Counter 裡面需要的資料是通過小部件的構造函數,從 StateManagementDemo 那裡直接傳遞過來的 … 假設在這兩個小部件之間還有一個小部件 … 比如可能有一個 CounterWrapper … 在這個 CounterWrapper 裡面使用了 Counter …

這樣,這個 Counter 需要的資料要先從 StateManagementDemo 傳遞給 CounterWrapper … 然後再由這個 CounterWrapper 傳遞給 Counter … 需要一級一級往下傳 …

Flutter移動應用:狀态管理

InheritedWidget

InheritedWidget:直接把資料傳遞給需要的小部件

在我們這個示例裡面,Counter 小部件需要的資料是從 StateManagementDemo 傳遞給 CounterWrapper,又從 CounterWrapper 傳遞給了 Counter … 現在我們需要一種方法,可以把資料直接傳遞給需要的小部件 … 也就是 Counter 需要的資料可以不通過 CounterWrapper,而是直接從 StateManagementDemo 那裡傳遞過來 …

可以試一下 Flutter 的 InheritedWidget … 用法就是可以去建立一個 InheritedWidget,在這個小部件裡面設定其它小部件需要的資料,然後把這個 InheritedWidget 放在小部件樹的某個地方,這樣在樹下面的小部件都可以直接通路到在 InheritedWidget 小部件裡的資料了 …

Flutter移動應用:狀态管理

ScopedModel

ScopedModel:安裝與基本用法介紹

ScopedModel 也可以把資料直接交給需要的小部件 … 它是一個第三方的包,是以要使用它得先去安裝一下 … 打開項目下面的 pubspec.yaml …

Flutter移動應用:狀态管理

使用 ScopedModel … 我們得先去建立一個 Model … 在裡面添加需要的資料 … 然後把 ScopedModel 小部件放到 Widget Tree 的某個位置上 … 設定一下它的 model … 這樣在它下面的小部件都可以直接通路到它設定的 model 裡的東西 …

使用 ScopedModel 傳遞資料

先建立一個 Model … 添加一個類 … 名字可以是 CounterModel … 它要繼承一下 Model … 再導入需要的包 … 就是之前我們安裝的這個 scoped_model …

裡添加一個 int _count … 讓它先等于 0 … 再添加一個 getter 方法, 名字叫 count … 讓它傳回 _count 的值 … 在使用了這個 model 的小部件裡面,可以使用這個 getter 方法擷取到 _count 的值 …

Flutter移動應用:狀态管理

按一下界面上的 ActionChip ,會讓 CounterModel 的 _count 的值增加 1 ,有變化就會重建這個部件顯示出變化之後的樣子 …

同樣,按一下漂浮動作按鈕,也可以讓 CounterModel 裡的 _count 的值加上 1 …

最終代碼

import 'package:flutter/material.dart';
import "package:scoped_model/scoped_model.dart";

class StateManagementDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ScopedModel(
        model: CounterModel(),
        child: Scaffold(
            appBar: AppBar(
              title: Text('StateManagementDemo'),
              elevation: 0.0,
            ),
            body: CouterWrapper(),
            floatingActionButton: ScopedModelDescendant<CounterModel>(
              rebuildOnChange: false,
              builder: (context, _, model) => FloatingActionButton(
                child: Icon(Icons.add),
                onPressed: model.increaseCount,
              ),
            )));
  }
}

class CouterWrapper extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Counter(),
    );
  }
}

class Counter extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ScopedModelDescendant<CounterModel>(
        builder: (context, _, model) => ActionChip(
              label: Text('${model.count}'),
              onPressed: model.increaseCount,
            ));
  }
}

class CounterProvider extends InheritedWidget {
  final int count;
  final VoidCallback increaseCount;
  final Widget child;

  // 構造函數
  CounterProvider({this.count, this.increaseCount, this.child})
      : super(child: child);

  static CounterProvider of(BuildContext context) =>
      context.dependOnInheritedWidgetOfExactType();

  @override
  bool updateShouldNotify(InheritedWidget oldWidget) {
    return true;
  }
}

class CounterModel extends Model {
  int _count = 0;
  int get count => _count;

  void increaseCount() {
    _count += 1;
    notifyListeners();
  }
}