使用Sencha Touch开发移动Web应用平台
Sencha Touch 是由 Sencha 公司开发的移动 Web 应用开发框架,用以提升主流移动设备在浏览器上的触碰操作,增强用户体验。该框架以久负盛名的 Ext JS 富客户端框架为基础,并支持最新的 HTML5 及 CSS3 标准,与流行的 Apple iOS 和 Andriod 设备兼容。一方面,它以 Webkit 浏览器引擎为基础,提供了出色的性能和用户体验;另一方面,它提供了基于 GPL V3 许可的开源版本和详尽的 API 文档,体现了良好的开放性和易用性。因此,该框架可帮助移动应用开发人员提升开发效率,从而创造出更多富有创意的移动应用。
随着智能移动设备的普及和 3G 通讯技术的发展,将会有越来越多的传统应用部署为移动 Web 应用,而良好兼容性和操控性是 Web 应用成功的关键。本文将分为以下四个部分介绍 Sencha Touch 的独特之处,并结合示例为相关移动应用的开发人员编写良好兼容性和操控性的 Web 程序提供借鉴。
与众不同的 Sencha Touch:功能和特性
Sencha Touch 是世界上第一个支持 HTML5 和 CSS3 标准的移动应用框架,你可以使用 HTML5 来编写音频和视频组件,还可以使用 LocalStorage Proxy 来存储离线数据,同时,大量 CSS3 样式表为你提供了创建健壮样式层的可能。该框架在提供丰富功能的基础上对 JavaScript 库文件进行合理优化,使得经过 gzipped 压缩后的库文件在 120kb 以下,最大限度地提升了Web 应用在浏览器中的加载速度,增强了用户体验。
除了对最新标准的支持,该框架最大的特色正如其名,增强了对手持移动设备触控操作的支持,除了支持浏览器标准的触摸事件,还额外添加了如 tap, double tap, swipe, tap-hold, pinch 和 rotate 等富有吸引力的操作事件,使用户体验到与原生程序一样的效果。
Sencha Touch 另一大优势在于其跨平台性,由于 Apple iOS 和 Andriod 设备有其独立的开发、测试和运行环境,针对某一平台开发的应用在另一平台是不兼容的,这大大增加了移动应用的开发成本。而基于 Sencha Touch 开发的 Web 应用具有与原生应用相同的用户体验,同时兼容 Apple iOS、Andriod 和黑莓 RIM 6 设备,可以满足大部分的市场需求。
此外,借助 Ext JS 多年来对 Ajax 数据集成的经验,该框架提供了丰富的数据处理功能。开发人员能够方便地处理各种格式的数据如 XML、JSON,并能灵活地绑定到可视化组件加以展示。
个性鲜明的 UI 组件
表单是用户与应用程序交互的基本媒介,如用户信息注册、应用程序配置、个人评论的发表这些常见的 Web 应用场景都需要表单组件的支持。Sencha Touch 为我们提供了形式多样、操作简单的 表单组件 。图 1 中第一个界面所展示的是基本表单元素,包括多种类型的输入框,如纯文本、密码、邮件、URL 地址等类型,并能根据用户输入的文本进行有效性验证,以减少开发者对用户输入格式的验证代码,同时,基本表单还支持单选、多选、日历选择、多行文本输入等控件类型。图 1 第二个界面展示了触控屏手持设备所特有的滑动条组件,适合调整一些连续性的数值和作为切换的开关按钮。图 1 中第三个界面展示了建立在工具条上的表单控件,非常适用于搜索和文本过滤的应用场景。
图 1 表单组件
列表是移动 Web 应用展示信息最为常见的组件,其中比较有特色的是分组列表(Grouped List)(如 图 2 中第一个界面),它可以根据所列项目的首字母进行排序分组,当用户触摸屏幕右侧字母索引时,屏幕可快速滚动并定位至对应分组,非常适合于列表信息较多的情况,如联系人列表,歌曲列表等。图 2 中第二个界面所示的嵌套列表(Nested List)则非常适合于展示信息有层级关系的情况,如浏览论坛时的“讨论区 -> 主题帖 -> 原帖及回复内容”这样的层次关系。
图 2 列表组件
精致形象的图标和布置合理的工具栏是 Apple iOS 原生应用引以为傲的部分,而 Sencha Touch 也可以做到这一点。图 3 中第一个界面所示的是框架内置的图标样式,已可以满足大部分应用的需要,开发人员还可以通过自定义图标样式来扩展出更多更丰富的图标。如 图 3 中第二个界面所示,图标所在的工具栏布置方式也灵活多样,即可在屏幕顶部或者底部,也可以多个层叠,并可以在工具栏上布置形状各异的按钮。图 3 中第三个界面所展示的是根据底部 Tab 标签页而进行切换的面板,不同的面板中可以包含不同的主题内容。
图 3 图标、工具栏和标签页
如果你以为 Sencha Touch 只能做到以上这些小儿科,那就错了,下面展示了一些高级的 UI 功能。图 4 中第一个界面类似于 Apple iOS 设备上的 SpringBoard 操作,可以通过手指的左右或者上下滑动,来旋转切换界面窗口;图 4 中第二个界面显示了一个窗口重叠的效果,当上层弹出窗口激活时,下层窗口的操作是被屏蔽的,在提醒用户执行一些重要操作的场景中(如删除或者保存),这样的 UI 组件是非常好用的。
图 4 旋转切换与窗口的重叠效果
酷炫的动画效果
一直以来,基于浏览器的 Web 程序动画效果常被人诟病,尤其是基于 JavaScript 的动画效果库相对于原生的应用程序来说,还是存在一定的差距,而刚发布的 Sencha Touch 1.1.0 版本就支持多达六种动画效果,分别是 cube、fade、flip、pop、slide 和 wipe。以最为酷炫的 3D 旋转 Cube 动画 为例,它将当前显示的界面面板(称之为 Card)想象为立方体的一个面,而即将展示的 Card 作为相邻的另外一个面,以左上方的顶点作为旋转基点进行旋转,从而得到 Card 之间切换的动画效果。大家一定很好奇它是如何做到这一点的,我们通过查看该动画效果的源代码即可找到答案。
在文件 sencha-touch-1.1.0\src\core\Anim.more.js 中,可以看到以下代码片段
清单 1
this.from = { '-webkit-transform': 'rotate' + rotateProp + '(' + fromRotate + 'deg)' + (showTranslateZ ? ' translateZ(' + fromZ + 'px)': '') + fromTranslate, '-webkit-transform-origin': origin }; this.to = { '-webkit-transform': 'rotate' + rotateProp + '(' + toRotate + 'deg) translateZ(' + toZ + 'px)' + toTranslate, '-webkit-transform-origin': origin };
由于 Sencha Touch 的动画组件是基于 Webkit 核心的浏览器,所以其动画效果实际上是基于 Webkit 的 3D 转换引擎,代码中 this.from 指的是当前 Card 如何旋转消失的属性,而 this.to 指的是要目标的 Card 如何旋转得以呈现,具体 CSS 属性的含义可参考 官方文档 。
基于 Web 的博客浏览示例:应用开发环境的搭建、代码结构及测试
随时随地获取自己想要关注的信息是移动计算环境最直接的用途。本文将以一个简单的博客订阅与浏览程序为例,展示基于 Sencha Touch 进行移动 Web 应用开发的流程,帮助开发人员更快的熟悉该编程框架。博客订阅与浏览应用的主要功能是订阅自己关注的博客 RSS 源,浏览对应博客的主题列表,查看博文内容。为了实现以上需求,开发人员需要完成以下几个步骤的工作。
搭建开发环境
第一,下载 Sencha Touch 库文件 ,并将其解压到本地目录 %sencha-touch-home%;第二,进入 Eclipse Java EE IDE,创建一个名为 myblog 动态 Web 工程;第三,在本地安装 Apache Tomcat 6.0.x ,在 eclipse 中将其配置为 Web server 并将 myblog 工程部署其中以备测试;第四,安装 Andriod Virtul Machine 环境,用以启动一个虚拟设备来测试 Web 应用的效果。需要说明的是,对于静态的 Sencha Touch 工程,Tomcat 并不是必须的,可使用任意 HTTP Server 来部署应用,但由于本例中使用了 Servlet 解析 RSS 源来降低客户端负载,因此采用了 Servlet 容器 Tomcat。
创建代码结构
一个典型的 Sencha Touch 工程主要由几个部分组成:sencha-touch 库文件,JavaScript 文件,CSS 文件,图标文件以及静态 HTML 文件。sencha-touch 库文件至少要包含默认的 CSS 文件 sencha-touch.css 和默认的 JavaScript 文件 sencha-touch.js,值得一提的是,为了便于在开发调试阶段更准确地定位和解决问题,开发包中还包含了 CSS 和 JavaScript 对应的 debug 版本,开发人员可在开发阶段使用该版本,而在产品部署阶段再替换为对应的正式版。
创建 HTML 和 JavaScript 文件
创建 Sencha Touch 应用的第一步就是创建一个 HTML 首页文件用于链接 Sencha Touch库的 CSS 和 JavaScript 文件。我们博客浏览示例的 HTML 文件是 index.html,其内容如下:
清单 2
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>My BLOG</title> <link rel="stylesheet" href="sencha-touch/resources/css/sencha-touch.css" type="text/css"> <link rel="stylesheet" href="css/index.css" type="text/css"> <script type="text/javascript" src="sencha-touch/sencha-touch-debug.js"></script> <script type="text/javascript" src="js/index.js"></script> </head> <body></body> </html>
当创建好 HTML 文件之后,接下来就需要创建应用程序的 JavaScript 文件 index.js,由于该示例是以浏览为主,因此选用 NestedList 组件作为 UI 界面的主体,相关代码如下:
清单 3
Ext.setup({ icon : 'img/icon.png', tabletStartupScreen : 'img/tablet_startup.png', phoneStartupScreen : 'img/phone_startup.png', glossOnIcon : false, onReady : function() { ....... var nestedList = new Ext.NestedList({ fullscreen : true, title : '我的订阅博客', displayField : 'text', dockedItems : [ topbar, bottombar ], store : store, getDetailCard : function(record, parentRecord) { return new Ext.ux.DescBox({ value : 'Loading...', scroll : { direction : 'both', eventTarget : 'parent' } }); } }); ...... }); } });
可以看到,index.js 的第一行代码调用了 Ext.setup() 方法,用以建立一个触控设备的 Web 页面,该方法可以为你的应用设置不同的启动属性和行为,例如示例代码中的:
icon,设置该应用默认的图标;
tabletStartupScreen,该属性设置在平板电脑上的启动图标;
phoneStartupScreen,该属性设置在智能手机上的启动图标;
glossOnIcon,该属性设置是否在默认图标上呈现光环效果;
onReady,该方法会在页面加载完毕,浏览器中的 DOM 模型已经建立完成时被调用。由于为了保证程序在运行时所依赖的 JavaScript 文件都已经加载完毕,我们一般将应用启动的逻辑置于该方法内,类似于 Java 程序的 main 方法。
在定义 NestedList 组件时,有四点值得我们注意:
界面布局:通过 dockedItems 属性,指明了 NestedList 顶部和底部分别放置了工具栏 topbar 和 bottombar,topbar 主要用来便于用户登录和设置偏好信息,bottombar 主要是用来提供浏览博客时的一些常用操作,如订阅新的 RSS 源,删除选择的博客,刷新博客列表,给好的博文加星推荐以及回复功能。为了生成工具栏,需要生成一个 Ext.Toolbar 对象的实例,以 bottombar 为例,其代码如下:
清单 4
var bottombar = new Ext.Toolbar({ dock : 'bottom', defaults : { ui : 'plain', iconMask : true }, scroll : 'horizontal', sortable : true, layout : { pack : 'center' }, items : [ { iconCls : 'add', handler : function(btn, event) { addform.setCentered(true); addform.show(); } }, { iconCls : 'trash' }, { iconCls : 'refresh' }, { iconCls : 'favorites' }, { iconCls : 'action' } ] });
该对象中主要定义了以下属性:
dock,工具栏的放置位置,可选值有 top 和 bottom;
defaults,默认图标的 UI 效果,其中 ui 指背景颜色的样式,可选值有 dark,light 和 plain;
scroll,滚动方向,可选值有 horizon,vertical 和 both;
layout,表示工具栏图标的布局方式,示例中表示的是居中排列。值得注意的是该属性应该由一个 Object 对象来指定而不是 string;
items,该属性用于指定一个数组,来表示工具栏中的图标元素的集合,每个图标对象至少需要有一个 iconCls 属性来指定其样式,而 handler 属性则用于指定处理图标点击事件的方法,该方法回调时会传入两个参数 function(btn, event),第一个指当前被触发事件的对象,第二个指被触发的事件类型,本例中通过该方法弹出一个表单窗口用于提供给用户输入感兴趣的博客 RSS 订阅源。
图 7 RSS 订阅源添加表单
获取数据:从后台通过相关 API 获取数据并展示在 UI 组件上,是实现 Web 应用的核心问题,Sencha Touch 组件一般都是通过指定 store 数据源对象来实现的。例如在本例中,采用 Ext.data.TreeStore 对象来定义在 NestedList 中层次化数据的获取,其相关代码如下:
清单 5
Ext.regModel('ListItem', { idProperty : 'text', fields : [ { name : 'text', type : 'string' }, { name : 'link', type : 'string' }, { name : 'description', type : 'string' } ] }); var store = new Ext.data.TreeStore({ model : 'ListItem', proxy : { type : 'ajax', url : '/myblog/list', reader : { type : 'tree', root : 'items' } } });
首先,通过 model 属性来指明返回数据的模型,该模型是通过 Ext.regModel() 方法来建立的,主要是为了告诉程序返回数据是什么结构;其次,通过 proxy 属性来指明返回数据的获取方式,该框架中主要有两种 Proxy,Client proxy 和 Server proxy,Client Proxy 主要用于存储本地数据,其子类有三个:
LocalStorageProxy,在浏览器支持的情况下将数据保存至 localStorage;
SessionStorageProxy,在浏览器支持的情况下将数据保存至 sessionStorage;
MemoryProxy,将数据保存在内存中,但是当页面刷新时,数据都将会丢失。
Server proxy 主要用于存储一些通过远程请求服务器而获取的数据,它包括:
AjaxProxy,发送一个 HTTP 请求到相同域的服务器;
ScriptTagProxy,使用 JSON-P 发送请求到不同域的服务器。
本例中采用的是最为常用的 Ajax 方式通过请求 servlet URL(/myblog/list) 来获取 JSON 数据。
自定义组件:使用 NestedList 时,开发者要注意的是我们需要自己实现 getDetailCard() 方法,用于定义对叶子节点数据的查看 UI 组件。非常幸运的是,Sencha Touch 框架为我们提供了良好的扩展机制用于自定义组件,这为我们构建结构清晰、面向对象的 JavaScript 程序打下了基础,示例中展示了如何扩展出一个自定义组件,代码片段如下:
清单 6
ExtExt.ux.DescBox = Ext.extend(Ext.Component, { ... afterRender : function() { Ext.ux.DescBox.superclass.afterRender.apply(this, arguments); thisthis.description = this.getTargetEl().createChild({ tag : 'pre', html : this.value }); }, getValue : function() { return this.value; }, setValue : function(description) { this.value = description; if (this.rendered) { this.description.update(this.value); } } });
我们定义了一个博客内容描述信息展示组件 Ext.ux.DescBox,它继承自 Ext.Component 组件,并且自定义了 Get 和 Set 方法,同时重写了父类的 afterRender 方法,其中第一行的代码 Ext.ux.DescBox.superclass.afterRender.apply(this, arguments);必须调用,指的是将子类的参数应用到父类的构造方法中,类似于 Java 程序中的 super() 方法;第二行代码 this.description = this.getTargetEl().createChild(...)指在 Component 组件中创建一个 HTML 标签 pre, 并将 value 的值放置于 pre 标签中。
事件处理:人机交互和 UI 组件之间的切换,需要事件来驱动或触发,因此各 UI 组件都支持一系列特定的事件及其处理方法,可参阅 API 文档 。示例中需要依靠叶子节点触摸事件 leafitemtap 将显示界面切换为 Detail Card,就需要在 NestedList 组件上注册处理方法,其代码为:
清单 7
nestedList.on('leafitemtap', function(subList, subIdx, el, e, detailCard) { var ds = subList.getStore(), r = ds.getAt(subIdx); detailCard.setValue(r.get("description")); });
其回调函数的参数依次代表“触控条目(item)所在 List 组件”,“触控条目的 ID”,“触控条目 element 对象”,“触控事件对象”和“接下来要显示的 Detail Card 组件对象”。
部署到 Apache Tomcat 6.x 进行测试
编写好对应的 HTML、JavaScript 和后台处理的 Servlet 之后,可将动态 Web 工程打包成为标准的 WAR 包,部署至 Tomcat 的 webapp 文件夹,启动服务器;随后打开 Android Virtual Devices,启动其中的浏览器程序,并在 URL 地址栏输入 http://<localhost IP address>:8080/myblog/,便可以对该应用进行测试了,运行的画面如 图 8 所示。由于 Sencha Touch 应用的跨平台性,使用其他任意一款基于 Webkit 内核的浏览器,如 iPhone4 的 Safari,也能得到相关的测试结果,而不仅限于文中示例的 Android 设备浏览器。
图 8 应用运行及测试画面
开发与原生程序一样酷炫界面的 Web 移动应用,一直是 Web 开发者的梦想,Sencha Touch移动 Web 开发框架使得这一梦想不再遥远。该框架以其丰富的 UI 组件,个性化的动画效果,稳定的数据及事件处理机制,易扩展的编程模型,在移动 Web 应用这个新领域崭露头角。本文利用一个博客浏览程序简要介绍了利用该框架编写程序的流程及基本方法,然而距离一个成熟的 Web 应用还有相当的距离,如用户登录的安全性、多用户并发时后台的伸缩性以及客户端 JavaScript 的性能都有待提高,因此值得广大开发者深入的学习及实践。