Sencha Touch官网所给的例子还是很详尽的,只要把代码拷贝粘贴,稍微修改一下,就能用在自己的项目中了,或者仔细看官网给的例子,代码注释给的英文也不难,自学那些代码,完全不需要看什么视频和书籍。有学习过Extjs那就更好了,因为这两个产品都是Sencha公司的,代码风格几乎一样。
先看看Sencha CMD生成的项目中都有些什么。
.sencha : 自动生成的配置文件,不知是玩意,先不管。
app : 里面放着空的模型(model),视图(view)和存储(store)文件夹,再等着你编辑文件放进去呢!为什么会有这些文件夹,因为Sencha Touch是以MVC模式来进行开发的。
build : 编译后的文件存放在这个文件夹里面,我们编写完Sencha项目,可以编译成原生的Android项目或者IOS项目的,用Sencha CMD可以,用phonegap更好,这是后话了。
packages :包文件夹,放什么包的吧,我也不知道。
resources:这个比较重要,放css和图片资源的。
touch(sdk):Sencha Touch SDK的拷贝,不要修改这个文件夹里面的东西。
剩下的都不是文件夹了
app.js 主javascript入口
app.json 配置文件,用来创建你的应用的缩小版本
index.html 你的应用的HTML文件
packager.json 配置文件,用来打包成IOS和Android应用
bootstrap.js 流行的bootstrap框架应用文件
当我们访问这个项目时,打开的是根目录下的index.html文件,所以打开这个文件看一下里面的源代码:
<span style="font-size:24px;"><!DOCTYPE HTML> <html manifest="" lang="en-US"> <head> <meta charset="UTF-8"> <title>first</title> <style type="text/css"> /** * Example of an initial loading indicator. * It is recommended to keep this as minimal as possible to provide instant feedback * while other resources are still being loaded for the first time */ html, body { height: 100%; background-color: #1985D0 } #appLoadingIndicator { position: absolute; top: 50%; margin-top: -15px; text-align: center; width: 100%; height: 30px; -webkit-animation-name: appLoadingIndicator; -webkit-animation-duration: 0.5s; -webkit-animation-iteration-count: infinite; -webkit-animation-direction: linear; } #appLoadingIndicator > * { background-color: #FFFFFF; display: inline-block; height: 30px; -webkit-border-radius: 15px; margin: 0 5px; width: 30px; opacity: 0.8; } @-webkit-keyframes appLoadingIndicator{ 0% { opacity: 0.8 } 50% { opacity: 0 } 100% { opacity: 0.8 } } </style> <!-- The line below must be kept intact for Sencha Command to build your application --> <script id="microloader" type="text/javascript" src=".sencha/app/microloader/development.js"></script> </head> <body> <div id="appLoadingIndicator"> <div></div> <div></div> <div></div> </div> </body> </html></span>
非常简单,只有css样式代码和一个引用的js文件,这样就能丰富地表达我们的例子 ?
按住“ctrl”键,点击development.js。跳转到里面看看。这个文件居然在.sencha文件夹里面的。Javascript对于我来说不是擅长的语言,所以看这些代码开始一头雾水。源代码下面有很多设备的名称,看来是匹配这些设备,让体验更佳的。可是我们所看到的页面的那些文字在这里面一点都没有涉及,难道还有引用文件,仔细一看,不小心还看不到呢,最后看到Ajax技术的异步请求。
而在touch\microloader\development.js中,指向的却是
前者是CMD编译产生的,后者是自己编写项目用的。
进入bootstrap.json里面
Sencha需要引用的文件从这引用。主入口的js和css文件都提示出来了。进入app.js里面
这里面居然也没有界面展现的内容,直到Ext.Viewport.add(Ext.create('first.view.Main'));这一行。点击进入Main.js文件,这个文件在app文件夹里面的视图层view文件夹里面。打开
<span style="font-size:24px;">Ext.define('first.view.Main', { extend: 'Ext.tab.Panel', xtype: 'main', requires: [ 'Ext.TitleBar', 'Ext.Video' ], config: { tabBarPosition: 'bottom', items: [ { title: 'Welcome', iconCls: 'home', styleHtmlContent: true, scrollable: true, items: { docked: 'top', xtype: 'titlebar', title: 'Welcome to Sencha Touch 2' }, html: [ "You've just generated a new Sencha Touch 2 project. What you're looking at right now is the ", "contents of <a target='_blank' href=\"app/view/Main.js\">app/view/Main.js</a> - edit that file ", "and refresh to change what's rendered here." ].join("") }, { title: 'Get Started', iconCls: 'action', items: [ { docked: 'top', xtype: 'titlebar', title: 'Getting Started' }, { xtype: 'video', url: 'http://av.vimeo.com/64284/137/87347327.mp4?token=1330978144_f9b698fea38cd408d52a2393240c896c', posterUrl: 'http://b.vimeocdn.com/ts/261/062/261062119_640.jpg' } ] } ] } });</span>
到此一切都明朗了!
我们打开index页面,index加载的development.js,适配我们的设备,然后页面展现进度的过程同时,异步请求bootstrap.json中的js文件和CSS文件,加载Sencha的js文件和样式文件,加载进根目录下的app.js文件,触发first.view.Main,加载Main.js文件,从而真正展现Sencha的页面出来,Main.js里面的内容和我们看到的文字是一样的,所以稍微修改一些内容,就会展现不同的内容出来。
<span style="font-size:24px;">Ext.define('first.view.Main', { extend: 'Ext.tab.Panel', xtype: 'main', requires: [ 'Ext.TitleBar', 'Ext.Video' ], config: { tabBarPosition: 'bottom', items: [ { title: '欢迎', iconCls: 'home', styleHtmlContent: true, scrollable: true, items: { docked: 'top', xtype: 'titlebar', title: '欢迎来到 Sencha Touch' }, html: [ "这是个Sencha Touch页面" ].join("") }, { title: '视频', iconCls: 'action', items: [ { docked: 'top', xtype: 'titlebar', title: '请看视频' }, { xtype: 'video', url: 'http://av.vimeo.com/64284/137/87347327.mp4?token=1330978144_f9b698fea38cd408d52a2393240c896c', posterUrl: 'http://b.vimeocdn.com/ts/261/062/261062119_640.jpg' } ] } ] } });</span>
=================================================================================
先不管其他的东西,app.js文件是我们的入口,按照MVC模式来编写的,你仔细一看,还是能看到模型,控制,视图分开的风格。Main.js是我们的视图展示层,只不过需要单独引用而已。要学习Sencha,对app.js这个文件进行修改,我们就可以简单地学Sencha Touch了。
下面我们来学习并改一下demo中的源代码,以list为例子。
在手机上的表现是这样的:
源代码在touch-2.3.0\examples\list中。其中加入了一些个人理解的中文注释,英语还可以的可看官方的注释,写得很简单明白。
<span style="font-size:24px;">//<debug> Ext.Loader.setPath({ 'Ext': '../../src' }); //</debug> /** * This simple example shows the ability of the Ext.List component. * * In this example, it uses a grouped store to show group headers in the list. It also * includes an indicator so you can quickly swipe through each of the groups. On top of that * it has a disclosure button so you can disclose more information for a list item. */ //define the application //定义程序,入口 Ext.application({ //define the startupscreens for tablet and phone, as well as the icon //根据移动设备的分辨率来选择不同的图片 startupImage: { '320x460': 'resources/startup/Default.jpg', // Non-retina iPhone, iPod touch, and all Android devices '640x920': 'resources/startup/640x920.png', // Retina iPhone and iPod touch '640x1096': 'resources/startup/640x1096.png', // iPhone 5 and iPod touch (fifth generation) '768x1004': 'resources/startup/768x1004.png', // Non-retina iPad (first and second generation) in portrait orientation '748x1024': 'resources/startup/748x1024.png', // Non-retina iPad (first and second generation) in landscape orientation '1536x2008': 'resources/startup/1536x2008.png', // : Retina iPad (third generation) in portrait orientation '1496x2048': 'resources/startup/1496x2048.png' // : Retina iPad (third generation) in landscape orientation }, isIconPrecomposed: false, icon: { 57: 'resources/icons/icon.png', 72: 'resources/icons/icon@72.png', 114: 'resources/icons/icon@2x.png', 144: 'resources/icons/icon@144.png' }, //require any components/classes what we will use in our example //需要用到的组件 requires: [ 'Ext.MessageBox', 'Ext.data.Store', 'Ext.List', 'Ext.plugin.PullRefresh' ], /** * The launch method is called when the browser is ready, and the application can launch. * * Inside our launch method we create the list and show in in the viewport. We get the lists configuration * using the getListConfiguration method which we defined below. * * If the user is not on a phone, we wrap the list inside a panel which is centered on the page. */ //触发,英文上说得很浅显易懂 launch: function() { //get the configuration for the list //list界面对象 var listConfiguration = this.getListConfiguration(); //if the device is not a phone, we want to create a centered panel and put the list //into that //phone与其他设备是有区别的,源于iphone的分辨率比较特殊 if (!Ext.os.is.Phone) { //use Ext.Viewport.add to add a new component into the viewport Ext.Viewport.add({ //give it an xtype of panel xtype: 'panel', //give it a fixed witdh and height width: 350, height: 370, //make it centered centered: true, //make the component modal so there is a mask around the panel modal: true, //set hideOnMaskTap to false so the panel does not hide when you tap on the mask hideOnMaskTap: false, //give it a layout of fit so the list stretches to the size of this panel layout: 'fit', //insert the listConfiguration as an item into this panel items: [listConfiguration] }); } else { //if we are a phone, simply add the list as an item to the viewport Ext.Viewport.add(listConfiguration); } }, /** * Returns a configuration object to be used when adding the list to the viewport. */ //界面对象,返回到面板里面填充,从而有内容展现 getListConfiguration: function() { //create a store instance //存储器 var store = Ext.create('Ext.data.Store', { //give the store some fields fields: ['firstName', 'lastName'], //filter the data using the firstName field sorters: 'firstName', //autoload the data from the server autoLoad: true, //setup the grouping functionality to group by the first letter of the firstName field grouper: { groupFn: function(record) { return record.get('firstName')[0]; } }, //setup the proxy for the store to use an ajax proxy and give it a url to load //the local contacts.json file //异步加载数据,这里可以改编成与后台的controller交互,controller从数据库中获取数据 proxy: { type: 'ajax', url: 'contacts.json' } }); return { //give it an xtype of list for the list component xtype: 'list', id: 'list', // scrollable: { // indicators: false // }, //set the itemtpl to show the fields for the store //从store里面提取的数据怎么展现 itemTpl: '{firstName} {lastName}', //enable disclosure icons //disclosure: true, //group the list //分组 grouped: true, //enable the indexBar indexBar: true, infinite: true, useSimpleItems: true, variableHeights: true, striped: true, //ui主题 ui: 'round', //set the function when a user taps on a disclsoure icon // onItemDisclosure: function(record, item, index, e) { // //show a messagebox alert which shows the persons firstName // e.stopEvent(); // Ext.Msg.alert('Disclose', 'Disclose more info for ' + record.get('firstName')); // }, //bind the store to this list store: store }; } });</span>
看一下存储器store请求的异步数据,这是一个静态数据contacts.json文件,这里我们当然可以用各种后台技术把关系型数据库转换为json数据传到这里来,或者一些key-value数据库直接传过来,也可以经过过滤。
<span style="font-size:24px;">[ { "firstName": "Tommy", "lastName": "Maintz" }, { "firstName": "Ed", "lastName": "Spencer" }, { "firstName": "Jamie", "lastName": "Avins" }, { "firstName": "Aaron", "lastName": "Conran" }, { "firstName": "Dave", "lastName": "Kaneda" }, { "firstName": "Michael", "lastName": "Mullany" }, { "firstName": "Abraham", "lastName": "Elias" }, { "firstName": "Jay", "lastName": "Robinson" }, { "firstName": "Tommy", "lastName": "Maintz" }, { "firstName": "Ed", "lastName": "Spencer" }, { "firstName": "Jamie", "lastName": "Avins" }, { "firstName": "Aaron", "lastName": "Conran" }, { "firstName": "Ape", "lastName": "Evilias" }, { "firstName": "Dave", "lastName": "Kaneda" }, { "firstName": "Michael", "lastName": "Mullany" }, { "firstName": "Abraham", "lastName": "Elias" }, { "firstName": "Jay", "lastName": "Robinson" }, { "firstName": "Tommy", "lastName": "Maintz" }, { "firstName": "Ed", "lastName": "Spencer" }, { "firstName": "Jamie", "lastName": "Avins" }, { "firstName": "Aaron", "lastName": "Conran" }, { "firstName": "Dave", "lastName": "Kaneda" }, { "firstName": "Michael", "lastName": "Mullany" }, { "firstName": "Abraham", "lastName": "Elias" }, { "firstName": "Jay", "lastName": "Robinson" }, { "firstName": "Tommy", "lastName": "Maintz" }, { "firstName": "Ed", "lastName": "Spencer" }, { "firstName": "Jamie", "lastName": "Avins" }, { "firstName": "Aaron", "lastName": "Conran" }, { "firstName": "Dave", "lastName": "Kaneda" }, { "firstName": "Michael", "lastName": "Mullany" }, { "firstName": "Abraham", "lastName": "Elias" }, { "firstName": "Jay", "lastName": "Robinson" }, { "firstName": "Tommy", "lastName": "Maintz" }, { "firstName": "Ed", "lastName": "Spencer" }, { "firstName": "Jamie", "lastName": "Avins" }, { "firstName": "Aaron", "lastName": "Conran" }, { "firstName": "Dave", "lastName": "Kaneda" }, { "firstName": "Michael", "lastName": "Mullany" }, { "firstName": "Abraham", "lastName": "Elias" }, { "firstName": "Jay", "lastName": "Robinson" }, { "firstName": "Tommy", "lastName": "Maintz" }, { "firstName": "Ed", "lastName": "Spencer" }, { "firstName": "Jamie", "lastName": "Avins" }, { "firstName": "Aaron", "lastName": "Conran" }, { "firstName": "Ape", "lastName": "Evilias" }, { "firstName": "Dave", "lastName": "Kaneda" }, { "firstName": "Michael", "lastName": "Mullany" }, { "firstName": "Abraham", "lastName": "Elias" }, { "firstName": "Jay", "lastName": "Robinson" }, { "firstName": "Tommy", "lastName": "Maintz" }, { "firstName": "Ed", "lastName": "Spencer" }, { "firstName": "Jamie", "lastName": "Avins" }, { "firstName": "Aaron", "lastName": "Conran" }, { "firstName": "Dave", "lastName": "Kaneda" }, { "firstName": "Michael", "lastName": "Mullany" }, { "firstName": "Abraham", "lastName": "Elias" }, { "firstName": "Jay", "lastName": "Robinson" }, { "firstName": "Tommy", "lastName": "Maintz" }, { "firstName": "Ed", "lastName": "Spencer" }, { "firstName": "Jamie", "lastName": "Avins" }, { "firstName": "Aaron", "lastName": "Conran" }, { "firstName": "Dave", "lastName": "Kaneda" }, { "firstName": "Michael", "lastName": "Mullany" }, { "firstName": "Abraham", "lastName": "Elias" }, { "firstName": "Jay", "lastName": "Robinson" }, { "firstName": "Tommy", "lastName": "Maintz" }, { "firstName": "Ed", "lastName": "Spencer" }, { "firstName": "Jamie", "lastName": "Avins" }, { "firstName": "Aaron", "lastName": "Conran" }, { "firstName": "Dave", "lastName": "Kaneda" }, { "firstName": "Michael", "lastName": "Mullany" }, { "firstName": "Abraham", "lastName": "Elias" }, { "firstName": "Jay", "lastName": "Robinson" }, { "firstName": "Tommy", "lastName": "Maintz" }, { "firstName": "Ed", "lastName": "Spencer" }, { "firstName": "Jamie", "lastName": "Avins" }, { "firstName": "Aaron", "lastName": "Conran" }, { "firstName": "Ape", "lastName": "Evilias" }, { "firstName": "Dave", "lastName": "Kaneda" }, { "firstName": "Michael", "lastName": "Mullany" }, { "firstName": "Abraham", "lastName": "Elias" }, { "firstName": "Jay", "lastName": "Robinson" }, { "firstName": "Tommy", "lastName": "Maintz" }, { "firstName": "Ed", "lastName": "Spencer" }, { "firstName": "Jamie", "lastName": "Avins" }, { "firstName": "Aaron", "lastName": "Conran" }, { "firstName": "Dave", "lastName": "Kaneda" }, { "firstName": "Michael", "lastName": "Mullany" }, { "firstName": "Abraham", "lastName": "Elias" }, { "firstName": "Jay", "lastName": "Robinson" }, { "firstName": "Tommy", "lastName": "Maintz" }, { "firstName": "Ed", "lastName": "Spencer" }, { "firstName": "Jamie", "lastName": "Avins" }, { "firstName": "Aaron", "lastName": "Conran" }, { "firstName": "Dave", "lastName": "Kaneda" }, { "firstName": "Michael", "lastName": "Mullany" }, { "firstName": "Abraham", "lastName": "Elias" }, { "firstName": "Jay", "lastName": "Robinson" }, { "firstName": "Tommy", "lastName": "Maintz" }, { "firstName": "Ed", "lastName": "Spencer" }, { "firstName": "Jamie", "lastName": "Avins" }, { "firstName": "Aaron", "lastName": "Conran" }, { "firstName": "Dave", "lastName": "Kaneda" }, { "firstName": "Michael", "lastName": "Mullany" }, { "firstName": "Abraham", "lastName": "Elias" }, { "firstName": "Jay", "lastName": "Robinson" }, { "firstName": "Tommy", "lastName": "Maintz" }, { "firstName": "Ed", "lastName": "Spencer" }, { "firstName": "Jamie", "lastName": "Avins" }, { "firstName": "Aaron", "lastName": "Conran" }, { "firstName": "Ape", "lastName": "Evilias" }, { "firstName": "Dave", "lastName": "Kaneda" }, { "firstName": "Michael", "lastName": "Mullany" }, { "firstName": "Abraham", "lastName": "Elias" }, { "firstName": "Jay", "lastName": "Robinson" }, { "firstName": "Tommy", "lastName": "Maintz" }, { "firstName": "Ed", "lastName": "Spencer" }, { "firstName": "Jamie", "lastName": "Avins" }, { "firstName": "Aaron", "lastName": "Conran" }, { "firstName": "Dave", "lastName": "Kaneda" }, { "firstName": "Michael", "lastName": "Mullany" }, { "firstName": "Abraham", "lastName": "Elias" }, { "firstName": "Jay", "lastName": "Robinson" }, { "firstName": "Tommy", "lastName": "Maintz" }, { "firstName": "Ed", "lastName": "Spencer" }, { "firstName": "Jamie", "lastName": "Avins" }, { "firstName": "Aaron", "lastName": "Conran" }, { "firstName": "Dave", "lastName": "Kaneda" }, { "firstName": "Michael", "lastName": "Mullany" }, { "firstName": "Abraham", "lastName": "Elias" }, { "firstName": "Jay", "lastName": "Robinson" }, { "firstName": "Zed", "lastName": "Zacharias "} ]</span>
看到这里,事情就变得很简单了,按照这个例子稍微改一下就可以用了,至于各个参数的含义,还有其他的各种参数,要参考官网的API文档才可以。Sencha Touch在这一点不够Extjs好,Extjs可以离线看整个API文档,浏览速度非常快,可是Sencha Touch却要联网到它的官网上查看,我所在的这个地方浏览官网非常慢,不知是不是地区网络的问题,反正想看一下API文档非常困难,以后想办法找个离线的才好,即使低版本的也好!
我们改这个例子,成为中文的通讯录列表。
先修改contacts.json文件,这些都是静态数据,纯粹无脑机械编辑,这应该留给后台灵活返回数据显示出来的,不过刚入门,还是以静态数据为例子吧!把这个文件放到我们的项目的根目录下。
[ { "name": "李四", "age": 23, "address":"中山路1号" }, { "name": "王五", "age": 26, "address":"中山路12号" }, { "name": "王五1", "age": 34, "address":"中山路18号" }, { "name": "王五2", "age": 55, "address":"中山路156号" }, { "name": "王五3", "age": 34, "address":"解放路34号" }, { "name": "王五4", "age": 12, "address":"解放路45号" }, { "name": "王五5", "age": 89, "address":"解放路67号" }, { "name": "王五6", "age": 45, "address":"解放路93号" }, { "name": "赵六", "age": 67, "address":"解放路57号" }, { "name": "王7", "age": 23, "address":"白云路3号" }, { "name": "张三", "age": 67, "address":"昆明路78号" }, { "name": "赵霁", "age": 34, "address":"解放路98号" }, { "name": "周丽", "age": 56, "address":"解放路175号" }, { "name": "李贤", "age": 34, "address":"解放路876号" }, { "name": "孙琦", "age": 56, "address":"解放路32号" }, { "name": "黄连", "age": 28, "address":"解放路78号" }, { "name": "张丽", "age": 25, "address":"解放路45号" }, { "name": "里人", "age": 22, "address":"解放路89号" }, { "name": "方琪", "age": 23, "address":"解放路23号" }, { "name": "李燕", "age": 34, "address":"中山路4号" } ]
备份app.js文件,接着改app.js文件内容。不熟悉的话一个个敲,培养一下敲码的感觉。
虽然是照抄右边的,可是边敲边加深了理解。这里推荐用intellij这个IDE,因为不用安装插件就可以提示Sencha Touch函数、参数等信息了,非常智能!
改的app.js如下:
<span style="font-size:24px;">Ext.application({ startupImage: { '320x460': 'resources/startup/Default.jpg', '640x920': 'resources/startup/640x920.png', '640x1096': 'resources/startup/640x1096.png', '768x1004': 'resources/startup/768x1004.png', '748x1024': 'resources/startup/748x1024.png', '1536x2008': 'resources/startup/1536x2008.png', '1496x2048': 'resources/startup/1496x2048.png' }, isIconPrecomposed: false, icon: { 57: 'resources/icons/icon.png', 72: 'resources/icons/icon@72.png', 114: 'resources/icons/icon@2x.png', 144: 'resources/icons/icon@144.png' }, requires: [ 'Ext.MessageBox', 'Ext.data.Store', 'Ext.List', 'Ext.plugin.PullRefresh' ], launch : function() { var listConfiguration = this.getListConfiguration(); if (!Ext.os.is.Phone) { Ext.Viewport.add({ xtype: 'panel', width: 350, height: 370, centered: true, modal: true, hideOnMaskTap: false, layout: 'fit', items: [listConfiguration] }); } else { Ext.Viewport.add(listConfiguration); } }, getListConfiguration : function() { var store = Ext.create('Ext.data.Store',{ fields:['name','age','address'], sorters:'name', autoLoad:true, grouper:{ groupFn:function(record) { return record.get('name')[0]; } }, proxy:{ type:'ajax', url:'contacts.json' } }); return { xtype:'list', id:'list', itemTpl:'{name}{age}{address}', grouped:true, indexBar:true, infinite:true, useSimpleItems:true, variableHeights:true, striped:true, ui:'round', store:store } } });</span>
在电脑上显示如下:
在手机上显示如下: