原文連結https://siques.cn/p/41
這個章節我們會先介紹一下 Flutter 的 StatelessWidget … 還有 StatefulWidget …
然後會用一個非常簡單的例子來了解一下 Flutter 應用的狀态管理 … 狀态就是小部件裡的資料 … 小部件可以自己管理需要的資料 … 這些資料也可以通過小部件的構造函數從它的父輩那裡傳遞過來 …
我們還會學一下,使用 InheritedWidget … 還有 ScopedModel,更有效的去把資料傳遞給需要的小部件 …
小部件
StatelessWidget:無變化狀态的小部件
StatelessWidget,這種小部件裡面不包含可以變化的狀态,State 指的就是狀态,狀态可以想成是小部件裡的資料 … 也就是 StatelessWidget 小部件一但被建立以後 … 它裡面的狀态一般不會有什麼變化 …
下面再把 count 的值輸出到控制台上檢查一下 … 打開調試控制台 …
按一下界面上的這個漂浮按鈕 … 你會發現,每次按這個按鈕的時候,count 的值都會加上 1 … 不過在界面上這個 Chip 裡面顯示的 count 的值沒有發生改變 …
我們建立的小部件的狀态會根據使用者行為發生變化 … 這個時候我們需要考慮使用另一種小部件,StatefulWidget …
StatefulWidget:帶變化狀态的小部件
StatefulWidget,這種類型的小部件裡面可以有一些能夠變化的狀态 … 也就是 StaefulWidget 建構的使用者界面可以動态的發生變化 … 比如在我們這個例子裡面 … 點了這個漂浮按鈕,界面上顯示的這個數字要動态的發生變化 … 這種情況我們就可以使用一個 StaefulWidget …
下面可以把我們這個小部件改造成一個 StatefulWidget … 讓它繼承一下 StatefulWidget … 要注意的是 StaefulWidget 小部件本身也是 immutable ,不可改變的 …
小部件需要的可以變化的那些狀态要單獨放在一個 State 對象裡面 … 這個 State 對象可以使用小部件裡的 createState 這個方法來建立 .
然後到模拟器上再試一下 … 按一下這個動作按鈕 … count 的值會加上 1,這個變化是在 setState 方法裡面完成的,是以每次有變化,小部件都會使用新的狀态被重建 … 重建以後,界面上會顯示小部件變化之後的狀态 …
狀态管理(由父輩管理狀态)
在我們這個 StateManagementDemo 小部件裡面 … 小部件的狀态是它自己管理的 … 它需要的所有的狀态都在這個小部件自己這裡 … 有時候這個狀态可能在小部件的父輩那裡 … 把 State 從父輩那裡傳遞過來,可以通過小部件的構造函數 …
使用這個 Counter 的時候,現在需要提供一個 count 屬性 … 這個屬性的值可以使用上面定義的這個 _count …
現在 Counter 小部件裡面需要的資料是從它爸爸,也就是 StateManagementDemo 那裡傳遞過來的 …
從父輩那裡傳遞個回調
下面我們再改造一下這個 Counter 小部件 … 把這個 Chip 換成一個 ActionChip ,這樣可以給它一個 onPressed 屬性,設定一下點按這個 Chip 要執行的動作 … 我想在按它的時候也可以讓 _count 的值加上 1 …
如果在這個 Counter 小部件裡面設定它的這個 count ,界面上顯示的數字是不會有什麼變化的 … 因為這個數字其實是 StateManagementDemo 裡面的 _count 的的值 …
按一下界面上的這個 ActionChip … 執行的就是它爸爸傳遞過來的一個回調 … 做的事兒就是讓小部件的 _count 的值加上 1 …
再按一下漂浮動作按鈕 … 同樣可以增加 _count 的值 …
狀态樹
現在,在 Counter 裡面需要的資料是通過小部件的構造函數,從 StateManagementDemo 那裡直接傳遞過來的 … 假設在這兩個小部件之間還有一個小部件 … 比如可能有一個 CounterWrapper … 在這個 CounterWrapper 裡面使用了 Counter …
這樣,這個 Counter 需要的資料要先從 StateManagementDemo 傳遞給 CounterWrapper … 然後再由這個 CounterWrapper 傳遞給 Counter … 需要一級一級往下傳 …
InheritedWidget
InheritedWidget:直接把資料傳遞給需要的小部件
在我們這個示例裡面,Counter 小部件需要的資料是從 StateManagementDemo 傳遞給 CounterWrapper,又從 CounterWrapper 傳遞給了 Counter … 現在我們需要一種方法,可以把資料直接傳遞給需要的小部件 … 也就是 Counter 需要的資料可以不通過 CounterWrapper,而是直接從 StateManagementDemo 那裡傳遞過來 …
可以試一下 Flutter 的 InheritedWidget … 用法就是可以去建立一個 InheritedWidget,在這個小部件裡面設定其它小部件需要的資料,然後把這個 InheritedWidget 放在小部件樹的某個地方,這樣在樹下面的小部件都可以直接通路到在 InheritedWidget 小部件裡的資料了 …
ScopedModel
ScopedModel:安裝與基本用法介紹
ScopedModel 也可以把資料直接交給需要的小部件 … 它是一個第三方的包,是以要使用它得先去安裝一下 … 打開項目下面的 pubspec.yaml …
使用 ScopedModel … 我們得先去建立一個 Model … 在裡面添加需要的資料 … 然後把 ScopedModel 小部件放到 Widget Tree 的某個位置上 … 設定一下它的 model … 這樣在它下面的小部件都可以直接通路到它設定的 model 裡的東西 …
使用 ScopedModel 傳遞資料
先建立一個 Model … 添加一個類 … 名字可以是 CounterModel … 它要繼承一下 Model … 再導入需要的包 … 就是之前我們安裝的這個 scoped_model …
裡添加一個 int _count … 讓它先等于 0 … 再添加一個 getter 方法, 名字叫 count … 讓它傳回 _count 的值 … 在使用了這個 model 的小部件裡面,可以使用這個 getter 方法擷取到 _count 的值 …
按一下界面上的 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();
}
}