NavigationView 是官方根据Container控件扩展而来的,由一个导航栏和一个card组成,具备导航和返回时自动销毁当前界面的功能,非常适合新手使用。
其中导航栏的代码如下:
Ext.define('Ext.navigation.Bar', {
extend: 'Ext.TitleBar',
requires: ['Ext.Button', 'Ext.Spacer'],
isToolbar: true,
config: {
baseCls: Ext.baseCSSPrefix + 'toolbar',
cls: Ext.baseCSSPrefix + 'navigation-bar',
ui: 'dark',
title: null,
defaultType: 'button',
layout: {
type: 'hbox'
},
defaultBackButtonText: 'Back',
animation: {
duration: 300
},
useTitleForBackButtonText: null,
view: null,
android2Transforms: false,
backButton: {
align: 'left',
ui: 'back',
hidden: true
}
},
platformConfig: [{
theme: ['Blackberry'],
animation: false
}],
constructor: function(config) {
config = config || {};
if (!config.items) {
config.items = []
}
this.backButtonStack = [];
this.activeAnimations = [];
this.callParent([config])
},
applyBackButton: function(config) {
return Ext.factory(config, Ext.Button, this.getBackButton())
},
updateBackButton: function(newBackButton, oldBackButton) {
if (oldBackButton) {
this.remove(oldBackButton)
}
if (newBackButton) {
this.add(newBackButton);
newBackButton.on({
scope: this,
tap: this.onBackButtonTap
})
}
},
onBackButtonTap: function() {
this.fireEvent('back', this)
},
updateView: function(newView) {
var me = this,
backButton = me.getBackButton(),
innerItems,
i,
backButtonText,
item,
title,
titleText;
me.getItems();
if (newView) {
innerItems = newView.getInnerItems();
for (i = 0; i < innerItems.length; i++) {
item = innerItems[i];
title = (item.getTitle) ? item.getTitle() : item.config.title;
me.backButtonStack.push(title || ' ')
}
titleText = me.getTitleText();
if (titleText === undefined) {
titleText = ''
}
me.setTitle(titleText);
backButtonText = me.getBackButtonText();
if (backButtonText) {
backButton.setText(backButtonText);
backButton.show()
}
}
},
onViewAdd: function(view, item) {
var me = this,
backButtonStack = me.backButtonStack,
hasPrevious, title;
me.endAnimation();
title = (item.getTitle) ? item.getTitle() : item.config.title;
backButtonStack.push(title || ' ');
hasPrevious = backButtonStack.length > 1;
me.doChangeView(view, hasPrevious, false)
},
onViewRemove: function(view) {
var me = this,
backButtonStack = me.backButtonStack,
hasPrevious;
me.endAnimation();
backButtonStack.pop();
hasPrevious = backButtonStack.length > 1;
me.doChangeView(view, hasPrevious, true)
},
doChangeView: function(view, hasPrevious, reverse) {
var me = this,
leftBox = me.leftBox,
leftBoxElement = leftBox.element,
titleComponent = me.titleComponent,
titleElement = titleComponent.element,
backButton = me.getBackButton(),
titleText = me.getTitleText(),
backButtonText = me.getBackButtonText(),
animation = me.getAnimation() && view.getLayout().getAnimation(),
animated = animation && animation.isAnimation && view.isPainted(),
properties,
leftGhost,
titleGhost,
leftProps,
titleProps;
if (animated) {
leftGhost = me.createProxy(leftBox.element);
leftBoxElement.setStyle('opacity', '0');
backButton.setText(backButtonText);
backButton[hasPrevious ? 'show': 'hide']();
titleGhost = me.createProxy(titleComponent.element.getParent());
titleElement.setStyle('opacity', '0');
me.setTitle(titleText);
properties = me.measureView(leftGhost, titleGhost, reverse);
leftProps = properties.left;
titleProps = properties.title;
me.isAnimating = true;
me.animate(leftBoxElement, leftProps.element);
me.animate(titleElement, titleProps.element,
function() {
titleElement.setLeft(properties.titleLeft);
me.isAnimating = false;
me.refreshTitlePosition()
});
if (Ext.browser.is.AndroidStock2 && !this.getAndroid2Transforms()) {
leftGhost.ghost.destroy();
titleGhost.ghost.destroy()
} else {
me.animate(leftGhost.ghost, leftProps.ghost);
me.animate(titleGhost.ghost, titleProps.ghost,
function() {
leftGhost.ghost.destroy();
titleGhost.ghost.destroy()
})
}
} else {
if (hasPrevious) {
backButton.setText(backButtonText);
backButton.show()
} else {
backButton.hide()
}
me.setTitle(titleText)
}
},
measureView: function(oldLeft, oldTitle, reverse) {
var me = this,
barElement = me.element,
newLeftElement = me.leftBox.element,
titleElement = me.titleComponent.element,
minOffset = Math.min(barElement.getWidth() / 3, 200),
newLeftWidth = newLeftElement.getWidth(),
barX = barElement.getX(),
barWidth = barElement.getWidth(),
titleX = titleElement.getX(),
titleLeft = titleElement.getLeft(),
titleWidth = titleElement.getWidth(),
oldLeftX = oldLeft.x,
oldLeftWidth = oldLeft.width,
oldLeftLeft = oldLeft.left,
useLeft = Ext.browser.is.AndroidStock2 && !this.getAndroid2Transforms(),
newOffset,
oldOffset,
leftAnims,
titleAnims,
omega,
theta;
theta = barX - oldLeftX - oldLeftWidth;
if (reverse) {
newOffset = theta;
oldOffset = Math.min(titleX - oldLeftWidth, minOffset)
} else {
oldOffset = theta;
newOffset = Math.min(titleX - barX, minOffset)
}
if (useLeft) {
leftAnims = {
element: {
from: {
left: newOffset,
opacity: 1
},
to: {
left: 0,
opacity: 1
}
}
}
} else {
leftAnims = {
element: {
from: {
transform: {
translateX: newOffset
},
opacity: 0
},
to: {
transform: {
translateX: 0
},
opacity: 1
}
},
ghost: {
to: {
transform: {
translateX: oldOffset
},
opacity: 0
}
}
}
}
theta = barX - titleX + newLeftWidth;
if ((oldLeftLeft + titleWidth) > titleX) {
omega = barX - titleX - titleWidth
}
if (reverse) {
titleElement.setLeft(0);
oldOffset = barX + barWidth - titleX - titleWidth;
if (omega !== undefined) {
newOffset = omega
} else {
newOffset = theta
}
} else {
newOffset = barX + barWidth - titleX - titleWidth;
if (omega !== undefined) {
oldOffset = omega
} else {
oldOffset = theta
}
newOffset = Math.max(titleLeft, newOffset)
}
if (useLeft) {
titleAnims = {
element: {
from: {
left: newOffset,
opacity: 1
},
to: {
left: titleLeft,
opacity: 1
}
}
}
} else {
titleAnims = {
element: {
from: {
transform: {
translateX: newOffset
},
opacity: 0
},
to: {
transform: {
translateX: titleLeft
},
opacity: 1
}
},
ghost: {
to: {
transform: {
translateX: oldOffset
},
opacity: 0
}
}
}
}
return {
left: leftAnims,
title: titleAnims,
titleLeft: titleLeft
}
},
animate: function(element, config, callback) {
var me = this,
animation;
element.setLeft(0);
config = Ext.apply(config, {
element: element,
easing: 'ease-in-out',
duration: me.getAnimation().duration || 250,
preserveEndState: true
});
animation = new Ext.fx.Animation(config);
animation.on('animationend',
function() {
if (callback) {
callback.call(me)
}
},
me);
Ext.Animator.run(animation);
me.activeAnimations.push(animation)
},
endAnimation: function() {
var activeAnimations = this.activeAnimations,
animation, i, ln;
if (activeAnimations) {
ln = activeAnimations.length;
for (i = 0; i < ln; i++) {
animation = activeAnimations[i];
if (animation.isAnimating) {
animation.stopAnimation()
} else {
animation.destroy()
}
}
this.activeAnimations = []
}
},
refreshTitlePosition: function() {
if (!this.isAnimating) {
this.callParent()
}
},
getBackButtonText: function() {
var text = this.backButtonStack[this.backButtonStack.length - 2],
useTitleForBackButtonText = this.getUseTitleForBackButtonText();
if (!useTitleForBackButtonText) {
if (text) {
text = this.getDefaultBackButtonText()
}
}
return text
},
getTitleText: function() {
return this.backButtonStack[this.backButtonStack.length - 1]
},
beforePop: function(count) {
count--;
for (var i = 0; i < count; i++) {
this.backButtonStack.pop()
}
},
doSetHidden: function(hidden) {
if (!hidden) {
this.element.setStyle({
position: 'relative',
top: 'auto',
left: 'auto',
width: 'auto'
})
} else {
this.element.setStyle({
position: 'absolute',
top: '-1000px',
left: '-1000px',
width: this.element.getWidth() + 'px'
})
}
},
createProxy: function(element) {
var ghost, x, y, left, width;
ghost = element.dom.cloneNode(true);
ghost.id = element.id + '-proxy';
element.getParent().dom.appendChild(ghost);
ghost = Ext.get(ghost);
x = element.getX();
y = element.getY();
left = element.getLeft();
width = element.getWidth();
ghost.setStyle('position', 'absolute');
ghost.setX(x);
ghost.setY(y);
ghost.setHeight(element.getHeight());
ghost.setWidth(width);
return {
x: x,
y: y,
left: left,
width: width,
ghost: ghost
}
}
});
可以看出他是继承于一个TitleBar,中间为标题,左侧有一个默认的返回按钮这些代码看似复杂,其实逻辑很简单。
他的主要作用就是监听返回按钮,为其添加一个back自定义事件。并且通过this.backButtonStack这个数组来储存标题显示记录。
在返回时动态更新标题栏,并且有切换的动画效果。由于标题长短不一,所以整个导航栏的代码大部分都是来处理切换动画了。
实际上它的核心方法只有:
constructor:进行配置的初始化处理
applyBackButton:动态创建返回按钮
updateBackButton:动态更新返回按钮,并且添加监听(触发onBackButtonTap)
onBackButtonTap:将返回按钮的点击转换为自定义事件back
updateView:更新导航栏按钮,标题。NavigationView中更新视图时会触发它
onViewAdd:添加新的历史记录。NavigationView中添加新的视图时会触发它
onViewRemove:移除当前的历史记录,NavigationView中移除视图时会触发它
doChangeView:视图改变后处理标题,返回按钮。大部分代码其实是处理切换动画效果
getBackButtonText:获取返回按钮的text值,如果useTitleForBackButtonText为ture就需要它来处理
getTitleText:获取最后一个标题
beforePop:点返回按钮时处理this.backButtonStack这个数组
我们如果想要重写它可以注意这些方法,其他的都是用来处理动画效果的。个人觉得没必要有这些方法,会影响性能
NavigationView代码如下:
Ext.define('Ext.navigation.View', {
extend: 'Ext.Container',
alternateClassName: 'Ext.NavigationView',
xtype: 'navigationview',
requires: ['Ext.navigation.Bar'],
config: {
baseCls: Ext.baseCSSPrefix + 'navigationview',
navigationBar: {
docked: 'top'
},
defaultBackButtonText: 'Back',
useTitleForBackButtonText: false,
layout: {
type: 'card',
animation: {
duration: 300,
easing: 'ease-out',
type: 'slide',
direction: 'left'
}
}
},
platformConfig: [{
theme: ['Blackberry'],
navigationBar: {
splitNavigation: true
}
}],
initialize: function() {
var me = this,
navBar = me.getNavigationBar();
if (navBar) {
navBar.on({
back: me.onBackButtonTap,
scope: me
});
me.relayEvents(navBar, 'rightbuttontap');
me.relayEvents(me, {
add: 'push',
remove: 'pop'
})
}
var layout = me.getLayout();
if (layout && !layout.isCard) {
Ext.Logger.error('The base layout for a NavigationView must always be a Card Layout')
}
},
applyLayout: function(config) {
config = config || {};
return config
},
onBackButtonTap: function() {
this.pop();
this.fireEvent('back', this)
},
push: function(view) {
return this.add(view)
},
pop: function(count) {
if (this.beforePop(count)) {
return this.doPop()
}
},
beforePop: function(count) {
var me = this,
innerItems = me.getInnerItems();
if (Ext.isString(count) || Ext.isObject(count)) {
var last = innerItems.length - 1,
i;
for (i = last; i >= 0; i--) {
if ((Ext.isString(count) && Ext.ComponentQuery.is(innerItems[i], count)) || (Ext.isObject(count) && count == innerItems[i])) {
count = last - i;
break
}
}
if (!Ext.isNumber(count)) {
return false
}
}
var ln = innerItems.length,
toRemove;
if (!Ext.isNumber(count) || count < 1) {
count = 1
}
count = Math.min(count, ln - 1);
if (count) {
me.getNavigationBar().beforePop(count);
toRemove = innerItems.splice( - count, count - 1);
for (i = 0; i < toRemove.length; i++) {
this.remove(toRemove[i])
}
return true
}
return false
},
doPop: function() {
var me = this,
innerItems = this.getInnerItems();
me.remove(innerItems[innerItems.length - 1]);
if (innerItems.length < 3 && this.$backButton) {
this.$backButton.hide()
}
if (this.$titleContainer) {
if (!this.$titleContainer.setTitle) {
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.')
}
var item = innerItems[innerItems.length - 2];
this.$titleContainer.setTitle((item.getTitle) ? item.getTitle() : item.config.title)
}
return this.getActiveItem()
},
getPreviousItem: function() {
var innerItems = this.getInnerItems();
return innerItems[innerItems.length - 2]
},
updateUseTitleForBackButtonText: function(useTitleForBackButtonText) {
var navigationBar = this.getNavigationBar();
if (navigationBar) {
navigationBar.setUseTitleForBackButtonText(useTitleForBackButtonText)
}
},
updateDefaultBackButtonText: function(defaultBackButtonText) {
var navigationBar = this.getNavigationBar();
if (navigationBar) {
navigationBar.setDefaultBackButtonText(defaultBackButtonText)
}
},
applyNavigationBar: function(config) {
if (!config) {
config = {
hidden: true,
docked: 'top'
}
}
if (config.title) {
delete config.title;
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.")
}
config.view = this;
config.useTitleForBackButtonText = this.getUseTitleForBackButtonText();
if (config.splitNavigation) {
this.$titleContainer = this.add({
docked: 'top',
xtype: 'titlebar',
ui: 'light',
title: this.$currentTitle || ''
});
var containerConfig = (config.splitNavigation === true) ? {}: config.splitNavigation;
this.$backButtonContainer = this.add(Ext.apply({
xtype: 'toolbar',
docked: 'bottom'
},
containerConfig));
this.$backButton = this.$backButtonContainer.add({
xtype: 'button',
text: 'Back',
hidden: true,
ui: 'back'
});
this.$backButton.on({
scope: this,
tap: this.onBackButtonTap
});
config = {
hidden: true,
docked: 'top'
}
}
return Ext.factory(config, Ext.navigation.Bar, this.getNavigationBar())
},
updateNavigationBar: function(newNavigationBar, oldNavigationBar) {
if (oldNavigationBar) {
this.remove(oldNavigationBar, true)
}
if (newNavigationBar) {
this.add(newNavigationBar)
}
},
applyActiveItem: function(activeItem, currentActiveItem) {
var me = this,
innerItems = me.getInnerItems();
me.getItems();
if (!me.initialized) {
activeItem = innerItems.length - 1
}
return this.callParent([activeItem, currentActiveItem])
},
doResetActiveItem: function(innerIndex) {
var me = this,
innerItems = me.getInnerItems(),
animation = me.getLayout().getAnimation();
if (innerIndex > 0) {
if (animation && animation.isAnimation) {
animation.setReverse(true)
}
me.setActiveItem(innerIndex - 1);
me.getNavigationBar().onViewRemove(me, innerItems[innerIndex], innerIndex)
}
},
doRemove: function() {
var animation = this.getLayout().getAnimation();
if (animation && animation.isAnimation) {
animation.setReverse(false)
}
this.callParent(arguments)
},
onItemAdd: function(item, index) {
if (item && item.getDocked() && item.config.title === true) {
this.$titleContainer = item
}
this.doItemLayoutAdd(item, index);
var navigaitonBar = this.getInitialConfig().navigationBar;
if (!this.isItemsInitializing && item.isInnerItem()) {
this.setActiveItem(item);
if (navigaitonBar) {
this.getNavigationBar().onViewAdd(this, item, index)
}
if (this.$backButtonContainer) {
this.$backButton.show()
}
}
if (item && item.isInnerItem()) {
this.updateTitleContainerTitle((item.getTitle) ? item.getTitle() : item.config.title)
}
if (this.initialized) {
this.fireEvent('add', this, item, index)
}
},
updateTitleContainerTitle: function(title) {
if (this.$titleContainer) {
if (!this.$titleContainer.setTitle) {
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.')
}
this.$titleContainer.setTitle(title)
} else {
this.$currentTitle = title
}
},
reset: function() {
return this.pop(this.getInnerItems().length)
}
});
这些代码就是核心了,作用如下:
initialize:初始化,为导航栏的返回事件添加监听(触发onBackButtonTap方法),为add和romove方法添加监听
onBackButtonTap:点击返回按钮时触发,触发pop方法,并且添加自定义事件。
push:其实就是调用add方法,添加新视图
pop:会调用beforePop方法和doPop方法
beforePop:有时候不止移除一项,这里的逻辑很复杂
doPop:移除card中最后一项
getPreviousItem:获取倒数第二项
updateUseTitleForBackButtonText:作用顾名思义
updateDefaultBackButtonText:同上
applyNavigationBar:创建导航栏而已,别怕代码多
updateNavigationBar:更新导航栏
applyActiveItem:为什么card始终显示最后一项,就是因为重写了它
doResetActiveItem:点击返回按钮时反转切换动画的
doRemove:调用remove方法后会触发也是反转切换动画
onItemAdd:项第一次被添加到card中触发,一系列的逻辑处理
updateTitleContainerTitle:顾名思义
reset:清空所有项,不过这里逻辑比较复杂。会调用pop方法
http://www.cnblogs.com/mlzs/p/3376399.html这里我有对代码进行一些注释,可以参考一下。
下面说说用法,个人推荐先创建一个视图继承它,如下:
Ext.define('app.view.Main', {
extend: 'Ext.NavigationView',
xtype: 'main',
config: {
navigationBar: {
backButton: {
iconCls: 'arrow_left',
ui: '',
cls: 'back'
}
},
cls: 'cardPanel'
}
});
app.js中初始化它:
launch: function () {
// Destroy the #appLoadingIndicator element
Ext.fly('appLoadingIndicator').destroy();
// Initialize the main view
Ext.Viewport.add(Ext.create('app.view.Main'));
}
单独的main控制层监听它:
//引用
refs: {
main: 'main'
}
添加一个路由监听
routes: {
'redirect/:view': 'showView'
}
main控制层中写一个方法:
//展示页面
showView: function (view, isPop) {
var main = this.getMain(),
view = Ext.create(xtype);
main.push(view, params);
}
任何控制层之中都可以通过如下方法触发这个方法
this.redirectTo('redirect/xtype');
当然这个只是简单的用法,有兴趣的可以看看http://www.cnblogs.com/mlzs/p/3498846.html,里面有免费视频听,也可以参考官方的示例。
值得注意的是,NavigationView作为一个容器,虽然是继承于Container,里面的tpl,data,html这些都是不能使用的,他的布局也不能随便更改。