背景介绍
之前一直从事游戏开发,最近接到一个需求,要快速开发一个聊天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);
未知变量,部分会在后面的文章出现