從零開始的Flutter之旅: StatelessWidgetgit
從零開始的Flutter之旅: StatefulWidgetgithub
從零開始的Flutter之旅: InheritedWidget緩存
在上篇文章中咱們介紹了InheritedWidget,並在最後引起出一個問題。bash
雖然InheritedWidget能夠提供共享數據,而且經過getElementForInheritedWidgetOfExactType來解除didChangeDependencies的調用,但仍是沒有避免CountWidget的從新build,並無將build最小化。微信
咱們今天就來解決如何避免沒必要要的build構建,將build縮小到最小的CountText。網絡
首先咱們來分析下爲何會致使父widget的從新build。架構
class CountWidget extends StatefulWidget {
@override
_CountState createState() {
return _CountState();
}
}
class _CountState extends State<CountWidget> {
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: CountInheritedWidget(
count: count,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
CountText(),
RaisedButton(
onPressed: () => setState(() => count++),
child: Text("Increment"),
)
],
),
),
),
),
);
}
}
複製代碼
爲了方便分析,我把以前的代碼提到這裏來。app
咱們來看,在點擊RaisedButton的時候,咱們會經過setState將count進行更新。而此時的setState方法的提供者是_CountState,即CountWidget。而state的改變會致使build的從新構建,致使的效果是CountWidget的build被從新調用,繼而它的子widget也相繼被從新build。框架
既然已經知道了緣由,那麼咱們再來思考下解決方案。less
若是你對InheritedWidget不熟悉,推薦閱讀從零開始的Flutter之旅: InheritedWidget
咱們來總結一下,在Column外套一層Widget,並將Column進行緩存,而後外層的Widget結合InheritedWidget來提供共享count的數據源。一旦count更新將會調用外層Widget的setState,而且從新build,但咱們使用的是Column緩存,同時CountText經過依賴的方式引用了共享的count數據源,從而會同步build更新。而RaisedButton使用的是未依賴的共享count數據源,因此並不會從新build。這樣就保證了只刷新CountText。
這種方式統必定義爲Provider,其實Flutter內部已經有Provider的完整實現,不過咱們爲了學習這種解決方法的思想,本身來實現一個簡易版的Provider。以後再去看Flutter的Provider將會更加簡單。
方案已經有了,下面咱們直接來看具體實現細節。
實現一個本身的InheritedWidget,主要用來提供共享數據源,並接受緩存的child。
class ProviderInheritedWidget<T> extends InheritedWidget {
final T data;
final Widget child;
ProviderInheritedWidget({@required this.data, this.child})
: super(child: child);
@override
bool updateShouldNotify(ProviderInheritedWidget oldWidget) {
// true -> 通知樹中依賴改共享數據的子widget
return true;
}
}
複製代碼
爲了監聽共享數據count的變化,咱們經過觀察者訂閱模式來實現。
class NotifyModel implements Listenable {
List _listeners = [];
@override
void addListener(listener) {
_listeners.add(listener);
}
@override
void removeListener(listener) {
_listeners.remove(listener);
}
void notifyDataSetChanged() {
_listeners.forEach((item) => item());
}
}
複製代碼
Listenable提供一個簡單的監聽接口,經過add與remove來增長與移除監聽,而後提供一個notify方法來進行通知監聽者。
最後咱們經過繼承NotifyModel來使count具備可監聽能力
class CountModel extends NotifyModel {
int count = 0;
CountModel({this.count});
void increment() {
count++;
notifyDataSetChanged();
}
}
複製代碼
一旦count自增,就調用notifyDataSetChanged來通知訂閱的監聽者。
有了上面的Provider與Model,咱們在提供一個外部Widget來統一管理它們,將它們結合起來。
class ModelProviderWidget<T extends NotifyModel> extends StatefulWidget {
final T data;
final Widget child;
// context 必須爲當前widget的context
static T of<T>(BuildContext context, {bool listen = true}) {
return (listen ? context.dependOnInheritedWidgetOfExactType<ProviderInheritedWidget<T>>()
: (context.getElementForInheritedWidgetOfExactType<ProviderInheritedWidget<T>>()
.widget as ProviderInheritedWidget<T>)).data;
}
ModelProviderWidget({Key key, @required this.data, @required this.child})
: super(key: key);
@override
_ModelProviderState<T> createState() => _ModelProviderState<T>();
}
class _ModelProviderState<T extends NotifyModel>
extends State<ModelProviderWidget> {
void notify() {
setState(() {
print("notify");
});
}
@override
void initState() {
// 添加監聽
widget.data.addListener(notify);
super.initState();
}
@override
void dispose() {
// 移除監聽
widget.data.removeListener(notify);
super.dispose();
}
@override
void didUpdateWidget(ModelProviderWidget<T> oldWidget) {
// data 更新時移除老的data監聽
if (oldWidget.data != widget.data) {
oldWidget.data.removeListener(notify);
widget.data.addListener(notify);
}
super.didUpdateWidget(oldWidget);
}
@override
Widget build(BuildContext context) {
return ProviderInheritedWidget<T>(
data: widget.data,
child: widget.child,
);
}
}
複製代碼
在這裏咱們提供可監聽的data數據與須要緩存的child,同時在state中對可監聽的data在合適的地方進行監聽訂閱與移除訂閱,並在收到data數據改變時調用notify進行setState操做,通知widget刷新。
在build中引用了ProviderInheritedWidget,來實現對共享子widget的數據共享,同時在ModelProviderWidget中提供of方法來暴露ProviderInheritedWidget的統一獲取方式。
經過參數listen(默認true)來控制獲取共享數據的方式,來決定是否創建依賴關係,即共享數據改變時,引用共享數據的widget是否從新build。
這一幕是否是有點似曾相識,基本上都是上篇文章中提到的InheritedWidget使用的細節。
接下來就是最終的方案替換
咱們經過ModelProviderWidget.of來獲取共享的數據,因此只要使用到了共享數據,將要調用該方法。爲了不沒必要要的重複書寫,咱們將其單獨封裝到Consumer中,內部來實現對其的調用,而且將調用的結果暴露出來。
class Consumer<T> extends StatelessWidget {
final Widget Function(BuildContext context, T value) builder;
const Consumer({Key key, @required this.builder}) : super(key: key);
@override
Widget build(BuildContext context) {
print("Consumer build");
return builder(context, ModelProviderWidget.of<T>(context));
}
}
複製代碼
一切準備就緒,咱們再對以前的代碼進行優化。
class CountWidget extends StatefulWidget {
@override
_CountState createState() {
return _CountState();
}
}
class _CountState extends State<CountWidget> {
@override
Widget build(BuildContext context) {
print("CountWidget build");
return MaterialApp(
title: 'Count App',
theme: new ThemeData(primarySwatch: Colors.blue),
home: Scaffold(
appBar: AppBar(
title: Text("Count"),
),
body: Center(
child: ModelProviderWidget<CountModel>(
data: CountModel(count: 0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Consumer<CountModel>(
builder: (context, value) => Text("count: ${value.count}")),
Builder(
builder: (context) {
print("RaiseButton build");
return RaisedButton(
onPressed: () => ModelProviderWidget.of<CountModel>(
context,
listen: false)
.increment(),
child: Text("Increment"),
);
},
),
],
),
),
),
),
);
}
}
複製代碼
咱們將Column緩存到ModelProviderWidget中,同時對CountModel數據進行共享;經過Consumer進行Text的封裝,引用共享數據CountModel中的count。
對於RaisedButton,由於它只是提供點擊,而且觸發count的自增操做、沒有發生ui上的任何變化。因此爲了不RaisedButton引用的共享數據進行自增時從新build,這裏將listen參數置爲false。
最後咱們運行上面的代碼,咱們點擊Increment按鈕時,控制檯將會輸出以下日誌:
I/flutter ( 3141): notify
I/flutter ( 3141): Consumer build
複製代碼
說明只有Consumer從新調用了build,即Text進行了刷新。其它的widget都沒有變化。
這樣就解決了開篇提到的疑問,達到了widget刷新的最小化。
以上是一個簡單地Provider-Consumer的使用。Flutter對這一塊有更完善的實現方案。可是通過咱們這一輪分析,你再去看Flutter中Provider的源碼將會更加簡單易懂。
若是你想了解Flutter中Provider的使用,你能夠經過flutter_github來了解它的具體實戰使用技巧。
想要查看Provider實戰技巧,須要將分支切換到
sample_provider
下面介紹一個完整的Flutter項目,對於新手來講是個不錯的入門。
flutter_github,這是一個基於Flutter的Github客戶端同時支持Android與IOS,支持帳戶密碼與認證登錄。使用dart語言進行開發,項目架構是基於Model/State/ViewModel的MSVM;使用Navigator進行頁面的跳轉;網絡框架使用了dio。項目正在持續更新中,感興趣的能夠關注一下。
固然若是你想了解Android原生,相信flutter_github的純Android版本AwesomeGithub是一個不錯的選擇。
若是你喜歡個人文章模式,或者對我接下來的文章感興趣,建議您關注個人微信公衆號:【Android補給站】
或者掃描下方二維碼,與我創建有效的溝通,同時更快更準的收到個人更新推送。