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. 8款最新CSS3表单 环形表单很酷

    当我们在网站上注册登录还是提交评论,都需要用到表单,今天我们来分享8款最新CSS3表单,有几个效果很酷很特别,有些也非常实用,一起来看看. 1.CSS3环形特色表单 转圈切换表单焦点 这款CSS3表单 ...

  2. 谈谈如何优化MYSQL数据库查询

    1.优化数据类型 MySQL中数据类型有多种,如果你是一名DBA,正在按照优化的原则对数据类型进行严格的检查,但开发人员可能会选择他们认为最简单的方案,以加快编码速度,或者选择最明显的选择,因此,你可 ...

  3. iOS :ViewDidAppear

    进入一个 UIViewController 会调用它的三个方法,分别是 viewDidLoad, viewWillAppear, viewDidAppear. 如每个方法的名字一样,在不同的方法中要处 ...

  4. eclipse 安装图形插件(图形化编程)

    打开eclipse 查看什么版本 ,我的是Oxygen help --> install newsoftware 打开地址 http://www.eclipse.org/windowbuilde ...

  5. 下拉刷新XListView的简单分析

    依照这篇博文里的思路分析和理解的 先要理解Scroller,看过的博文: http://ipjmc.iteye.com/blog/1615828 http://blog.csdn.net/wangji ...

  6. textarea标签内容为(英文或数字不自动换行)的解决方法

    textarea 显示一串英文时不会发生换行. 以下是两种解决方法:1.限制textarea的大小 width 设置为 00px (不要设置为00%)cols  设置为 30+ (也有类似效果) 2. ...

  7. 如何使用 URLOpenStream 函数

    URLOpenStream 和 URLDownloadToFile 类似, 都是下载文件的 COM 函数; 前者是下载到 IStream 流, 后者是直接下载到指定路径; 不如后者使用方便. 它们都声 ...

  8. 前端如何获取http状态码400的返回值

    axios.get("/check_mobile_and_sent_code",{withCredentials:true,params:{mobile:formInline.mo ...

  9. Java SQL注入学习笔记

    1 简介 文章主要内容包括: Java 持久层技术/框架简单介绍 不同场景/框架下易导致 SQL 注入的写法 如何避免和修复 SQL 注入 2 JDBC 介绍 JDBC: 全称 Java Databa ...

  10. Redis集群版在Java中的应用

    1.配置redis集群 <?xml version="1.0" encoding="UTF-8"?> <redisCluster> &l ...