NavigationView 是官方根据Container控件扩展而来的,由一个导航栏和一个card组成,具备导航和返回时自动销毁当前界面的功能,非常适合新手使用。
其中导航栏的代码如下:
1 Ext.define('Ext.navigation.Bar', { 2 extend: 'Ext.TitleBar', 3 requires: ['Ext.Button', 'Ext.Spacer'], 4 isToolbar: true, 5 config: { 6 baseCls: Ext.baseCSSPrefix + 'toolbar', 7 cls: Ext.baseCSSPrefix + 'navigation-bar', 8 ui: 'dark', 9 title: null, 10 defaultType: 'button', 11 layout: { 12 type: 'hbox' 13 }, 14 defaultBackButtonText: 'Back', 15 animation: { 16 duration: 300 17 }, 18 useTitleForBackButtonText: null, 19 view: null, 20 android2Transforms: false, 21 backButton: { 22 align: 'left', 23 ui: 'back', 24 hidden: true 25 } 26 }, 27 platformConfig: [{ 28 theme: ['Blackberry'], 29 animation: false 30 }], 31 constructor: function(config) { 32 config = config || {}; 33 if (!config.items) { 34 config.items = [] 35 } 36 this.backButtonStack = []; 37 this.activeAnimations = []; 38 this.callParent([config]) 39 }, 40 applyBackButton: function(config) { 41 return Ext.factory(config, Ext.Button, this.getBackButton()) 42 }, 43 updateBackButton: function(newBackButton, oldBackButton) { 44 if (oldBackButton) { 45 this.remove(oldBackButton) 46 } 47 if (newBackButton) { 48 this.add(newBackButton); 49 newBackButton.on({ 50 scope: this, 51 tap: this.onBackButtonTap 52 }) 53 } 54 }, 55 onBackButtonTap: function() { 56 this.fireEvent('back', this) 57 }, 58 updateView: function(newView) { 59 var me = this, 60 backButton = me.getBackButton(), 61 innerItems, 62 i, 63 backButtonText, 64 item, 65 title, 66 titleText; 67 me.getItems(); 68 if (newView) { 69 innerItems = newView.getInnerItems(); 70 for (i = 0; i < innerItems.length; i++) { 71 item = innerItems[i]; 72 title = (item.getTitle) ? item.getTitle() : item.config.title; 73 me.backButtonStack.push(title || ' ') 74 } 75 titleText = me.getTitleText(); 76 if (titleText === undefined) { 77 titleText = '' 78 } 79 me.setTitle(titleText); 80 backButtonText = me.getBackButtonText(); 81 if (backButtonText) { 82 backButton.setText(backButtonText); 83 backButton.show() 84 } 85 } 86 }, 87 onViewAdd: function(view, item) { 88 var me = this, 89 backButtonStack = me.backButtonStack, 90 hasPrevious, title; 91 me.endAnimation(); 92 title = (item.getTitle) ? item.getTitle() : item.config.title; 93 backButtonStack.push(title || ' '); 94 hasPrevious = backButtonStack.length > 1; 95 me.doChangeView(view, hasPrevious, false) 96 }, 97 onViewRemove: function(view) { 98 var me = this, 99 backButtonStack = me.backButtonStack, 100 hasPrevious; 101 me.endAnimation(); 102 backButtonStack.pop(); 103 hasPrevious = backButtonStack.length > 1; 104 me.doChangeView(view, hasPrevious, true) 105 }, 106 doChangeView: function(view, hasPrevious, reverse) { 107 var me = this, 108 leftBox = me.leftBox, 109 leftBoxElement = leftBox.element, 110 titleComponent = me.titleComponent, 111 titleElement = titleComponent.element, 112 backButton = me.getBackButton(), 113 titleText = me.getTitleText(), 114 backButtonText = me.getBackButtonText(), 115 animation = me.getAnimation() && view.getLayout().getAnimation(), 116 animated = animation && animation.isAnimation && view.isPainted(), 117 properties, 118 leftGhost, 119 titleGhost, 120 leftProps, 121 titleProps; 122 if (animated) { 123 leftGhost = me.createProxy(leftBox.element); 124 leftBoxElement.setStyle('opacity', '0'); 125 backButton.setText(backButtonText); 126 backButton[hasPrevious ? 'show': 'hide'](); 127 titleGhost = me.createProxy(titleComponent.element.getParent()); 128 titleElement.setStyle('opacity', '0'); 129 me.setTitle(titleText); 130 properties = me.measureView(leftGhost, titleGhost, reverse); 131 leftProps = properties.left; 132 titleProps = properties.title; 133 me.isAnimating = true; 134 me.animate(leftBoxElement, leftProps.element); 135 me.animate(titleElement, titleProps.element, 136 function() { 137 titleElement.setLeft(properties.titleLeft); 138 me.isAnimating = false; 139 me.refreshTitlePosition() 140 }); 141 if (Ext.browser.is.AndroidStock2 && !this.getAndroid2Transforms()) { 142 leftGhost.ghost.destroy(); 143 titleGhost.ghost.destroy() 144 } else { 145 me.animate(leftGhost.ghost, leftProps.ghost); 146 me.animate(titleGhost.ghost, titleProps.ghost, 147 function() { 148 leftGhost.ghost.destroy(); 149 titleGhost.ghost.destroy() 150 }) 151 } 152 } else { 153 if (hasPrevious) { 154 backButton.setText(backButtonText); 155 backButton.show() 156 } else { 157 backButton.hide() 158 } 159 me.setTitle(titleText) 160 } 161 }, 162 measureView: function(oldLeft, oldTitle, reverse) { 163 var me = this, 164 barElement = me.element, 165 newLeftElement = me.leftBox.element, 166 titleElement = me.titleComponent.element, 167 minOffset = Math.min(barElement.getWidth() / 3, 200), 168 newLeftWidth = newLeftElement.getWidth(), 169 barX = barElement.getX(), 170 barWidth = barElement.getWidth(), 171 titleX = titleElement.getX(), 172 titleLeft = titleElement.getLeft(), 173 titleWidth = titleElement.getWidth(), 174 oldLeftX = oldLeft.x, 175 oldLeftWidth = oldLeft.width, 176 oldLeftLeft = oldLeft.left, 177 useLeft = Ext.browser.is.AndroidStock2 && !this.getAndroid2Transforms(), 178 newOffset, 179 oldOffset, 180 leftAnims, 181 titleAnims, 182 omega, 183 theta; 184 theta = barX - oldLeftX - oldLeftWidth; 185 if (reverse) { 186 newOffset = theta; 187 oldOffset = Math.min(titleX - oldLeftWidth, minOffset) 188 } else { 189 oldOffset = theta; 190 newOffset = Math.min(titleX - barX, minOffset) 191 } 192 if (useLeft) { 193 leftAnims = { 194 element: { 195 from: { 196 left: newOffset, 197 opacity: 1 198 }, 199 to: { 200 left: 0, 201 opacity: 1 202 } 203 } 204 } 205 } else { 206 leftAnims = { 207 element: { 208 from: { 209 transform: { 210 translateX: newOffset 211 }, 212 opacity: 0 213 }, 214 to: { 215 transform: { 216 translateX: 0 217 }, 218 opacity: 1 219 } 220 }, 221 ghost: { 222 to: { 223 transform: { 224 translateX: oldOffset 225 }, 226 opacity: 0 227 } 228 } 229 } 230 } 231 theta = barX - titleX + newLeftWidth; 232 if ((oldLeftLeft + titleWidth) > titleX) { 233 omega = barX - titleX - titleWidth 234 } 235 if (reverse) { 236 titleElement.setLeft(0); 237 oldOffset = barX + barWidth - titleX - titleWidth; 238 if (omega !== undefined) { 239 newOffset = omega 240 } else { 241 newOffset = theta 242 } 243 } else { 244 newOffset = barX + barWidth - titleX - titleWidth; 245 if (omega !== undefined) { 246 oldOffset = omega 247 } else { 248 oldOffset = theta 249 } 250 newOffset = Math.max(titleLeft, newOffset) 251 } 252 if (useLeft) { 253 titleAnims = { 254 element: { 255 from: { 256 left: newOffset, 257 opacity: 1 258 }, 259 to: { 260 left: titleLeft, 261 opacity: 1 262 } 263 } 264 } 265 } else { 266 titleAnims = { 267 element: { 268 from: { 269 transform: { 270 translateX: newOffset 271 }, 272 opacity: 0 273 }, 274 to: { 275 transform: { 276 translateX: titleLeft 277 }, 278 opacity: 1 279 } 280 }, 281 ghost: { 282 to: { 283 transform: { 284 translateX: oldOffset 285 }, 286 opacity: 0 287 } 288 } 289 } 290 } 291 return { 292 left: leftAnims, 293 title: titleAnims, 294 titleLeft: titleLeft 295 } 296 }, 297 animate: function(element, config, callback) { 298 var me = this, 299 animation; 300 element.setLeft(0); 301 config = Ext.apply(config, { 302 element: element, 303 easing: 'ease-in-out', 304 duration: me.getAnimation().duration || 250, 305 preserveEndState: true 306 }); 307 animation = new Ext.fx.Animation(config); 308 animation.on('animationend', 309 function() { 310 if (callback) { 311 callback.call(me) 312 } 313 }, 314 me); 315 Ext.Animator.run(animation); 316 me.activeAnimations.push(animation) 317 }, 318 endAnimation: function() { 319 var activeAnimations = this.activeAnimations, 320 animation, i, ln; 321 if (activeAnimations) { 322 ln = activeAnimations.length; 323 for (i = 0; i < ln; i++) { 324 animation = activeAnimations[i]; 325 if (animation.isAnimating) { 326 animation.stopAnimation() 327 } else { 328 animation.destroy() 329 } 330 } 331 this.activeAnimations = [] 332 } 333 }, 334 refreshTitlePosition: function() { 335 if (!this.isAnimating) { 336 this.callParent() 337 } 338 }, 339 getBackButtonText: function() { 340 var text = this.backButtonStack[this.backButtonStack.length - 2], 341 useTitleForBackButtonText = this.getUseTitleForBackButtonText(); 342 if (!useTitleForBackButtonText) { 343 if (text) { 344 text = this.getDefaultBackButtonText() 345 } 346 } 347 return text 348 }, 349 getTitleText: function() { 350 return this.backButtonStack[this.backButtonStack.length - 1] 351 }, 352 beforePop: function(count) { 353 count--; 354 for (var i = 0; i < count; i++) { 355 this.backButtonStack.pop() 356 } 357 }, 358 doSetHidden: function(hidden) { 359 if (!hidden) { 360 this.element.setStyle({ 361 position: 'relative', 362 top: 'auto', 363 left: 'auto', 364 width: 'auto' 365 }) 366 } else { 367 this.element.setStyle({ 368 position: 'absolute', 369 top: '-1000px', 370 left: '-1000px', 371 width: this.element.getWidth() + 'px' 372 }) 373 } 374 }, 375 createProxy: function(element) { 376 var ghost, x, y, left, width; 377 ghost = element.dom.cloneNode(true); 378 ghost.id = element.id + '-proxy'; 379 element.getParent().dom.appendChild(ghost); 380 ghost = Ext.get(ghost); 381 x = element.getX(); 382 y = element.getY(); 383 left = element.getLeft(); 384 width = element.getWidth(); 385 ghost.setStyle('position', 'absolute'); 386 ghost.setX(x); 387 ghost.setY(y); 388 ghost.setHeight(element.getHeight()); 389 ghost.setWidth(width); 390 return { 391 x: x, 392 y: y, 393 left: left, 394 width: width, 395 ghost: ghost 396 } 397 } 398 });
可以看出他是继承于一个TitleBar,中间为标题,左侧有一个默认的返回按钮这些代码看似复杂,其实逻辑很简单。
他的主要作用就是监听返回按钮,为其添加一个back自定义事件。并且通过this.backButtonStack这个数组来储存标题显示记录。
在返回时动态更新标题栏,并且有切换的动画效果。由于标题长短不一,所以整个导航栏的代码大部分都是来处理切换动画了。
实际上它的核心方法只有:
constructor:进行配置的初始化处理
applyBackButton:动态创建返回按钮
updateBackButton:动态更新返回按钮,并且添加监听(触发 )
onBackButtonTap:将返回按钮的点击转换为自定义事件back
updateView:更新导航栏按钮,标题。NavigationView中更新视图时会触发它
onViewAdd:添加新的历史记录。NavigationView中添加新的视图时会触发它
onViewRemove:移除当前的历史记录,NavigationView中移除视图时会触发它
doChangeView:视图改变后处理标题,返回按钮。大部分代码其实是处理切换动画效果
getBackButtonText:获取返回按钮的text值,如果useTitleForBackButtonText为ture就需要它来处理
getTitleText:获取最后一个标题
beforePop:点返回按钮时处理this.backButtonStack这个数组
我们如果想要重写它可以注意这些方法,其他的都是用来处理动画效果的。个人觉得没必要有这些方法,会影响性能
NavigationView代码如下:
1 Ext.define('Ext.navigation.View', { 2 extend: 'Ext.Container', 3 alternateClassName: 'Ext.NavigationView', 4 xtype: 'navigationview', 5 requires: ['Ext.navigation.Bar'], 6 config: { 7 baseCls: Ext.baseCSSPrefix + 'navigationview', 8 navigationBar: { 9 docked: 'top' 10 }, 11 defaultBackButtonText: 'Back', 12 useTitleForBackButtonText: false, 13 layout: { 14 type: 'card', 15 animation: { 16 duration: 300, 17 easing: 'ease-out', 18 type: 'slide', 19 direction: 'left' 20 } 21 } 22 }, 23 platformConfig: [{ 24 theme: ['Blackberry'], 25 navigationBar: { 26 splitNavigation: true 27 } 28 }], 29 initialize: function() { 30 var me = this, 31 navBar = me.getNavigationBar(); 32 if (navBar) { 33 navBar.on({ 34 back: me.onBackButtonTap, 35 scope: me 36 }); 37 me.relayEvents(navBar, 'rightbuttontap'); 38 me.relayEvents(me, { 39 add: 'push', 40 remove: 'pop' 41 }) 42 } 43 var layout = me.getLayout(); 44 if (layout && !layout.isCard) { 45 Ext.Logger.error('The base layout for a NavigationView must always be a Card Layout') 46 } 47 }, 48 applyLayout: function(config) { 49 config = config || {}; 50 return config 51 }, 52 onBackButtonTap: function() { 53 this.pop(); 54 this.fireEvent('back', this) 55 }, 56 push: function(view) { 57 return this.add(view) 58 }, 59 pop: function(count) { 60 if (this.beforePop(count)) { 61 return this.doPop() 62 } 63 }, 64 beforePop: function(count) { 65 var me = this, 66 innerItems = me.getInnerItems(); 67 if (Ext.isString(count) || Ext.isObject(count)) { 68 var last = innerItems.length - 1, 69 i; 70 for (i = last; i >= 0; i--) { 71 if ((Ext.isString(count) && Ext.ComponentQuery.is(innerItems[i], count)) || (Ext.isObject(count) && count == innerItems[i])) { 72 count = last - i; 73 break 74 } 75 } 76 if (!Ext.isNumber(count)) { 77 return false 78 } 79 } 80 var ln = innerItems.length, 81 toRemove; 82 if (!Ext.isNumber(count) || count < 1) { 83 count = 1 84 } 85 count = Math.min(count, ln - 1); 86 if (count) { 87 me.getNavigationBar().beforePop(count); 88 toRemove = innerItems.splice( - count, count - 1); 89 for (i = 0; i < toRemove.length; i++) { 90 this.remove(toRemove[i]) 91 } 92 return true 93 } 94 return false 95 }, 96 doPop: function() { 97 var me = this, 98 innerItems = this.getInnerItems(); 99 me.remove(innerItems[innerItems.length - 1]); 100 if (innerItems.length < 3 && this.$backButton) { 101 this.$backButton.hide() 102 } 103 if (this.$titleContainer) { 104 if (!this.$titleContainer.setTitle) { 105 Ext.Logger.error('You have selected to display a title in a component that does not support titles in NavigationView. Please remove the `title` configuration from your NavigationView item, or change it to a component that has a `setTitle` method.') 106 } 107 var item = innerItems[innerItems.length - 2]; 108 this.$titleContainer.setTitle((item.getTitle) ? item.getTitle() : item.config.title) 109 } 110 return this.getActiveItem() 111 }, 112 getPreviousItem: function() { 113 var innerItems = this.getInnerItems(); 114 return innerItems[innerItems.length - 2] 115 }, 116 updateUseTitleForBackButtonText: function(useTitleForBackButtonText) { 117 var navigationBar = this.getNavigationBar(); 118 if (navigationBar) { 119 navigationBar.setUseTitleForBackButtonText(useTitleForBackButtonText) 120 } 121 }, 122 updateDefaultBackButtonText: function(defaultBackButtonText) { 123 var navigationBar = this.getNavigationBar(); 124 if (navigationBar) { 125 navigationBar.setDefaultBackButtonText(defaultBackButtonText) 126 } 127 }, 128 applyNavigationBar: function(config) { 129 if (!config) { 130 config = { 131 hidden: true, 132 docked: 'top' 133 } 134 } 135 if (config.title) { 136 delete config.title; 137 Ext.Logger.warn("Ext.navigation.View: The 'navigationBar' configuration does not accept a 'title' property. You set the title of the navigationBar by giving this navigation view's children a 'title' property.") 138 } 139 config.view = this; 140 config.useTitleForBackButtonText = this.getUseTitleForBackButtonText(); 141 if (config.splitNavigation) { 142 this.$titleContainer = this.add({ 143 docked: 'top', 144 xtype: 'titlebar', 145 ui: 'light', 146 title: this.$currentTitle || '' 147 }); 148 var containerConfig = (config.splitNavigation === true) ? {}: config.splitNavigation; 149 this.$backButtonContainer = this.add(Ext.apply({ 150 xtype: 'toolbar', 151 docked: 'bottom' 152 }, 153 containerConfig)); 154 this.$backButton = this.$backButtonContainer.add({ 155 xtype: 'button', 156 text: 'Back', 157 hidden: true, 158 ui: 'back' 159 }); 160 this.$backButton.on({ 161 scope: this, 162 tap: this.onBackButtonTap 163 }); 164 config = { 165 hidden: true, 166 docked: 'top' 167 } 168 } 169 return Ext.factory(config, Ext.navigation.Bar, this.getNavigationBar()) 170 }, 171 updateNavigationBar: function(newNavigationBar, oldNavigationBar) { 172 if (oldNavigationBar) { 173 this.remove(oldNavigationBar, true) 174 } 175 if (newNavigationBar) { 176 this.add(newNavigationBar) 177 } 178 }, 179 applyActiveItem: function(activeItem, currentActiveItem) { 180 var me = this, 181 innerItems = me.getInnerItems(); 182 me.getItems(); 183 if (!me.initialized) { 184 activeItem = innerItems.length - 1 185 } 186 return this.callParent([activeItem, currentActiveItem]) 187 }, 188 doResetActiveItem: function(innerIndex) { 189 var me = this, 190 innerItems = me.getInnerItems(), 191 animation = me.getLayout().getAnimation(); 192 if (innerIndex > 0) { 193 if (animation && animation.isAnimation) { 194 animation.setReverse(true) 195 } 196 me.setActiveItem(innerIndex - 1); 197 me.getNavigationBar().onViewRemove(me, innerItems[innerIndex], innerIndex) 198 } 199 }, 200 doRemove: function() { 201 var animation = this.getLayout().getAnimation(); 202 if (animation && animation.isAnimation) { 203 animation.setReverse(false) 204 } 205 this.callParent(arguments) 206 }, 207 onItemAdd: function(item, index) { 208 if (item && item.getDocked() && item.config.title === true) { 209 this.$titleContainer = item 210 } 211 this.doItemLayoutAdd(item, index); 212 var navigaitonBar = this.getInitialConfig().navigationBar; 213 if (!this.isItemsInitializing && item.isInnerItem()) { 214 this.setActiveItem(item); 215 if (navigaitonBar) { 216 this.getNavigationBar().onViewAdd(this, item, index) 217 } 218 if (this.$backButtonContainer) { 219 this.$backButton.show() 220 } 221 } 222 if (item && item.isInnerItem()) { 223 this.updateTitleContainerTitle((item.getTitle) ? item.getTitle() : item.config.title) 224 } 225 if (this.initialized) { 226 this.fireEvent('add', this, item, index) 227 } 228 }, 229 updateTitleContainerTitle: function(title) { 230 if (this.$titleContainer) { 231 if (!this.$titleContainer.setTitle) { 232 Ext.Logger.error('You have selected to display a title in a component that does not support titles in NavigationView. Please remove the `title` configuration from your NavigationView item, or change it to a component that has a `setTitle` method.') 233 } 234 this.$titleContainer.setTitle(title) 235 } else { 236 this.$currentTitle = title 237 } 238 }, 239 reset: function() { 240 return this.pop(this.getInnerItems().length) 241 } 242 });
这些代码就是核心了,作用如下:
initialize:初始化,为导航栏的返回事件添加监听(触发onBackButtonTap方法),为add和romove方法添加监听
onBackButtonTap:点击返回按钮时触发,触发pop方法,并且添加自定义事件。
push:其实就是调用add方法,添加新视图
pop:会调用beforePop方法和doPop方法
beforePop:有时候不止移除一项,这里的逻辑很复杂
doPop:移除card中最后一项
getPreviousItem:获取倒数第二项
updateUseTitleForBackButtonText:作用顾名思义
updateDefaultBackButtonText:同上
applyNavigationBar:创建导航栏而已,别怕代码多
updateNavigationBar:更新导航栏
applyActiveItem:为什么card始终显示最后一项,就是因为重写了它
doRemove:调用remove方法后会触发也是反转切换动画
onItemAdd:项第一次被添加到card中触发,一系列的逻辑处理
updateTitleContainerTitle:顾名思义
reset:清空所有项,不过这里逻辑比较复杂。会调用pop方法
http://www.cnblogs.com/mlzs/p/3376399.html这里我有对代码进行一些注释,可以参考一下。
下面说说用法,个人推荐先创建一个视图继承它,如下:
1 Ext.define('app.view.Main', { 2 extend: 'Ext.NavigationView', 3 xtype: 'main', 4 config: { 5 navigationBar: { 6 backButton: { 7 iconCls: 'arrow_left', 8 ui: '', 9 cls: 'back' 10 } 11 }, 12 cls: 'cardPanel' 13 } 14 });
app.js中初始化它:
1 launch: function () { 2 // Destroy the #appLoadingIndicator element 3 Ext.fly('appLoadingIndicator').destroy(); 4 // Initialize the main view 5 Ext.Viewport.add(Ext.create('app.view.Main')); 6 }
单独的main控制层监听它:
1 //引用 2 refs: { 3 main: 'main' 4 }
添加一个路由监听
1 routes: { 2 'redirect/:view': 'showView' 3 }
main控制层中写一个方法:
1 //展示页面 2 showView: function (view, isPop) { 3 var main = this.getMain(), 4 view = Ext.create(xtype); 5 main.push(view, params); 6 }
任何控制层之中都可以通过如下方法触发这个方法
this.redirectTo('redirect/xtype');
当然这个只是简单的用法,有兴趣的可以看看http://www.cnblogs.com/mlzs/p/3498846.html,里面有免费视频听,也可以参考官方的示例。
值得注意的是,NavigationView作为一个容器,虽然是继承于Container,里面的tpl,data,html这些都是不能使用的,他的布局也不能随便更改。