窗口管理三步走(一)

682 阅读3分钟

背景介绍

之前一直从事游戏开发,最近接到一个需求,要快速开发一个聊天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);

未知变量,部分会在后面的文章出现