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 || '&nbsp;')
}
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 || '&nbsp;');
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这些都是不能使用的,他的布局也不能随便更改。

sencha touch NavigationView的更多相关文章

  1. sencha touch NavigationView 源码详解(注释)

    Ext.define('Ext.navigation.View', { extend: 'Ext.Container', alternateClassName: 'Ext.NavigationView ...

  2. sencha touch NavigationView 嵌套 TabPanel 的问题

    在st2.1之中,在NavigationView视图之中在嵌套一个TabPanel会有以下问题 下面我们监控TabPanel的activate事件和activeitemchange事件 会发现当首页加 ...

  3. 跟我一起玩转Sencha Touch 移动 WebApp 开发(一)

    1.目录 移动框架简介,为什么选择Sencha Touch? 环境搭建 创建项目框架,框架文件简介 创建简单Tabpanel案例 自定义图标的方式 WebApp产品测试和发布 HTML5离线缓存 发布 ...

  4. 再探 Ext JS 6 (sencha touch/ext升级版) 变化篇 (编译命令、滚动条、控制层、模型层、路由)

    从sencha touch 2.4.2升级到ext js 6,cmd版本升级到6.0之后发生了很多变化 首先从cmd说起,cmd 6 中sencha app build package不能使用了,se ...

  5. Sencha Touch xtype对应的class

    Sencha Touch 2的有效xtype xtype Class ----------------- --------------------- actionsheet Ext.ActionShe ...

  6. sencha touch 常见问题解答(1-25)

    欢迎留言补充,持续更新中... 1.sencha touch 是什么? 答:Sencha touch框架是世界上第一个基于HTML 5的移动应用框架.它可以让你的Web应用看起来像网络应用.美丽的用户 ...

  7. Sencha Touch 实战开发培训 视频教程 第二期 第一节

    经过忙碌的准备,终于在2014.4.7晚上8:10分开课. 本来预定在8点开课的,不过电脑出了点问题,推迟了. 本期培训一共八节,前两节免费,后面的课程需要付费才可以观看. 本节内容: 了解Sench ...

  8. Sencha Touch 实战开发培训 视频教程 第二期 基础提高篇 预告

    “抛砖网”国内首家首创纯实战型培训机构,提供在线培训.技术指导及答疑! 团队通过360全方位技术培训+1度手把手技术指导,保证每一个学员能最快掌握实际工作技能: 让每一个学员都能站在我们的肩膀上,展翅 ...

  9. sencha touch Container

    Container控件是我们在实际开发中最常用的控件,大部分视图控件都是继承于Container控件,了解此控件能帮我们更好的了解sencha touch. layout是一个很重要的属性,能够帮助你 ...

随机推荐

  1. 前端Table数据导出Excel使用HSSFWorkbook(Java)

    一.实现原理: 1. 前端查询列表数据并渲染至table(<table>...</table>)表格 2. 表格html代码传输至后台 3. 后台把html转成Excel输出流 ...

  2. Redhat 5.6(RHEL 5.6)下安装PostgreSQL9.3

    1,下载Respository的更新包 http://yum.postgresql.org/9.3/redhat/rhel-5-x86_64/pgdg-redhat93-9.3-1.noarch.rp ...

  3. Thinkphp5笔记二:创建模块

    系统:window 7 64位 Thinkphp版本:5.0.5 环境:wampserver集成 我的项目是部署在本地www/thinkphp  目录下.在做之前,先要考虑清楚,你需要几个模块来完成你 ...

  4. C语言的基本数据类型长度

    PS:以下内容是在Xcode的编辑器64位环境下的测试结果,网上有关于64位和32位各数据类型存在的差异,请自行online search. main.m #import <Foundation ...

  5. Linux 目录下属性查看操作

    1. 查看当前目录所有文件和文件夹的大小 方法一: $du -sh * 或 $du -h -d 0 * '-d 0' 代表查询目录的深度为0 ,也就是当前目录,'-d 3' 表示文件目录深度为3,可以 ...

  6. web.py开发

    web.py需要使用python2.X,所以安装python版本2.7.9 web.py 是一个轻量级Python web框架,它简单而且功能强大 web.py安装 安装python (1)使用pip ...

  7. 查询SQL阻塞语句

    SELECT SPID=p.spid, DBName = convert(CHAR(),d.name), ProgramName = program_name, LoginName = convert ...

  8. Java实现高效的枚举元素集合

    Set是Java集合类的重要组成部分,它用来存储不能重复的对象.枚举类型也要求其枚举元素各不相同.看起来枚举类型和集合是很相似的.然而枚举类型中的元素不能随意的增加.删除,作为集合而言,枚举类型非常不 ...

  9. mongodb安装使用笔记

    mongodb安装使用 安装后配置环境变量 创建数据库文件夹并连接数据库,并执行mongod --dbpath c:\workname 打开新的cmd,执行mongo命令,管理数据库 show dbs ...

  10. 有人在群里问关于SQL表组合数据问题

    他的问题如下 如此我建表如下: 如果想根据用户进行分组后 又要显示所属门店在同一个字段中的话,这里需要用group_concat来显示 同时关联的时候可用find_in_set来处理 我设计的SQL如 ...