'use strict'; const logger = require('Logger').getLogger('Navigator.js'); /** * 导航模式 * @type {{New: number, Back: number, Refresh: number}} */ const navigatorMode = { New: 0, Back: 1, Refresh: 2 }; /** * Navigator介绍: * ------------------------ * 提供一个支持导航栈的Navigator类,支持以下特性 * 0,记录场景切换的导航栈。 * 1,场景之间可以传递参数,比如场景A要传个字符串给场景B。 * 2,多个场景进入同一场景后,从场景返回前一个场景,不需要再判断前一个场景,可以直接goBack返回。 * 3,支持场景返回后页面数据恢复,比如场景A界面,输入框输入了一段文字,然后进入场景B, * 从场景B返回后可以恢复输入框文字(需要在场景A脚本实现固定接口支持)。 * * Navigator使用方法: * ------------------------ * a)在场景A向前加载新场景B[带参数][带回调] * /// 默认 * navigator.navigate('B'); * * /// [带参数] * let parameter = {}; * parameter.title = 'i am wang ronghui'; * navigator.navigate('B', parameter); * * /// [带回调] * navigator.navigate('B', function(scene){ * /// 切换成功处理 * }); * * /// [带参数] + [带回调] * let parameter = {}; * parameter.title = 'i am wang ronghui'; * navigator.navigate('B', parameter, function(scene){ * /// 切换成功处理 * }); * * ~如果有传递parameter需在相应B.js内部实现loadState(navigatorMode, parameter, state)函数接收参数parameter。 * ~如果要存储当前UI状态则实现saveState(state){ //将UI状态存储在参数state中,后续在loadState里恢复state }。 * * c)场景B向后返回前一个场景A * /// 默认 * navigator.goBack(); * * /// [带参数] * let parameter = {}; * parameter.title = 'i am wang ronghui'; * navigator.goBack(parameter); * * d)场景B向后返回指定名字场景A * /// 默认 * navigator.goBackToScene('A'); * * /// [带参数] * let parameter = {}; * parameter.title = 'i am wang ronghui'; * navigator.goBackToScene('A', parameter); * * e)场景B向后返回根场景 * /// 默认 * navigator.goBackToRootScene(); * * /// [带参数] * let parameter = {}; * parameter.title = 'i am wang ronghui'; * navigator.goBackToRootScene(parameter); * * 注意事项: * ------------------------ * 挂载到场景的Canvas的自定义脚本的名字,必须要和场景文件的名字一致,否则无法调用到loadState或者saveState * */ class Navigator { /** * 构造方法 */ constructor(){ logger.info('constructor'); this._allState = new Map(); this._scenesStack = []; this._sceneLaunchHandle = false; /* * 支持外部使用cc.director.loadScene直接导航,记录下导航栈 */ cc.director.on(cc.Director.EVENT_AFTER_SCENE_LAUNCH, function (eventCustom) { /// 内部处理了这里就忽略,这里仅为支持监听外部导航。 if(this._sceneLaunchHandle) { this._sceneLaunchHandle = false; return; } /// 获取当前场景 let sceneName = eventCustom.name; logger.info('EVENT_AFTER_SCENE_LAUNCH sceneName = ' + sceneName); /// 先检查下导航栈有没有该场景,如果有,则回退到相应场景,防止出现场景循环 let level = this.sceneStackLevel(sceneName); if(level !== -1){ this.goBackToSceneStackLevel(level, null); return; } this.handleForward(sceneName, null); }.bind(this)); } /** * 向前加载sceneName场景 * @param {string} sceneName -场景名字 * @param {object} [parameter] -参数对象 * @param {function()} [onSceneLaunched] -新场景运行成功后回调 */ navigate(sceneName, parameter, onSceneLaunched){ logger.info('navigate sceneName = ' + sceneName); logger.log('navigate parameter = ' + parameter); logger.log('navigate onSceneLaunched = ' + onSceneLaunched); /// 可能parameter和onSceneLaunched只传了某一个 let argsLength = arguments.length; if(argsLength === 2) { if (typeof parameter === 'function') { onSceneLaunched = parameter; parameter = undefined; } } /// 先检查下导航栈有没有该场景,如果有,则回退到相应场景,防止出现场景循环 let level = this.sceneStackLevel(sceneName); if(level !== -1){ this.goBackToSceneStackLevel(level, parameter); return; } let readyToLeaveSceneJS = this.getCurrentSceneJS(); if(readyToLeaveSceneJS){ let sceneKey = 'Scene-' + this._scenesStack.length; let sceneState = this._allState.get(sceneKey); let state = {}; sceneState.state = state; if(typeof readyToLeaveSceneJS.saveState === 'function'){ readyToLeaveSceneJS.saveState.call(readyToLeaveSceneJS, state); } } cc.director.loadScene(sceneName, function () { /// 加载新场景成功处理 logger.log('navigate loadScene complete sceneName = ' + sceneName); this._sceneLaunchHandle = true; this.handleForward(sceneName, parameter); /// 回调通知场景切换成功 if(onSceneLaunched){ onSceneLaunched(); } }.bind(this)); logger.log('navigate end'); } /** * 向后返回前一个场景 * @param {object} [parameter] -参数对象 */ goBack(parameter){ logger.log('goBack'); /// 当前Scene出导航栈 this._scenesStack.pop(); /// 加载栈顶Scene let sceneName = this._scenesStack[this._scenesStack.length - 1]; logger.info('goBack to sceneName = ' + sceneName); cc.director.loadScene(sceneName, function () { logger.log('goBack loadScene complete sceneName = ' + sceneName); this._sceneLaunchHandle = true; this.handleBack(parameter); }.bind(this)); } /** * 向后返回前根场景 * @param {object} [parameter] -参数对象 */ goBackToRootScene(parameter){ logger.log('goBackToRootScene'); this.goBackToSceneStackLevel(1, parameter); } /** * 向后返回指定场景 * @param {string} sceneName -场景名字 * @param {object} [parameter] -参数对象 */ goBackToScene(sceneName, parameter){ logger.log('goBackToScene sceneName = ' + sceneName); let level = this.sceneStackLevel(sceneName); if(level !== -1){ this.goBackToSceneStackLevel(level, parameter); } } /*-------------------------私有方法begin-------------------------*/ /** * 前进页面处理,加入导航栈,分配state * @param {string} sceneName -场景名字 * @param {object} [parameter] -参数对象 */ handleForward(sceneName, parameter){ logger.info('handleForward sceneName = ' + sceneName); logger.info('handleForward parameter = ' + parameter); /// 0,入导航栈 if(sceneName){ this._scenesStack.push(sceneName); } /// 加载新场景成功处理 let enterSceneJS = this.getCurrentSceneJS(); if(enterSceneJS){ /// 向前导航时只有parameter,没有页面状态,所以页面状态为null if(typeof enterSceneJS.loadState === 'function'){ enterSceneJS.loadState.call(enterSceneJS, navigatorMode.New, parameter, null); } /// 1,由于后退时不清理状态,在这里将当前页面以及向前所有的状态清除 let nextSceneKey = 'Scene-' + this._scenesStack.length; let nextSceneIndex = this._scenesStack.length; while (this._allState.delete(nextSceneKey)) { nextSceneIndex ++; nextSceneKey = 'Scene-' + nextSceneIndex; } /// 2,设置个state给当前Scene let sceneState = {}; let sceneKey = 'Scene-' + this._scenesStack.length; logger.log('handleForward sceneKey = ' + sceneKey); this._allState.set(sceneKey, sceneState); /// 3,记录下参数 parameter = parameter || {}; sceneState.parameter = parameter; sceneState.state = {}; } } /** * 后退页面处理,恢复场景 * @param {object} [parameter] -参数对象 */ handleBack(parameter){ logger.info('handleBack'); /// 加载新场景成功处理 let enterSceneJS = this.getCurrentSceneJS(); if(enterSceneJS){ let sceneKey = 'Scene-' + this._scenesStack.length; logger.log('handleBack sceneKey = ' + sceneKey); let sceneState = this._allState.get(sceneKey); /// 获取参数和页面状态,传入场景js,用于场景页面恢复 if(typeof enterSceneJS.loadState === 'function'){ /// 如果Back有带参数,优先使用参数,否则使用保留参数。 parameter = parameter || sceneState.parameter; enterSceneJS.loadState.call(enterSceneJS, navigatorMode.Back, parameter, sceneState.state); } } } /** * 刷新页面处理 * @param {object} [parameter] -参数对象 */ handleRefresh(parameter){ logger.info('handleRefresh'); let enterSceneJS = this.getCurrentSceneJS(); if(enterSceneJS){ if(typeof enterSceneJS.loadState === 'function'){ enterSceneJS.loadState.call(enterSceneJS, navigatorMode.Refresh, parameter, null); } } } /** * 获取当前场景脚本类 */ getCurrentSceneJS(){ let currentScene = cc.director.getScene(); if(currentScene){ let currentCanvas = currentScene.getChildByName('Canvas'); if(currentCanvas){ let currentCustomJS = currentCanvas.getComponent(currentScene.name); if(currentCustomJS){ return currentCustomJS; } } } return null; } /** * 返回到固定Level的场景 * @param {number} level -层级,比如1代表第一层 * @param {object} [parameter] -参数对象 */ goBackToSceneStackLevel(level, parameter){ logger.info('goBackToSceneStackLevel'); let locScenesStack = this._scenesStack; let c = locScenesStack.length; if (c === 0) { return; } // current level or lower -> nothing if (level > c) return; // pop stack until reaching desired level while (c > level) { let current = locScenesStack.pop(); c--; } let sceneName = locScenesStack[locScenesStack.length - 1]; logger.info('goBackToSceneStackLevel sceneName = ' + sceneName); let currentSceneName = cc.director.getScene().name; logger.info('goBackToSceneStackLevel currentSceneName = ' + currentSceneName); if(currentSceneName !== sceneName){ /// 加载栈顶Scene cc.director.loadScene(sceneName, function () { logger.log('goBackToSceneStackLevel loadScene complete sceneName = ' + sceneName); this._sceneLaunchHandle = true; this.handleBack(parameter); }.bind(this)); }else { this.handleRefresh(parameter); } } /** * 获取指定scene名字的导航栈层级 * @param {string} sceneName -场景名字 */ sceneStackLevel(sceneName){ logger.log('sceneStackLevel sceneName = ' + sceneName); let locScenesStack = this._scenesStack; let i = locScenesStack.length-1; let exist = false; for(; i>=0; --i){ if(locScenesStack[i] === sceneName){ exist = true; break; } } logger.log('sceneStackLevel i = ' + i); if(exist){ return i+1; } return -1; } /*-------------------------私有方法end-------------------------*/ } module.exports = { /** * 导航模式 */ NavigatorMode: navigatorMode, /** * get Navigator * @returns {Navigator} */ getNavigator: function(){ return new Navigator(); } };