淺嘗Flutter(一)
承接上文。
頁(yè)面路由框架
其實(shí)可以講的主要是Flutter的特性導(dǎo)致的一些架構(gòu)上的變化。
Android的界面跳轉(zhuǎn)主要涉及到系統(tǒng)層面的各個(gè)服務(wù)靠柑。
但是到了Flutter其實(shí)就沒(méi)有這些概念了,F(xiàn)lutter其實(shí)是單Activity架構(gòu)。
我另一個(gè)B端項(xiàng)目POS項(xiàng)目也采用的這種架構(gòu),作用很明顯塞茅,數(shù)據(jù)通信極其方便。缺點(diǎn)是頁(yè)面之間的路由維護(hù)會(huì)稍微復(fù)雜點(diǎn)季率。而且Flutter的Navigator用起來(lái)并不方便野瘦,一開(kāi)始項(xiàng)目對(duì)Navigator1.0做了一層封裝,初始化的參數(shù)定義特別死飒泻,很不方便鞭光,是一個(gè)List<String,Map<String,String>>,參數(shù)在傳遞過(guò)程中需要來(lái)回轉(zhuǎn)換泞遗,而且頁(yè)面標(biāo)識(shí)也不明顯惰许,非schema內(nèi)部跳轉(zhuǎn)也需要把參數(shù)進(jìn)行path轉(zhuǎn)換。
比如A--》B攜帶參數(shù){ "t1" : 1, "t2" : 2 }史辙,B定義為/test
那么跳轉(zhuǎn)path需要先拼接成/test?t1=1&t2=2汹买,然后到終端頁(yè)面還解析成了ArrayList<String,Map<String,Strign>>,這可太低效,且難以理解了聊倔。
屬實(shí)不好用晦毙,我覺(jué)的是一個(gè)很失敗的封裝,然后重新查閱了Flutter官網(wǎng)資料發(fā)現(xiàn)Flutter有一個(gè)Navigator2,采用了Page的概念去維護(hù)頁(yè)面棧方库,棧采用Arraylist<Page>结序,挺清晰的障斋。
所以在其他人開(kāi)發(fā)業(yè)務(wù)的同時(shí)我對(duì)項(xiàng)目的路由框架下層進(jìn)行了重構(gòu)纵潦。對(duì)外的接口類(lèi)不動(dòng),但是增加了一些可變參數(shù)垃环,減少改動(dòng)邀层。
class XXXNavigator{
static Future navigateTo(BuildContext context, String pageDef,
{bool clearStack = false,
bool replace = false,
String? clearToPageDef,
Map<String, dynamic>? params}) {
unfocus(context);
if (globalDebugCtl) {
print("Jump to $pageDef");
}
return CommonRouterDelegate.getInstance().addPage(context, pageDef, params,
replace: replace,
clearToPageDef: clearToPageDef,
clearStack: clearStack);
}
static Future navigateTo(BuildContext context, String pageDef,
{bool clearStack = false,
bool replace = false,
String? clearToPageDef,
Map<String, dynamic>? params})
//.....省略其他方法
}
CommonRouterDelegate是我實(shí)現(xiàn)Navigator的地方。
RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();
typedef RouterBuilder = BasePage? Function(
String page, Map<String, dynamic>? params);
class CommonRouterDelegate extends RouterDelegate<CommonRoutePath>
with ChangeNotifier, PopNavigatorRouterDelegateMixin<CommonRoutePath> {
static CommonRouterDelegate? _instance;
static CommonRouterDelegate getInstance() {
_instance ??= CommonRouterDelegate._private();
return _instance!;
}
CommonRouterDelegate._private();
@override
CommonRoutePath? get currentConfiguration => _currentConfiguration;
@override
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
CommonRoutePath? _currentConfiguration;
var test = false;
List<BasePage> pages = [
const BasePage(child: SplashScreen()),
];
@override
Widget build(BuildContext context) {
return Navigator(
key: navigatorKey,
pages: List.of(pages),
observers: [routeObserver],
onPopPage: (route, result) {
if (!route.didPop(result)) {
return false;
}
BasePage page = pages.removeLast();
notifyListeners();
_popCompleter[route.settings.name]?.complete(result);
if (result is! LoadingPopEntity) {
CommonRouterObserver.getInstance().onPageEnd(page);
}
//通知路由變化
return true;
},
);
}
@override
Future<void> setNewRoutePath(CommonRoutePath configuration) async {
_currentConfiguration = configuration;
notifyListeners();
}
final Map<String, Completer<dynamic>?> _popCompleter = {};
//執(zhí)行指定行為并打開(kāi)某個(gè)頁(yè)面
Future addPage(
BuildContext context, String pageDef, Map<String, dynamic>? params,
{bool replace = false,
bool clearStack = false,
String? clearToPageDef}) async {
BasePage? page = getPage(pageDef, params);
if (page != null) {
if (replace) {
pages.removeLast();
}
if (clearStack) {
pages.clear();
}
if (clearToPageDef != null) {
int index =
pages.indexWhere((element) => element.name == clearToPageDef);
if (index > 0) {
pages = pages.sublist(0, index + 1);
}
}
pages.add(page);
notifyListeners();
CommonRouterObserver.getInstance().onPageStart(page);
_popCompleter[pageDef] = Completer<dynamic>();
return await _popCompleter[pageDef]!.future;
}
}
void clear() {
pages.clear();
notifyListeners();
}
void popUtil(BuildContext context, String clearToPageDef) {
int index = pages.indexWhere((element) => element.name == clearToPageDef);
while (index > 0 && index < pages.length) {
BasePage page = pages.removeLast();
// print("<<<<<<<<popUtil>clearToPageDef $page , index = $index");
_popCompleter[page.name!] = Completer<dynamic>();
_popCompleter[page.name]?.complete();
CommonRouterObserver.getInstance().onPageEnd(page);
PageRoute cur = ModalRoute.of(context) as PageRoute;
routeObserver.didPop(cur, cur);
index++;
}
notifyListeners();
}
BasePage<dynamic>? getPage(String pageDef, Map<String, dynamic>? params) {
return baseModuleBuilder.call(pageDef, params) ??
homeModuleBuilder.call(pageDef, params) ??
accountModuleBuilder.call(pageDef, params) ??
payModuleBuilder.call(pageDef, params);
}
}
baseModuleBuilder遂庄,homeModuleBuilder寥院,accountModuleBuilder,payModuleBuilder等是我們的頁(yè)面工廠(chǎng)涛目,主要建立的是頁(yè)面和PageDef的關(guān)聯(lián)秸谢。
定義的PageDef中維護(hù)的是我們每個(gè)頁(yè)面的字符串路由描述凛澎。ModuleBuilder的作用還有對(duì)業(yè)務(wù)進(jìn)行分類(lèi)的作用」捞悖基于PageDef的映射關(guān)系我們實(shí)現(xiàn)頁(yè)面路由也是極其方便塑煎。
核心的幾個(gè)點(diǎn)主要是,頁(yè)面分發(fā)和頁(yè)面棧的維護(hù)臭蚁,以及Flutter的await機(jī)制的維護(hù)最铁,這個(gè)還挺好用的,比Android的startActivityForResult好用多了垮兑。
從主要方法addPage和Pop冷尉。
其中addPage中的幾個(gè)參數(shù)尤為重要,分為replace系枪,clearStack雀哨,clearToPageDef.
主要是實(shí)現(xiàn)了跳新頁(yè)面置換現(xiàn)在的頁(yè)面(啟動(dòng)頁(yè)面分發(fā)等場(chǎng)景),跳新頁(yè)面清棧(非游客模式的token實(shí)現(xiàn)場(chǎng)景)私爷,清理頁(yè)面到指定PageDef并打開(kāi)新頁(yè)面(主要是支付成功之后的下級(jí)頁(yè)面跳轉(zhuǎn)等場(chǎng)景)震束。
實(shí)際在使用一段時(shí)間之后,發(fā)現(xiàn)頁(yè)面嵌套await存在一些小問(wèn)題当犯,我知道是什么問(wèn)題垢村,前段時(shí)間太忙,加上代碼目前穩(wěn)定嚎卫,所以沒(méi)有來(lái)得及修復(fù)嘉栓,后面修復(fù)的更新一下。