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. iOS:DKLiveBlur

    https://github.com/kronik/DKLiveBlur Sources of DKLiveBlur and Demo app to show live blur effect sim ...

  2. touch事件的分发和消费机制

    Android 中与 Touch 事件相关的方法包括:dispatchTouchEvent(MotionEvent ev).onInterceptTouchEvent(MotionEvent ev). ...

  3. asp.net gridview实现正在加载效果方案一AJAX(转)

    前台代码: <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.as ...

  4. Xcode 6 免证书真机调试

    前景:在 iOS 开发中,如果想进行真机调试,普遍情况是掏钱,掏钱的情况有两种: 第一种就是:直接在 Apple 官网注册为 Developer,每年99美元:当然这种作为个人开发者的话,是很有必要的 ...

  5. Peckham添加引用文件模糊匹配智能提示

    下载地址:https://github.com/markohlebar/Peckham ​ 跟VVDocumenter规范注释生成器的安装方式一样: 下载开源工程在Xcode重新编译运行会自动安装此插 ...

  6. Java编程思想学习笔记——字符串

    前言 字符串操作是计算机程序设计中最常见的行为. 不可变String String对象是不可变的 重载"+"与StringBuilder String对象是不可变的,可以给Stri ...

  7. win8.1的ie11无法打开127.0.0.1和本机IP访问

    解决方法:把ie11安全选项里的启动保护模式对勾去掉!

  8. 怎么使用ABBYY中的Bates编号

    ABBYY PDF Transformer+ 可让您将 Bates 编号添加到 PDF 文档.Bates 编号可方便文档搜索和检索,并更加有利于电子归档.下面小编给小伙伴们讲讲ABBYY PDF Tr ...

  9. 【11-13】A股主要指数的市盈率(PE)估值高度

    全指材料(SH000987) - 2018-11-13日,当前值:12.4646,平均值:30.54,中位数:26.09865,当前 接近历史新低.全指材料(SH000987)的历史市盈率PE详情 内 ...

  10. 理解firewall

    http://blog.csdn.net/dream361/article/details/54022470  //firewall介绍 http://www.jb51.net/article/103 ...