Provider是目前Google推薦的狀態管理庫,國內鏡像的地址,現在的最新版本是4.1.1,但是我的sdk版本是1.16,不支持這個最新的,所以改版本用的4.0.0
基本使用
我先是以最簡單的主題更改來做基本的更改
第一步,添加Provider依賴,pubspec.yaml
dependencies:
provider: ^4.0.0
第二步,創建Model
import 'package:flutter/material.dart';
class ThemeModel with ChangeNotifier {
ThemeData themeData = light();
bool lighted = true;
changeTheme() {
if (lighted) {
themeData = dark();
} else {
themeData = light();
}
lighted = !lighted;
notifyListeners();
}
static ThemeData light() {
return ThemeData(
textTheme: TextTheme(
subtitle1: TextStyle(
color: Colors.black,
fontSize: 16,
)),
backgroundColor: Colors.white,
brightness: Brightness.light,
primaryColor: Color(0xff248bfe),
appBarTheme: AppBarTheme(
elevation: 0,
textTheme: TextTheme(
subtitle1: TextStyle(
color: Colors.white,
fontSize: 16,
)),
),
);
}
ThemeData dark() {
return ThemeData(
textTheme: TextTheme(
subtitle1: TextStyle(
color: Colors.white,
fontSize: 16,
)),
backgroundColor: Colors.black,
brightness: Brightness.dark,
primaryColor: Color(0xff000000),
appBarTheme: AppBarTheme(
elevation: 0,
textTheme: TextTheme(
subtitle1: TextStyle(
color: Colors.white,
fontSize: 16,
)),
),
);
}
}
一個白天模式一個夜間模式,數據需要混入ChangeNotifier
,數據更改後需要調用notifyListeners();
來發出通知。
第三步,使用ChangeNotifierProvider
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context1) {
return ChangeNotifierProvider<ThemeModel>(
create: (_)=>new ThemeModel(),
child: //這裏必須用Builder,因爲Provider.of<ThemeViewModel>(context,listen: true)要拿這裏傳入的context
Builder(
builder: (context)=>
MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: Provider.of<ThemeModel>(context,listen: true).themeData,//通過Provider.of取值
routes: <String,WidgetBuilder>{
"main/mainpage":(BuildContext context1)=>new MainPage(), //爲什麼這裏可以拿到context1,provider就不行呢???因爲該context中並沒有Provider
},
home: SplashPage(),
)
),
);
}
}
- 用
ChangeNotifierProvider
提供數據,如果用Provider
以後取的的model就感受不到變化了 -
child
需要傳入Builder
,因爲外層build
方法的context
把當前mode
l沒有包含進去,所以需要用build
來取含有此model
的context
. - 通過
Provider.of
取到對應的model
第四步,通過點擊按鈕,獲取model並更改模式
InkWell(
onTap: (){
Provider.of<ThemeModel>(context,listen:false).changeTheme();
},
child: Icon(Icons.cake),
)
簡單的封裝
用provider對數據更改來刷新界面時,我們需要有效的控制數據的範圍,如果全部放在最上層肯定會影響性能及數據的安全性。所以我們需要對provider寫一個通用的組件,方便我們使用。
ProviderWidget封裝
import 'package:flutter/cupertino.dart';
import 'package:provider/provider.dart';
class ProviderWidget<T extends ChangeNotifier> extends StatefulWidget{
final T model;
final Widget Function(BuildContext context, T value, Widget child) builder;
final Function(T) onReady;
ProviderWidget({this.model,this.onReady,this.builder});
@override
_ProviderWidgetState<T> createState() => _ProviderWidgetState<T>();
}
class _ProviderWidgetState<T extends ChangeNotifier> extends State<ProviderWidget<T>> {
@override
void initState() {
super.initState();
if(widget.onReady!=null){
widget.onReady(widget.model);
}
}
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<T>(
create: (_) => widget.model,
child: Consumer<T>(//因爲child
builder: widget.builder,
),
);
}
}
- 泛型T是我們model的類型
-
builder
是生成child
的builder
, 不直接傳入child
還是因爲之前說過的原因,構造對象的context
需要是含有當前model
的context
才能取到此model
,不然後報錯。 - 爲了能取到
context
,我們此處用到了Consumer
,合理的用Consumer
可以減少刷新範圍,這個類的具體說明請參考使用Provider前你應瞭解Consumer
使用示例
model類
class TestModel with ChangeNotifier {
int clickNum=0;
void add() {
clickNum++;
notifyListeners();
}
}
用widget的地方直接使用
ProviderWidget<TestModel>(
model:TestModel(),
onReady:(model){
model.toString();
},
builder:(context, model, child){
return Column(
children: <Widget>[
Text("${model.clickNum}"),
RaisedButton(child:Text("add"),onPressed: (){
model.add();
})
],
);
},
)
本文完整源碼請移步github