背景介紹
之前一直從事遊戲開發,最近接到一個需求,要快速開發一個聊天APP,主要功能和遊戲的反饋功能差不多,所有任務都落到客戶端這裡(單兵奮戰),瞬間壓力山大。幾經挑選,最終選擇了flutter來開發。搞了兩個星期,各種學習,各種研究。搞了一套和遊戲差不多的程式碼結構,可能和各位大神做APP的結構相差甚遠。不怎麼懂寫文章,就純分享下程式碼吧,希望各位大神指點一二。
簡單資訊
基礎介面:
StatefulWidget openScene(final sceneId, {Map param})
WindowPanel openWindow(final winId, {Map param})
void closeWindow(Widget widget)
複製程式碼
用於儲存資訊的資料結構:
class _WindowInfo{
final WindowPanel panel;
final PageRoute route;
final Map param;
Widget content;
_WindowInfo(this.panel, this.route, this.param);
}
複製程式碼
實現無Context跳轉,參考了這位大神的文章: juejin.cn/post/684490…
WindowPanel: 視窗皮膚,用於讀取配置和擴充套件功能
實現程式碼
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import '../common/constant.dart';
import '../window/window_panel.dart';
import '../data/developer/scene_data.dart';
import '../data/developer/window_data.dart';
/*
* 視窗資訊
*/
class _WindowInfo{
final WindowPanel panel;
final PageRoute route;
final Map param;
Widget content;
_WindowInfo(this.panel, this.route, this.param);
}
/*
* 視窗管理器
*/
class WindowManager{
final _navigatorState = new GlobalKey<NavigatorState>();
int _currentSceneId = -1;
StatefulWidget _currentScene;
List<_WindowInfo> _panels;
/*
* 獲取導航器的狀態
*/
GlobalKey<NavigatorState> getNavigatorState(){
return _navigatorState;
}
/*
* 獲取當前場景ID
*/
int getSceneId(){
return _currentSceneId;
}
/*
* 獲取當前場景的StatefulWidget物件
*/
StatefulWidget getScene(){
return _currentScene;
}
/*
* 開啟場景
* 引數說明: sceneId -> 場景ID
* 引數說明: param -> 引數
* 引數說明: dynamicSceneCfg -> 動態場景配置
*/
StatefulWidget openScene(final sceneId, {Map param, Map dynamicSceneCfg}){
Map data = sceneData[sceneId];
Map sceneCfg = _mergeMap(data, dynamicSceneCfg);
if(null == sceneCfg){
print("Unkown scene id:" + sceneId.toString());
return null;
}
Function builder = sceneCfg["builder"];
if(null == builder){
print("Builder function not found in scene id: " + sceneId.toString() + "'s config");
return null;
}
StatefulWidget scene = builder(param);
if(null == scene){
print("Scene id:" + sceneId.toString() + "'s builder function return a null value.");
return null;
}
if(null != _navigatorState.currentState){
PageRoute route = _genPageRoute(sceneCfg, scene);
_navigatorState.currentState.pushAndRemoveUntil(route, (route) => route == null);
}
_currentSceneId = sceneId;
_currentScene = scene;
_panels = null;
return scene;
}
/*
* 開啟視窗
* 引數說明: winId -> 場景ID
* 引數說明: param -> 引數
* 引數說明: dynamicWinCfg -> 動態視窗配置
*/
WindowPanel openWindow(final winId, {Map param, Map dynamicWinCfg}){
Map data = windowData[winId];
Map winCfg = _mergeMap(data, dynamicWinCfg);
if(null == winCfg){
print("Unkown window id:" + winId.toString());
return null;
}
WindowPanel panel = WindowPanel(winId, param);
PageRoute route = _genPageRoute(winCfg, panel);
_WindowInfo info = new _WindowInfo(panel, route, param);
_panels = _panels ?? [];
_panels.add(info);
//
_navigatorState.currentState.push(route);
return panel;
}
/*
* 根據皮膚關閉指定視窗
* 引數說明: widget -> 待關閉的WindowPanel物件
*/
void closeWindow(Widget widget){
if((null == widget) || (null == _panels)) return;
for(int idx = _panels.length-1; idx >= 0 ;idx--){
var info = _panels[idx];
var panel = info?.panel;
if((null != panel) && (panel.hashCode == widget.hashCode)){
bool isTopWindow = idx == _panels.length-1;
_panels.removeAt(idx);
if((null != info.route) && (null != info.route.navigator)) {
if(isTopWindow){ // for animation
_navigatorState.currentState.pop();
}else{
_navigatorState.currentState.removeRoute(info.route);
}
}
break;
}
}
}
/*
* 關閉最上面且視窗ID為winId的視窗
* 引數說明: winId -> 待關閉的視窗ID
*/
void closeWindowById(final winId){
if(null == _panels) return;
for(int idx = _panels.length-1; idx >= 0 ;idx--){
var info = _panels[idx];
var panel = info?.panel;
if((null != panel) && (panel.winId == winId)){
closeWindow(panel);
break;
}
}
}
/*
* 根據視窗內容關閉視窗
* 引數說明: contentWidget -> 視窗內容
*/
void closeWindowByContentWidget(Widget contentWidget){
closeWindow(getPanelByContentWidget(contentWidget));
}
/*
* 根據視窗內容獲取開啟視窗的引數
* 引數說明: contentWidget -> 視窗內容
*/
Map getParamByContentWidget(Widget contentWidget){
if((null == contentWidget) || (null == _panels)) return null;
for(int idx = _panels.length-1; idx >= 0 ;idx--) {
var info = _panels[idx];
var content = info?.content;
if((null != content) && (content.hashCode == contentWidget.hashCode)) {
return info.param;
}
}
return null;
}
/*
* 根據視窗內容獲取視窗皮膚
* 引數說明: contentWidget -> 視窗內容
*/
WindowPanel getPanelByContentWidget(Widget contentWidget){
if((null == contentWidget) || (null == _panels)) return null;
for(int idx = _panels.length-1; idx >= 0 ;idx--) {
var info = _panels[idx];
var content = info?.content;
if((null != content) && (content.hashCode == contentWidget.hashCode)) {
return info.panel;
}
}
return null;
}
/*
* 視窗皮膚和視窗內容關聯起來
* 引數說明: widget -> 視窗皮膚
* 引數說明: contentWidget -> 視窗內容
*/
void recordPanelContentWidget(Widget widget, Widget content){
if((null == widget) || (null == _panels)) return;
for(int idx = _panels.length-1; idx >= 0 ;idx--) {
var info = _panels[idx];
var panel = info?.panel;
if((null != panel) && (panel.hashCode == widget.hashCode)) {
info.content = content;
break;
}
}
}
//===================================================
//===================== 內部介面 =====================
//===================================================
/*
* map合併
*/
Map _mergeMap(Map map1, Map map2){
if(null == map2){
return map1;
}else if(null == map1){
return map2;
}
Map map = {};
map.addAll(map1);
map.addAll(map2);
return map;
}
/*
* 根據配置生成一個路由頁
*/
PageRoute _genPageRoute(Map winCfg, Widget widget){
PageRoute route;
bool isBackgroundTransparent = null != winCfg ? winCfg['isBackgroundTransparent'] : false;
Map animInfo = null != winCfg ? winCfg['animInfo'] : null;
int animType = null != animInfo ? animInfo['type'] : null;
if(animType == Const.animTypeFade){ // 淡出淡入動畫(預設無動畫)
int td = null != animInfo['transitionDuration'] ? animInfo['transitionDuration'] : 0;
int rtd = null != animInfo['reverseTransitionDuration'] ? animInfo['reverseTransitionDuration'] : 0;
route = PageRouteBuilder(
opaque: true != isBackgroundTransparent,
transitionDuration: Duration(milliseconds: td),
reverseTransitionDuration: Duration(milliseconds: rtd),
pageBuilder:(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation){
return new FadeTransition(
opacity: animation,
child: widget,
);
},
);
}else {
route = new CupertinoPageRoute(
builder: (BuildContext context) => widget,
);
}
return route;
}
}
複製程式碼
使用方法
啟動場景:
MaterialApp(
debugShowCheckedModeBanner: false,
navigatorKey: gWindowManager.getNavigatorState(),
onGenerateTitle: (context){
return Lang.of(context).appName;
},
home: gWindowManager.openScene(SceneConfig.LAUNCH),
);
複製程式碼
開啟視窗:
gWindowManager.openWindow(WindowConfig.TEST);
複製程式碼
未知變數,部分會在後面的文章出現