Navigator.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. 'use strict';
  2. const logger = require('Logger').getLogger('Navigator.js');
  3. /**
  4. * 导航模式
  5. * @type {{New: number, Back: number, Refresh: number}}
  6. */
  7. const navigatorMode = {
  8. New: 0,
  9. Back: 1,
  10. Refresh: 2
  11. };
  12. /**
  13. * Navigator介绍:
  14. * ------------------------
  15. * 提供一个支持导航栈的Navigator类,支持以下特性
  16. * 0,记录场景切换的导航栈。
  17. * 1,场景之间可以传递参数,比如场景A要传个字符串给场景B。
  18. * 2,多个场景进入同一场景后,从场景返回前一个场景,不需要再判断前一个场景,可以直接goBack返回。
  19. * 3,支持场景返回后页面数据恢复,比如场景A界面,输入框输入了一段文字,然后进入场景B,
  20. * 从场景B返回后可以恢复输入框文字(需要在场景A脚本实现固定接口支持)。
  21. *
  22. * Navigator使用方法:
  23. * ------------------------
  24. * a)在场景A向前加载新场景B[带参数][带回调]
  25. * /// 默认
  26. * navigator.navigate('B');
  27. *
  28. * /// [带参数]
  29. * let parameter = {};
  30. * parameter.title = 'i am wang ronghui';
  31. * navigator.navigate('B', parameter);
  32. *
  33. * /// [带回调]
  34. * navigator.navigate('B', function(scene){
  35. * /// 切换成功处理
  36. * });
  37. *
  38. * /// [带参数] + [带回调]
  39. * let parameter = {};
  40. * parameter.title = 'i am wang ronghui';
  41. * navigator.navigate('B', parameter, function(scene){
  42. * /// 切换成功处理
  43. * });
  44. *
  45. * ~如果有传递parameter需在相应B.js内部实现loadState(navigatorMode, parameter, state)函数接收参数parameter。
  46. * ~如果要存储当前UI状态则实现saveState(state){ //将UI状态存储在参数state中,后续在loadState里恢复state }。
  47. *
  48. * c)场景B向后返回前一个场景A
  49. * /// 默认
  50. * navigator.goBack();
  51. *
  52. * /// [带参数]
  53. * let parameter = {};
  54. * parameter.title = 'i am wang ronghui';
  55. * navigator.goBack(parameter);
  56. *
  57. * d)场景B向后返回指定名字场景A
  58. * /// 默认
  59. * navigator.goBackToScene('A');
  60. *
  61. * /// [带参数]
  62. * let parameter = {};
  63. * parameter.title = 'i am wang ronghui';
  64. * navigator.goBackToScene('A', parameter);
  65. *
  66. * e)场景B向后返回根场景
  67. * /// 默认
  68. * navigator.goBackToRootScene();
  69. *
  70. * /// [带参数]
  71. * let parameter = {};
  72. * parameter.title = 'i am wang ronghui';
  73. * navigator.goBackToRootScene(parameter);
  74. *
  75. * 注意事项:
  76. * ------------------------
  77. * 挂载到场景的Canvas的自定义脚本的名字,必须要和场景文件的名字一致,否则无法调用到loadState或者saveState
  78. *
  79. */
  80. class Navigator
  81. {
  82. /**
  83. * 构造方法
  84. */
  85. constructor(){
  86. logger.info('constructor');
  87. this._allState = new Map();
  88. this._scenesStack = [];
  89. this._sceneLaunchHandle = false;
  90. /*
  91. * 支持外部使用cc.director.loadScene直接导航,记录下导航栈
  92. */
  93. cc.director.on(cc.Director.EVENT_AFTER_SCENE_LAUNCH, function (eventCustom) {
  94. /// 内部处理了这里就忽略,这里仅为支持监听外部导航。
  95. if(this._sceneLaunchHandle) {
  96. this._sceneLaunchHandle = false;
  97. return;
  98. }
  99. /// 获取当前场景
  100. let sceneName = eventCustom.name;
  101. logger.info('EVENT_AFTER_SCENE_LAUNCH sceneName = ' + sceneName);
  102. /// 先检查下导航栈有没有该场景,如果有,则回退到相应场景,防止出现场景循环
  103. let level = this.sceneStackLevel(sceneName);
  104. if(level !== -1){
  105. this.goBackToSceneStackLevel(level, null);
  106. return;
  107. }
  108. this.handleForward(sceneName, null);
  109. }.bind(this));
  110. }
  111. /**
  112. * 向前加载sceneName场景
  113. * @param {string} sceneName -场景名字
  114. * @param {object} [parameter] -参数对象
  115. * @param {function()} [onSceneLaunched] -新场景运行成功后回调
  116. */
  117. navigate(sceneName, parameter, onSceneLaunched){
  118. logger.info('navigate sceneName = ' + sceneName);
  119. logger.log('navigate parameter = ' + parameter);
  120. logger.log('navigate onSceneLaunched = ' + onSceneLaunched);
  121. /// 可能parameter和onSceneLaunched只传了某一个
  122. let argsLength = arguments.length;
  123. if(argsLength === 2) {
  124. if (typeof parameter === 'function') {
  125. onSceneLaunched = parameter;
  126. parameter = undefined;
  127. }
  128. }
  129. /// 先检查下导航栈有没有该场景,如果有,则回退到相应场景,防止出现场景循环
  130. let level = this.sceneStackLevel(sceneName);
  131. if(level !== -1){
  132. this.goBackToSceneStackLevel(level, parameter);
  133. return;
  134. }
  135. let readyToLeaveSceneJS = this.getCurrentSceneJS();
  136. if(readyToLeaveSceneJS){
  137. let sceneKey = 'Scene-' + this._scenesStack.length;
  138. let sceneState = this._allState.get(sceneKey);
  139. let state = {};
  140. sceneState.state = state;
  141. if(typeof readyToLeaveSceneJS.saveState === 'function'){
  142. readyToLeaveSceneJS.saveState.call(readyToLeaveSceneJS, state);
  143. }
  144. }
  145. cc.director.loadScene(sceneName, function () {
  146. /// 加载新场景成功处理
  147. logger.log('navigate loadScene complete sceneName = ' + sceneName);
  148. this._sceneLaunchHandle = true;
  149. this.handleForward(sceneName, parameter);
  150. /// 回调通知场景切换成功
  151. if(onSceneLaunched){
  152. onSceneLaunched();
  153. }
  154. }.bind(this));
  155. logger.log('navigate end');
  156. }
  157. /**
  158. * 向后返回前一个场景
  159. * @param {object} [parameter] -参数对象
  160. */
  161. goBack(parameter){
  162. logger.log('goBack');
  163. /// 当前Scene出导航栈
  164. this._scenesStack.pop();
  165. /// 加载栈顶Scene
  166. let sceneName = this._scenesStack[this._scenesStack.length - 1];
  167. logger.info('goBack to sceneName = ' + sceneName);
  168. cc.director.loadScene(sceneName, function () {
  169. logger.log('goBack loadScene complete sceneName = ' + sceneName);
  170. this._sceneLaunchHandle = true;
  171. this.handleBack(parameter);
  172. }.bind(this));
  173. }
  174. /**
  175. * 向后返回前根场景
  176. * @param {object} [parameter] -参数对象
  177. */
  178. goBackToRootScene(parameter){
  179. logger.log('goBackToRootScene');
  180. this.goBackToSceneStackLevel(1, parameter);
  181. }
  182. /**
  183. * 向后返回指定场景
  184. * @param {string} sceneName -场景名字
  185. * @param {object} [parameter] -参数对象
  186. */
  187. goBackToScene(sceneName, parameter){
  188. logger.log('goBackToScene sceneName = ' + sceneName);
  189. let level = this.sceneStackLevel(sceneName);
  190. if(level !== -1){
  191. this.goBackToSceneStackLevel(level, parameter);
  192. }
  193. }
  194. /*-------------------------私有方法begin-------------------------*/
  195. /**
  196. * 前进页面处理,加入导航栈,分配state
  197. * @param {string} sceneName -场景名字
  198. * @param {object} [parameter] -参数对象
  199. */
  200. handleForward(sceneName, parameter){
  201. logger.info('handleForward sceneName = ' + sceneName);
  202. logger.info('handleForward parameter = ' + parameter);
  203. /// 0,入导航栈
  204. if(sceneName){
  205. this._scenesStack.push(sceneName);
  206. }
  207. /// 加载新场景成功处理
  208. let enterSceneJS = this.getCurrentSceneJS();
  209. if(enterSceneJS){
  210. /// 向前导航时只有parameter,没有页面状态,所以页面状态为null
  211. if(typeof enterSceneJS.loadState === 'function'){
  212. enterSceneJS.loadState.call(enterSceneJS, navigatorMode.New, parameter, null);
  213. }
  214. /// 1,由于后退时不清理状态,在这里将当前页面以及向前所有的状态清除
  215. let nextSceneKey = 'Scene-' + this._scenesStack.length;
  216. let nextSceneIndex = this._scenesStack.length;
  217. while (this._allState.delete(nextSceneKey))
  218. {
  219. nextSceneIndex ++;
  220. nextSceneKey = 'Scene-' + nextSceneIndex;
  221. }
  222. /// 2,设置个state给当前Scene
  223. let sceneState = {};
  224. let sceneKey = 'Scene-' + this._scenesStack.length;
  225. logger.log('handleForward sceneKey = ' + sceneKey);
  226. this._allState.set(sceneKey, sceneState);
  227. /// 3,记录下参数
  228. parameter = parameter || {};
  229. sceneState.parameter = parameter;
  230. sceneState.state = {};
  231. }
  232. }
  233. /**
  234. * 后退页面处理,恢复场景
  235. * @param {object} [parameter] -参数对象
  236. */
  237. handleBack(parameter){
  238. logger.info('handleBack');
  239. /// 加载新场景成功处理
  240. let enterSceneJS = this.getCurrentSceneJS();
  241. if(enterSceneJS){
  242. let sceneKey = 'Scene-' + this._scenesStack.length;
  243. logger.log('handleBack sceneKey = ' + sceneKey);
  244. let sceneState = this._allState.get(sceneKey);
  245. /// 获取参数和页面状态,传入场景js,用于场景页面恢复
  246. if(typeof enterSceneJS.loadState === 'function'){
  247. /// 如果Back有带参数,优先使用参数,否则使用保留参数。
  248. parameter = parameter || sceneState.parameter;
  249. enterSceneJS.loadState.call(enterSceneJS, navigatorMode.Back, parameter, sceneState.state);
  250. }
  251. }
  252. }
  253. /**
  254. * 刷新页面处理
  255. * @param {object} [parameter] -参数对象
  256. */
  257. handleRefresh(parameter){
  258. logger.info('handleRefresh');
  259. let enterSceneJS = this.getCurrentSceneJS();
  260. if(enterSceneJS){
  261. if(typeof enterSceneJS.loadState === 'function'){
  262. enterSceneJS.loadState.call(enterSceneJS, navigatorMode.Refresh, parameter, null);
  263. }
  264. }
  265. }
  266. /**
  267. * 获取当前场景脚本类
  268. */
  269. getCurrentSceneJS(){
  270. let currentScene = cc.director.getScene();
  271. if(currentScene){
  272. let currentCanvas = currentScene.getChildByName('Canvas');
  273. if(currentCanvas){
  274. let currentCustomJS = currentCanvas.getComponent(currentScene.name);
  275. if(currentCustomJS){
  276. return currentCustomJS;
  277. }
  278. }
  279. }
  280. return null;
  281. }
  282. /**
  283. * 返回到固定Level的场景
  284. * @param {number} level -层级,比如1代表第一层
  285. * @param {object} [parameter] -参数对象
  286. */
  287. goBackToSceneStackLevel(level, parameter){
  288. logger.info('goBackToSceneStackLevel');
  289. let locScenesStack = this._scenesStack;
  290. let c = locScenesStack.length;
  291. if (c === 0) {
  292. return;
  293. }
  294. // current level or lower -> nothing
  295. if (level > c)
  296. return;
  297. // pop stack until reaching desired level
  298. while (c > level) {
  299. let current = locScenesStack.pop();
  300. c--;
  301. }
  302. let sceneName = locScenesStack[locScenesStack.length - 1];
  303. logger.info('goBackToSceneStackLevel sceneName = ' + sceneName);
  304. let currentSceneName = cc.director.getScene().name;
  305. logger.info('goBackToSceneStackLevel currentSceneName = ' + currentSceneName);
  306. if(currentSceneName !== sceneName){
  307. /// 加载栈顶Scene
  308. cc.director.loadScene(sceneName, function () {
  309. logger.log('goBackToSceneStackLevel loadScene complete sceneName = ' + sceneName);
  310. this._sceneLaunchHandle = true;
  311. this.handleBack(parameter);
  312. }.bind(this));
  313. }else {
  314. this.handleRefresh(parameter);
  315. }
  316. }
  317. /**
  318. * 获取指定scene名字的导航栈层级
  319. * @param {string} sceneName -场景名字
  320. */
  321. sceneStackLevel(sceneName){
  322. logger.log('sceneStackLevel sceneName = ' + sceneName);
  323. let locScenesStack = this._scenesStack;
  324. let i = locScenesStack.length-1;
  325. let exist = false;
  326. for(; i>=0; --i){
  327. if(locScenesStack[i] === sceneName){
  328. exist = true;
  329. break;
  330. }
  331. }
  332. logger.log('sceneStackLevel i = ' + i);
  333. if(exist){
  334. return i+1;
  335. }
  336. return -1;
  337. }
  338. /*-------------------------私有方法end-------------------------*/
  339. }
  340. module.exports = {
  341. /**
  342. * 导航模式
  343. */
  344. NavigatorMode: navigatorMode,
  345. /**
  346. * get Navigator
  347. * @returns {Navigator}
  348. */
  349. getNavigator: function(){
  350. return new Navigator();
  351. }
  352. };