基于Provider實(shí)現(xiàn)MVVM框架僵朗,常用的方式是 ViewModel 繼承 ChangeNotifier 看彼,再通過 ChangeNotifierProvider 提供給子Widget,ViewModel數(shù)據(jù)刷新通過調(diào)用 notifyListeners() 來通知Widget進(jìn)行刷新,Widget 通過 Provider.of 、Consumer、Selector 來監(jiān)聽數(shù)據(jù)變化重新 build 更新UI韩肝。這種方式存在的問題有:
- ViewModel數(shù)據(jù)刷新需要每次調(diào)用 notifyListeners()容易被遺漏
- notifyListeners()作用在整個(gè)ViewModel,不方便進(jìn)行局部UI刷新控制
- Selector 雖然可以控制局部刷新九榔,但需要需要自定義 shouldRebuild 要去了解Provider原理
- 缺少 ViewModel 和 Widget 生命周期的管理
ViewModelProvider 在兼容現(xiàn)有功能基礎(chǔ)刷哀峻,實(shí)現(xiàn)最小改動、不需要每次調(diào)用notifyListeners()哲泊、支持局部刷新UI和生命周期管理的框架
先給出源碼后續(xù)有空再詳細(xì)介紹 view_model_provider
局部刷新控制
1. 通過ValueNotifier創(chuàng)建可觀察對象
class ViewModel extends ChangeNotifier {
final value1 = ValueNotifier(0);
final value2 = ValueNotifier(0);
}
2. 通過 ValueListenableBuilder 監(jiān)聽數(shù)據(jù)變化刷新
ValueListenableBuilder(
valueListenable: viewModel.value1,
builder: (context, value, child) {
debugPrint("ValueListenableBuilder $value");
return Text("ValueListenableBuilder $value");
},
)
列表刷新控制
1. 通過 ListNotifier 創(chuàng)建可觀察對象
class ViewModel extends ChangeNotifier {
final list = ListNotifier<String>([]);
}
2. 通過 ListListenableBuilder 監(jiān)聽數(shù)據(jù)變化刷新
ListListenableBuilder(
valueListenable: viewModel.list,
builder: (context, value, child) {
debugPrint("ValueListenableBuilder $value");
return Text("ValueListenableBuilder $value");
},
)
實(shí)現(xiàn)生命周期管理
LifecycleWidget剩蟀,提供Widget生命周期監(jiān)聽,開放了以下回調(diào)接口可進(jìn)行初始化和解綁操作
- create切威,可以監(jiān)聽一個(gè)數(shù)據(jù)變化
- initState育特,Widget initState 回調(diào)
- initFrame,Widget 第一幀繪制完成調(diào)用
- deactivate,Widget deactivate 回調(diào)
- dispose缰冤,Widget dispose 回調(diào)
- didUpdateWidget犬缨,Widget didUpdateWidget 回調(diào)
- didChangeDependencies,Widget didChangeDependencies 回調(diào)
ViewModelProvider
創(chuàng)建ViewModel 提供給子Widget使用棉浸,開放了以下回調(diào)接口可進(jìn)行初始化和解綁操作
- initViewModel怀薛,ViewModel首次初始化 Widget initState 期間執(zhí)行
- bindViewModel,ViewModel 首次綁定 Widget 迷郑,方法在Widget build 期間執(zhí)行
- disposeViewModel枝恋,ViewModel 銷毀,Widget dispose 時(shí)執(zhí)行
/// [ViewModelProvider] 創(chuàng)建ViewModel
class ProviderExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ViewModelProvider<ViewModel>(
create: (_) => ViewModel(),
initViewModel: (context, viewModel) {
debugPrint("ProviderBuilderExample initViewModel $viewModel");
},
bindViewModel: (context, viewModel) {
debugPrint("ProviderBuilderExample bindViewModel $viewModel");
},
disposeViewModel: (context, viewModel) {
debugPrint("ProviderBuilderExample disposeViewModel $viewModel");
},
builder: (context, viewModel, child) {
debugPrint("ProviderBuilderExample builder $viewModel");
return ViewModelWidget(viewModel);
},
);
}
}
另外還可以通過繼承ViewModelProviderWidget
來創(chuàng)建ViewModel
/// 繼承 [ViewModelProviderWidget] 創(chuàng)建ViewModel
class ProviderWidgetExample extends ViewModelProviderWidget<ViewModel> {
ProviderWidgetExample() : super();
@override
ViewModel create(BuildContext context) => ViewModel();
@override
void initViewModel(BuildContext context, ViewModel viewModel) {
debugPrint("ProviderWidgetExample initViewModel $viewModel");
}
@override
void bindViewModel(BuildContext context, ViewModel viewModel) {
debugPrint("ProviderWidgetExample bindViewModel $viewModel");
}
@override
Widget buildChild(BuildContext context, ViewModel viewModel, Widget child) {
debugPrint("ProviderWidgetExample build $viewModel");
return ViewModelWidget(viewModel);
}
}
ViewModel嵌套處理
ViewModel 嵌套 ViewModel 管理子 ViewModel 嗡害,提供了兩種方式焚碌,一種需要手動調(diào)用刷新,另一種通過ValueNotifier包裝替換ViewModel不需要手動刷新霸妹,同ViewModelProvider一樣也有相關(guān)的抽象類提供繼承支持十电。
class ParentViewModel extends ChangeNotifier {
final valueViewModel = ValueNotifier(ChildViewModel());
var childViewModel = ChildViewModel();
void valueNotifier() {
valueViewModel.value = ChildViewModel();
}
void notifyListenerChild() {
childViewModel = ChildViewModel();
notifyListeners();
}
}
class ChildViewModel extends ChangeNotifier {
final value = ValueNotifier(0);
addValue() {
value.value++;
}
}
1 通過 ViewModelProvider 創(chuàng)建父ViewModel
class ChildProviderExapmle extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ViewModelProvider<ParentViewModel>(
create: (context) => ParentViewModel(),
builder: (context, viewModel, child) {
return Scaffold(
body: Container(
child: Column(
children: [
ValueViewModelProviderExample(),
ChildViewModelProviderExample(),
ElevatedButton(
onPressed: () => viewModel.valueNotifier(),
child: Text("valueNotifier"),
),
ElevatedButton(
onPressed: () => viewModel.notifyListenerChild(),
child: Text("notifyListenerChild"),
),
],
),
),
);
},
);
}
}
2 創(chuàng)建子ViewModelProvider
2-1 ChildViewModelProvider
需要手動刷新通常用于列表刷新Item區(qū)域,在ViewModelProvider已有回調(diào)基礎(chǔ)上添加了
- changeViewModel 叹螟,在子 ViewModel 被替換后可重新執(zhí)行綁定流程
/// [ChildViewModelProvider] 獲取子 ViewModel 例子
class ChildViewModelProviderExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChildViewModelProvider<ParentViewModel, ChildViewModel>(
create: (_, parent) => parent.childViewModel,
changeViewModel: (context, parent, viewModel, oldViewModel) {
debugPrint(
"ChildViewModelProvider changeViewModel $viewModel, $oldViewModel");
},
builder: (context, parent, viewModel, child) {
debugPrint("ChildViewModelProvider builder $viewModel");
return Row(
children: [
ValueListenableBuilder(
valueListenable: viewModel.value,
builder: (context, value, child) => Text("${viewModel.value}"),
),
ElevatedButton(
onPressed: () => viewModel.addValue(),
child: Text("addValue"),
)
],
);
},
);
}
}
2-2 ValueViewModelProvider
作用和回調(diào)與 ChildViewModelProvider一樣摆出,接收數(shù)據(jù)類型為 ValueListenable<ChangeNotifier>
/// [ValueViewModelProvider] 獲取子 ViewModel 例子
class ValueViewModelProviderExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ValueViewModelProvider<ParentViewModel, ChildViewModel>(
create: (_, parent) => parent.valueViewModel,
changeViewModel: (context, parent, viewModel, oldViewModel) {
debugPrint(
"ValueViewModelProvider changeViewModel $viewModel, $oldViewModel");
},
builder: (context, parent, viewModel, child) {
debugPrint("ValueViewModelProvider builder $viewModel");
return Row(
children: [
ValueListenableBuilder(
valueListenable: viewModel.value,
builder: (context, value, child) => Text("${viewModel.value}"),
),
ElevatedButton(
onPressed: () => viewModel.addValue(),
child: Text("addValue"),
)
],
);
},
);
}
獲取ViewModel
1 擴(kuò)展函數(shù)
通過context.viewModel<ViewModel>()
可以快速取出ViewModelProvider
ChildViewModelProvider
和 ValueViewModelProvider
的ViewModel,這個(gè)方法在Widget build
期間使用首妖,如果要在initStatus
期間使用可以直接使用Provider提供的擴(kuò)展context.read<ViewModel>()
。
2 ViewModelBuilder
用于取出ViewModelProvider提供的ViewModel
ValueListenableBuilder
ValueListenableBuilder 只能監(jiān)聽當(dāng)額數(shù)據(jù)刷新爷恳,同時(shí)監(jiān)聽多個(gè)數(shù)據(jù)刷新可采用ValueTuple2WidgetBuilder到ValueListenableTuple7Builder 和 ValueListenableListBuilder
/// 外部傳入 ViewModel有缆,可采用[ValueListenableBuilder]系列監(jiān)聽數(shù)據(jù)變化
Widget _buildValueListenable(ViewModel viewModel) {
return Column(
children: [
/// 監(jiān)聽單個(gè)數(shù)據(jù)變化
ValueListenableBuilder(
valueListenable: viewModel.value1,
builder: (context, value, child) {
debugPrint("ValueListenableBuilder $value");
return Text("ValueListenableBuilder $value");
}),
/// 監(jiān)聽多個(gè)數(shù)據(jù)變化,繼承自
ValueListenableListBuilder(
valueListenables: [
viewModel.value1,
viewModel.value2,
],
builder: (context, value, child) {
debugPrint(
"ValueListenableListBuilder ${value.first}, ${value.last}");
return Text(
"ValueListenableListBuilder ${value.first}, ${value.last}");
},
),
/// 監(jiān)聽多個(gè)數(shù)據(jù)變化温亲,繼承自[ValueListenableListBuilder]可指定泛型
ValueListenableTuple2Builder(
valueListenables: Tuple2(viewModel.value1, viewModel.value2),
builder: (context, value, child) {
debugPrint(
"ValueListenableTuple2Builder ${value.item1}, ${value.item2}");
return Text(
"ValueListenableTuple2Builder ${value.item1}, ${value.item2}");
},
),
],
);
}
ViewModelValueBuilder
ViewModelBuilder 和 ValueListenableBuilder 組合棚壁,用于獲取 ViewModel 和管理Widget刷新區(qū)域。
提供過個(gè)實(shí)現(xiàn) ViewModelValueListBuilder栈虚,ViewModelValueTuple2Builder 到 ViewModelValueTuple7WidgetBuilder可同時(shí)監(jiān)聽多個(gè)ViewMode參數(shù)變化來刷新Widget
/// 不通過外部傳入 ViewModel袖外,可采用[ViewModelValueBuilder]系列獲取 ViewModel 并監(jiān)聽數(shù)據(jù)變化
Widget _buildViewModelValue() {
return Column(
children: [
ViewModelValueBuilder(
valueListenable: (ViewModel viewModel) => viewModel.value1,
builder: (context, viewModel, value, child) {
debugPrint("ViewModelValueBuilder $value");
return Text("ViewModelValueBuilder $value");
},
),
ViewModelValueListBuilder(
valueListenables: (ViewModel viewModel) => [
viewModel.value1,
viewModel.value2,
],
builder: (context, viewModel, value, child) {
debugPrint(
"ViewModelValueListBuilder ${value.first}, ${value.last}");
return Text(
"ViewModelValueListBuilder ${value.first}, ${value.last}");
},
),
ViewModelValueTuple2Builder(
valueListenables: (ViewModel viewModel) =>
Tuple2(viewModel.value1, viewModel.value2),
builder: (context, viewModel, value, child) {
debugPrint(
"ViewModelValueTuple2Builder ${value.item1}, ${value.item2}");
return Text(
"ViewModelValueTuple2Builder ${value.item1}, ${value.item2}");
},
),
],
);
}