扩展视频讲解:http://www.cnblogs.com/mlzs/p/3652094.html
官方NavigationView详解:http://www.cnblogs.com/mlzs/p/3550011.html
官方Container详解:http://www.cnblogs.com/mlzs/p/3548720.html
示例源码参见:https://bitbucket.org/moLangZaiShi/demo
注:扩展源码以示例为准
扩展源码:
1 Ext.define('ux.navigation.View', { 2 extend: 'Ext.Container', 3 alternateClassName: 'ux.NavigationView', 4 xtype: 'uxNavigationView', 5 requires: ['ux.navigation.Bar'], 6 7 config: { 8 baseCls: Ext.baseCSSPrefix + 'navigationview', 9 navigationBar: { 10 docked: 'top', 11 cls: 'navigationBar' 12 }, 13 defaultBackButtonText: 'Back', 14 useTitleForBackButtonText: false, 15 layout: { 16 type: 'card', 17 animation: { 18 duration: 300, 19 easing: 'ease-out', 20 type: 'slide', 21 direction: 'left' 22 } 23 }, 24 index: null, 25 //不推荐使用 26 menu: null 27 }, 28 29 platformConfig: [{ 30 theme: ['Blackberry'], 31 navigationBar: { 32 splitNavigation: true 33 } 34 }], 35 36 // @private 37 initialize: function () { 38 var me = this, 39 navBar = me.getNavigationBar(); 40 41 //监听导航栏返回按钮 42 if (navBar) { 43 navBar.on({ 44 back: me.onBackButtonTap, 45 scope: me 46 }); 47 48 me.relayEvents(navBar, 'rightbuttontap'); 49 50 me.relayEvents(me, { 51 add: 'push', 52 remove: 'pop' 53 }); 54 } 55 //<debug> 56 var layout = me.getLayout(); 57 if (layout && !layout.isCard) { 58 Ext.Logger.error('The base layout for a NavigationView must always be a Card Layout'); 59 } 60 //</debug> 61 }, 62 63 /** 64 * @private 65 */ 66 applyLayout: function (config) { 67 config = config || {}; 68 69 return config; 70 }, 71 //更新标题 72 setTitle: function (text) { 73 var me = this, 74 nav = me.getNavigationBar(); 75 nav.setTitle(text); 76 nav.backButtonStack[nav.backButtonStack.length - 1].title = text; 77 }, 78 /*创建其他导航栏*/ 79 applyMenu: function (newItem) { 80 if (!newItem) { 81 return false; 82 } 83 var me = this; 84 return me.factoryItem(newItem); 85 }, 86 /*更新其他导航栏*/ 87 updateMenu: function (newItem, oldItem) { 88 if (oldItem) { 89 //console.log('oldItem:', oldItem.getItemId()); 90 this.remove(oldItem); 91 } 92 if (newItem) { 93 //console.log('newItem:', newItem.getItemId()); 94 this.add(newItem); 95 } 96 }, 97 //更新index 98 updateIndex: function (newItem, oldItem) { 99 var me = this, 100 animation = this.getLayout().getAnimation(), 101 event = { 102 scope: me, 103 animationend: 'clearItem' 104 }; 105 //添加监听 106 if (oldItem) { 107 //移除动画结束监听 108 animation.un(event); 109 } 110 if (Ext.isNumber(newItem)) { 111 //添加动画结束监听 112 animation.on(event); 113 } 114 }, 115 //根据index清理多余的项 116 clearItem: function () { 117 var me = this, 118 innerItems = me.getInnerItems(), 119 length = innerItems.length, 120 index = me.getIndex(); 121 count = length - index - 1; 122 toRemove = innerItems.splice(me.getIndex(), count); 123 //移除子项 124 for (i = 0; i < toRemove.length; i++) { 125 me.remove(toRemove[i]); 126 } 127 }, 128 /** 129 * @private 130 * 点击返回按钮 131 */ 132 onBackButtonTap: function () { 133 this.pop(); 134 this.fireEvent('back', this); 135 }, 136 137 push: function (xtype, params) { 138 var me = this, 139 view = me.down('#' + xtype); 140 if (!view) { 141 params = params || {}; 142 params.itemId = xtype; 143 view = Ext.create(xtype, params); 144 me.add(view); 145 } else if (view != me.getActiveItem()) { 146 me.pop(xtype); 147 } 148 return view; 149 }, 150 /*移除当前激活项然后添加新的项xtype为null则只移除*/ 151 popAndPush: function (xtype, params) { 152 this.pop(null, xtype); 153 if (xtype) { 154 this.push(xtype, params); 155 } 156 }, 157 /** 158 * 不填写参数时,移除当前项,返回到上一项 159 * 如果参数是数字,则从最后一项开始移除指定数目的项 160 * 如果参数是string,则移除指定类型的项 161 * 如果参数是项,则移除传入的项 162 * 不论参数如何,都会保留一个活动项 163 * @return {Ext.Component} 当前活动项 164 */ 165 pop: function (count, hide) { 166 if (this.beforePop(count)) { 167 return this.doPop(hide); 168 } 169 }, 170 171 /*删除指定项*/ 172 beforePop: function (count) { 173 var me = this, 174 innerItems = me.getInnerItems(); 175 if (Ext.isString(count) || Ext.isObject(count)) { 176 var last = innerItems.length - 1, 177 i; 178 179 for (i = last; i >= 0; i--) { 180 if ((Ext.isString(count) && Ext.ComponentQuery.is(innerItems[i], count)) || (Ext.isObject(count) && count == innerItems[i])) { 181 count = last - i; 182 break; 183 } 184 } 185 186 if (!Ext.isNumber(count)) { 187 return false; 188 } 189 } 190 191 var ln = innerItems.length, 192 toRemove; 193 //default to 1 pop 194 if (!Ext.isNumber(count) || count < 1) { 195 count = 1; 196 } 197 198 //check if we are trying to remove more items than we have 199 count = Math.min(count, ln - 1); 200 201 if (count) { 202 //we need to reset the backButtonStack in the navigation bar 203 me.getNavigationBar().beforePop(count); 204 205 //get the items we need to remove from the view and remove theme 206 toRemove = innerItems.splice(-count, count - 1); 207 for (i = 0; i < toRemove.length; i++) { 208 this.remove(toRemove[i]); 209 } 210 211 return true; 212 } 213 214 return false; 215 }, 216 217 /** 218 * @private 219 */ 220 doPop: function (hide) { 221 var me = this, 222 innerItems = this.getInnerItems(), 223 item = innerItems[innerItems.length - 1]; 224 if (hide) { 225 item.hide(); 226 } 227 //set the new active item to be the new last item of the stack 228 me.remove(item); 229 230 // Hide the backButton 231 if (innerItems.length < 3 && this.$backButton) { 232 this.$backButton.hide(); 233 } 234 235 // Update the title container 236 if (this.$titleContainer) { 237 //<debug> 238 if (!this.$titleContainer.setTitle) { 239 Ext.Logger.error('You have selected to display a title in a component that does not \ 240 support titles in NavigationView. Please remove the `title` configuration from your \ 241 NavigationView item, or change it to a component that has a `setTitle` method.'); 242 } 243 //</debug> 244 var item = innerItems[innerItems.length - 2]; 245 this.$titleContainer.setTitle((item.getTitle) ? item.getTitle() : item.config.title); 246 } 247 248 return this.getActiveItem(); 249 }, 250 251 /** 252 * 返回上一项 253 * @return {Mixed} The previous view 254 */ 255 getPreviousItem: function () { 256 var innerItems = this.getInnerItems(); 257 return innerItems[innerItems.length - 2]; 258 }, 259 260 /** 261 * Updates the backbutton text accordingly in the {@link #navigationBar} 262 * @private 263 */ 264 updateUseTitleForBackButtonText: function (useTitleForBackButtonText) { 265 var navigationBar = this.getNavigationBar(); 266 if (navigationBar) { 267 navigationBar.setUseTitleForBackButtonText(useTitleForBackButtonText); 268 } 269 }, 270 271 /** 272 * Updates the backbutton text accordingly in the {@link #navigationBar} 273 * @private 274 */ 275 updateDefaultBackButtonText: function (defaultBackButtonText) { 276 var navigationBar = this.getNavigationBar(); 277 if (navigationBar) { 278 navigationBar.setDefaultBackButtonText(defaultBackButtonText); 279 } 280 }, 281 282 // @private 283 applyNavigationBar: function (config) { 284 if (!config) { 285 config = { 286 hidden: true, 287 docked: 'top' 288 }; 289 } 290 291 if (config.title) { 292 delete config.title; 293 //<debug> 294 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."); 295 //</debug> 296 } 297 298 config.view = this; 299 config.useTitleForBackButtonText = this.getUseTitleForBackButtonText(); 300 //是否分离导航按钮和标题栏 301 if (config.splitNavigation) { 302 this.$titleContainer = this.add({ 303 docked: 'top', 304 xtype: 'titlebar', 305 ui: 'light', 306 title: this.$currentTitle || '' 307 }); 308 309 var containerConfig = (config.splitNavigation === true) ? {} : config.splitNavigation; 310 311 this.$backButtonContainer = this.add(Ext.apply({ 312 xtype: 'toolbar', 313 docked: 'bottom' 314 }, 315 containerConfig)); 316 317 this.$backButton = this.$backButtonContainer.add({ 318 xtype: 'button', 319 text: 'Back', 320 hidden: true, 321 ui: 'back' 322 }); 323 324 this.$backButton.on({ 325 scope: this, 326 tap: this.onBackButtonTap 327 }); 328 329 config = { 330 hidden: true, 331 docked: 'top' 332 }; 333 } 334 335 return Ext.factory(config, ux.navigation.Bar, this.getNavigationBar()); 336 }, 337 338 // @private 339 updateNavigationBar: function (newNavigationBar, oldNavigationBar) { 340 if (oldNavigationBar) { 341 this.remove(oldNavigationBar, true); 342 } 343 344 if (newNavigationBar) { 345 this.add(newNavigationBar); 346 } 347 }, 348 349 /** 350 * @private 351 */ 352 applyActiveItem: function (activeItem, currentActiveItem) { 353 var me = this, 354 innerItems = me.getInnerItems(); 355 356 // 确保项目已初始化 357 me.getItems(); 358 359 // 如果没有初始化, 将最后的子项激活 360 if (!me.initialized) { 361 activeItem = innerItems.length - 1; 362 } 363 //console.log(this.getItems().keys); 364 return this.callParent([activeItem, currentActiveItem]); 365 }, 366 /*pop时反转切换动画*/ 367 doResetActiveItem: function (innerIndex) { 368 var me = this, 369 innerItems = me.getInnerItems(), 370 animation = me.getLayout().getAnimation(); 371 372 if (innerIndex > 0) { 373 if (animation && animation.isAnimation) { 374 animation.setReverse(true); 375 } 376 me.setActiveItem(innerIndex - 1); 377 me.getNavigationBar().onViewRemove(me, innerItems[innerIndex], innerIndex); 378 } 379 }, 380 381 /** 382 * 移除子项,调用remove方法后自动执行 383 * @private 384 */ 385 doRemove: function () { 386 var animation = this.getLayout().getAnimation(); 387 388 if (animation && animation.isAnimation) { 389 animation.setReverse(false); 390 } 391 392 this.callParent(arguments); 393 }, 394 395 onActivate: function (view) { 396 //更新menu 397 this.setMenu((view.getMenu) ? view.getMenu() : view.config.Menu); 398 }, 399 /** 400 * 添加子项,调用add方法后自动执行 401 * @private 402 */ 403 onItemAdd: function (item, index) { 404 405 // 检测title配置 406 if (item && item.getDocked() && item.config.title === true) { 407 this.$titleContainer = item; 408 } 409 if (item.isInnerItem()) { 410 //添加监听,监听视图激活事件 411 item.on({ 412 scope: this, 413 activate: 'onActivate' 414 }); 415 } 416 this.doItemLayoutAdd(item, index); 417 418 var navigaitonBar = this.getInitialConfig().navigationBar; 419 420 if (!this.isItemsInitializing && item.isInnerItem()) { 421 422 this.setActiveItem(item); 423 // 更新navigationBar 424 if (navigaitonBar) { 425 //强制排序 426 this.setIndex((item.getIndex) ? item.getIndex() : item.config.index); 427 428 this.getNavigationBar().onViewAdd(this, item, index); 429 } 430 431 // 更新返回按钮 432 if (this.$backButtonContainer) { 433 this.$backButton.show(); 434 } 435 } 436 437 if (item && item.isInnerItem()) { 438 // 更新标题 439 //console.log('更新标题', item); 440 this.updateTitleContainerTitle((item.getTitle) ? item.getTitle() : item.config.title); 441 } 442 443 if (this.initialized) { 444 this.fireEvent('add', this, item, index); 445 } 446 }, 447 448 /** 449 * @private 450 * 更新titleContainer的标题,如果存在 451 */ 452 updateTitleContainerTitle: function (title) { 453 if (this.$titleContainer) { 454 //<debug> 455 if (!this.$titleContainer.setTitle) { 456 Ext.Logger.error('You have selected to display a title in a component that does not \ 457 support titles in NavigationView. Please remove the `title` configuration from your \ 458 NavigationView item, or change it to a component that has a `setTitle` method.'); 459 } 460 //</debug> 461 this.$titleContainer.setTitle(title); 462 } else { 463 this.$currentTitle = title; 464 } 465 }, 466 467 /** 468 * 移除第一项和最后项之间的所有项(包括最后项) 469 * @return {Ext.Component} The view that is now active 470 */ 471 reset: function () { 472 return this.pop(this.getInnerItems().length); 473 } 474 });
1 /** 2 * This component is used in {@link Ext.navigation.View} to control animations in the toolbar. You should never need to 3 * interact with the component directly, unless you are subclassing it. 4 * @private 5 * @author Robert Dougan <rob@sencha.com> 6 */ 7 Ext.define('ux.navigation.Bar', { 8 extend: 'Ext.TitleBar', 9 10 requires: ['Ext.Button', 'Ext.Spacer'], 11 12 // @private 13 isToolbar: true, 14 15 config: { 16 /** 17 * @cfg 18 * @inheritdoc 19 */ 20 baseCls: Ext.baseCSSPrefix + 'toolbar', 21 /** 22 * @cfg {String} ui 23 * Style options for Toolbar. Either 'light' or 'dark'. 24 * @accessor 25 */ 26 ui: 'dark', 27 28 /** 29 * @cfg {String} title 30 * The title of the toolbar. You should NEVER set this, it is used internally. You set the title of the 31 * navigation bar by giving a navigation views children a title configuration. 32 * @private 33 * @accessor 34 */ 35 title: null, 36 37 /** 38 * @cfg 39 * @hide 40 * @accessor 41 */ 42 defaultType: 'button', 43 44 /** 45 * @cfg 46 * @ignore 47 * @accessor 48 */ 49 layout: { 50 type: 'hbox' 51 }, 52 53 /** 54 * @cfg {Array/Object} items The child items to add to this NavigationBar. The {@link #cfg-defaultType} of 55 * a NavigationBar is {@link Ext.Button}, so you do not need to specify an `xtype` if you are adding 56 * buttons. 57 * 58 * You can also give items a `align` configuration which will align the item to the `left` or `right` of 59 * the NavigationBar. 60 * @hide 61 * @accessor 62 */ 63 64 /** 65 * @cfg {String} defaultBackButtonText 66 * The text to be displayed on the back button if: 67 * a) The previous view does not have a title 68 * b) The {@link #useTitleForBackButtonText} configuration is true. 69 * @private 70 * @accessor 71 */ 72 defaultBackButtonText: '返回', 73 74 /** 75 * @cfg {Object} animation 76 * @private 77 * @accessor 78 */ 79 animation: { 80 duration: 300 81 }, 82 83 /** 84 * @cfg {Boolean} useTitleForBackButtonText 85 * Set to false if you always want to display the {@link #defaultBackButtonText} as the text 86 * on the back button. True if you want to use the previous views title. 87 * @private 88 * @accessor 89 */ 90 useTitleForBackButtonText: null, 91 92 /** 93 * @cfg {Ext.navigation.View} view A reference to the navigation view this bar is linked to. 94 * @private 95 * @accessor 96 */ 97 view: null, 98 99 /** 100 * @cfg {Boolean} androidAnimation Optionally enable CSS transforms on Android 2 101 * for NavigationBar animations. Note that this may cause flickering if the 102 * NavigationBar is hidden. 103 * @accessor 104 */ 105 android2Transforms: false, 106 107 /** 108 * @cfg {Ext.Button/Object} backButton The configuration for the back button 109 * @private 110 * @accessor 111 */ 112 backButton: { 113 align: 'left', 114 ui: 'back', 115 hidden: true 116 }, 117 /*导航栏临时控件组,在子项中配置*/ 118 tmpItems: null, 119 /*导航栏临时cls,在子项中配置*/ 120 tmpCls: null, 121 /*是否隐藏返回按钮,在子项中配置*/ 122 backHide: true 123 }, 124 125 platformConfig: [{ 126 theme: ['Blackberry'], 127 animation: false 128 }], 129 130 /** 131 * @event back 132 * Fires when the back button was tapped. 133 * @param {Ext.navigation.Bar} this This bar 134 */ 135 136 constructor: function (config) { 137 config = config || {}; 138 139 if (!config.items) { 140 config.items = []; 141 } 142 143 this.backButtonStack = []; 144 this.activeAnimations = []; 145 146 this.callParent([config]); 147 }, 148 /*创建导航栏临时控件组*/ 149 applyTmpItems: function (newItems) { 150 if (!newItems) return false; 151 var me = this, 152 navItems = [], 153 i, 154 ln; 155 newItems = Ext.Array.from(newItems); 156 for (i = 0, ln = newItems.length; i < ln; i++) { 157 var item = newItems[i], 158 fire = item.fire, 159 btn = me.factoryItem(item); 160 if (fire) { 161 btn.on({ 162 tap: 'onTmpItemTap', 163 scope: me 164 }); 165 } 166 navItems.push(me.factoryItem(btn)); 167 } 168 return navItems; 169 }, 170 //临时控件被点击时 171 onTmpItemTap: function (t) { 172 var me = this, 173 view = me.getView().getActiveItem(), 174 fire = t.fire; 175 view.fireAction(fire, [me, view, t], 'do' + fire); 176 }, 177 /*更新导航栏临时控件组*/ 178 updateTmpItems: function (newItem, oldItem) { 179 if (oldItem) { 180 var i, ln; 181 for (i = 0, ln = oldItem.length; i < ln; i++) { 182 this.remove(oldItem[i]); 183 } 184 } 185 if (newItem) { 186 this.add(newItem); 187 } 188 }, 189 /*更新导航栏临时cls*/ 190 updateTmpCls: function (newItem, oldItem) { 191 if (oldItem) { 192 this.removeCls(oldItem); 193 } 194 if (newItem) { 195 this.addCls(newItem); 196 } 197 }, 198 /** 199 * @private 200 */ 201 applyBackButton: function (config) { 202 return Ext.factory(config, Ext.Button, this.getBackButton()); 203 }, 204 205 /** 206 * @private 207 */ 208 updateBackButton: function (newBackButton, oldBackButton) { 209 if (oldBackButton) { 210 this.remove(oldBackButton); 211 } 212 213 if (newBackButton) { 214 this.add(newBackButton); 215 216 newBackButton.on({ 217 scope: this, 218 tap: this.onBackButtonTap 219 }); 220 } 221 }, 222 223 onBackButtonTap: function () { 224 this.fireEvent('back', this); 225 }, 226 227 /** 228 * @private 229 */ 230 updateView: function (newView) { 231 var me = this, 232 backButton = me.getBackButton(), 233 innerItems, 234 i, 235 backButtonText, 236 item, 237 title, 238 titleText, 239 bar; 240 241 me.getItems(); 242 243 if (newView) { 244 //update the back button stack with the current inner items of the view 245 innerItems = newView.getInnerItems(); 246 for (i = 0; i < innerItems.length; i++) { 247 item = innerItems[i]; 248 title = (item.getTitle) ? item.getTitle() : item.config.title; 249 bar = (item.getBar) ? item.getBar() : item.config.bar; 250 251 me.backButtonStack.push({ 252 title: title || ' ', 253 bar: bar 254 }); 255 } 256 titleText = me.getTitleText(); 257 258 if (titleText === undefined) { 259 titleText = ''; 260 } 261 262 me.setTitle(titleText); 263 264 backButtonText = me.getBackButtonText(); 265 if (backButtonText) { 266 backButton.setText(backButtonText); 267 backButton.show(); 268 } 269 //更新bar 270 me.updateBar(); 271 } 272 }, 273 274 /** 275 * @private 276 */ 277 onViewAdd: function (view, item) { 278 var me = this, 279 backButtonStack = me.backButtonStack, 280 hasPrevious, title, bar; 281 282 me.endAnimation(); 283 284 title = (item.getTitle) ? item.getTitle() : item.config.title; 285 bar = (item.getBar) ? item.getBar() : item.config.bar; 286 //移除额外的历史记录 287 var index = (item.getIndex) ? item.getIndex() : item.config.index; 288 if (Ext.isNumber(index)) { 289 var length = view.getInnerItems().length, 290 count = length - index; 291 me.beforePop(count); 292 } 293 backButtonStack.push({ 294 title: title || ' ', 295 bar: bar 296 }); 297 298 hasPrevious = backButtonStack.length > 1; 299 me.doChangeView(view, hasPrevious, false); 300 }, 301 302 /** 303 * @private 304 */ 305 onViewRemove: function (view) { 306 var me = this, 307 backButtonStack = me.backButtonStack, 308 hasPrevious; 309 310 me.endAnimation(); 311 backButtonStack.pop(); 312 hasPrevious = backButtonStack.length > 1; 313 314 me.doChangeView(view, hasPrevious, true); 315 }, 316 317 /** 318 * @private 319 */ 320 doChangeView: function (view, hasPrevious, reverse) { 321 var me = this, 322 leftBox = me.leftBox, 323 leftBoxElement = leftBox.element, 324 titleComponent = me.titleComponent, 325 titleElement = titleComponent.element, 326 backButton = me.getBackButton(), 327 titleText = me.getTitleText(), 328 backButtonText = me.getBackButtonText(), 329 animation = me.getAnimation() && view.getLayout().getAnimation(), 330 animated = animation && animation.isAnimation && view.isPainted(), 331 properties, 332 leftGhost, 333 titleGhost, 334 leftProps, 335 titleProps; 336 337 if (animated) { 338 leftGhost = me.createProxy(leftBox.element); 339 leftBoxElement.setStyle('opacity', '0'); 340 backButton.setText(backButtonText); 341 backButton[hasPrevious ? 'show' : 'hide'](); 342 343 titleGhost = me.createProxy(titleComponent.element.getParent()); 344 titleElement.setStyle('opacity', '0'); 345 me.setTitle(titleText); 346 347 properties = me.measureView(leftGhost, titleGhost, reverse); 348 leftProps = properties.left; 349 titleProps = properties.title; 350 351 me.isAnimating = true; 352 me.animate(leftBoxElement, leftProps.element); 353 me.animate(titleElement, titleProps.element, 354 function () { 355 titleElement.setLeft(properties.titleLeft); 356 me.isAnimating = false; 357 me.refreshTitlePosition(); 358 }); 359 360 if (Ext.browser.is.AndroidStock2 && !this.getAndroid2Transforms()) { 361 leftGhost.ghost.destroy(); 362 titleGhost.ghost.destroy(); 363 } else { 364 me.animate(leftGhost.ghost, leftProps.ghost); 365 me.animate(titleGhost.ghost, titleProps.ghost, 366 function () { 367 leftGhost.ghost.destroy(); 368 titleGhost.ghost.destroy(); 369 }); 370 } 371 372 } else { 373 if (hasPrevious) { 374 backButton.setText(backButtonText); 375 backButton.show(); 376 } else { 377 backButton.hide(); 378 } 379 me.setTitle(titleText); 380 } 381 me.updateBar(); 382 }, 383 //更新bar 384 updateBar: function () { 385 //更新其他 386 var bar = this.getBar() || {}; 387 //设置导航栏临时控件组 388 this.setTmpItems(bar.items); 389 //设置导航栏临时cls 390 this.setTmpCls(bar.cls); 391 //更新返回按钮状态 392 if (bar.backHide) { 393 backButton.hide(); 394 } 395 }, 396 /** 397 * Calculates and returns the position values needed for the back button when you are pushing a title. 398 * @private 399 */ 400 measureView: function (oldLeft, oldTitle, reverse) { 401 var me = this, 402 barElement = me.element, 403 newLeftElement = me.leftBox.element, 404 titleElement = me.titleComponent.element, 405 minOffset = Math.min(barElement.getWidth() / 3, 200), 406 newLeftWidth = newLeftElement.getWidth(), 407 barX = barElement.getX(), 408 barWidth = barElement.getWidth(), 409 titleX = titleElement.getX(), 410 titleLeft = titleElement.getLeft(), 411 titleWidth = titleElement.getWidth(), 412 oldLeftX = oldLeft.x, 413 oldLeftWidth = oldLeft.width, 414 oldLeftLeft = oldLeft.left, 415 useLeft = Ext.browser.is.AndroidStock2 && !this.getAndroid2Transforms(), 416 newOffset, 417 oldOffset, 418 leftAnims, 419 titleAnims, 420 omega, 421 theta; 422 423 theta = barX - oldLeftX - oldLeftWidth; 424 if (reverse) { 425 newOffset = theta; 426 oldOffset = Math.min(titleX - oldLeftWidth, minOffset); 427 } else { 428 oldOffset = theta; 429 newOffset = Math.min(titleX - barX, minOffset); 430 } 431 432 if (useLeft) { 433 leftAnims = { 434 element: { 435 from: { 436 left: newOffset, 437 opacity: 1 438 }, 439 to: { 440 left: 0, 441 opacity: 1 442 } 443 } 444 }; 445 } else { 446 leftAnims = { 447 element: { 448 from: { 449 transform: { 450 translateX: newOffset 451 }, 452 opacity: 0 453 }, 454 to: { 455 transform: { 456 translateX: 0 457 }, 458 opacity: 1 459 } 460 }, 461 ghost: { 462 to: { 463 transform: { 464 translateX: oldOffset 465 }, 466 opacity: 0 467 } 468 } 469 }; 470 } 471 472 theta = barX - titleX + newLeftWidth; 473 if ((oldLeftLeft + titleWidth) > titleX) { 474 omega = barX - titleX - titleWidth; 475 } 476 477 if (reverse) { 478 titleElement.setLeft(0); 479 480 oldOffset = barX + barWidth - titleX - titleWidth; 481 482 if (omega !== undefined) { 483 newOffset = omega; 484 } else { 485 newOffset = theta; 486 } 487 } else { 488 newOffset = barX + barWidth - titleX - titleWidth; 489 490 if (omega !== undefined) { 491 oldOffset = omega; 492 } else { 493 oldOffset = theta; 494 } 495 496 newOffset = Math.max(titleLeft, newOffset); 497 } 498 499 if (useLeft) { 500 titleAnims = { 501 element: { 502 from: { 503 left: newOffset, 504 opacity: 1 505 }, 506 to: { 507 left: titleLeft, 508 opacity: 1 509 } 510 } 511 }; 512 } else { 513 titleAnims = { 514 element: { 515 from: { 516 transform: { 517 translateX: newOffset 518 }, 519 opacity: 0 520 }, 521 to: { 522 transform: { 523 translateX: titleLeft 524 }, 525 opacity: 1 526 } 527 }, 528 ghost: { 529 to: { 530 transform: { 531 translateX: oldOffset 532 }, 533 opacity: 0 534 } 535 } 536 }; 537 } 538 539 return { 540 left: leftAnims, 541 title: titleAnims, 542 titleLeft: titleLeft 543 }; 544 }, 545 546 /** 547 * Helper method used to animate elements. 548 * You pass it an element, objects for the from and to positions an option onEnd callback called when the animation is over. 549 * Normally this method is passed configurations returned from the methods such as #measureTitle(true) etc. 550 * It is called from the #pushLeftBoxAnimated, #pushTitleAnimated, #popBackButtonAnimated and #popTitleAnimated 551 * methods. 552 * 553 * If the current device is Android, it will use top/left to animate. 554 * If it is anything else, it will use transform. 555 * @private 556 */ 557 animate: function (element, config, callback) { 558 var me = this, 559 animation; 560 561 //reset the left of the element 562 element.setLeft(0); 563 564 config = Ext.apply(config, { 565 element: element, 566 easing: 'ease-in-out', 567 duration: me.getAnimation().duration || 250, 568 preserveEndState: true 569 }); 570 571 animation = new Ext.fx.Animation(config); 572 animation.on('animationend', 573 function () { 574 if (callback) { 575 callback.call(me); 576 } 577 }, 578 me); 579 580 Ext.Animator.run(animation); 581 me.activeAnimations.push(animation); 582 }, 583 584 endAnimation: function () { 585 var activeAnimations = this.activeAnimations, 586 animation, i, ln; 587 588 if (activeAnimations) { 589 ln = activeAnimations.length; 590 for (i = 0; i < ln; i++) { 591 animation = activeAnimations[i]; 592 if (animation.isAnimating) { 593 animation.stopAnimation(); 594 } else { 595 animation.destroy(); 596 } 597 } 598 this.activeAnimations = []; 599 } 600 }, 601 602 refreshTitlePosition: function () { 603 if (!this.isAnimating) { 604 this.callParent(); 605 } 606 }, 607 608 /** 609 * Returns the text needed for the current back button at anytime. 610 * @private 611 */ 612 getBackButtonText: function () { 613 var text = this.backButtonStack[this.backButtonStack.length - 2], 614 useTitleForBackButtonText = this.getUseTitleForBackButtonText(); 615 616 if (!useTitleForBackButtonText) { 617 if (text) { 618 text = this.getDefaultBackButtonText(); 619 } 620 } 621 622 return text; 623 }, 624 625 /** 626 * Returns the text needed for the current title at anytime. 627 * @private 628 */ 629 getTitleText: function () { 630 if (this.backButtonStack.length == 0) { 631 return ''; 632 } 633 return this.backButtonStack[this.backButtonStack.length - 1].title; 634 }, 635 getBar: function () { 636 if (this.backButtonStack.length == 0) { 637 return false; 638 } 639 return this.backButtonStack[this.backButtonStack.length - 1].bar; 640 }, 641 /** 642 * Handles removing back button stacks from this bar 643 * @private 644 */ 645 beforePop: function (count) { 646 count--; 647 for (var i = 0; i < count; i++) { 648 this.backButtonStack.pop(); 649 } 650 }, 651 652 /** 653 * We override the hidden method because we don't want to remove it from the view using display:none. Instead we just position it off 654 * the screen, much like the navigation bar proxy. This means that all animations, pushing, popping etc. all still work when if you hide/show 655 * this bar at any time. 656 * @private 657 */ 658 doSetHidden: function (hidden) { 659 if (!hidden) { 660 this.element.setStyle({ 661 position: 'relative', 662 top: 'auto', 663 left: 'auto', 664 width: 'auto' 665 }); 666 } else { 667 this.element.setStyle({ 668 position: 'absolute', 669 top: '-1000px', 670 left: '-1000px', 671 width: this.element.getWidth() + 'px' 672 }); 673 } 674 }, 675 676 /** 677 * Creates a proxy element of the passed element, and positions it in the same position, using absolute positioning. 678 * The createNavigationBarProxy method uses this to create proxies of the backButton and the title elements. 679 * @private 680 */ 681 createProxy: function (element) { 682 var ghost, x, y, left, width; 683 684 ghost = element.dom.cloneNode(true); 685 ghost.id = element.id + '-proxy'; 686 687 //insert it into the toolbar 688 element.getParent().dom.appendChild(ghost); 689 690 //set the x/y 691 ghost = Ext.get(ghost); 692 x = element.getX(); 693 y = element.getY(); 694 left = element.getLeft(); 695 width = element.getWidth(); 696 ghost.setStyle('position', 'absolute'); 697 ghost.setX(x); 698 ghost.setY(y); 699 ghost.setHeight(element.getHeight()); 700 ghost.setWidth(width); 701 702 return { 703 x: x, 704 y: y, 705 left: left, 706 width: width, 707 ghost: ghost 708 }; 709 } 710 });