視窗管理三步走(一)

Afofp發表於2021-03-12

背景介紹

之前一直從事遊戲開發,最近接到一個需求,要快速開發一個聊天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);
複製程式碼

未知變數,部分會在後面的文章出現

相關文章