基于 Getx
實(shí)現(xiàn)的 MVVM
在原生的iOS墩崩、Android中我們已經(jīng)習(xí)慣了使用MVVM取代MVC氓英,來(lái)實(shí)現(xiàn)業(yè)務(wù)頁(yè)面,這樣結(jié)構(gòu)更加清晰鹦筹,也便于管理和功能擴(kuò)展铝阐。在Flutter通過Getx來(lái)實(shí)現(xiàn)MVVM,如播放器的首頁(yè)的實(shí)現(xiàn)铐拐,簡(jiǎn)化之后的代碼如下徘键。
View
的實(shí)現(xiàn):在View
中實(shí)例化一個(gè)HomeController
并交給 Get
, build
方法中返回一個(gè)由GetBuilder <HomeController>( builder: (controller) {return ......;}
包裹的子widget,子widget的顯示的數(shù)據(jù)有HomeController
調(diào)用update()
后來(lái)更新遍蟋。但是需要注意的是吹害,這里的HomeController
在退出本頁(yè)面后不會(huì)主動(dòng)銷毀,如需要銷毀匿值,在dispose()
函數(shù)中調(diào)用Get.delete<HomeController>();
即可赠制。
class HomeView extends StatefulWidget {
const HomeView({Key? key}) : super(key: key);
@override
_HomeViewState createState() => _HomeViewState();
}
class _HomeViewState extends State<HomeView> {
final HomeController controller = Get.put(HomeController());
@override
Widget build(BuildContext context) {
return GetBuilder<HomeController>(
builder: (controller) {
return ......;
},
);
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
Get.delete<HomeController>();
}
}
Model
主要用來(lái)保存網(wǎng)絡(luò)請(qǐng)求的數(shù)據(jù)、記錄頁(yè)面widget的狀態(tài)等
class HomeState {
HistoryPo? selectedItem;
int selectedIndex = -1;
List<PlayListItemModel> playList = [];
List<HistoryPo> ranks = [];
}
ViewModel
HomeController
繼承GetxController
來(lái)充當(dāng)ViewModel
的角色,一般會(huì)在重寫的父類onReady()
方法中發(fā)送物流異步請(qǐng)求钟些,拿到數(shù)據(jù)后再調(diào)用update()
就會(huì)更新 GetBuilder <HomeController>( builder: (controller) {return ......;}
中的子Widget顯示烟号。
class HomeController extends GetxController {
HomeState state = HomeState();
@override
void onReady(){
fetchPlayList();
fetchTopSongs();
}
fetchTopSongs() async {
var dio = Dio();
final response = await dio.get(Api.top500);
String sst =
response.toString().replaceAll(RegExp(r'<!--KG_TAG_RES_START-->'), "");
sst = sst.replaceAll(RegExp(r'<!--KG_TAG_RES_END-->'), "");
Map<String, dynamic> data = jsonDecode(sst);
List dataList = data["data"]["info"];
List<HistoryPo> ranks =
dataList.map((e) => HistoryPo.fromKugouRankJson(e)).toList();
state.ranks = ranks;
update();
}
fetchPlayList() async {
var dio = Dio();
final response =
await dio.get(Api.neteasePlayList, queryParameters: {"offset": 0});
Map<String, dynamic> data = jsonDecode(response.toString());
List dataList = data["rows"];
var playList = dataList.map((e) => PlayListItemModel.fromJson(e)).toList();
state.playList = playList;
update();
}
chooseSong(HistoryPo item, int index) {
state.selectedItem = item;
state.selectedIndex = index;
Get.find<PlayController>().initState(state.ranks, index);
update();
}
morePlayList(int index) {
PlayListItemModel playListItem = state.playList[index];
}
}
HomeController
的實(shí)現(xiàn)覆蓋的Widget范圍可以更小,這樣在調(diào)用update()
時(shí)需要更新的Widget也少一些政恍,減少大面積的Widget更新汪拥,一定程度上提升頁(yè)面流暢度,特別適合于比較復(fù)雜的頁(yè)面篙耗。
換主題只需要這4步就能實(shí)現(xiàn)
- 定義好主題顯示的數(shù)據(jù)迫筑,如
light
和dark
。
///白天模式
static ThemeData lightTheme = ThemeData.light().copyWith(
primaryColor: const Color(0xffffffff),
splashColor: Colors.white,
highlightColor: Colors.white,
appBarTheme: const AppBarTheme(
systemOverlayStyle: SystemUiOverlayStyle.dark,
elevation: 0,
backgroundColor: Color(0xffffffff),
iconTheme: IconThemeData(color: Colors.black,size: 20),
titleTextStyle: TextStyle(
fontSize: 15,
color: Color(0xFF2d2d2d),
),
centerTitle: true,
),
scaffoldBackgroundColor: const Color(0xffF6F8F9),
backgroundColor: const Color(0xffF6F8F9),
dividerColor: const Color(0xffF6F8F9),
);
///夜間模式
static ThemeData darkTheme = ThemeData.dark().copyWith(
primaryColor: ThemeData.dark().primaryColor,
splashColor: ThemeData.dark().splashColor,
highlightColor: Colors.black,
appBarTheme: AppBarTheme(
systemOverlayStyle: SystemUiOverlayStyle.light,
elevation: 0,
backgroundColor: ThemeData.dark().cardColor,
iconTheme: const IconThemeData(color: Colors.white, size: 20),
titleTextStyle: const TextStyle(
fontSize: 15,
color: Color(0xFFeeeeee),
),
centerTitle: true,
),
scaffoldBackgroundColor: ThemeData.dark().scaffoldBackgroundColor,
backgroundColor: Colors.black,
iconTheme: const IconThemeData(
color: Colors.blue,
),
dividerColor: ThemeData.dark().dividerColor,
);
- 在入口的Widget宗弯,即
runApp(const MyApp());
的MyApp
的GetMaterialApp
配置好上面聲明的lightTheme
和darkTheme
脯燃。
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
theme: ThemeConfig.lightTheme,
darkTheme: ThemeConfig.darkTheme,
);
}
}
- 修改頁(yè)面中需要更新的地方,如背景顏色蒙保、文字顏色等辕棚。
Container(
color: Get.theme.primaryColor,
)
- 手動(dòng)觸發(fā)切換
GestureDetector(
onTap: () {
Get.changeThemeMode(Get.isDarkMode ? ThemeMode.light : ThemeMode.dark);
Future.delayed(Duration(milliseconds: 250), () {
Get.forceAppUpdate();
});
}}
跟隨系統(tǒng)變化
class AppLifeCycleDelegate with WidgetsBindingObserver {
static final AppLifeCycleDelegate _appLifeCycleDelegate =
AppLifeCycleDelegate._inital();
AppLifeCycleDelegate._inital() {
WidgetsBinding.instance?.addObserver(this);
}
factory AppLifeCycleDelegate() {
return _appLifeCycleDelegate;
}
@override
void didChangePlatformBrightness() {
super.didChangePlatformBrightness();
Get.forceAppUpdate();
}
}
- 在
main()
添加WidgetsFlutterBinding.ensureInitialized();
小結(jié):以上實(shí)現(xiàn)是簡(jiǎn)化后的代碼,具體實(shí)現(xiàn)在gtihub有詳細(xì)實(shí)現(xiàn)邓厕。