Appcelerate-Titanium-智能手机应用开发-全-

Appcelerate Titanium 智能手机应用开发(全)

原文:zh.annas-archive.org/md5/3ab326f8c2ac147132b75ce202c43d5b

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

不久以前,创建一个移动应用程序并发布它是困难的、昂贵的,并且对于大多数开发者来说,在商业上是不切实际的。快进几年,iPhone 和 App Store 的推出,突然之间,任何能够用 Apple 的 Objective-C 语言编写代码,并且对 Mac 的配置文件证书有充分理解的人,都可以编写智能手机应用程序,并以极少的麻烦和非常少的官僚程序在全球范围内分发。在过去十年中,我们中的许多人已经从陈旧的基于 C 的语言中移开,并将我们的编程知识主要集中在 JavaScript 上,这个无处不在的小语言尽管存在许多缺点,但已经获得了动力,并且在网络内外都变得普遍。

在那之后不久,我们开始看到许多“替代”平台出现,这些平台承诺开发者能够无需在 Objective-C 或者在 Android 的情况下重新学习 Java,就能构建移动应用程序。其中之一是一个新出现的平台叫做 Titanium Mobile,它承诺只使用 JavaScript 就能构建原生应用程序,并且能够使这些应用程序跨平台(跨越 iOS 和 Android)。截至 2011 年 12 月,Appcelerator 的 Titanium Mobile 拥有超过 150 万活跃开发者,并在市场上发布了 30,000 个应用程序。它得到了 eBay 等主要玩家的支持,并推动了世界上一些最受欢迎的应用程序,包括 Wunderlist、eBay Mobile 和 GetGlue。它还支持 Blackberry 和移动网络。它甚至能够使用流行的引擎,如 OpenGL 和 Box2D,来构建跨平台游戏。它甚至拥有自己的移动市场,开发者可以在那里出售和分发他们的 Titanium 模块给整个社区。

在本书中,我们将涵盖使用 Titanium Mobile 构建移动应用程序的所有方面,从布局到地图和 GPS,一直到社交媒体集成以及访问设备的输入硬件,包括摄像头和麦克风。书中描述的每个“食谱”都是一个自包含的课程。你可以挑选你想要阅读和使用的区域,并将其作为参考。或者,你也可以按照章节顺序依次阅读每个食谱,从开始到结束构建一个小型应用程序。我们还将介绍如何使用自定义模块扩展应用程序,以及如何打包它们以便在 iTunes App Store 和 Android Marketplace 中分发和销售。

本书涵盖的内容

在第一章中,我们将通过了解布局的基础和创建控件来开始我们的 Titanium Mobile 之旅,然后转向标签界面、网页视图,以及如何添加和打开多个窗口。

在第二章,使用本地和远程数据源中,我们将构建一个迷你应用,使用 HTTP 请求从 Web 读取数据,并了解如何解析和迭代 XML 和 JSON 格式的数据。我们还将了解如何使用 SQLite 数据库和一些基本的 SQL 查询在本地存储和检索数据。

在第三章,集成 Google Maps 和 GPS中,我们将向您的应用中添加一个 MapView,并使用注释、地理编码和跟踪用户位置的事件与之交互。我们还将介绍添加路线和使用设备内置的指南针跟踪航向的基础知识。

在第四章,使用音频、视频和摄像头增强您的应用中,我们将了解如何使用 Titanium 与设备的媒体功能进行交互,包括摄像头、照片库和音频录制器。

在第五章,将您的应用与社交媒体和电子邮件连接中,我们将了解如何利用 Titanium 并将其与 Facebook、Twitter 以及移动设备的电子邮件功能集成。我们还将介绍设置 Facebook 应用,并简要介绍 OAuth 的世界。

在第六章,掌握事件和属性中,我们将简要介绍 Titanium 中属性的工作原理,以及如何在您的应用中获取和设置全局变量。我们还将解释事件监听器和处理程序的工作方式,以及如何从您的控件和应用程序中的任何位置触发事件。

在第七章,创建动画、变换以及理解拖放中,我们将向您展示如何创建动画,以及如何使用 Titanium 中的 2D 和 3D 矩阵来变换您的对象。我们还将演示如何使用内置的"toImage"功能进行拖放控制和截图。

在第八章,与原生手机应用和 API 交互中,我们将发现如何与原生设备 API 交互,例如设备的联系人日历。我们还将了解如何使用本地通知和后台服务。

在第九章,将您的应用与外部服务集成中,我们将深入了解 OAuth 和 HTTP 身份验证,并展示如何连接到外部 API,例如 Yahoo! YQL 和 Foursquare。我们还将介绍推送通知的设置和集成到 Titanium 应用中。

在第十章中,使用自定义模块扩展你的应用,我们将看到如何扩展 Titanium 的本地功能,并使用 Objective-C 和 Xcode 添加你自己的自定义、本地模块。我们将从开始到结束在 Xcode 中运行一个示例模块,使用 Bit.ly 服务创建短网址。

在第十一章中,平台差异、设备信息和怪癖,我们将探讨如何使用 Titanium 获取设备信息,包括重要功能,如打电话、检查内存和检查电池剩余分配。我们还将介绍屏幕方向以及如何在 iOS 和 Android 平台之间编写代码差异。

在第十二章中,准备你的应用以分发和发布以及如何发布,我们将看到如何准备和打包你的应用程序以供分发和销售到 iTunes App Store 和 Android 市场,以及如何使用配置文件和开发证书正确设置和配置你的应用。

你需要这本书的什么

你需要一个运行 Xcode(最新版本,可在developer.apple.com获取)的 Mac 和 Titanium Studio 软件(可在www.appcelerator.com获取)。你必须使用 Mac,因为所有说明都是基于它的(Unix),这是由于 iPhone 的原因。不推荐或以任何方式支持使用 PC 来开发苹果 iPhone。

这本书适合谁

这本书对于任何拥有一些 JavaScript 或 Web 开发知识并希望跃入为 iPhone 和 Android 构建原生应用的开发者来说都是必不可少的。不需要 Objective-C 或 Java 的知识。

术语

在这本书中,你将找到多种文本样式,用于区分不同类型的信息。以下是一些这些样式的示例及其含义的解释。

文本中的代码词汇如下所示:“首先,打开你的app.js文件,还有两个名为recipes.jsfavorites.js的 JavaScript 文件”

代码块如下设置:

//add an image to the left of the annotation
var leftImage = Titanium.UI.createImageView({
image: 'images/start.png',
width: 25,
height: 25
});
annotation.leftView = leftImage;
//add the start button
var startButton = 'images/startbutton.png';
annotation.rightButton = startButton;
mapview.addAnnotation(annotation);

任何命令行输入或输出都如下所示:

cd /<path to your android sdk>/tools

新术语重要词汇将以粗体显示。屏幕上显示的词汇,例如在菜单或对话框中,将以如下方式显示:“一旦登录,点击新建项目,创建新项目的详细信息窗口将出现。”

注意

警告或重要注意事项将以这样的框显示。

小贴士

小技巧和窍门如下所示。

读者反馈

我们始终欢迎读者的反馈。请告诉我们您对这本书的看法——您喜欢什么或可能不喜欢什么。读者反馈对我们开发您真正能从中获得最大收益的图书非常重要。

要发送一般反馈,只需发送一封电子邮件到<feedback@packtpub.com>,并在邮件主题中提及书名。

如果您有一本书需要我们出版,请通过www.packtpub.com上的建议书名表单或通过电子邮件<suggest@packtpub.com>发送给我们。

如果您在某个主题上具有专业知识,并且您有兴趣撰写或为图书做出贡献,请参阅我们关于www.packtpub.com/authors的作者指南。

客户支持

现在您已经是 Packt 图书的骄傲拥有者了,我们有一些事情可以帮助您从您的购买中获得最大收益。

下载示例代码

您可以从您在www.PacktPub.com的账户下载您购买的所有 Packt 图书的示例代码文件。如果您在其他地方购买了这本书,您可以访问www.PacktPub.com/support,注册以直接将文件通过电子邮件发送给您。

错误清单

尽管我们已经尽一切努力确保我们内容的准确性,但错误仍然会发生。如果您在我们的书中发现错误——可能是文本或代码中的错误——如果您能向我们报告这一点,我们将不胜感激。通过这样做,您可以节省其他读者的挫败感,并帮助我们改进此书的后续版本。如果您发现任何错误清单,请通过访问www.packtpub.com/support,选择您的书,点击错误提交表单链接,并输入您的错误详细信息来报告它们。一旦您的错误清单得到验证,您的提交将被接受,错误清单将被上传到我们的网站,或添加到该标题的错误清单部分。任何现有的错误清单都可以通过从www.packtpub.com/support中选择您的标题来查看。

盗版

互联网上版权材料的盗版是一个跨所有媒体的持续问题。在 Packt,我们非常重视我们版权和许可证的保护。如果您在互联网上发现我们作品的任何非法副本,无论形式如何,请立即提供位置地址或网站名称,以便我们可以追究补救措施。

请通过<copyright@packtpub.com>与我们联系,并提供涉嫌盗版材料的链接。

我们感谢您在保护我们的作者以及为我们提供有价值内容的能力方面的帮助。

问题

如果您在图书的任何方面遇到问题,可以通过<questions@packtpub.com>与我们联系,我们将尽力解决。

第一章. 使用原生 UI 组件构建应用

在本章中,我们将涵盖:

  • 使用窗口和视图构建

  • 将 TabGroup 添加到您的应用中

  • 创建和格式化标签

  • 创建用于用户输入的 TextField

  • 与键盘和键盘工具栏一起工作

  • 使用滑块和开关增强您的应用

  • 在窗口之间传递自定义变量

  • 创建按钮并捕获点击事件

  • 使用对话框和警报通知您的用户

  • 使用 Raphael JS 创建图表

简介

能够使用丰富、直观的控件创建用户友好的布局是成功应用设计的重要因素。在移动应用及其有限的屏幕空间中,这一点变得更加重要。Titanium 利用了 iPhone/iPod Touch 和 Android 平台中大量本机控件,允许开发者创建与本地语言开发者创建的应用一样功能丰富的应用。

这与移动网络相比如何?当涉及到仅使用 HTML/CSS 的移动应用时,精明的用户肯定能感觉到与 Titanium 这样的平台相比的差异,Titanium 允许您使用平台特定的约定并访问您 iPhone 或 Android 设备的最新和最强大的功能。用 Titanium 编写的应用感觉和操作就像原生应用一样,因为本质上所有的 UI 组件都是原生的。这意味着利用设备的全部功能和能力的清晰、响应式的 UI 组件。

到目前为止,大多数其他书籍都会开始解释 Titanium 的基本原理,也许会给你一个关于架构的概述,并扩展所需的语法。

呦……

我们不会这么做。相反,我们将直接进入有趣的部分,构建您的用户界面并制作一个真实世界的应用!在本章中,您将学习:

  • 如何使用WindowsViews构建应用,并理解两者之间的区别

  • 使用所有常见组件(包括TextFieldLabelsSwitches)组合 UI

  • Titanium 组件属性在格式化 UI 时与 CSS 的相似程度如何

如果您想的话,您可以从中选择任何菜谱,因为每个菜谱都是一个自包含的示例,将解释一个特定的组件或过程。或者,您也可以从本章的开始到结束逐章阅读,以构建一个用于计算贷款还款的真实世界应用,从现在起我们将称之为LoanCalc

注意

本章的完整源代码可以在/Chapter 1/LoanCalc文件夹中找到。

使用窗口和视图构建

我们将从所有 Titanium 应用的基石开始,即窗口和视图。在本菜谱结束时,您将了解如何实现窗口并向其中添加视图,以及理解两者之间的基本区别,这并不像乍一看那么明显。

如果你打算跟随整个章节并构建 LoanCalc 应用,那么请仔细关注本章的前几个步骤,因为你在书中每个后续应用中都需要再次执行这些步骤。

注意

我们假设你已经下载并安装了 Titanium Studio 以及 Apple XCode 与 iOS SDK 或 Google 的 Android SDK,或者两者都要。如果没有,你可以通过在线教程boydlee.com/titanium-appcelerator-cookbook/setup跟随安装过程。

准备工作

要跟随这个食谱,你需要安装 Titanium Studio。我们使用的是 1.0.7 版本,这是撰写时的最新版本。此外,你还需要安装 iOS SDK 与 XCode 或 Google Android SDK。除非在章节开头明确说明,否则我们所有的示例通常都适用于这两个平台。你还需要一个 IDE 来编写你的代码。任何 IDE,包括记事本、TextMate、Dashcode、Eclipse 等,都可以使用。然而,自 2011 年 6 月以来,Appcelerator 已经提供自己的 IDE,称为“Titanium Studio”,它基于 Aptana。Titanium Studio 允许开发者在一个单一的开发环境中构建、测试和部署 iOS、Android、Blackberry 和移动 Web 应用。本书中的所有食谱都基于假设你正在使用 Titanium Studio 产品,该产品可以从my.appcelerator.com/auth/signup/offer/community免费下载。

为了准备这个食谱,打开 Titanium Studio 并登录,如果你还没有这样做的话。如果你需要注册新账户,你可以在应用程序内部直接免费注册。登录后,点击文件 | 新建 | 新建 Titanium Mobile 项目,将出现创建新项目的详细信息窗口。在 LoanCalc 中输入应用的名称,并填写以下截图所示的其余详细信息。你也可以取消选择“iPad”选项,因为我们只为 iPhone 和 Android 平台构建我们的应用程序。

准备工作

注意

注意应用程序标识符,它通常以反向域名表示法(即,com.packtpub.loancalc)书写。在项目创建后,这个标识符不容易更改,你将在创建用于分发应用的配置文件时需要精确匹配它。

本食谱的完整源代码可以在/Chapter 1/Recipe 1文件夹中找到。

如何操作...

首先,在 Titanium Studio 中打开 app.js 文件。如果这是一个新项目,默认情况下 Titanium Studio 会创建一个包含几个 TabGroup 内部的示例应用,这当然很有用,但我们在后面的菜谱中会介绍 TabGroup,所以请继续删除所有生成的代码。现在让我们创建一个 Window 对象,我们将向其中添加一个 View 对象。这个 View 对象将包含我们所有的控件,例如 TextFieldsLabels

除了创建我们的基本 WindowView,我们还将创建一个 ImageView 组件来显示我们的应用标志,然后再将其添加到我们的 View 中(你可以从章节的源代码中获取我们使用的图片)。

最后,我们将在 Window 上调用 open() 方法来启动它:

//create the window
var win1 = Titanium.UI.createWindow({
width: 320,
height: 480,
top: 0,
left: 0,
backgroundImage: 'background.png'
});
//create the view, this will hold all of our UI controls
//note the height of this view is the height of the window //minus 40px for the status bar and padding
var view = Titanium.UI.createView({
width: 300,
height: win1.height - 40,
left: 10,
top: 10,
backgroundColor: '#fff',
borderRadius: 5
});
//we will give the logo a left margin so it centers neatly //within our view
var _logoMarginLeft = (view.width - 253) / 2;
//now let's add our logo to an imageview and add that to our //view object
var logo = Titanium.UI.createImageView({
image: 'logo.png',
width: 253,
height: 96,
left: _logoMarginLeft,
top: 0
});
view.add(logo);
//add the view to our window
win1.add(view);
//finally, open the window to launch the app
win1.open();

小贴士

下载示例代码

你可以从你购买的所有 Packt 书籍的账户中下载示例代码文件。www.PacktPub.com。如果你在其他地方购买了这本书,你可以访问 www.PacktPub.com/support 并注册以直接将文件通过电子邮件发送给你。

如何做...

它是如何工作的...

首先,重要的是要解释 WindowsViews 之间的区别,因为有一些基本区别可能会影响你决定使用其中一个而不是另一个。与 Views 不同,Windows 有一些额外的功能,包括 open()close() 方法。如果你来自桌面开发背景,你可以想象 Window 是表单或屏幕的等价物。如果你更喜欢网络类比,那么 Window 更像是一个页面,而 Views 更像是一个 Div。除了这些方法之外,Windows 还有一些显示属性,如 fullscreenmodal,这些属性在 Views 中不可用。你还会注意到,在创建新对象时,create 关键字会弹出,即 Titanium.UI.createView()。这种命名约定在 Titanium API 中是一致的,几乎所有组件都是以这种方式实例化的。

可以将 Windows 和 Views 视为你的 Titanium 应用的构建块。所有的 UI 组件都添加到 Window 或其子对象 View 中。这两个对象都有许多格式化选项,其属性和语法对以前使用过 CSS 的人来说非常熟悉。字体、颜色、边框宽度、边框半径、宽度、高度、顶部和左侧都是与你在 CSS 中期望的完全相同的属性,它们适用于 Window 和几乎所有 View。

小贴士

重要的是要注意,你的应用至少需要一个 Window 才能运行,并且这个 Window 必须在入口点(即 app.js 文件)中调用。

您可能也注意到,我们有时使用 Titanium.UI.createXXX 实例化对象或调用方法,而在其他时候使用 Ti.UI.createXXX。使用 "Ti" 只是一个简写命名空间,旨在节省您在编码中的时间,并且将以与完整 "Titanium" 命名空间完全相同的方式执行您的代码。

将 TabGroup 添加到您的应用程序中

TabGroups 是最常用的 UI 元素之一,构成了市场上许多 iPhone 和 Android 应用程序的布局基础。TabGroup 由一组分区的标签组成,每个标签包含一个单独的窗口,该窗口反过来包含一个导航栏和标题。在 iPhone 上,这些标签出现在屏幕底部的水平列表中。在 Android 设备上,默认情况下,它们以“颠倒”的标签形式出现在屏幕顶部,如下一张截图所示:

将 TabGroup 添加到您的应用程序中

准备工作

本食谱的完整源代码可在 /Chapter 1/Recipe 2 文件夹中找到。

如何操作...

我们将创建两个独立的 Window——其中一个将在内联定义,另一个 Window 将从名为 window2.js 的外部 JavaScript 文件中加载。在编写任何代码之前,创建一个名为 window2.js 的新 JavaScript 文件,并将其保存到您的资源目录中——与您的 app.js 当前所在的同一文件夹。

如果您到目前为止一直在跟随 LoanCalc 应用程序,那么请删除我们创建的当前代码,并用下面的源代码替换它:

//create tab group
var tabGroup = Ti.UI.createTabGroup();
//create the window
var win1 = Titanium.UI.createWindow({
width: 320,
height: 480,
top: 0,
left: 0,
backgroundImage: 'background.png',
title: 'Loan Calculator',
barImage: 'navbar.png'
});
//create the view, this will hold all of our UI controls
//note the height of this view is the height of the window //minus 134px for the status bar and padding and adjusted for //navbar
var view = Titanium.UI.createView({
width: 300,
height: win1.height - 134,
left: 10,
top: 10,
backgroundColor: '#fff',
borderRadius: 5
});
//we will give the logo a left margin so it centers neatly //within our view
var _logoMarginLeft = (view.width - 253) / 2;
//now let's add our logo to an imageview and add that to our //view object
var logo = Titanium.UI.createImageView({
image: 'logo.png',
width: 253,
height: 96,
left: _logoMarginLeft,
top: 0
});
view.add(logo);
//add the view to our window
win1.add(view);
//add the first tab and attach our window object (win1) to it
var tab1 = Ti.UI.createTab({
icon:'icon_calculator.png',
title:'Calculate',
window: win1
});
//create the second window for settings tab
var win2 = Titanium.UI.createWindow({
width: 320,
height: 480,
top: 0,
left: 0,
backgroundImage: 'background.png',
url: 'window2.js',
title: 'Settings',
barImage: 'navbar.png'
});
//add the second tab and attach our external window object //(win2 / window2.js) to it
var tab2 = Ti.UI.createTab({
icon:'icon_settings.png',
title:'Settings',
window: win2
});
//now add the tabs to our tabGroup object
tabGroup.addTab(tab1);
tabGroup.addTab(tab2);
//finally, open the tabgroup to launch the app
tabGroup.open();

它是如何工作的...

从逻辑上讲,重要的是要意识到,当使用时,TabGroup 是应用程序的根,不能从任何其他 UI 组件中包含。TabGroup 中的每个 Tab 实质上是一个单个 Window 的包装器,该 Window 可以通过内联定义或通过使用 url 属性提供外部 JavaScript 文件的地址来定义。这些 Window 只在相应的 Tab 首次获得焦点时加载,通常是通过用户点击 Tab 图标来获得该特定 Window 的焦点。

Tab 图标是从图像文件加载的,通常是 PNG 格式,但需要注意的是,在 Android 和 iPhone 上,所有图标都将以灰度形式带有 alpha 透明度渲染——当您运行应用程序时,任何颜色信息都将被丢弃。

还有更多...

苹果在您的应用程序中使用图标时可能特别挑剔。每当苹果定义了一个标准图标(如 设置 中的齿轮图标)时,您应该使用相同的图标。

一套额外的 200 个免费标签栏图标可在:glyphish.com 获取。

创建和格式化标签

不论是为了在屏幕上呈现文本内容、标识输入字段还是在 TableRow 中显示数据,标签都是您在使用 Titanium 时会经常使用的基础 UI 元素之一。通过它们,您将向用户展示大部分信息,因此了解如何正确创建和格式化它们非常重要。

在这个菜谱中,我们将创建三个不同的标签,每个标签对应于我们稍后将要添加到我们的应用程序中的每个输入组件。通过这些示例,我们将解释如何定位您的标签,为其提供文本值,并对其进行格式化。

准备工作

本菜谱的完整源代码可以在 /Chapter 1/Recipe 3 文件夹中找到。

如何做到这一点...

打开您的 app.js 文件,并首先在代码文件顶部放置以下两个变量,直接位于 TabGroup 创建声明之下。这些将是我们的应用程序中利率和贷款期限的默认值:

//application variables
var numberMonths = 36; //loan length
var interestRate = 6.0; //interest rate

让我们创建标签来标识我们稍后将要实现的输入字段。在您的 app.js 文件中输入以下源代码。如果您正在跟随 LoanCalc 示例应用程序,则此代码应在上一菜谱中添加到 View 的 ImageView logo 之后:

//create a label to identify the textfield to the user
var labelAmount = Titanium.UI.createLabel({
width: 'auto',
height: 30,
top: 100,
left: 20,
font: {fontSize: 14, fontFamily: 'Helvetica',
fontWeight:'bold'},},
text: 'Loan amount: $'
});
view.add(labelAmount);
//create a label to identify the textfield to the user
var labelInterestRate = Titanium.UI.createLabel({
width: 'auto',
height: 30,
top: 150,
left: 20,
font: {fontSize: 14, fontFamily: 'Helvetica',
fontWeight: 'bold'},
text: 'Interest Rate: %'
});
view.add(labelInterestRate);
//create a label to identify the textfield to the user
var labelLoanLength = Titanium.UI.createLabel({
width: 100,
height: 'auto',
top: 200,
left: 20,
font: {fontSize: 14, fontFamily: 'Helvetica',
fontWeight: 'bold'},
text: 'Loan length (' + numberMonths + ' months):'
});
view.add(labelLoanLength);

它是如何工作的...

到现在为止,您应该已经注意到了 Titanium 实例化对象并将其添加到 Views/Windows 的方式,以及将格式应用于大多数基本 UI 元素的方式趋势,使用 JavaScript 对象属性。边距和填充使用 topleft 的绝对定位值添加,而字体样式使用标准的 CSS 字体属性完成;在我们的示例代码中是 fontSize, fontFamilyfontWeight

需要注意的几个重要点:

  • 我们前两个标签的 width 属性设置为 auto,这意味着 Titanium 将根据标签内部的内容(在这种情况下是一个字符串值)自动计算标签的宽度。这个 auto 属性也可以用于许多其他 UI 元素的宽度和高度(正如我们在创建的第三个标签中可以看到的,它具有动态高度以匹配标签的文本)。如果没有指定高度或宽度属性,UI 组件将假设其父视图或窗口包围的确切尺寸。

  • 标签的 textAlign 属性在 HTML 中的工作方式与您预期的相同。然而,只有当您的标签宽度未设置为 auto 时,您才会注意到文本的对齐,除非该标签恰好跨越多行。

它是如何工作的...

创建用于用户输入的文本字段

Titanium 中的 TextField 是单行文本框,用于通过键盘捕获用户输入,通常构成任何应用程序中用户输入的最常见 UI 元素,与 Labels 和 Buttons 一起。在本食谱中,我们将向您展示如何创建 TextField,将其添加到应用程序的视图中,并使用它来捕获用户输入。我们将通过使用常量值来首次样式化我们的 TextField 组件。

准备工作

本食谱的完整源代码可以在 /Chapter 1/Recipe 4 文件夹中找到。

如何操作…

在创建视图但在我们将其添加到窗口之前,输入以下代码。如果您一直跟随前面的食谱,则此代码应在创建标签之后输入:

//creating the textfield for our loan amount input
var tfAmount = Titanium.UI.createTextField({ width: 140,
height: 30,
top: 100,
right: 20,
borderStyle:Titanium.UI.INPUT_BORDERSTYLE_ROUNDED,
returnKeyType:Titanium.UI.RETURNKEY_DONE,
hintText: '1000.00'
});
view.add(tfAmount);
//creating the textfield for our percentage interest
//rate input
var tfInterestRate = Titanium.UI.createTextField({
width: 140,
height: 30,
top: 150,
right: 20,
borderStyle:Titanium.UI.INPUT_BORDERSTYLE_ROUNDED,
returnKeyType:Titanium.UI.RETURNKEY_DONE,
value: interestRate
});
view.add(tfInterestRate);

工作原理…

在本例中,我们创建了一些具有圆角边框的基本 TextField,并介绍了一些在 Labels 和 ImageViews 中没有出现的新属性类型,包括 hintTexthintText 属性会在 TextField 获得焦点时显示一个值(例如,当用户轻触它以使用键盘输入数据时,该值会消失)。

用户输入可通过 TextField 的 value 属性获取。正如您在前一个食谱中注意到的,访问此值只需将其分配给一个变量(即 var myName = txtFirstName.value),或者直接使用值属性。

还有更多…

TextField 是任何应用程序中最常见的组件之一,在 Titanium 中,使用它们时需要考虑一些要点和选项。

获取文本…

重要的是要注意,当您想要检索用户在 TextField 中输入的文本时,您需要引用 value 属性,而不是 text,就像许多其他基于字符串的控制一样!

尝试使用其他 TextField 边框样式…

尝试使用其他 TextField 边框样式来为您的应用程序提供不同的外观。其他可能的值包括:

Titanium.UI.INPUT_BORDERSTYLE_BEZEL
Titanium.UI.INPUT_BORDERSTYLE_LINE
Titanium.UI.INPUT_BORDERSTYLE_NONE
Titanium.UI.INPUT_BORDERSTYLE_ROUNDED

与键盘和键盘工具栏一起工作

当 TextField 或 TextArea 控件在 iPhone 或 Android 上获得焦点时,默认键盘会出现在屏幕上。然而,有时您可能希望改变这种行为,例如,您可能只想让用户在提供数值(如年龄或货币价值)时将数字字符输入到 TextField 中。此外,可以创建键盘工具栏,使其出现在键盘上方,这将允许您提供用户其他选项,例如通过简单的按钮点击从窗口中移除键盘,或允许通过简单的按钮点击进行复制/粘贴操作。

在以下示例中,我们将创建一个包含系统按钮和另一个称为FlexibleSpace的系统组件的工具栏。这些将被添加到我们的数字键盘顶部,当金额或利率的 TextField 获得焦点时将显示。请注意,在这个例子中,我们已经更新了tfAmounttfInterestRateTextField 对象,现在它们包含keyboardTypereturnKeyType属性。

准备工作

注意,工具栏是 iPhone 特有的,并且当前 Titanium SDK 中可能不可用于 Android。

注意

本示例的完整源代码可以在/Chapter 1/Recipe 5文件夹中找到。

如何做到这一点...

打开你的app.js文件,并输入以下代码。如果你一直在跟随之前的示例,此代码应替换之前示例中用于添加金额和利率 TextField 的代码:

//flexible space for button bars
var flexSpace = Titanium.UI.createButton({
systemButton:Titanium.UI.iPhone.SystemButton.FLEXIBLE_SPACE
});
//done system button
var buttonDone = Titanium.UI.createButton({
systemButton:Titanium.UI.iPhone.SystemButton.DONE,
bottom: 0
});
//add the event listener 'click' event to our done button
buttonDone.addEventListener('click', function(e){
tfAmount.blur();
tfInterestRate.blur();
tfInterestRate.top = 150;
labelInterestRate.top = 150;
interestRate = tfInterestRate.value;
tfAmount.visible = true;
labelAmount.visible = true;
});
//creating the textfield for our loan amount input
var tfAmount = Titanium.UI.createTextField({
width: 140,
height: 30,
top: 100,
right: 20,
borderStyle:Titanium.UI.INPUT_BORDERSTYLE_ROUNDED,
returnKeyType:Titanium.UI.RETURNKEY_DONE,
hintText: '1000.00',
keyboardToolbar: [flexSpace,buttonDone],
keyboardType:Titanium.UI.KEYBOARD_PHONE_PAD
});
view.add(tfAmount);
//creating the textfield for our percentage interest rate //input
var tfInterestRate = Titanium.UI.createTextField({
width: 140,
height: 30,
top: 150,
right: 20,
borderStyle:Titanium.UI.INPUT_BORDERSTYLE_ROUNDED,
returnKeyType:Titanium.UI.RETURNKEY_DONE,
value: interestRate,
keyboardToolbar: [flexSpace,buttonDone],
keyboardType:Titanium.UI.KEYBOARD_PHONE_PAD
});
//if the interest rate is focused change its top value so we //can see it (only for the iphone platform though!)
tfInterestRate.addEventListener('focus', function(e){
if(Ti.Platform.osname == 'iphone') {
tfInterestRate.top = 100;
labelInterestRate.top = 100;
tfAmount.visible = false;
labelAmount.visible = false;
}
});
view.add(tfInterestRate);

它是如何工作的...

在这个示例中,我们创建了一个 TextField 并将其添加到我们的视图中。到目前为止,你应该已经注意到不同 UI 组件之间有多少通用属性;widthheighttopright只是四个在我们之前示例中用于其他组件的tfAmountTextField 中使用的属性。许多触摸屏手机没有物理键盘;相反,我们使用触摸屏键盘来收集我们的输入数据。根据你需要的数据,你可能不需要带有所有 QWERTY 键的全键盘,而只想显示一个数字键盘(如以下截图所示);例如,当你使用 iPhone 或 Android 设备上的电话拨号功能时。此外,你可能需要 QWERTY 键,但以特定的格式;自定义键盘通过提供自定义选项(如输入网址和电子邮件的键盘,其中包含所有'www'和'@'符号,并位于方便的触摸位置)来使用户输入更快,减少用户的挫败感。

如何工作...

还有更多...

尝试在你的 Titanium 应用中实验其他键盘样式!

实验键盘样式

其他可能的值包括:

Titanium.UI.KEYBOARD_DEFAULT
Titanium.UI.KEYBOARD_EMAIL
Titanium.UI.KEYBOARD_ASCII
Titanium.UI.KEYBOARD_URL
Titanium.UI.KEYBOARD_NUMBER_PAD
Titanium.UI.KEYBOARD_NUMBERS_PUNCTUATION
Titanium.UI.KEYBOARD_PHONE_PAD

使用滑块和开关增强你的应用

滑块和开关是两种易于实现的 UI 组件,可以为你的应用带来额外的交互性。正如其名所示,开关只有两种状态:开启和关闭,分别由布尔值(truefalse)表示。

相反,滑块接受两个浮点值,一个最小值和一个最大值,并允许用户选择这两个值之间的任何数字(包括这两个值)。除了默认样式外,滑块 API 还允许你使用图像作为'轨道'和沿其运行的'滑块拇指'图像的两侧。这允许你创建一些真正定制的样式。

我们将添加一个开关来指示开启/关闭状态,并添加一个滑动条来表示贷款期限,其值从 6 个月的最小值到 72 个月的最大值。此外,我们还将添加一些事件处理程序来捕获每个组件的更改值,在滑动条的情况下,更新现有的标签以显示新的滑动条值。如果您对事件处理程序的工作方式还不是百分之百确定,请不要担心,我们将在第六章中进一步详细解释,

准备工作

本示例的完整源代码可以在/Chapter 1/Recipe 6文件夹中找到。

如何操作...

如果您正在跟随 LoanCalc 应用程序,下面的代码应放置在您的window2.js文件中,用于开关。我们还将添加一个标签来标识开关组件的功能,以及一个视图组件来组合所有这些:

//reference the current window var win1 = Titanium.UI.currentWindow;
//create the view, this will hold all of our UI controls
var view = Titanium.UI.createView({
width: 300,
height: 70,
left: 10,
top: 10,
backgroundColor: '#fff',
borderRadius: 5
});
//create a label to identify the switch control to the user
var labelSwitch = Titanium.UI.createLabel({
width: 'auto',
height: 30,
top: 20,
left: 20,
font: {fontSize: 14, fontFamily: 'Helvetica',
fontWeight: 'bold'},
text: 'Auto Show Chart?'
});
view.add(labelSwitch);
//create the switch object
var switchChartOption = Titanium.UI.createSwitch({
right: 20,
top: 20,
value: false
});
view.add(switchChartOption);
win1.add(view);

现在,让我们编写滑动条代码。回到您的app.js文件,并在view.add(tfInterestRate);行下面输入以下代码:

//create the slider to change the loan length
var lengthSlider = Titanium.UI.createSlider({
width: 140,
top: 200,
right: 20,
min: 12,
max: 60,
value: numberMonths,
thumbImage: 'sliderThumb.png',
selectedThumbImage: 'sliderThumbSelected.png',
highlightedThumbImage: 'sliderThumbSelected.png'
});
lengthSlider.addEventListener('change', function(e){
//output the value to the console for debug
Ti.API.info(lengthSlider.value);
//update our numberMonths variable
numberMonths = Math.round(lengthSlider.value);
//update label
labelLoanLength.text = 'Loan length (' + Math.round(numberMonths) + ' months):';
});
view.add(lengthSlider);

工作原理...

在这个示例中,我们在两个不同的窗口中的两个不同的视图中添加了两个新的组件。第一个组件是一个开关(Switch),操作相当直接,除了标准的布局和定位属性外,它只需要一个主要的布尔值来确定其开启或关闭状态。它也只有一个事件,即change事件,该事件在开关从开启状态变为关闭状态或反之亦然时执行。

在 Android 平台上,开关可以被修改为显示为切换按钮(默认)或复选框。此外,Android 用户还可以使用title属性显示文本标签,该属性可以通过titleOfftitleOn属性编程更改。

滑动条组件比开关更有趣,并且具有更多属性。在需要允许用户在一系列值之间进行选择的情况下,滑动条非常有用,在我们的例子中,是一个从 12 到 60 个月的数值范围。例如,这种方法比在选择器中列出所有可能的选项更有效,也比让用户通过文本字段或文本区域组件输入可能无效的值更安全。

几乎所有的滑动条都可以使用 Titanium API 中提供的默认属性进行样式化,包括我们在本示例中使用的thumbImage, selectedThumbImagehighlightedThumbImagehighlightedThumbImage的工作方式与您可能习惯的 CSS 类似。在这种情况下,缩略图的图像只有在用户点击并保持在该组件上以更改其值时才会改变。

工作原理...

还有更多...

尝试通过使用图像来扩展滑动条组件的样式,这些图像用于滑动条的左右两侧,滑动条是位于移动开关下方水平运行的元素。

在窗口之间传递自定义变量

你经常会发现需要在你的应用中不同屏幕对象之间传递变量和对象,例如 Windows。一个例子是在主视图和子视图之间。例如,如果你有一个表格数据列表,每行可能只显示少量信息,而你希望查看完整描述,你可能将描述数据作为变量传递到子窗口。

在这个菜谱中,我们将应用同样的原则到设置窗口中的一个变量(在我们的 LoanCalc 应用的第二个标签页中),通过在一个窗口中设置变量,然后将其传递回主窗口使用。

准备工作

这个菜谱的完整源代码可以在/Chapter 1/Recipe 7文件夹中找到。

如何操作...

在你的app.js文件中为你的第二个窗口(win2)声明下,添加以下额外的属性名为autoShowChart并将其设置为false。这是一个自定义属性,也就是说,一个不是由 Titanium API 预先定义的属性。如果你需要 API 默认不提供的某些参数,添加额外的属性到你的对象中通常很有用:

//
//////set the initial value of win2's custom property
win2.autoShowChart = false;

现在在包含你第二个窗口所有子组件的window2.js文件中,添加以下代码,扩展现有的 Switch 控件,以便它可以更新引用窗口的autoShowChart变量:

//create the switch object
var switchChartOption = Titanium.UI.createSwitch({
right: 20,
top: 20,
value: false
});
//add the event listener for the switch when it changes
switchChartOption.addEventListener('change', function(e){
win2.autoShowChart = switchChartOption.value;
});
//add the switch to the view
view.add(switchChartOption);

工作原理…

这段代码实际上相当简单。在 Titanium 中创建对象时,所有标准属性都可以在键值对字典对象中访问。我们在这里所做的只是扩展这个字典对象,添加我们自己的属性。

我们可以通过两种方式之一来做这件事。首先,正如我们的菜谱源代码所示,这可以在 Window 对象(win2)实例化之后完成。其次,它也可以在实例化代码中立即完成。在第二个窗口的源代码中,我们只是引用了这个相同的对象,因此它的所有属性都已经可供我们读取和写入。

还有更多...

在 Windows 之间传递和访问对象和变量的方法有很多,包括使用 App Properties。这些内容将在后面的章节中介绍。

创建按钮并捕获点击事件

在任何给定的应用中,你会发现创建按钮并捕获它们的点击事件是你最常做的任务之一。这个菜谱将展示如何在 Titanium 中声明一个按钮控件并将其点击事件附加到它上。在这个click事件中,我们将执行一个任务并将其记录到 Titanium Studio 的 Info 窗口中。

这个菜谱还将演示如何通过 API 实现一些默认的样式机制。

准备工作

这个菜谱的完整源代码可以在/Chapter 1/Recipe 8文件夹中找到。

如何操作...

打开你的app.js文件并输入以下代码。如果你正在跟随 LoanCalc 应用,这段代码应该在创建并添加 TextField 控件到视图之后执行:

//calculate the interest for this loan button
var buttonCalculateInterest = Titanium.UI.createButton({
image: 'calculateInterestButton.png',
id: 1,
top: 255,
width: 252,
height: 32,
left: 23
});
//add the event listener
buttonCalculateInterest.addEventListener('click', calculateAndDisplayValue);
//add the first button to our view
view.add(buttonCalculateInterest);
//calculate the interest for this loan button
var buttonCalculateRepayments = Titanium.UI.createButton({
image: 'calculateRepaymentsButton.png',
id: 2,
top: 300,
width: 252,
height: 32,
left: 23
});
//add the event listener
buttonCalculateRepayments.addEventListener('click',
calculateAndDisplayValue);
//add the second and final button to our view
view.add(buttonCalculateRepayments);

现在我们已经创建了两个按钮并添加了它们的事件监听器,让我们扩展 calculateAndDisplayValue() 函数以执行一些简单的固定利率数学运算,并生成我们将记录到 Titanium Studio 控制台的结果:

//add the event handler which will be executed when either of //our calculation buttons are tapped
function calculateAndDisplayValue(e)
{
//log the button id so we can debug which button was tapped
Ti.API.info('Button id = ' + e.source.id);
if (e.source.id == 1)
{
//Interest (I) = Principal (P) times Rate Per Period
//(r) times Number of Periods (n) / 12
var totalInterest = (tfAmount.value * (interestRate /
100) * numberMonths) / 12;
//log result to console
Ti.API.info(totalInterest);
}
else
{
//Interest (I) = Principal (P) times Rate Per Period (r)
//times Number of Periods (n) / 12
var totalInterest = (tfAmount.value * (interestRate /
100) * numberMonths) / 12;
var totalRepayments = Math.round(tfAmount.value) +
totalInterest;
//log result to console
Ti.API.info(totalRepayments);
}
} //end function

它是如何工作的...

大多数 Titanium 控件都能够触发一个或多个事件,例如 focus, onload,或者在我们的食谱中,click。无疑,click 事件将是您比其他任何事件都更频繁使用的事件。在上面的源代码中,您会注意到,为了从该事件执行代码,我们正在向我们的按钮添加一个事件监听器,其签名为 'click'。这个签名是一个字符串,构成了我们事件监听器的第一部分,第二部分是事件执行的函数。

需要注意的是,也可以以类似的方式使用其他组件类型。例如,可以声明一个 ImageView,其中包含自定义按钮图像,并且可以将其点击事件附加到它,就像常规按钮一样。

如何工作...

使用对话框和警报通知您的用户

Titanium API 中提供了许多可供使用的对话框,但为了本食谱的目的,我们将专注于两个主要的对话框——AlertDialogOptionDialog。这两个简单的组件执行两个类似的角色,但有一个关键的区别。AlertDialog 通常仅用于向用户显示消息,而 OptionDialog 则显示消息并要求用户从多个按钮中选择响应。通常,AlertDialog 只允许用户从两个标准响应中选择,即确定或取消,而 OptionDialog 可以包含更多。

这两个对话框组件的布局也存在一些关键差异,这些差异将在下面的食谱中变得明显。

准备工作

本食谱的完整源代码可以在 /Chapter 1/Recipe 9 文件夹中找到。

如何做到这一点…

首先,我们将创建一个 AlertDialog,它只是通知用户由于缺少信息而无法完成某个操作——在我们的例子中,他们没有在 tfAmount TextField 中提供贷款金额的值:

if (tfAmount.value == '' || tfAmount.value == null)
{
var errorDialog = Titanium.UI.createAlertDialog({
title: 'Error!',
message: 'You must provide a loan amount.'
});
errorDialog.show();
}
}

现在让我们添加 OptionDialog。OptionDialog 将显示我们的计算结果,然后给用户选择查看结果作为饼图(在新窗口中)或取消并保持在同一屏幕上的选项:

//check our win2 autoShowChart boolean value first (coming //from the switch on window2.js)
if (win2.autoShowChart == true)
{
openChartWindow();
}
else
{
var resultOptionDialog = Titanium.UI.createOptionDialog({
title: optionsMessage + '\n\nDo you want to
view this in a chart?',
options: ['Okay', 'No'],
cancel: 1
});
//add the click event listener to the option dialog
resultOptionDialog.addEventListener('click', function(e){
Ti.API.info('Button index tapped was: ' + e.index);
if (e.index == 0)
{
openChartWindow();
}
});
resultOptionDialog.show();
} //end if

如何工作...

AlertDialog 是一个非常简单的组件,它以模态方式向用户显示消息,并且只有一个可能的响应,即关闭警报。请注意,你应该小心不要在挂起的警报仍然可见时多次调用 AlertDialog,例如,如果你是在循环中调用该警报。

OptionDialog 是一个更大的模态组件,它从屏幕底部显示一系列按钮和消息,通常用于允许用户从多个选项中选择。在我们的代码中,resultOptionDialog向用户提供了两个选项的选择——“确定”或“否”。这个对话框的一个有趣属性是取消,它在不触发点击事件的情况下关闭对话框,并且以请求的索引样式按钮,使其与其他按钮组区分开来。

就像 Window 对象一样,这两个对话框都不是添加到另一个 View 中,而是通过调用show()方法来呈现。你应该只在对话框已经正确实例化并且创建了任何事件监听器之后调用show()方法。

以下截图显示了 AlertDialog 和 OptionDialog 之间的区别:

如何工作...

还有更多...

你还可以使用基本的 JavaScript 创建预定义的 AlertDialog,使用语法:alert('Hello world!');。不过,请注意,你只能使用这种方法控制消息的内容,你的 AlertDialog 的标题将始终设置为'Alert'。

使用 Raphael JS 创建图表

让我们为这个应用程序和第一章执行最后一个任务;显示图表和图形。钛并没有原生的图表 API,然而,有一些开源选项可以实现图表,例如 Google Charts。虽然 Google 解决方案是免费的,但它要求你的应用程序每次需要生成图表时都必须在线。这可能适用于某些情况,但并不是任何旨在离线使用的应用程序的最佳解决方案。此外,Google Charts 返回的生成 JPG 或 PNG 文件是请求的大小和栅格化格式,这在 iPhone 或 iPad 上查看时放大效果不佳。

一个更好的解决方案是使用开源的 MIT 许可的 Raphael 库,幸运的是,它有一个图表组件!不仅它是免费的,而且 Raphael 是完全基于向量的,这意味着你创建的任何图表在任何分辨率上看起来都很棒,并且可以放大而不会损失质量。

注意

注意,这个菜谱可能不适用于所有 Android 设备,因为当前版本的 Raphael 不受非 webkit 移动浏览器的支持。然而,它将如这里所述在 iPhone 和 iPod Touch 上工作。

准备工作

  1. raphaeljs.com(直接链接:github.com/DmitryBaranovskiy/raphael/raw/master/raphael-min.js)下载主要的 Raphael JS 库。

  2. g.raphaeljs.com(直接链接:github.com/DmitryBaranovskiy/g.raphael/blob/master/g.raphael-min.js?raw=true)下载主要的图表库以及你希望使用的任何其他图表库。

在这个例子中,我们正在实现饼图,它在这里:github.com/DmitryBaranovskiy/g.raphael/blob/master/g.pie-min.js?raw=true

注意

这个菜谱的完整源代码可以在/Chapter 1/Recipe 10文件夹中找到。

如何做...

  1. 在 Titanium Studio 中创建一个新的项目(或者,如果你正在跟随 LoanCalc 示例应用进行操作,那么打开你的项目目录),并将你下载的文件放入一个名为charts的新文件夹中,该文件夹位于你的Resources目录下。你也可以将它们放入根目录中,但请注意,你需要确保在以下步骤中的引用是正确的。

  2. 下一步是将你的raphael-min.js文件重命名为raphael-min.lib。主要原因是你如果文件是一个已知的 JavaScript 文件(例如,以'.js'结尾),Titanium 中的 JSLint 验证器会尝试验证 Raphael JS 库并失败,导致 Titanium 锁定。这意味着你将无法运行你的应用,并需要重新启动 Titanium Studio!

  3. 在你的应用中创建一个 WebView,引用一个变量来保存要显示的 Raphael 图表的 HTML 代码,我们将它称为chartHTML。WebView 是一个 UI 组件,允许你在应用中显示网页或 HTML。它不包含完整浏览器的任何功能,如导航控件或地址栏。在你的chartwin.js文件顶部输入以下代码,在你包含图表库并创建图表视图标题之后:

    var chartWin = Titanium.UI.currentWindow;
    //include the chart library
    Titanium.include('charts/chart.js');
    //create the chart title using the variables we passed in from
    //app.js (our first window)
    var chartTitleInterest = 'Total Interest: $' +
    chartWin.totalInterest;
    var chartTitleRepayments = 'Total Repayments: $' +
    chartWin.totalRepayments;
    //create the chart using the sample html from the
    //raphaeljs.com website
    var chartHTML = '<html><head> <title>RaphaelJS Chart</title><meta name="viewport" content="width=device-width, initial-scale=1.0"/> <script src="img/raphael.js.lib" type="text/javascript" charset="utf-8"></script> <script src="img/g.raphael-min.lib" type="text/javascript" charset="utf-8"></script> <script src="img/g.pie-min.lib" type="text/javascript" charset="utf-8"></script> <script type="text/javascript" charset="utf-8"> window.onload = function () { var r = Raphael("chartDiv"); r.g.txtattr.font = "12px Verdana, Tahoma, sans-serif"; r.g.text(150, 10, "';
    chartHTML = chartHTML + chartTitleInterest + '").attr({"font-size": 14}); r.g.text(150, 30, "' + chartTitleRepayments + '").attr({"font-size": 14});';
    chartHTML = chartHTML + ' r.g.piechart(150, 180, 130, [' + Math.round(chartWin.totalInterest) + ',' + Math.round(chartWin.principalRepayments) + ']); }; </script> </head><body> <div id="chartDiv" style="width:320px; height: 320px; margin: 0"></div> </body></html>';
    //add a webview to contain our chart
    var webview = Titanium.UI.createWebView({
    width: 320,
    height: 367,
    top: 0,
    html: chartHTML
    });
    chartWin.add(webview);
    
    
  4. 现在回到你的app.js文件中,创建一个新的函数openChartWindow(),当用户从上一个菜谱的选项对话框中选择“Okay”时将执行此函数。这个函数将创建一个新的 Window 对象,基于chartwin.js文件,并将所需的值传递给它以显示图表:

    //we'll call this function if the user opts to view the loan //chart
    function openChartWindow(totalInterest, total)
    {
    //Interest (I) = Principal (P) times Rate Per Period (r)
    //times Number of Periods (n) / 12
    var totalInterest = (tfAmount.value * (interestRate / 100)
    * numberMonths) / 12;
    var totalRepayments = Math.round(tfAmount.value) +
    totalInterest;
    var chartWindow = Titanium.UI.createWindow({
    url: 'chartwin.js',
    title: 'Loan Pie Chart',
    barImage: 'navbar.png',
    barColor: '#000',
    numberMonths: numberMonths,
    interestRate: interestRate,
    totalInterest: totalInterest,
    totalRepayments: totalRepayments,
    principalRepayments: (totalRepayments - totalInterest)
    });
    tab1.open(chartWindow);
    }
    
    

它是如何工作的...

我们在这里做的基本上是将 Raphael 库包装起来,这个库最初是为桌面浏览器构建的,现在可以以 iPhone 的 WebKit 浏览器可以消费和显示的格式使用。Raphael 最初是为了简化网页上的矢量图形生成而创建的,后来作为 gRaphael 扩展,以便渲染静态和交互式图表。

raphaeljs.comg.raphaeljs.com上有关于 Raphael 的一系列文档,介绍了它是如何通过其 JavaScript 库来渲染图表的。我们不会详细解释这一点,而是将重点放在如何使库与 Titanium 一起使用。

我们的实现首先包括将 Raphael 库中的charts.js库集成到我们的 Titanium 项目中。这是库使用的源文件。从那里,我们创建了一种新的组件类型,即 WebView,它将(在这种情况下)包含我们在变量chartHTML中构建的 HTML 数据。这些 HTML 数据包含渲染图表所需的所有包含项,这些项在本食谱的“准备就绪”部分的第 2 项中列出。如果你有一个包含静态数据的图表,你也可以使用 WebView 对象的url属性从文件中引用 HTML,而不是将所有 HTML 作为字符串传递。图表本身是通过嵌入在 HTML 数据字符串中的简单 JavaScript 创建的,例如 r.g.piechart(150, 180, 130, n1, n2),其中 n1 和 n2 是我们希望在饼图中显示为切片的两个值。其他值分别定义了从顶部和左侧开始的图表中心点,然后是图表半径。

所有这些都被封装在一个新的由chartwin.js文件定义的窗口中,该窗口访问来自我们 LoanCalc 应用第一个标签页窗口传递的属性。这些数据使用与之前“在窗口之间传递自定义变量”食谱中解释的完全相同的机制传递。

以下截图显示了使用 Raphael JS 库根据我们的贷款数据显示饼图:

工作原理...

第二章:使用本地和远程数据源

在本章中,我们将涵盖:

  • 通过 HTTPClient 从远程 XML 读取数据

  • 使用 TableView 显示数据

  • 使用自定义行增强 TableView

  • 使用 SearchBar 控件过滤 TableView

  • 使用 JSON 和 Yahoo! YQL 加速远程数据访问

  • 创建 SQLite 数据库

  • 使用 SQLite 数据库本地保存数据

  • 从 SQLite 数据库检索数据

  • 创建“拉动和释放”刷新机制

简介

完全理解您在 Titanium Studio 中可用于读取、解析和保存数据的方法,对于您将构建的应用的成功至关重要。钛金属为您提供所有您需要的工具,从简单的通过 HTTP 的 XML 调用,到实现 JSON 以提高网络速度,再到运行本地化关系数据库(SQLite)以满足离线存储需求的应用程序。

在本章中,我们不仅将涵盖通过 HTTP 实现远程数据访问的基本方法,还将介绍如何使用 TableViews、TableRows 和其他自定义用户界面有效地存储和展示数据。

前提条件

您应该对 XML 和 JSON 数据格式有基本的了解,这些是广泛使用的标准方法,用于在网络上传输数据。此外,您还应该了解 SQL结构化查询语言)是什么以及如何创建基本的 SQL 语句,如 CREATE、SELECT、DELETE 和 INSERT。如果您需要参考有关如何执行常见类型数据库查询的教程,sqlzoo.net 提供了一个很好的 SQL 初学者介绍。

通过 HTTPClient 从远程 XML 读取数据

能够通过 RSS 源或替代 API 从互联网消费和显示数据,这是许多移动应用的基础。更重要的是,您可能希望集成到您的应用中的许多服务可能需要您在某个时候这样做,例如 Twitter 或 Wikipedia,因此理解和能够实现远程数据馈送和 XML 是至关重要的。本章的第一个食谱介绍了钛金属中的一些新功能,以帮助解决这一需求。

如果您打算跟随整个章节并构建 MyRecipes 应用,那么请仔细注意此食谱的第一个 准备工作 部分,因为它将指导您设置项目。

准备工作

准备此食谱,请打开 Titanium Studio 并登录,如果您尚未登录的话。如果您需要注册新账户,您可以直接在应用程序内免费注册。登录后,点击新建项目,创建新项目的详细信息窗口将出现。将应用程序名称输入为MyRecipes,并使用您自己的信息填写其余的详细信息。

注意

请注意应用程序标识符,它通常以反向域名表示法正常书写(即 com.packtpub.myrecipes)。在项目创建后,此标识符不能轻易更改,并且在创建用于分发应用的配置文件时,您需要精确地匹配它。

整个章节的完整源代码可以在 /Chapter 2/RecipeFinder 文件夹中找到,而本食谱的源代码可以在 /Chapter 2/Recipe 1 文件夹中找到。

如何做到...

现在我们的项目外壳已经设置好了,让我们开始工作吧!首先,打开您的 app.js 文件,以及另外两个名为 recipes.jsfavorites.js 的 JavaScript 文件。在您的 app.js 中,将 recipes.jsfavorites.js 分别引用到 win1win2,并为每个窗口提供一个有意义的标题(例如,“食谱”)。我们还将更改标签图标从默认图标更改为两个图标 'fork-and-knife.png' 和 'heart.png'。这两个图标都包含在配套的源文件中。

在您的 IDE 中打开 recipes.js 文件。这是将包含我们从 RSS 提供程序检索和显示食谱的代码的文件。在您的 recipes.js 文件顶部输入以下代码。此代码将创建一个 HTTPClient 并从食谱网站读取提供程序的 XML。

var win = Titanium.UI.currentWindow;
//declare the http client object
var xhr = Titanium.Network.createHTTPClient();
//this method will process the remote data
xhr.onload = function() {
Ti.API.info(this.responseText);
};
//this method will fire if there's an error in accessing the //remote data
xhr.onerror = function() {
//log the error to our Titanium Studio console
Ti.API.error(this.status + ' - ' + this.statusText);
};
//open up the recipes xml feed
xhr.open('GET', 'http://www.cuisine.com.au/feed/all-recipes');
//finally, execute the call to the remote feed
xhr.send();

现在尝试运行 Android 或 iPhone 的模拟器。屏幕上应该出现两个标签页,如图所示,几秒钟后,在 Titanium Studio 的控制台日志中打印出一堆 XML 数据。

如何做到...

它是如何工作的...

如果您已经熟悉网络上的 JavaScript,这应该对您来说很有意义。在这里,我们使用 Titanium.Network 命名空间创建了一个 HTTPClient,并在食谱网站的 URL 上通过一个名为 xhr 的对象打开一个 GET 连接。

通过实现 onload 事件监听器,我们可以捕获 xhr 对象检索到的 XML 数据。在源代码中您会注意到我们使用了 Ti.API.info() 将信息回显到 Titanium Studio 屏幕,这是一种调试和跟踪应用事件的好方法。如果您的连接和 GET 请求成功,您应该在 Titanium Studio 的信息日志中看到一个大的 XML 字符串输出。食谱的最后一部分虽然很小但非常重要——调用 xhr 对象的 send() 方法。这将启动 GET 请求。如果没有它,您的应用将永远不会加载数据。需要注意的是,如果您忘记实现 xhr.send(),您将不会收到任何错误或警告。如果您的应用没有收到任何数据,这是您首先需要检查的地方。

注意事项

如果您在解析 XML 时遇到困难,请始终首先检查它是否有效!在浏览器中打开 XML 提供程序通常会为您提供足够的信息来判断您的提供程序是否有效,或者它是否有损坏的元素。

使用 TableView 显示数据

TableView 是整个 iPhone 和 Android SDK 中最常用的组件,你设备上的几乎所有原生应用程序都会以某种形式使用表格。它们用于以有效的方式显示大量数据,允许自定义滚动列表,可以进行搜索,或者深入查看以暴露子视图。由于有这么多可用的属性和选项,很容易迷失在这些组件的功能和能力中。幸运的是,Titanium 使得将 TableView 实现到应用程序中变得容易。在本食谱中,我们将实现一个 TableView,并使用之前食谱中的 XML 数据源来填充它,显示食谱列表。

注意

本食谱的完整源代码可以在/Chapter 2/Recipe 2文件夹中找到。

如何实现...

一旦我们将我们的应用程序连接到数据源,并通过 XHR 对象检索 XML 数据,我们就需要能够操作这些数据并将其显示到 TableView 组件中。首先,在recipes.js文件的顶部创建一个名为data的数组对象。这个数组将在全局范围内存储我们 TableView 的所有信息:

var data = []; //empty data array

现在,我们将在创建 TableView 并将其data属性分配给我们的数据对象之前,将 XML 数据分发并读取所需元素到我们的data数组对象中:

//declare the http client object
var xhr = Titanium.Network.createHTTPClient();
//create the table view
var tblRecipes = Titanium.UI.createTableView({
height: 366,
width: 320,
top: 0,
left: 0
});
win.add(tblRecipes);
//this method will process the remote data
xhr.onload = function() {
var xml = this.responseXML;
//get the item nodelist from our response xml object
var items = xml.documentElement.getElementsByTagName("item");
//loop each item in the xml
for (var i = 0; i < items.length; i++) {
//create a table row
var row = Titanium.UI.createTableViewRow({
title:
items.item(i).getElementsByTagName("title").item(0).text
});
//add the table row to our data[] object
data.push(row);
} //end for loop
//finally, set the data property of the tableView to our //data[] object
tblRecipes.data = data;
};

以下截图显示了包含我们从 XML 源中获取的食谱标题的表视图:

如何实现...

它是如何工作的...

你首先会注意到,我们正在使用Ti.XML命名空间将元素列表分配给一个名为items的新对象。这允许我们使用for循环结构来遍历项目,并将每个单独的项目分配给我们创建并赋予全局作用域的data数组对象。

从那里,我们通过实现Titanium.UI.createTableView()函数来创建 TableView。你应该会立即注意到,我们的一些常规属性也被表格使用,包括宽度、高度和定位。然而,TableView 有一个额外且重要的属性:数据属性。数据属性接受一个数据数组,其值可以是动态的(就像我们在这里使用标题属性那样),也可以分配给 TableRow 的子组件。随着你开始构建更复杂的应用程序,你将学会完全理解基于表格的布局有多么灵活。

通过自定义行增强 TableView

到目前为止,我们已经创建了一个 TableView,虽然它完全可用并且显示了 XML 源中的食谱名称,但看起来有点单调。为了自定义我们的表格,我们需要创建并添加自定义 TableRow 对象到行数组中,然后我们可以将这些行分配给 TableView 对象。这些 TableRow 对象本质上是一种 View 类型,我们可以向其中添加任何数量的组件,例如标签、ImageView 和按钮。

接下来,我们将创建我们的 TableRow 对象,并将我们从 XML 源中获取的食谱名称、简短描述和缩略图(我们将从Resources目录下的images文件夹中获取)添加到每个对象中。如果您还没有图像目录,请现在创建一个,并将此食谱的源代码中的图像复制到其中,这些源代码可以在/Chapter 2/Recipe 3文件夹中找到。

如何实现...

打开您的recipe.js文件,并输入以下代码。如果您一直在跟随前面的食谱,那么以下代码将扩展您已经编写的代码:

var data = []; //empty data array
//declare the http client object
var xhr = Titanium.Network.createHTTPClient();
var tblRecipes = Titanium.UI.createTableView({
height: 366,
width: 320,
top: 0,
left: 0,
rowHeight: 70
});
win.add(tblRecipes);
//this method will process the remote data
xhr.onload = function() {
var xml = this.responseXML;
//get the item nodelist from our response xml object
var items = xml.documentElement.getElementsByTagName("item");
//loop each item in the xml
for (var i = 0; i < items.length; i++) {
//create a table row
var row = Titanium.UI.createTableViewRow({
hasChild: true,
className: 'recipe-row'
});
//title label
var titleLabel = Titanium.UI.createLabel({
text:
items.item(i).getElementsByTagName("title").item(0).text,
font: {fontSize: 14, fontWeight: 'bold'},
left: 70,
top: 5,
height: 20,
width: 210
});
row.add(titleLabel);
//description label
var descriptionLabel = Titanium.UI.createLabel({
text:
items.item(i).getElementsByTagName("description").item(0).text,
font: {fontSize: 10, fontWeight: 'normal'},
left: 70,
top: 25,
height: 40,
width: 200
});
if(descriptionLabel.text == '') {
descriptionLabel.text = 'No description is available.';
}
row.add(descriptionLabel);
//add our little icon to the left of the row
var iconImage = Titanium.UI.createImageView({
image: 'images/foodicon.jpg',
width: 50,
height: 50,
left: 10,
top: 10
});
row.add(iconImage);
//add the table row to our data[] object
data.push(row);
}
//finally, set the data property of the tableView to our
//data[] object
tblRecipes.data = data;
};

工作原理...

应该立即明显的一点是,一个TableRow对象可以包含任何数量的组件,您可以通过标准方式定义并添加(例如,请参阅第一章,使用原生 UI 组件构建应用,了解实现不同 UI 组件的示例)。

那么className属性是用来做什么的呢?当行在您的设备上渲染时,这一切都是在请求时发生的,也就是说,只有那些可见的行实际上是由操作系统渲染的,这可以在以下屏幕截图中看到。原因有两个。首先,为了节省内存,与桌面计算机相比,大多数设备都很少。其次,为了通过仅执行绝对必要的 CPU 任务来加快您的应用程序的速度。

对于几行,不使用className的内存使用量不会太高,但对于许多行,这取决于您在行中使用了多少和什么界面组件,您的应用程序可能会加载得很慢,甚至可能崩溃。

工作原理...

使用 SearchBar 组件过滤 TableView

当您的用户想要在 TableView 中搜索所有数据时会发生什么?到目前为止,最简单的方法是使用SearchBar组件。这是一个标准控件,由一个可搜索的文本字段和一个可选的取消按钮组成,并使用表格视图的searchBar属性将其附加到表格视图的顶部。

在下一个食谱中,我们将实现一个搜索栏到我们的MyRecipes应用程序中,根据title属性过滤我们的食谱。

注意

此食谱的完整源代码可以在/Chapter 2/Recipe 4文件夹中找到。

如何实现...

首先,在定义tableView之前创建一个searchBar组件,然后创建事件监听器。

//define our search bar which will attach
//to our table view
var searchBar = Titanium.UI.createSearchBar({
showCancel:true,
height:43,
top:0
});
//print out the searchbar value whenever it changes
searchBar.addEventListener('change', function(e){
//search the tableview as user types
Ti.API.info('user searching for: ' + e.value);
});
//when the return key is hit, remove focus from
//our searchBar
searchBar.addEventListener('return', function(e){
searchBar.blur();
});
//when the cancel button is tapped, remove focus
//from our searchBar
searchBar.addEventListener('cancel', function(e){
searchBar.blur();
});

现在,将我们的 tableView 的搜索属性设置为我们的 searchBar 组件,然后将我们的 tableView 的filterAttribute设置为'filter'。我们将在每个行对象中定义这个名为'filter'的自定义属性。

//define our table view
var tblRecipes = Titanium.UI.createTableView({
height: 366,
width: 320,
top: 0,
left: 0,
rowHeight: 70,
search: searchBar,
filterAttribute:'filter' //here is the search filter which
//appears in TableViewRow
});
win.add(tblRecipes);

现在,在遍历我们的 xml 数据时定义的每一行中,添加一个名为'filter'的自定义属性,并将其值设置为 XML 源中的标题文本:

//this method will process the remote data
xhr.onload = function() {
var xml = this.responseXML;
//get the item nodelist from our response xml object
var items = xml.documentElement.getElementsByTagName("item");
//loop each item in the xml
for (var i = 0; i < items.length; i++) {
//create a table row
var row = Titanium.UI.createTableViewRow({
hasChild: true,
className: 'recipe-row',
filter: items.item(i).getElementsByTagName("title").item(0).text //this is the data we want to search on (title)
});

就这样!运行你的项目,你现在应该已经在你的 TableView 上附加了一个搜索栏,如图所示。点击它并输入菜谱标题的任何部分,你将看到在表格中过滤出的结果。

如何操作...

它是如何工作的...

在第一块代码中,我们只是像定义任何其他 UI 组件一样定义我们的SearchBar对象,然后在第二块代码中将它附加到 TableView 的searchBar属性上。searchBar的事件监听器确保当用户点击“搜索”或“取消”按钮时,文本输入的焦点会丢失,因此键盘将会隐藏。

最后一块代码定义了我们正在搜索的数据,在这种情况下,我们的filter属性已经被设置为菜谱的标题。这个属性需要在绑定到 TableView 之前添加到我们定义的每一行。

使用 JSON 和 Yahoo! YQL 加速远程数据访问

如果你已经熟悉在网络上大量使用 JavaScript,尤其是使用像 jQuery 或 Prototype 这样的流行库,那么你可能已经知道使用 JSON 而不是 XML 的好处。JSON 数据格式比 XML 简洁得多,这意味着文件大小更小,数据传输更快。这在用户可能因为网络访问和带宽限制而在移动设备上数据速度有限的情况下尤为重要。

如果你从未见过 Yahoo 的 YQL 控制台,或者听说过 YQL 语言 Web 服务,它本质上是一个免费的 Web 服务,允许开发者和应用程序查询、过滤和组合来自互联网的多个数据源。

在这个菜谱中,我们将使用 Yahoo! YQL 控制台和 Web 服务从我们的菜谱数据源中获取数据,并将这些数据转换成一个 JSON 对象,然后我们将把这个对象绑定到我们的 TableView 上。

注意

本菜谱的完整源代码可以在/Chapter 2/Recipe 5文件夹中找到。

如何操作...

首先,通过在浏览器中打开developer.yahoo.com/yql/console来访问 Yahoo 的 YQL 控制台页面。在浏览器窗口的右侧,你应该注意到一个名为“数据表”的部分。从数据表列表中选择“data”,然后选择“feed”。你的 SQL 语句应该自动更改为来自 Yahoo!新闻网络的简单数据源。现在,将 YQL 语句中的 URL 值替换为我们的菜谱源,即www.cuisine.com.au/feed/all-recipes,从单选按钮中选择“JSON”而不是XML,然后点击“测试”,如图所示。你应该在结果窗口中看到一组格式化的数据以 JSON 格式返回!

如何操作...

要使用这些数据,我们需要从 YQL 控制台复制并粘贴完整的 REST 查询。这位于浏览器的底部,是一个单行文本框。将整个 URL 复制并粘贴到您的xhr.open()方法中,替换现有的菜谱源 URL。

小贴士

确保在粘贴字符串时,它没有被任何引号打断。如果被打断,您需要通过将'替换为'来转义任何引号字符。您可能还想从 URL 中移除&callback=cbfunc参数,因为它有时会导致 JSON 停止解析。

现在,回到xhr.onload()函数中,让我们用解析 JSON 格式的代码替换所有的 XML 解析代码:

//this method will process the remote data
xhr.onload = function() {
//create a json object using the JSON.PARSE function
var jsonObject = JSON.parse(this.responseText);
//print out how many items we have to the console
Ti.API.info(jsonObject.query.results.item.length);
//loop each item in the json object
for(var i = 0; i < jsonObject.query.results.item.length; i++) {
//create a table row
var row = Titanium.UI.createTableViewRow({
hasChild: true,
className: 'recipe-row',
backgroundColor: '#fff',
filter: jsonObject.query.results.item[i].title
//this is the data we want to search on (title)
});
//title label
var titleLabel = Titanium.UI.createLabel({
text: jsonObject.query.results.item[i].title,
font: {fontSize: 14, fontWeight: 'bold'},
left: 70,
top: 5,
height: 20,
width: 210,
color: '#000'
});
row.add(titleLabel);
//description label
var descriptionLabel = Titanium.UI.createLabel({
text: jsonObject.query.results.item[i].description,
font: {fontSize: 10, fontWeight: 'normal'},
left: 70,
top: 25,
height: 40,
width: 200,
color: '#000'
});
if(descriptionLabel.text == '') {
descriptionLabel.text = 'No description is
available.';
}
row.add(descriptionLabel);
//add our little icon to the left of the row
var iconImage = Titanium.UI.createImageView({
image: 'images/foodicon.jpg',
width: 50,
height: 50,
left: 10,
top: 10
});
row.add(iconImage);
//add the table row to our data[] object
data.push(row);
}
//finally, set the data property of the tableView
//to our data[] object
tblRecipes.data = data;
};
//this method will fire if there's an error in accessing
//the remote data
xhr.onerror = function() {
//log the error to our Titanium Studio console
Ti.API.error(this.status + ' - ' + this.statusText);
};
//open up the recipes xml feed
xhr.open('GET', 'http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20feed%20where%20url%3D%22http%3A%2F%2Fwww.cuisine.com.au%2Ffeed%2Fall-recipes%22&format=json&diagnostics=false');
//finally, execute the call to the remote feed
xhr.send();

它是如何工作的…

正如您在这道菜谱中看到的那样,访问 YQL 网络服务只是将一个 HTTP GET 查询传递给 YQL 服务 URL,使用 YQL 语句作为 URL 参数。当它处理查询时,Yahoo! YQL 服务获取并转换请求的数据,并以您指定的格式返回(在我们的例子中,是 JSON)。

与 XML 相比,访问 JSON 数据对象的属性也有所不同,并且可以说是更简单,因为 XML 更复杂。在 JSON 中,我们使用简单的点符号来导航数据树层次结构并选择我们想要使用的属性。如果您已经理解了 PHP、JavaScript 和其他一些 C 风格语言中的数组语法,这应该对您来说相当熟悉!

更多内容…

在本章中,我们只使用了一个 RSS 源,但如果你有多个 RSS 源希望同时阅读,会怎样呢?

结合多个 RSS 源…

之前问题的答案是使用 Yahoo! Pipes——由 Yahoo!提供的一项免费服务,允许您创建一个由一个或多个 RSS、JSON 或数据源组成的“管道”,在允许您输出数据到单个源之前,可以对数据进行过滤和排序。免费注册试用,请访问pipes.yahoo.com

创建 SQLite 数据库

有许多原因使得 SQLite 成为移动手机的首选关系数据库——它是可扩展的、快速的、用原生 C 语言编写的,并且非常便携,还有一个额外的好处是它具有异常小的占用空间。我们需要在设备上使用本地数据库来存储设备离线时的数据,或者甚至存储仅在本地上需要的数据(例如游戏中的高分)。

此外,远程数据的缓存可以帮助加快我们应用程序中的数据访问时间——尤其是在移动设备可能有限连接性和带宽的情况下尤为重要。

在您的应用程序中创建 SQLite 数据库有两种方法,一种是在代码中使用 SQL 创建数据库,另一种是通过“安装”方法复制并附加现有的数据库到您的应用程序中。在本菜谱中,我们将解释如何通过 SQL 语句创建数据库。

注意

本菜谱的完整源代码可以在/Chapter 1/Recipe 6文件夹中找到。

如何做到这一点…

创建一个名为 database.js 的新 JavaScript 文件,并在新文件顶部输入以下代码:

//create the database object
var db = Titanium.Database.open('mydb');
db.execute('CREATE TABLE IF NOT EXISTS favorites (ID INTEGER PRIMARY KEY AUTOINCREMENT, TITLE TEXT, LINK TEXT, DESCRIPTION TEXT)');

现在将以下行添加到每个我们需要从其中引用数据库函数的 Window 的顶部。对您的 recipes.jsfavorites.js 文件都执行此操作。

Ti.include('database.js');

它是如何工作的...

SQLite 的一个优点是其创建的简单性。在上面的示例代码中,您可以看到我们甚至没有执行任何“创建数据库”查询。只需尝试打开一个不存在的数据库,在本例中是 mydb,就会告诉 SQLite 引擎自动创建它!

从那里我们可以使用标准 SQL 语法创建我们的 SQL 表。在我们的例子中,我们创建了一个具有既是主键又是自动递增数字的 ID、标题、链接和描述的表。后三个字段与我们的食谱数据源返回的数据相匹配。在下一个食谱中,我们可以使用这个表来本地存储我们的食谱数据。

还有更多...

让我们看看如何附加一个预先填充的数据库文件。

附加一个预先填充的数据库文件…

如果您希望单独创建数据库并在运行时将其附加到您的应用程序中,有一个名为 Titanium.Database.install().Implementing 的方法。实现此方法非常简单,因为它只需要接受两个参数——数据库文件和数据库名称。例如:

Var db = Titanium.Database.install('data.db', 'packtData');

还有许多免费的 SQLite 应用程序用于创建和管理 SQLite 数据库。开源的 SQLite 数据库浏览器 工具可以从 sqlitebrowser.sourceforge.net 免费获取,并在 Windows、Linux 和 Mac OS X 上运行。

使用 SQLite 数据库本地保存数据

将数据和更新数据保存到您的 SQLite 数据库只是创建一个用于每个所需 CRUD 操作的函数,并在执行之前形成 SQL 语句,然后对本地数据库(我们的 'db' 对象)执行它。

在这个食谱中,我们将编辑 database.js 文件以包含两个新函数,一个用于在我们的收藏夹表中插入记录,另一个用于删除记录。我们还将捕获表行的点击事件,以便用户可以在详细子窗口中查看记录,并添加一个创建收藏的按钮。

注意

本食谱的完整源代码可以在 /Chapter 2/Recipe 7 文件夹中找到。

如何做到这一点...

打开您的名为 database.js 的 JavaScript 文件,并在新文件顶部(在您的表创建脚本之后)输入以下代码:

function insertFavorite(title, description, link) {
var sql = "INSERT INTO favorites (title, description, link) VALUES (";
sql = sql + "'" + title.replace("'", "''") + "', ";
sql = sql + "'" + description.replace("'", "''") + "', ";
sql = sql + "'" + link.replace("'", "''") + "')";
db.execute(sql);
return db.lastInsertRowId;
}
function deleteFavorite(id) {
var sql = "DELETE FROM favorites WHERE id = " + id;
db.execute(sql);
}

然后,回到我们的 recipes.js 文件中,我们将捕获 tblRecipes TableView 的点击事件,以便获取被点击行的数据并将其保存到我们的 SQLite 的 favorites 表中:

//create a new window and pass through data from the
//tapped row
tblRecipes.addEventListener('click', function(e){
var selectedRow = e.rowData; //row index clicked
var detailWindow = Titanium.UI.createWindow({
title: selectedRow._title,
_description: selectedRow._description,
_link: selectedRow._link,
backgroundColor: '#fff',
id: 0
});
//add the favorite button
var favButton = Titanium.UI.createButton({
title: 'Add Favorite',
left: 10,
top: 10,
width: 140,
height: 30,
added: 0
});
favButton.addEventListener('click',function(e){
if (favButton.added == 0) {
var newId = insertFavorite(detailWindow.title, detailWindow._description, detailWindow._link);
Ti.API.info('Newly created favorite id = ' + newId);
detailWindow.id = newId;
alert('This recipe has been added as a favorite!');
favButton.added = 1;
favButton.title = 'Remove Favorite';
}
else {
deleteFavorite(detailWindow.id);
Ti.API.info('Deleted ' + affectedRows + ' favorite records. (id ' + detailWindow.id + ')');
detailWindow.id = 0;
alert('This recipe has been removed from favorites!');
favButton.added = 0;
favButton.title = 'Add Favorite';
}
});
detailWindow.add(favButton);
//let's also add a button to open a link in safari
var linkButton = Titanium.UI.createButton({
title: 'View In Safari',
right: 10,
top: 10,
width: 140,
height: 30,
added: 0
});
//this event listener will open the link in safari
linkButton.addEventListener('click',function(e){
Ti.Platform.openURL(detailWindow._link);
});
detailWindow.add(linkButton);
//finally, add the full description so we can read the
//whole recipe
var lblDescription = Titanium.UI.createLabel({
text: detailWindow._description,
left: 10,
top: 50,
width: 300,
height: 'auto',
color: '#000'
});
detailWindow.add(lblDescription);
//open the detail window
Titanium.UI.currentTab.open(detailWindow);
});

它是如何工作的...

首先,我们正在创建函数,这些函数将接受插入收藏记录的参数,创建一个 SQL 语句,然后在我们的 SQLite 数据库上执行该 SQL 查询语句。这只是一个基本的 SQL 查询,尽管请注意,就像您在桌面应用程序或网站上做的那样,任何输入参数都应该被正确转义,以避免 SQL 注入!在我们的食谱中,我们通过简单地替换任何单引号字符为双引号来实现这一点。

我们代码的后半部分定义了一个新的窗口,并向其中添加了几个按钮和一个标签,用于显示我们食谱的全文。您应该参考第一章,使用原生 UI 组件构建应用,以获取更多关于打开窗口以及向其中添加和自定义 UI 组件的详细信息。一个有趣的观点,以及我们之前未曾遇到的方法,是Ti.Platform.openURL().这个方法简单地接受一个有效的 URL,并在您的手机上启动 Safari 浏览器(或 Android 浏览器)。在我们的食谱中,我们传递了数据的"链接"属性,以便用户可以从原始网站完整地查看食谱。

还有更多...

安卓用户可以在浏览器启动后按下设备上的返回按钮返回到应用,但值得注意的是,iPhone 用户在按下链接按钮后需要关闭 Safari 并从主屏幕重新启动应用。为了避免这种情况,您可以在另一个子窗口中创建一个 WebView 组件,通过Titanium.UI.currentTab.open()方法打开它,就像我们在本食谱中的详细视图一样。

以下截图显示了我们的食谱详细视图窗口,在我们在 SQLite 数据库表中插入收藏记录之前和之后。

针对 iPhone:

还有更多...

针对 Android 手机:

还有更多...

从 SQLite 数据库中检索数据

如果我们不知道如何检索数据并以某种有用的方式将其呈现给用户,那么创建表格并插入数据到其中就没有多大用处了!现在,我们将介绍 SQLite 中的resultSet(如果您更喜欢,可以称为 recordSet)的概念,以及如何通过这个 resultSet 对象检索数据,并将其收集并返回到适合在 TableView 中使用的数组格式。

注意

本食谱的完整源代码可以在/Chapter 2/Recipe 8文件夹中找到。

如何操作...

在您的database.js文件中,添加以下函数:

function getFavorites() {
var sql = "SELECT * FROM favorites ORDER BY title ASC";
var results = [];
var resultSet = db.execute(sql);
while (resultSet.isValidRow()) {
results.push({
id: resultSet.fieldByName('id'),
title: resultSet.fieldByName('title'),
description: resultSet.fieldByName('description'),
link: resultSet.fieldByName('link')
});
//iterates to the next record
resultSet.next();
}
//you must close the resultset
resultSet.close();
//finally, return our array of records!
return results;
}

现在,首次打开favorites.js文件,并输入以下代码。现在,这部分代码中的大部分应该对您来说都很熟悉,包括定义和向我们的窗口添加 TableView,以及通过我们的Ti.include()方法包含database.js文件。

Ti.include('database.js');
var win = Titanium.UI.currentWindow;
var data = []; //empty data array
var tblFavorites = Titanium.UI.createTableView({
height: 366,
width: 320,
top: 0,
left: 0
});
win.add(tblFavorites);
function loadFavorites(){
data = []; //set our data object to empty
data = getFavorites();
tblFavorites.data = data;
}
//the focus event listener will ensure that the list
//is refreshed whenever the tab is changed
win.addEventListener('focus', loadFavorites);

它是如何工作的...

第一块代码实际上只是我们之前食谱的扩展。但不是创建或删除记录,而是将它们选择到名为“resultSet”的数据库记录集中,然后遍历这个 resultSet 对象,将每个记录所需的数据添加到我们的results数组中。

然后,可以将results数组添加到 TableView 的数据属性中,就像任何其他数据源一样,例如你在本章开头从外部 XML 信息流中获取的数据。需要注意的是,你必须始终使用resultSet.next()迭代到 resultSet 中的新记录,完成时,始终使用resultSet.close()关闭 resultSet。未能执行这些操作中的任何一个都可能使你的应用程序记录无效数据,严重泄漏内存,在最坏的情况下,可能导致应用程序崩溃!

它是如何工作的...

上一张截图显示了我们的收藏标签页中的 TableView,显示了我们已经添加为“收藏”到本地 SQLite 数据库中的记录。

创建一个“下拉并释放”刷新机制

如果你想让用户能够刷新表格中的信息流,你可以创建一个常规按钮,或者可能在任意时间间隔检查新数据。或者,你可以实现一个由 Twitter 应用如 Tweetie 和 Twitter for Android 等非常流行的“下拉并释放”刷新机制。

在我们食谱查找器应用的最终版本中,我们将为我们的食谱信息流实现相同类型的刷新机制,使用表格视图的headerPullView属性。

注意

本食谱的完整源代码可以在/Chapter 2/Recipe 9文件夹中找到,而本章的完整源代码可以在/Chapter 2/RecipeFinder文件夹中找到。

如何实现...

打开你的recipes.js文件,在Ti.include语句下输入以下代码。这就是我们将创建下拉视图并将用户界面组件添加到其中的地方,在创建将执行数据请求的事件监听器之前。

//this variable defines whether the user is currently pulling
//the refresh mechanism or not
var pulling = false;
//defines whether we're currently fetching data or not
var reloading = false;
//let's create our 'pull to refresh' view
var tableHeader = Ti.UI.createView({
backgroundImage: 'images/header.png',
width:320,
height:81
});
var arrowImage = Ti.UI.createView({
backgroundImage:"images/arrow-down.png",
width: 40,
height: 40,
bottom: 20,
left:20
});
var statusLabel = Ti.UI.createLabel({
text:"Pull to refresh...",
left:85,
width:200,
bottom:28,
height:"auto",
color:"#000",
textAlign:"center",
font:{fontSize:20,fontWeight:"bold"},
shadowColor:"#999",
shadowOffset:{x:0,y:1}
});
var actInd = Titanium.UI.createActivityIndicator({
left:20,
bottom:20,
width: 40,
height: 40
});
tableHeader.add(actInd);
tableHeader.add(arrowImage);
tableHeader.add(statusLabel);
//define our table view
var tblRecipes = Titanium.UI.createTableView({
height: 366,
width: 320,
top: 0,
left: 0,
rowHeight: 70,
search: searchBar,
filterAttribute:'filter' //here is the search filter which
//appears in TableViewRow
});
//add the header pull view
tblRecipes.headerPullView = tableHeader;
tblRecipes.addEventListener('scroll',function(e)
{
var offset = e.contentOffset.y;
if (offset <= -80.0 && !pulling)
{
pulling = true;
arrowImage.backgroundImage = 'images/arrow-up.png';
statusLabel.text = "Release to refresh...";
}
else if (pulling && offset > -80.0 && offset < 0)
{
pulling = false;
arrowImage.backgroundImage = 'images/arrow-down.png';
statusLabel.text = "Pull to refresh...";
}
});
tblRecipes.addEventListener('scrollEnd',function(e)
{
if (pulling && !reloading && e.contentOffset.y <= -80.0)
{
reloading = true;
pulling = false;
arrowImage.hide();
actInd.show();
statusLabel.text = "Reloading recipes...";
tblRecipes.setContentInsets({top:80},{animated:true});
//null out the existing recipe data
tblRecipes.data = null;
data = [];
//open up the recipes xml feed
xhr.open('GET', 'http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20feed%20where%20url%3D%22http%3A%2F%2Fwww.cuisine.com.au%2Ffeed%2Fall-recipes%22&format=json&diagnostics=false');
//and fetch it again
xhr.send();
}
});

最后,在你的xhr.open()方法中,我们将检查这是否是我们第一次加载数据,或者这是否是由我们的“下拉并释放”机制发起的重新加载调用。如果是后者,我们将隐藏头部下拉视图并重置其内容回到原始状态。

//check to see if we are refreshing the data via our
//pull and release mechanism
if(reloading == true){
//when done, reset the header to its original style
tblRecipes.setContentInsets({top:0},{animated:true});
reloading = false;
statusLabel.text = "Pull to refresh...";
actInd.hide();
arrowImage.backgroundImage = 'images/arrow-down.png';
arrowImage.show();
}

它是如何工作的...

我们在这里所做的工作实际上可以分解为两个主要部分。首先,我们使用标准 UI 组件创建一个头部视图。其次,我们使用事件来知道我们的头部视图已经被“下拉”到足够远的位置,这样我们就可以从 XML/JSON 信息流中刷新我们的食谱数据。

我们通过 TableView 的contentOffset属性知道头部视图下拉了多少。在这种情况下,当内容偏移量达到 80px 时,我们将执行数据的刷新,这是头部视图的高度,也是 TableView 数据行从屏幕顶部的高度。

最后,pullingreloading 这两个变量结合使用,我们可以通过编程方式确定我们“拉取并释放”刷新机制中的步骤序列:

  1. 当用户正在下拉 TableView,使用点击并保持的手势时(pulling = true

  2. 当 TableView 已完成拉取操作,但尚未开始从我们的远程数据源重新加载数据时(pulling = truereloading = false

  3. 当我们的拉取操作已完成,但我们正在等待从我们的远程数据源返回数据完成时(pulling = falsereloading = true

  4. 最后,当我们的远程数据源已完成流式传输所需数据,并且头部拉取视图可以关闭,我们的表格滚动偏移可以恢复正常时(pulling = falsereloading = false

第三章:集成谷歌地图和 GPS

在本章中,我们将涵盖:

  • 向你的应用程序添加 MapView

  • 使用地理位置获取你的当前位置

  • 将地址转换为经纬度位置

  • 向你的 MapView 添加注释

  • 自定义注释并捕获 MapView 事件

  • 使用设备罗盘监控你的航向

  • 在你的 MapView 上绘制路线

简介

利用地图和基于位置技术的应用程序在用户数量和 iTunes 商店的下载量上仅次于游戏和娱乐。考虑到我们迄今为止发现的多种用途,这种消费者的流行度并不令人惊讶。从帮助我们开车和步行导航的应用程序,到能够找到附近的咖啡馆或餐厅,这项技术的用途实际上才刚刚被探索。

Titanium 通过将谷歌地图和基于 GPS 的服务紧密集成到 iPhone 和 Android 平台中,为我们提供了这个技术的构建块。内置的地理位置、反向地理位置和点对点路由都可以通过 Titanium 的本地 API 集访问。有了这些工具,你可以构建从商店位置查找器到增强现实应用程序的任何东西。

在本章的整个过程中,我们将介绍所有这些核心地图概念,并使用它们来构建一个锻炼追踪器应用程序,该应用程序将在特定点访问我们的位置,并就我们走了多远提供反馈。

前置条件

你应该已经熟悉 Titanium 的基本知识,包括创建 UI 对象和使用 Titanium Studio。此外,了解纬度和经度定位的工作原理会有所帮助,这是计算地球上任何地方的人或物体位置的标准方法。

向你的应用程序添加 MapView

地图已经无处不在,渗透到技术的各个层面。现在,从我们的电脑到汽车、网络,当然还有移动设备,我们都可以获取实时地图。谷歌地图是最常见的平台实现,也是 Android 和 iPhone 平台所使用的平台。在本章的第一个示例中,我们将实现一个 MapView,并为其提供以经纬度值形式存在的区域坐标。

如果你打算跟随整个章节并构建锻炼追踪器应用程序,那么请仔细注意本食谱的第一个准备就绪部分,因为它将指导你设置项目。

准备就绪

为了准备这个食谱,打开 Titanium Studio 并登录,如果您还没有这样做。如果您需要注册新账户,您可以直接在应用程序内免费注册。登录后,点击新建项目,创建新项目的详细信息窗口将出现。将应用程序名称输入为Exercise Tracker,并填写其他详细信息。

注意

注意应用程序标识符,它通常以反向域名表示法书写(即 com.packtpub.exercisetracker)。在项目创建后,此标识符不易更改,并且您在创建用于分发应用的配置文件时需要精确匹配它。

本食谱的完整源代码可以在/Chapter 3/Recipe 1文件夹中找到,而本章的完整源代码可以在/Chapter 3/Exercise Tracker文件夹中找到。

如何操作...

我们现在已使用 Titanium Studio 创建了项目。让我们开始吧!在您的编辑器中打开app.js文件,并删除所有现有代码。完成后,输入以下内容并保存:

//create the window
var win1 = Titanium.UI.createWindow({
title:'Exercise Tracker',
backgroundColor: '#000'
});
//create our mapview
var mapview = Titanium.Map.createView({
top: 110,
height: 350,
mapType: Titanium.Map.STANDARD_TYPE,
region: {latitude: 51.50015,
longitude:-0.12623,
latitudeDelta:0.5,
longitudeDelta:0.5},
animate: true,
regionFit: true,
userLocation: true
});
//add the map to the window
win1.add(mapview);
//finally, open the window
win1.open();

现在尝试运行 Android 或 iPhone 的模拟器。您应该在屏幕底部三分之二的位置看到一个地图出现,几秒钟后它应该中心对准英国伦敦,如下面的截图所示。此时,模拟器可能会请求您是否可以使用您的位置。如果出现在您的屏幕上,只需选择

如何操作...

它是如何工作的…

到现在为止,这段代码中的大部分应该都很熟悉。我们创建了一个Window对象,并在该窗口中添加了一个类型为 MapView 的对象,然后通过win1.open()方法打开它。MapView 本身有许多只与其相关且重要的新属性。这些包括:

  • region: 区域属性接受一个参数数组,其中包含我们希望地图中心化的纬度和经度点,以及纬度和经度增量值。增量值表示地图根据其中心位置缩放级别。

  • userLocation: 这个布尔值将开启或关闭表示位置的蓝色指示器(在 Android 设备上是一个箭头),它表示您相对于 MapView 的位置。请注意,由于模拟器无法正确确定您的当前位置,这可能在模拟器中无法正常工作。

  • animate: 这个布尔值将开启或关闭 MapView 中的缩放和移动动画。在针对处理能力较低和/或带宽较低的旧设备时很有用。

  • regionFit: 一个布尔值,表示所选区域是否适合给定的视图尺寸。

还有更多...

在将地图视图添加到您的应用程序后,让我们看看我们如何可以更改 MapView 的样式。

更改 MapView 的样式

实际上,你可以将多种 MapView 类型添加到你的应用程序中,所有这些类型都可以通过TITANIUM.MAP枚举器访问。可用的完整地图类型如下:

  • TITANIUM.MAP.STANDARD_TYPE

  • TITANIUM.MAP.SATELLITE_TYPE

  • TITANIUM.MAP.HYBRID_TYPE

使用 GeoLocation 获取您的当前位置

我们的地图可能正在工作,但它目前是硬编码为出现在英国伦敦上方,并不是我们所有人都在为女王陛下工作。地图技术的伟大之处在于,我们可以通过 GPS 卫星从世界任何地方确定我们的位置,当 GPS 失效时,通过移动塔信号。这使我们能够将地图置于上下文中,并允许我们向用户发布针对其环境的数据。

为了获取我们的当前位置,我们需要使用Titanium.Geolocation命名空间,它包含一个名为getCurrentPosition的方法。下一个菜谱将解释如何使用此命名空间来调整 MapView 的边界以适应我们的当前位置。

注意

本菜谱的完整源代码可以在/Chapter 3/Recipe 2文件夹中找到。

如何做到这一点...

在你将 MapView 组件添加到窗口之后,添加以下代码:

//set the distance filter
Titanium.Geolocation.distanceFilter = 10;
//apple now requires this parameter so it can inform the user //of why you are accessing their location data
Ti.Geolocation.purpose = "To obtain user location for tracking distance travelled.";
Titanium.Geolocation.getCurrentPosition(function(e)
{
if (e.error)
{
//if mapping location doesn't work, show an alert
alert('Sorry, but it seems geo location
is not available on your device!');
return;
}
//get the properties from Titanium.GeoLocation
var longitude = e.coords.longitude;
var latitude = e.coords.latitude;
var altitude = e.coords.altitude;
var heading = e.coords.heading;
var accuracy = e.coords.accuracy;
var speed = e.coords.speed;
var timestamp = e.coords.timestamp;
var altitudeAccuracy = e.coords.altitudeAccuracy;
//apply the lat and lon properties to our mapview
mapview.region = {latitude: latitude,
longitude: longitude,
latitudeDelta:0.5,
longitudeDelta:0.5
};
});

现在在模拟器中运行你的应用程序,你应该会看到一个类似于以下截图的屏幕出现。注意,如果你在模拟器中运行代码,地图将缩放到你的当前位置,但不会显示表示当前位置的蓝色点。你需要在一个设备上运行应用程序才能看到本菜谱的完整结果。

如何做到这一点...

它是如何工作的…

获取我们的当前位置只需调用Titanium.Geolocation命名空间中的getCurrentPosition方法,并在该事件触发时捕获返回的属性。所有我们需要的信息都可以通过事件对象的coords属性(e)访问。在前面示例中提到的源代码中,我们已经将这些属性设置到变量中,其中一些我们将在后面的练习追踪器应用程序中使用。最后,我们从coords对象中获取纬度和经度属性,并根据这些新值重置 MapView 的region。设置的距离过滤器属性决定了你希望你的 GPS 位置/位置的精确度。在我们的例子中,我们将其设置为 10 米,这对于我们的目的来说已经足够精确了。

小贴士

关于 iPhone 应用程序的重要注意事项…

我们代码块的第二行是苹果提出的新要求。它指出,你必须确切地说明你请求用户位置的原因。这是为了你的用户隐私和安全,所以每次你使用 Geolocation 时,别忘了添加这一行,否则苹果可能会拒绝你的应用程序进入 iTunes 商店!

将地址转换为经纬度位置

当我们为我们完成定位时,这当然很好,但人类不会用经纬度值来思考地点,我们使用传统的地址来定义地图上的点。要将地址转换为经纬度十进制值,我们再次可以使用 Titanium.Geolocation 命名空间,特别是其中的 forwardGeocoder 方法。Titanium 内置了地理编码方法,这些方法利用并本质上“黑盒”了 Google 地图 API 提供的服务。Google 地理编码 API 将地址(例如,“1600 Amphitheatre Parkway, Mountain View, CA”)转换为地理坐标(例如,纬度:37.423021 和经度:122.083739),您可以使用这些坐标来放置标记或定位地图。Google 地理编码 API 提供了一种通过 HTTP 请求直接访问地理编码器的方式。

注意

本食谱的完整源代码可以在 /Chapter 3/Recipe 3 文件夹中找到。

如何做…

首先,我们需要创建一些输入字段,以便用户可以为我们提供起始地址和结束地址。让我们创建一个新的 View 并将其添加到窗口的顶部,位于 MapView 之上。我们还需要添加一个按钮来触发 forwardGeocoder 转换。View 的背景渐变图像可以在源代码的 images 文件夹中找到:

//create the search view
var searchview = Titanium.UI.createView({
top: 0,
left: 0,
width: 320,
height: 110,
backgroundImage: 'images/gradient.png'
});
//style it up a bit
var bottomBorder = Titanium.UI.createView({
height: 1,
width: 320,
left: 0,
bottom: 0,
backgroundColor: '#000'
});
searchview.add(bottomBorder);
//add a search box for starting location
var txtStartLocation = Titanium.UI.createTextField({
backgroundColor: '#fff',
left: 10,
top: 20,
width: 200,
height: 30,
borderColor: '#000',
borderRadius: 5,
hintText: 'Current Location',
paddingLeft: 10
});
searchview.add(txtStartLocation);
//add a search box for starting location
var txtEndLocation = Titanium.UI.createTextField({
backgroundColor: '#fff',
left: 10,
top: 60,
width: 200,
height: 30,
borderColor: '#000',
borderRadius: 5,
hintText: 'End Location',
paddingLeft: 10
});
searchview.add(txtEndLocation);
//add the button with an empty click event, this will fire off
//our forwardGeocoder
var btnSearch = Titanium.UI.createButton({
width: 80,
height: 30,
top: 60,
right: 10,
backgroundImage: 'images/search.png',
borderRadius: 3
});
//btnsearch event listener fires on button tap
btnSearch.addEventListener('click',function(e){
});
searchview.add(btnSearch);

现在,因为我们有一些输入字段,让我们使用搜索按钮来捕获这些地址并将它们转换为我们可以用来定义 MapView 区域的位置值。将以下代码块放入按钮的 click 事件处理程序中:

//btnsearch event listener fires on button tap
btnSearch.addEventListener('click',function(e){
//check for a start address
if(txtStartLocation.value != '')
{
//works out the start co-ords
Ti.Geolocation.forwardGeocoder(txtStartLocation.value, function(e){
//we'll set our map view to this initial region so it
//appears on screen
mapview.region = {latitude: e.latitude,
longitude: e.longitude,
latitudeDelta:0.5,
longitudeDelta:0.5
};
Ti.API.info('Start location co-ordinates are: ' +
e.latitude + ' lat, ' + e.longitude +
'lon');
});
}
else
{
alert('You must provide a start address!');
}
//check for an end address
if(txtEndLocation.value != '')
{
//do the same and work out the end co-ords
Ti.Geolocation.forwardGeocoder(txtEndLocation.value, function(e){
Ti.API.info('End location co-ordinates are: ' + e.latitude + ' lat, ' + e.longitude + ' lon');
});
}
else
{
alert('You must provide an end address!');
}
});

在模拟器中运行您的应用程序,并提供起始地址和结束地址,然后点击 搜索。几秒钟后,您应该会在控制台输出这些地址的地理定位值,并且您的 MapView 应该会重新定位到起始地址所在的区域。以下截图显示了起始和结束地址转换为纬度和经度坐标,并输出到 Titanium Studio 控制台:

如何做…

它是如何工作的…

本食谱中的第一段代码很简单。创建几个用于起始地址和结束地址的 TextFields,并捕获 Button 组件的点击事件,我们将这些地址值传递给我们的 Titanium.Geolocation.forwardGeocoder 方法。

前向地理定位任务实际上是在 Google 服务器上执行的。Titanium 为您封装了一个简单的方法,以便您可以直接调用,这样您就无需亲自创建针对 Google 服务器的 HTTP 请求,然后手动解析响应。

如果您愿意,可以手动尝试,通过阅读 Google 自己网站上的说明:code.google.com/apis/maps/documentation/geocoding/index.html

为您的 MapView 添加注释

在地图上查找位置的能力非常有用,但用户需要的是屏幕上该位置的某种视觉表示。这就是标注的作用所在。在下一个食谱中,我们将使用forwardGeocoder创建的纬度和经度值,为起始地址和结束地址创建标注图钉。

注意

本食谱的完整源代码可在/Chapter 3/Recipe 4文件夹中找到。

如何操作...

在你的搜索按钮函数和之前食谱中调用的forwardGeocoder方法中,添加以下代码以创建起始位置的标注:

//works out the start co-ords
Ti.Geolocation.forwardGeocoder(txtStartLocation.value, function(e)
{
//we'll set our map view to this initial region so it appears
//on screen
mapview.region = {latitude: e.latitude,
longitude: e.longitude,
latitudeDelta: 0.5,
longitudeDelta: 0.5
};
Ti.API.info('Start location co-ordinates are: ' +
e.latitude + ' lat, ' + e.longitude + ' lon');
//add an annotation to the mapview for the start location
var annotation = Titanium.Map.createAnnotation({
latitude: e.latitude,
longitude: e.longitude,
title: 'Start location',
subtitle: txtStartLocation.value,
animate:true,
id: 1,
pincolor: Titanium.Map.ANNOTATION_GREEN
});
//add the annotation pin to the mapview
mapview.addAnnotation(annotation);
});

一旦你将此代码添加到forwardGeocoder方法中的起始位置,请对结束位置执行完全相同的操作,除了给结束位置一个'myid'属性值为2。稍后,当我们从标注捕获事件时,我们将使用这些自定义 ID 值;它们将使我们能够确定哪个标注图钉被点击。此外,为你的第二个标注提供一个pinColor属性为Titanium.Map.ANNOTATION_RED,这将有助于在地图上区分两个图钉。

在模拟器中加载你的应用程序,并指定起始位置和结束位置,然后按搜索——你应该在你的 MapView 上看到几个图钉,如下面的截图所示:

如何操作...

工作原理...

在你的搜索按钮函数和之前食谱中调用的forwardGeocoder方法中,是使用Titanium.Map.createAnnotation()创建一个新对象,该对象代表一个图钉图标,它被放置在地图上以标识特定位置,并且具有许多有趣的属性。除了标准的经纬度值外,它还可以接受标题和副标题,标题在标注顶部显示得更为突出,副标题位于其下方。你还应该给你的标注提供一个id属性(我们在这个例子中使用了id),这样在向MapView添加事件时更容易识别它们。这将在下一个食谱中进一步解释。

自定义标注和捕获 MapView 事件

标注也可以自定义,以便用户更好地了解你的位置符号代表什么。例如,如果你正在绘制特定区域的餐厅,你可以为每个标注提供一个图标,以表示其代表的餐厅类型——无论是意大利披萨切片、酒吧食物的品脱还是快餐连锁店的汉堡。

在本食谱中,我们将为起始位置和结束位置标注添加左侧图像,分别使用代表“起始”的“S”图标和代表“结束”的“E”图标,以帮助用户识别它们。我们还将为第一个图钉添加一个开始按钮,为第二个图钉添加一个停止按钮,我们将使用这些按钮来控制后续的练习计时器。

注意

本食谱的完整源代码可在/Chapter 3/Recipe 5文件夹中找到。

如何操作...

在声明注释之后,但在将其添加到mapView对象之前,输入以下代码以创建自定义的leftView和自定义rightButton。你应该对起始位置针和结束位置针都做同样的事情。

//add an image to the left of the annotation
var leftImage = Titanium.UI.createImageView({
image: 'images/start.png',
width: 25,
height: 25
});
annotation.leftView = leftImage;
//add the start button
var startButton = 'images/startbutton.png';
annotation.rightButton = startButton;
mapview.addAnnotation(annotation);

现在,让我们为mapView对象创建事件监听器。当用户在地图上点击任何注释时,此函数将执行。你应该将此代码放置在 JavaScript 的底部附近,就在mapView被添加到我们的窗口之前:

//create the event listener for when annotations
//are tapped on the map
mapview.addEventListener('click', function(e){
Ti.API.info('Annotation id that was tapped: ' + e.source.id);
Ti.API.info('Annotation button source that was tapped: ' + e.clicksource);
});

如何工作...

在这个食谱的开始,我们只是在每个注释上指向一些新的属性。我们的leftView通过使用“S”和“E”的图像图标被填充。注释还接受一个简单的 URL 字符串作为rightButton属性,正是在这里我们提供了“开始”和“停止”按钮的图像位置(两者都可以在源代码的images文件夹中找到)。

与其他事件监听器相比,mapView的事件监听器工作方式略有不同。你需要从mapView的父对象捕获注释点击,然后通过自定义 ID 来确定哪个注释被点击。在这种情况下,我们使用了id属性来确定哪个注释是起始位置,哪个是结束位置。起始位置被设置为id 1,而结束位置简单地设置为id 2

此外,你可能希望根据注释的右键或左键是否被点击执行不同的操作。我们可以通过使用事件属性的(e) clicksource来确定这一点。将字符串与leftButtonrightButton进行比较,可以让你知道哪个被点击了,并且你可以根据应用程序相应地编写函数。

如何工作...

在你的MapView上绘制路线

为了追踪我们的移动并在地图上绘制路线,我们需要创建一个包含各个点的数组,每个点都有其自己的纬度和经度值。MapView将接受这个点的数组作为名为route的属性,并绘制一系列线条,为用户提供路线的视觉表示。

在这个食谱中,我们将创建一个计时器,每分钟记录我们的位置,并将其添加到点数组中。当每个新点被记录时,我们将访问 Google Directions API 来确定距离,并将其添加到我们旅行的总距离中。

注意

注意,这个食谱在 Android 设备上可能无法工作,因为 Titanium 目前不支持 Android 路由。然而,它将如描述的那样在 iPhone 和 iPod Touch 上工作。Android 中有一个不受支持的路由方法,你可以在bit.ly/pUq2v2上阅读有关内容。你需要使用实际的 iPhone 或 iPod Touch 设备来测试这个食谱,因为模拟器无法获取你的当前位置。

本食谱的完整源代码可以在/Chapter 3/Recipe 6文件夹中找到。

如何实现...

在你的mapView点击事件中,在控制台日志确定哪个按钮被轻触以及哪个注释之后,输入以下代码:

//create the event listener for when annotations
//are tapped on the map
mapview.addEventListener('click', function(e){
Ti.API.info('Annotation id that was tapped: ' + e.source.id);
Ti.API.info('Annotation button source that was tapped: ' + e.clicksource);
Ti.API.info('Annotation button title that was tapped: ' + e.title);
if(timerStarted == false && (e.clicksource == 'rightButton' && e.title == 'Start location'))
{
Ti.API.info('Timer will start...');
points = [];
//set our first point
Ti.Geolocation.forwardGeocoder(txtStartLocation.value, function(e){
points.push({latitude: e.coords.latitude,
longitude: e.coords.longitude
});
route.points = points;
//add route to our mapview object
mapview.addRoute(route);
timerStarted = true;
//start our timer and refresh it every minute
//1 minute = 60,000 milliseconds
intTimer = setInterval(recordCurrentLocation,
60000);
});
}
else
{
//stop any running timer
if(timerStarted == true &&
(e.clicksource == 'rightButton'
&& e.title == 'End location'))
{
clearInterval(intTimer);
timerStarted = false;
alert('You travelled ' + distanceTraveled
+ ' meters!');
}
}
});

现在我们需要创建一些需要全局访问的变量。将以下代码添加到app.js文件的顶部:

//create the variables
var timerStarted = false;
var intTimer;
//this array will hold all the latitude and
//longitude points in our route
var points = [];
//this will hold the distance traveled
var distanceTraveled = 0;
//route object
var route = {
name: 'Exercise Route',
color: "#00f",
width: 2
};

接下来,我们需要创建一个函数来获取用户的新当前位置,并确定新位置与之前位置的距离。在mapView组件的点击事件上方创建这个新函数:

//this function records the current location and
//calculates distance between it and the last location,
//adding that to our overall distance count
function recordCurrentLocation()
{
Ti.API.info('getting next position...');
points.push({latitude:-27.466175,
longitude:153.030426
});
route.points = points;
//get the current position
Titanium.Geolocation.getCurrentPosition(function(e) {
var currLongitude = e.coords.longitude;
var currLatitude = e.coords.latitude;
points.push({latitude: e.currLatitude, longitude: e.currLongitude});
//add new point to route
route.points = points;
//remove the old route and add this new one
mapview.removeRoute(route);
mapview.addRoute(route);
});
//ask google for the distance between this point
//and the previous point in the points[] array
var url = 'http://maps.googleapis.com/maps/api/directions/json?travelMode=Walking&origin=' + points[points.length-2].latitude + ',' + points[points.length-2].longitude + '&destination=' + points[points.length-1].latitude + ',' + points[points.length-1].longitude + '&sensor=false';
var req = Ti.Network.createHTTPClient();
req.open('GET', url);
req.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
req.onreadystate = function(){};
req.onload = function()
{
//record the distance values
Ti.API.info(req.responseText);
var data = JSON.parse(req.responseText);
Ti.API.info("distance.text " + data.routes[0].legs[0].distance.text);
Ti.API.info("distance.value " + data.routes[0].legs[0].distance.value);
distanceTraveled = distanceTraveled + data.routes[0].legs[0].distance.value;
};
req.send();
}

它是如何工作的...

在这个菜谱中发生了很多事情,所以让我们逻辑地将其分解成各自的独立部分。首先,我们在启动按钮的click事件中再次获取用户的当前位置,并将其作为points数组中的第一个点。为了使mapView组件能够使用点位置数组,我们需要创建一个路线对象。这个路线对象包含点数组,以及路线的线条、颜色和粗细等视觉信息。

从那里,我们使用setInterval()创建一个计时器。这个计时器只有在timerStarted变量设置为 false,并且我们可以确定按钮轻触确实是我们的注释中的一个“开始”按钮时才会启动。

我们的时间计时器设置为每 60 秒执行一次,或者按照代码要求,60,000 毫秒。这意味着每分钟都会执行一次名为recordCurrentLocation()的函数。这个函数负责处理确定我们当前位置,并将新位置添加到我们的“points”数组中。然后,它会对 Google API 执行 HTTP 请求调用,计算我们最新点和之前所在位置之间的距离。这个新的距离会被添加到名为distanceTraveled的总距离变量中。

最后,当用户在结束注释上的停止按钮上轻触时,计时器停止,并显示一个alertDialog,显示用户已经走过的总距离(以米为单位)。以下截图显示了从起点到终点位置的路线绘制,以及当轻触停止按钮时的距离警报。

如何工作...

使用设备罗盘监控航向

在这个菜谱中,我们章节关于地图和 GPS 的最终菜谱,我们将使用内置的设备罗盘来确定你的航向。我们将使用箭头的图像来表示方向,直观地展示这个航向。

注意

注意,这个菜谱在较旧的 iPhone 设备上可能无法工作,例如 iPhone 3G,因为它缺少罗盘。你需要使用实际设备来测试这个菜谱,因为模拟器无法获取你的当前航向。

本菜谱的完整源代码可以在/Chapter 3/Recipe 7文件夹中找到。

本章构建的练习跟踪应用程序的完整源代码可以在/Chapter 3/Exercise Tracker文件夹中找到。

如何操作...

在文件的末尾执行win1.open()方法调用之前,将以下代码添加到你的app.js文件中:

//this image will appear over the map and indicate our
//current compass heading
var imageCompassArrow = Titanium.UI.createImageView({
image: 'images/arrow.gif',
width: 50,
height: 50,
right: 25,
top: 5
});
win1.add(imageCompassArrow);
//how to monitor your heading using the compass
if(Titanium.Geolocation.hasCompass)
{
//this is the degree of angle change our heading
//events don't fire unless this value changes
Titanium.Geolocation.headingFilter = 90;
//this event fires only once to get our intial
//heading and to set our compass "arrow" on screen
Ti.Geolocation.getCurrentHeading(function(e) {
if (e.error) {
return;
}
var x = e.heading.x;
var y = e.heading.y;
var z = e.heading.z;
var magneticHeading = e.heading.magneticHeading;
accuracy = e.heading.accuracy;
var trueHeading = e.heading.trueHeading;
timestamp = e.heading.timestamp;
var rotateArrow = Titanium.UI.create2DMatrix();
var angle = 360 - magneticHeading;
rotateArrow = rotateArrow.rotate(angle);
imageCompassArrow.transform = rotateArrow;
});
//this event will fire repeatedly depending on the change
//in angle of our heading filter
Titanium.Geolocation.addEventListener('heading',function(e) {
if (e.error) {
return;
}
var x = e.heading.x;
var y = e.heading.y;
var z = e.heading.z;
var magneticHeading = e.heading.magneticHeading;
accuracy = e.heading.accuracy;
var trueHeading = e.heading.trueHeading;
timestamp = e.heading.timestamp;
var rotateArrow = Titanium.UI.create2DMatrix();
var angle = 360 - magneticHeading;
rotateArrow = rotateArrow.rotate(angle);
imageCompassArrow.transform = rotateArrow;
});
}
else
{
//log an error to the console if this device has no compass
//older devices such as the iphone 3g don't have this
Titanium.API.info("No Compass on device");
//you can uncomment this to test rotation when using the emulator
//var rotateArrow = Titanium.UI.create2DMatrix();
//var angle = 45;
//rotateArrow = rotateArrow.rotate(angle);
//imageCompassArrow.transform = rotateArrow;
}

它是如何工作的...

我们首先创建一个imageView,并将其图像属性设置为我们的箭头图像。最初,它将面向屏幕顶部,指示北方。然后,我们将这个imageView添加到我们的Window对象中。这个菜谱的标题源代码执行了两个类似任务:一个获取我们的初始航向,另一个在设定的时间间隔内触发以获取我们的当前航向。当获取到当前位置或新位置的航向时,我们使用magneticHeading属性来确定我们面向的角度(方向),并使用简单的变换将箭头旋转到那个方向。

注意

如果你不理解什么是二维矩阵,或者变换是如何执行我们图像的旋转,请不要担心!我们将在第七章中介绍变换、旋转和动画。

第四章:使用音频、视频和相机增强应用

在本章中,我们将涵盖以下内容:

  • 使用 OptionDialog 选择捕获设备

  • 从相机捕获照片

  • 从照片库中选择现有照片

  • 使用 ScrollableView 显示照片

  • 将捕获的照片保存到设备文件系统

  • 通过音频录制器捕获和播放音频

  • 通过视频录制器捕获视频

  • 从文件系统播放视频文件

  • 安全地从文件系统中删除保存的文件

简介

虽然这可能难以相信,但使用手机无线拍摄照片并分享照片首次发生在 1997 年,直到大约 2004 年才流行起来。到 2010 年,几乎所有的手机都内置了数码相机,许多中端到高端设备也配备了音频和视频摄像机功能。现在,大多数 iPhone 和 Android 型号都具备这些功能,并为创业开发者开辟了新的途径。

Titanium 包含 API,允许你访问所有必需的接口,使用内置相机拍照或录像,录制音频,以及浏览设备中保存的图片和视频库。

在本章中,我们将介绍所有这些概念,并使用它们来构建一个基本的“假日回忆”应用,该应用将允许我们的用户从设备中捕获照片、视频和音频,将这些文件保存到本地文件存储,并再次读取它们。

前提条件

你应该已经熟悉 Titanium 基础知识,包括创建 UI 对象和使用 Titanium Studio。此外,为了测试相机功能,你需要一部能够录制照片和视频的 iPhone 或 Android 设备。iPhone 3GS 型号或更高版本就足够了,除了市场上最底端的 Android 手机外,所有 Android 手机都应该没问题。

使用 OptionDialog 选择捕获设备

OptionDialog 是一个仅模态的组件,允许你向用户展示一个或多个选项,通常还会包括一个取消选项,用于关闭对话框。我们将创建这个组件,并使用它向用户提供从相机或设备的照片库中选择图片的选项。

如果你打算跟随整个章节并构建假日回忆应用,那么请仔细注意这个食谱的第一个准备工作部分,因为它将指导你设置项目。

准备工作

为了准备这个食谱,打开 Titanium Studio 并登录(如果你还没有登录的话)。如果你需要注册新账户,你可以在应用程序内部免费注册。登录后,点击新建项目,创建新项目的详细信息窗口将出现。将应用名称输入为假日回忆,并使用你自己的信息填写其余的详细信息。

注意应用程序标识符,它通常以反向域名表示法书写(即 com.packtpub.holidaymemories)。在项目创建后,此标识符不能轻易更改,并且您在创建用于分发应用的配置文件时需要精确匹配它。您可以通过下载以下源文件来获取本食谱中使用的所有图像,以及整个章节的所有内容:

注意

本食谱的完整源代码可以在/Chapter 4/Recipe 1文件夹中找到。

整个章节的完整源代码可以在/Chapter 4/Holiday Memories文件夹中找到。

如何操作...

现在我们已经使用 Titanium Studio 创建了项目。让我们开始工作!在您的编辑器中打开app.js文件,并删除所有现有代码。完成此操作后,输入以下内容并保存:

//this sets the background color of the master UIView (when there are no windows/tab groups on it)
Titanium.UI.setBackgroundColor('#000');
//create tab group
var tabGroup = Titanium.UI.createTabGroup();
//
//create base UI tab and root window
//
var win1 = Titanium.UI.createWindow({
title:'Photos',
backgroundImage: 'images/background.png',
barColor: '#000',
url: 'photos.js'
});
var tab1 = Titanium.UI.createTab({
icon:'images/photos.png',
title:'Photos',
window:win1
});
//
//create tab and root window
//
var win2 = Titanium.UI.createWindow({
title:'Video',
backgroundImage: 'images/background.png',
barColor: '#000'
});
var tab2 = Titanium.UI.createTab({
icon:'images/movies.png',
title:'Video',
window:win2
});
//
//create tab and root window
//
var win3 = Titanium.UI.createWindow({
title:'Audio',
backgroundImage: 'images/background.png',
barColor: '#000'
});
var tab3 = Titanium.UI.createTab({
icon:'images/audio.png',
title:'Audio',
window:win3
});
//
//add tabs
//
tabGroup.addTab(tab1);
tabGroup.addTab(tab2);
tabGroup.addTab(tab3);
//open tab group
tabGroup.open();

现在我们需要创建我们的第一个窗口的 JavaScript 文件,我们将称之为photo.js。在您的Resources文件夹中创建此空白文件,并在您的 IDE 中打开它。输入以下代码以创建“选择照片”按钮,该按钮将实例化您的 OptionDialog:

var win = Titanium.UI.currentWindow;
//our dialog with the options of where to get an
//image from
var dialog = Titanium.UI.createOptionDialog({
title: 'Choose an image source...',
options: ['Camera','Photo Gallery', 'Cancel'],
cancel:2
});
//add event listener
dialog.addEventListener('click',function(e) {
Ti.API.info('You selected ' + e.index);
});
//choose a photo button
var btnGetPhoto = Titanium.UI.createButton({
title: 'Choose'
});
btnGetPhoto.addEventListener('click', function(e){
dialog.show();
});
//set the right nav button to our btnGetPhoto object
//note that we're checking the osname and changing the
//button location depending on if it's iphone/android
//this is explained further on in the "Platform Differences" chapter
if(Ti.Platform.osname == 'iphone') {
win.rightNavButton = btnGetPhoto;
}
else {
//add it to the main window because android does
//not have 'right nav button'
btnGetPhoto.right = 20;
btnGetPhoto.top = 20;
win.add(btnGetPhoto);
}

如何工作…

第一段代码中的代码正在创建我们的带有标签和窗口的导航视图,所有这些内容都在第一章、使用原生 UI 组件构建应用和第二章、与本地和远程数据源一起工作中有所介绍。我们的第一个标签和窗口使用photo.js文件,其内容在本页前面的第二段代码中可见。

OptionDialog 本身是通过Titanium.UI.createOptionDialog()方法创建的,并且只需要几个简单的参数。title参数,在这种情况下,出现在按钮选项的顶部,目的是向用户简要说明他们选择的选项将用于什么。在我们的情况下,我们只是通知他们我们将使用他们选择的选项来启动相应的图像捕获应用程序。选项数组是这里的重要属性,它包含您希望向用户展示的所有按钮选择。请注意,我们还在我们的数组中包含了一个cancel项,并且有一个相应的cancel属性,其索引与我们的createOptionDialog()中的相同。当我们的 OptionDialog 在屏幕上显示时,这将绘制不同的按钮样式以取消操作。

最后,我们在我们的 OptionDialog 中添加了一个事件监听器,并使用e.index属性将所选按钮索引输出到 Titanium Studio 控制台。我们将在下一个食谱中使用这个标志来根据用户的选择启动相机或照片库。下面的 OptionDialog 为用户提供两个图像源选项:

如何工作…

从相机捕获照片

要使用设备相机,我们需要访问 Titanium.Media 命名空间,特别是 showCamera 方法。这将显示用于拍照的本地操作系统界面,并公开三个事件,这些事件将帮助我们决定对捕获的图像进行什么操作。我们还将检查用户的设备是否能够拍摄照片,然后再尝试这样做,因为一些设备(包括 iPod Touch 和模拟器)没有这种功能。

注意

注意,此菜谱仅在您使用物理设备时才会生效!Android 和 iPhone 的模拟器都没有内置相机功能。如果您尝试在模拟器上运行此代码,您将只会看到一个错误对话框。

本菜谱的完整源代码可以在 /Chapter 4/Recipe 2 文件夹中找到。

如何操作…

我们将使用以下代码扩展我们的 OptionDialog 的事件监听器:

//add event listener
dialog.addEventListener('click',function(e)
{
Ti.API.info('You selected ' + e.index);
if(e.index == 0)
{
//from the camera
Titanium.Media.showCamera({
success:function(event)
{
var image = event.media;
if(event.mediaType == Ti.Media.MEDIA_TYPE_PHOTO)
{
//set image view
var imgView =
Titanium.UI.createImageView({
top: 20,
left: 20,
width: 280,
height: 320
});
imgView.image = image;
win.add(imgView);
}
},
cancel:function()
{
//getting image from camera was cancelled
},
error:function(error)
{
//create alert
var a = Titanium.UI.createAlertDialog({title:'Camera'});
// set message
if (error.code == Titanium.Media.NO_CAMERA)
{
a.setMessage('Device does not have image recording capabilities');
}
else
{
a.setMessage('Unexpected error: ' + error.code);
}
// show alert
a.show();
},
allowImageEditing:true,
saveToPhotoGallery:false
});
}
else
{
//cancel was tapped
//user opted not to choose a photo
}
});

现在将您的应用运行在物理设备上,您应该能够从我们的 OptionDialog 中选择相机按钮,并用您的设备拍摄一张照片。这张图片应该随后出现在我们的临时 ImageView 中,就像以下截图所示:

如何操作...

它是如何工作的…

从相机获取图片实际上相当简单。首先,您会注意到我们已经通过一个 if 语句扩展了 OptionDialog。如果我们的对话框的索引属性为 0(第一个按钮),那么我们正在启动相机。我们通过 Titanium.Media.showCamera() 方法来完成此操作。这将触发三个事件,我们在这里捕获它们,称为 成功、错误取消。我们忽略取消事件,因为如果用户选择取消图像捕获,则不需要进行任何处理。在错误事件中,我们将显示一个 AlertDialog,解释说无法启动相机。如果您使用模拟器运行此代码,您将看到此对话框。

我们的大部分处理都在成功事件中完成。首先,我们将捕获的照片保存到一个名为 image 的新变量中。然后,我们通过比较其 mediaType 属性来检查所选媒体实际上是否为照片。正是在这一点上,所选媒体实际上可能是一段视频,因此在使用它之前,我们必须再次确认它是什么,因为我们不知道用户是在拍照还是拍摄视频,直到事件发生后。最后,为了向用户展示我们实际上已经用我们的相机捕获了一张图片,我们创建了一个 ImageView 并将其 image 属性设置为捕获的图片文件,然后再将整个内容添加到我们的窗口中。

从相册中选择现有照片

在设备上从相册中选择图片的过程与使用摄像头的非常相似。然而,我们仍然使用 Titanium.Media 命名空间,但这次我们将执行名为 openPhotoLibrary() 的方法,正如其名所示。与之前的配方一样,一旦我们从相册中检索到一张图片,我们将使用简单的 ImageView 控件将其显示在屏幕上供用户查看。

注意

本配方的完整源代码可在 /Chapter 4/Recipe 3 文件夹中找到。

如何操作...

我们将扩展我们的 OptionDialog,以便现在可以选择从相册中选择一张图片,如果 index 属性为 1(第二个按钮)被选中。将以下代码添加到对话框的事件监听器中:

//add event listener
dialog.addEventListener('click',function(e)
{
Ti.API.info('You selected ' + e.index);
if(e.index == 1)
{
//obtain an image from the gallery
Titanium.Media.openPhotoGallery({
success:function(event)
{
var image = event.media;
// set image view
Ti.API.debug('Our type was: '+event.mediaType);
if(event.mediaType == Ti.Media.MEDIA_TYPE_PHOTO)
{
var imgView = Titanium.UI.createImageView({
top: 20,
left: 20,
width: 280,
height: 320
});
imgView.image = image;
win.add(imgView);
}
},
cancel:function()
{
//user cancelled the action from within
//the photo gallery
}
});
}
else
{
//cancel was tapped
//user opted not to choose a photo
}
});

在模拟器或设备上运行您的应用,并从我们的对话框中选择第二个选项。相册库应该会出现,并允许您选择一张图片。此选择屏幕看起来可能如下截图所示:

如何操作...

它是如何工作的…

这个配方基本上遵循了我们使用摄像头获取图片时的相同模式。首先,我们扩展了 OptionDialog 事件监听器,以便在按钮索引选中的等于 1 时执行操作,在这种情况下是我们的相册按钮。我们的 openPhotoGallery() 方法也会触发三个事件:成功、错误取消

就像之前的配方一样,我们的大部分处理都在成功事件中完成。我们通过比较其 mediaType 属性来检查所选媒体是否实际上是一张照片,并最终创建一个 ImageView 并将其 image 属性设置为捕获的图片文件,然后将整个内容添加到我们的窗口中。

还有更多...

现在,让我们探索媒体类型。

理解媒体类型

当通过内置摄像头捕获照片或视频时,您可以通过 mediaType 枚举获得两种主要的媒体类型。这些是:

  • MEDIA_TYPE_PHOTO

  • MEDIA_TYPE_VIDEO

此外,还有许多其他更具体的 mediaType 枚举集,包括以下内容:

  • MUSIC_MEDIA_TYPE_ALL

  • MUSIC_MEDIA_TYPE_ANY_AUDIO

  • MUSIC_MEDIA_TYPE_AUDIOBOOK

  • MUSIC_MEDIA_TYPE_MUSIC

  • MUSIC_MEDIA_TYPE_PODCAST

  • VIDEO_MEDIA_TYPE_AUDIO

  • VIDEO_MEDIA_TYPE_NONE

  • VIDEO_MEDIA_TYPE_VIDEO

这些类型通常仅在利用 VideoPlayerAudioPlayer 组件中的 mediaType 属性时适用。

保存到照片

您可以在模拟器中运行此代码,但您可能会注意到库中没有图片,也没有明显的获取图片的方法!幸运的是,这实际上相当容易克服。只需打开网络浏览器,并使用 Google Images 或类似服务找到您想要测试的图片。在浏览器中点击并按住图片,您应该会看到一个选项保存到照片。然后您可以使用这些图片在模拟器中测试您的代码。

使用 ScrollableView 显示照片

在移动设备上显示多张照片和图像的最常见方法之一是 ScrollableView。这种视图类型允许图片左右滑动,在许多应用中都很常见,包括 Facebook 移动应用。以这种方式显示图片的方式让人联想到“翻书”或“翻相册”,因为它自然的感觉和简单的实现而非常受欢迎。

在这个食谱中,我们将实现一个 ScrollableView,它将包含可以从相机或照片库选择的任意数量的图片。这个食谱的完整源代码可以在 /Chapter 4/Recipe 4 文件夹中找到。

注意

注意,这个食谱应该适用于 Android 和 iPhone 设备。然而,Titanium SDK 最新版本(1.7.2)中的一个最近发现的错误可能会导致它在 Android 上失败。如果你想在 Android 模拟器上测试这个,你应该检查你是否在使用 Titanium SDK 的最新版本。

如何做到这一点...

首先,让我们创建我们的 ScrollableView 对象,我们将称之为 scrollingView,并将其添加到我们的 photo.js 窗口中:

//this is the scroll view the user will use to swipe
//through the selected photos
scrollingView = Titanium.UI.createScrollableView({
left: 17,
top: 15,
width: win.width - 14,
height: win.height - 25,
views: [],
currentPage: 0,
zIndex: 1
});
scrollingView.addEventListener('scroll',function(e){
Ti.API.info('Current scrollableView page = ' + e.source.currentPage);
});
win.add(scrollingView);

现在我们将修改对话框事件监听器,以便将我们选定的照片分配给 ScrollableView,而不是我们之前创建的临时 ImageView。将你的 if(event.mediaType == Ti.Media.MEDIA_TYPE_PHOTO) 代码块及其内部的所有代码替换为以下代码。注意,你需要对从照片库和设备相机收集的图像都这样做。

...
//output the mediaType to the console log for debugging
Ti.API.debug('Our type was: '+event.mediaType);
if(event.mediaType == Ti.Media.MEDIA_TYPE_PHOTO)
{
// set image view
var imgView = Titanium.UI.createImageView({
top: 0,
left: 0,
width: 286,
height: 337,
image: image
});
//add the imageView to our scrollableView object
scrollingView.addView(imgView);
}

现在在你的模拟器或设备上运行你的应用,并依次选择一些图片。你可以使用来自相机或照片库的图片组合。一旦你选择了至少两张图片,你应该能够通过左右滑动来在它们之间切换。

它是如何工作的...

ScrollableView 实际上只是一个包含许多特殊事件和属性的视图集合,正如你可能通过我们在 createScrollableView() 方法中给 views 属性赋予的空数组值所察觉到的。在实例化 ScrollableView 对象时设置此属性是必要的,将 currentPage 索引属性设置为 0(即我们的第一个视图)也是一个好主意。我们仍然按照之前的食谱创建 ImageView。然而,这次我们不是将这个视图添加到窗口中,而是添加到 ScrollableView 组件中。我们通过使用 addView() 方法添加视图来实现这一点。最后,我们还创建了一个事件,将其附加到我们的 ScrollableView 上,并输出 currentPage 属性到 Titanium 控制台以进行调试和测试。

如您所见,ScrollableView 是一个简单且易于使用的组件,非常适合用于相册应用或任何其他需要显示一系列相似视图的应用。您可以通过添加空白 View 对象并放置任意数量的文本字段、标签或图像视图来扩展它,每个空白视图中都可以放置您想要的任何内容——“这里的唯一限制就是您的想象力!”

如何工作…

将捕获的图片保存到设备文件系统中

拍照固然很好,但当我们希望将图像保存到文件系统中以便稍后再次检索时怎么办?在这个菜谱中,我们将做这件事,并介绍内置在许多 Titanium 控件中的 toImage() 方法。此方法接受被调用视图的整个视图的扁平化图像,对于截图或抓取单个视图中组合在一起的多个控件图像非常有用。

注意

本菜谱的完整源代码可以在 /Chapter 4/Recipe 5 文件夹中找到。

如何操作...

在创建 btnGetPhoto 对象之后,输入以下代码。您可以替换我们编写的将 btnGetPhoto 对象添加到导航栏的现有代码,因为这段代码重复了那段代码并进行了扩展。

//save a photo to file system button
var btnSaveCurrentPhoto = Titanium.UI.createButton({
title: 'Save Photo',
zIndex: 2 //this appears over top of other components
});
btnSaveCurrentPhoto.addEventListener('click', function(e){
var media = scrollingView.toImage();
//if it doesn't exist, create it create a directory called
//"photos"
//and it will hold our saved images
var newDir = Titanium.Filesystem.getFile (Titanium.Filesystem.applicationDataDirectory,'photos');
if(!newDir.exists()){
newDir.createDirectory();
}
var fileName = 'photo-' + scrollingView.currentPage.toString() + '.png';
writeFile = Titanium.Filesystem.getFile(newDir.nativePath, fileName);
writeFile.write(media);
alert('You saved a file called ' + fileName + ' to the directory ' + newDir.nativePath);
var _imageFile = Titanium.Filesystem.getFile(newDir.nativePath, fileName);
if (!_imageFile.exists()) {
Ti.API.info('ERROR: The file ' + fileName + ' in the directory ' + newDir.nativePath + ' does not exist!');
}
else {
Ti.API.info('OKAY!: The file ' + fileName + ' in the directory ' + newDir.nativePath + ' does exist!');
}
});
//set the right nav button to our photo get button
if(Ti.Platform.osname == 'iphone') {
win.leftNavButton = btnSaveCurrentPhoto;
win.rightNavButton = btnGetPhoto;
}
else
{
//add it to the main window because android does
//not have 'right nav button'
btnGetPhoto.right = 20;
btnGetPhoto.top = 20;
win.add(btnGetPhoto);
//add it to the main window because android does
//not have 'left nav button'
btnSaveCurrentPhoto.left = 20;
btnSaveCurrentPhoto.top = 20;
win.add(btnSaveCurrentPhoto);
}

它是如何工作的...

Titanium.FileSystem 命名空间提供了一系列文件操作功能,但最重要的是,它为我们提供了基本工具,以便将文件读写到设备上应用程序的存储空间。在这个菜谱中,我们使用 scrollingViewtoImage() 方法来返回视图图像表示的 blob。

然后,我们可以获取我们希望存储图像文件数据的文件夹的引用。如您在代码中所见,我们通过创建一个新变量,例如 var newDir = Titanium.Filesystem.getFile(Titanium.Filesystem.applicationDataDirectory,'photos') 来获取该文件夹的引用;然后确保该文件夹存在。如果不存在,我们可以通过在 newDir 对象上调用 createDirectory() 方法来创建该文件夹。

最后,我们的图像数据以类似的方式保存。首先,我们创建一个名为 writeFile 的变量,它引用我们已创建的 newDir 对象文件夹中的文件名。然后,我们可以使用 writeFile"write()" 方法将文件输出到文件系统,并将图像媒体变量作为要保存的文件数据传递。

通过音频录制器捕获和播放音频

iPhone 和大多数 Android 手机的一个方便功能是能够录制音频数据——非常适合在会议或那些漫长、无聊的讲座中做可听笔记!在这个菜谱中,我们将使用 Titanium.Media.AudioRecorder 类捕获一些音频,然后允许用户播放录制的声音文件。

本菜谱的完整源代码可以在 /Chapter 4/Recipe 6 文件夹中找到。

注意

注意,这个菜谱只适用于 iPhone。您还需要一个物理设备来完成这个菜谱。Titanium 框架的后续版本应该可以通过使用意图来支持 Android 的音频录制。此外,iPhone 3G 型号可能无法录制以下某些压缩格式,尤其是高保真格式,如 AAC。如有疑问,您应该尝试使用 MP4A 或 WAV 格式。

准备工作

创建一个名为audio.js的新 JavaScript 文件,并将其保存到您的资源目录中。然后在app.js文件中,添加 window3 的 URL 属性,并给它一个值为audio.js。这将加载我们的视频 JavaScript 文件,用于应用程序的第三个选项卡窗口。

如何操作...

将以下代码输入到您的audio.js文件中并保存。这将设置一个带有按钮和标签的界面,以便我们可以开始、停止和播放我们的录音音频。

var win = Titanium.UI.currentWindow;
var file;
var timer;
var sound;
var duration = 0;
var label = Titanium.UI.createLabel({
text:'',
top:150,
color:'#999',
textAlign:'center',
width: 250,
height:'auto'
});
win.add(label);
var linetype = Titanium.UI.createLabel({
text: "audio line type: "+lineTypeToStr(),
bottom: 15,
color:'#999',
textAlign:'center',
width: 250,
height:'auto'
});
win.add(linetype);
var volume = Titanium.UI.createLabel({
text: "volume: "+Ti.Media.volume,
bottom:50,
color:'#999',
textAlign:'center',
width: 250,
height:'auto'
});
win.add(volume);
var switchLabel = Titanium.UI.createLabel({
text:'Hi-fidelity:',
width: 250,
height:'auto',
textAlign:'center',
color:'#999',
bottom:115
});
var switcher = Titanium.UI.createSwitch({
value:false,
bottom:80
});
win.add(switchLabel);
win.add(switcher);
var b2 = Titanium.UI.createButton({
title:'Playback Recording',
width:200,
height:40,
top:80
});
win.add(b2);
var b1 = Titanium.UI.createButton({
title:'Start Recording',
width:200,
height:40,
top:20
});
win.add(b1);

现在,在模拟器中运行您的应用程序,并切换到音频选项卡。你应该会看到一个看起来就像以下截图的屏幕:

如何操作...

现在,我们将创建一个名为recordingAudioRecorder方法的对象实例,并给它一个压缩值和格式值。我们还将添加事件监听器以监视音量、音频线路和录音属性何时发生变化,以及事件处理程序来捕获和处理这些变化。在上一页创建的代码之后直接输入以下内容:

var recording = Ti.Media.createAudioRecorder();
// default compression is Ti.Media.AUDIO_FORMAT_LINEAR_PCM
// default format is Ti.Media.AUDIO_FILEFORMAT_CAF
// this will give us a wave file with µLaw compression which
// is a generally small size and suitable for telephony //recording for high end quality, you'll want LINEAR PCM
//however, that will result in uncompressed audio and will be //very large in size
recording.compression = Ti.Media.AUDIO_FORMAT_LINEAR_PCM;
recording.format = Ti.Media.AUDIO_FILEFORMAT_CAF;
Ti.Media.addEventListener('recordinginput', function(e) {
Ti.API.info('Input availability changed: '+e.available);
if (!e.available && recording.recording) {
b1.fireEvent('click', {});
}
});
Ti.Media.addEventListener('linechange',function(e)
{
linetype.text = "audio line type: "+lineTypeToStr();
});
Ti.Media.addEventListener('volume',function(e)
{
volume.text = "volume: "+e.volume;
});

最后,在您之前创建的Ti.Media事件监听器之后添加代码部分。此代码将处理音频输入控制的所有事件(停止/开始按钮和我们的高保真开关)。

function lineTypeToStr()
{
var type = Ti.Media.audioLineType;
switch(type)
{
case Ti.Media.AUDIO_HEADSET_INOUT:
return "headset";
case Ti.Media.AUDIO_RECEIVER_AND_MIC:
return "receiver/mic";
case Ti.Media.AUDIO_HEADPHONES_AND_MIC:
return "headphones/mic";
case Ti.Media.AUDIO_HEADPHONES:
return "headphones";
case Ti.Media.AUDIO_LINEOUT:
return "lineout";
case Ti.Media.AUDIO_SPEAKER:
return "speaker";
case Ti.Media.AUDIO_MICROPHONE:
return "microphone";
case Ti.Media.AUDIO_MUTED:
return "silence switch on";
case Ti.Media.AUDIO_UNAVAILABLE:
return "unavailable";
case Ti.Media.AUDIO_UNKNOWN:
return "unknown";
}
}
function showLevels()
{
var peak = Ti.Media.peakMicrophonePower;
var avg = Ti.Media.averageMicrophonePower;
duration++;
label.text = 'duration: '+duration+' seconds\npeak power:
' + peak +'\navg power: ' +avg;
}
b1.addEventListener('click', function()
{
if (b1.title == "Stop Recording")
{
file = recording.stop();
b1.title = "Start Recording";
b2.show();
clearInterval(timer);
Ti.Media.stopMicrophoneMonitor();
}
else
{
if (!Ti.Media.canRecord) {
Ti.UI.createAlertDialog({
title:'Error!',
message:'No audio recording hardware is currently
connected.'
}).show();
return;
}
b1.title = "Stop Recording";
recording.start();
b2.hide();
Ti.Media.startMicrophoneMonitor();
duration = 0;
timer = setInterval(showLevels,1000);
}
});
b2.addEventListener('click', function()
{
if (sound && sound.playing)
{
sound.stop();
sound.release();
sound = null;
b2.title = 'Playback Recording';
}
else
{
Ti.API.info("recording file size: "+file.size);
sound = Titanium.Media.createSound({sound:file});
sound.addEventListener('complete', function()
{
b2.title = 'Playback Recording';
});
sound.play();
b2.title = 'Stop Playback';
}
});
switcher.addEventListener('change',function(e)
{
if (!switcher.value)
{
recording.compression = Ti.Media.AUDIO_FORMAT_ULAW;
}
else
{
recording.compression = Ti.Media.AUDIO_FORMAT_LINEAR_PCM;
}
});

现在,在设备上运行您的应用程序(模拟器可能无法录制音频),你应该能够开始、停止,然后播放您的音频录音,同时高保真开关将改变音频压缩为更高保真度的格式。

它是如何工作的...

在这个菜谱中,我们正在创建一个AudioRecorder对象的实例,并将其命名为recording。我们给这个对象设置了压缩和音频格式。目前,我们将其设置为默认值(PCM 压缩和标准 CAF 格式)。然后,我们添加了来自Titanium.Media命名空间的事件监听器,当它们被触发时,将分别更改线条类型或音量标签。

此菜谱的主要处理发生在“开始/停止”和“播放”按钮的事件处理器中,分别称为b1b2。我们的第一个按钮b1首先检查其标题以确定是停止还是开始录音,通过一个简单的 if 语句。如果没有开始录音,那么我们通过调用我们的recording对象的start方法启动这个过程。为此,我们还需要启动麦克风监控器,这是通过执行Ti.Media.startMicrophoneMonitor()行来完成的。然后我们的设备将开始录音。再次点击b1按钮将执行停止代码,并同时将我们的文件对象(生成的声音音频文件)设置为recording对象的输出。

b2按钮事件处理器检查我们是否有一个有效的声音文件,以及它是否已经在播放。如果我们有一个有效的文件并且它正在播放,那么播放将停止。否则,如果有有效的声音文件并且它还没有通过扬声器播放过,我们将创建一个新的对象,称为sound,使用Titanium.Media.createSound方法。此方法需要一个sound参数。我们将创建于我们的录音会话期间的file对象传递给它。然后执行sound对象的play方法开始播放,同时播放完成的事件监听器/处理器在播放完成后重置我们的b2按钮标题。

最后,开关(在这个例子中称为switcher)只是将录制格式从高保真压缩更改为低压缩。质量和压缩越低,最终生成的音频文件将越小。

通过视频录制捕获视频

您还可以使用 iPhone(3GS 及以上)或 Android 设备的内置相机录制视频。您可以录制的视频质量和长度取决于您的设备内存能力和硬件中包含的相机类型。然而,您至少应该能够以 VGA 分辨率捕获短视频剪辑作为最低要求。

在这个菜谱中,我们将为我们的视频标签创建一个基本界面,包括一个录制按钮,该按钮将启动相机并在您的设备上录制视频。我们还将以两种不同的方式执行此操作:使用 iPhone 的标准 Titanium 代码和使用 Android 的意图。

注意

注意,此菜谱将需要一个物理设备进行测试。此外,iPhone 3G 型号无法录制视频,但所有从 3GS 开始的型号应该都没有问题。

准备工作

创建一个名为video.js的新 JavaScript 文件,并将其保存到您的资源文件夹中。然后,在app.js文件中,添加 window2 的 URL 属性并给它一个值为video.js。这将加载我们的视频 JavaScript 文件作为应用程序第二个标签窗口的文件。

注意

此菜谱的完整源代码可以在/Chapter 4/Recipe 7文件夹中找到。

如何操作...

首先,让我们设置基本界面,添加一个录制按钮(在 iPhone 的导航栏部分作为正常按钮,在 Android 上作为正常按钮),以及videoFile变量。这个变量将保存我们录制视频的路径作为一个字符串。

var win = Titanium.UI.currentWindow;
var videoFile = 'video/video-test.mp4';
var btnGetVideo = Titanium.UI.createButton({
title: 'Record Video'
});
//set the right nav butto to our get button
if(Ti.Platform.osname == 'iphone') {
win.rightNavButton = btnGetVideo;
}
else {
//add it to the main window because android does
//not have 'right nav button'
btnGetVideo.right = 20;
btnGetVideo.top = 20;
win.add(btnGetVideo);
}

现在让我们为录制按钮创建事件监听器和处理代码。这将检查我们的当前平台(iPhone 或 Android),并执行正确平台的录制视频代码:

//get video from the device
btnGetVideo.addEventListener('click', function()
{
if(Titanium.Platform.osname == 'iphone') {
//record for iphone
Titanium.Media.showCamera({
success:function(event)
{
var video = event.media;
movieFile = Titanium.Filesystem.getFile(
Titanium.Filesystem.applicationDataDirectory,
mymovie.mov');
movieFile.write(video);
videoFile = movieFile.nativePath;
btnGetVideo.title = 'Play Video';
},
cancel:function()
{
},
error:function(error)
{
// create alert
var a =
Titanium.UI.createAlertDialog({title:'Video'});
// set message
if (error.code == Titanium.Media.NO_VIDEO)
{
a.setMessage('Device does not have video recording
capabilities');
}
else
{
a.setMessage('Unexpected error: ' + error.code);
}
// show alert
a.show();
},
mediaTypes: Titanium.Media.MEDIA_TYPE_VIDEO,
videoMaximumDuration:10000,
videoQuality:Titanium.Media.QUALITY_HIGH
});
}
else
{
//record for android using intents
var intent = Titanium.Android.createIntent({
action: 'android.media.action.VIDEO_CAPTURE'
});
Titanium.Android.currentActivity.startActivityForResult(
intent, function(e) {
if (e.error) {
Ti.UI.createNotification({
duration: Ti.UI.NOTIFICATION_DURATION_LONG,
message: 'Error: ' + e.error
}).show();
}
else {
if (e.resultCode === Titanium.Android.RESULT_OK) {
videoFile = e.intent.data;
var source = Ti.Filesystem.getFile(videoFile);
movieFile =
Titanium.Filesystem.getFile(
Titanium.Filesystem.applicationDataDirectory,
'mymovie.3gp');
source.copy(movieFile.nativePath);
videoFile = movieFile.nativePath;
btnGetVideo.title = 'Play Video';
}
else {
Ti.UI.createNotification({
duration: Ti.UI.NOTIFICATION_DURATION_LONG,
message: 'Canceled/Error? Result code: ' +
e.resultCode
}).show();
}
}
});
}
});

它是如何工作的…

让我们先从 iPhone 设备的录制代码开始,如前一段代码中提到的if(Titanium.Plaform.osname == 'iphone')语句封装。在这里,我们以与捕获普通照片相同的方式执行相机,但是传递了额外的参数。第一个参数称为mediaType,它告诉设备我们想要捕获一个mediaTypeMEDIA_TYPE_VIDEO的媒体类型。

其他两个参数定义了录制视频的时长和质量。参数videoMaximumDuration是一个浮点数,定义了录制时长(在完成前允许捕获的毫秒数),而videoQuality常量表示捕获期间的视频质量。我们将其设置为 10 秒(10,000 毫秒)和“高”质量。

在成功录制视频后,我们使用与保存照片时几乎相同的方法将event.media(我们的原始格式视频)保存到文件系统。最后一步是将videoFile路径设置为文件系统中新保存的视频文件的位置。

对于 Android,我们以不同的方式捕获视频,使用一个intent。在这种情况下,我们使用名为android.media.action.VIDEO_CAPTURE的视频捕获 intent。android.content.Intent类型的对象用于在您的应用程序内或应用程序之间发送异步消息。Intents 允许应用程序向其他活动或服务发送或接收数据。它们还允许它广播某个事件已发生。在我们的食谱代码中,我们执行我们的 Intent 并捕获结果——如果resultCode等于Titanium.Android.RESULT_OK,那么我们知道我们已经成功录制了一段视频。然后我们可以将此文件从其临时存储位置移动到我们选择的新位置。

注意

注意,我们在 Android 上以 3GP 格式捕获视频,而在 iPhone 上则是 MP4/MOV 格式。

从文件系统播放视频文件

现在我们已经录制了视频,那么如何播放它呢?Titanium 内置了一个视频播放组件,可以播放本地文件和远程视频 URL。在本食谱中,我们将向您展示如何创建视频播放控件,并将之前食谱中捕获的视频的本地文件 URL 传递给它。

注意

本食谱的完整源代码可以在/Chapter 4/Recipe 8文件夹中找到。

如何实现…

在我们的 videos.js 文件中,在 videoFile 对象声明下方,创建以下函数:

function playMovie(){
//create the video player and add it to our window
//note the url property can be a remote url or a local file
var my_movie = Titanium.Media.createVideoPlayer({
url: videoFile,
width: 280,
height: 200,
top:20,
left:20,
backgroundColor:'#000'
});
win.add(my_movie);
my_movie.play();
}

然后,在你的 btnGetVideo 事件监听器中扩展代码,以便它检查按钮标题,并在视频已保存时播放记录的视频:

//get video from the device
btnGetVideo.addEventListener('click', function()
{
if(btnGetVideo.title == 'Record Video') {
//our record video code from the previous recipe
//...
}
else
{
playMovie();
}
});

它是如何工作的…

创建视频播放器对象与创建标签或按钮没有区别,因为许多相同的属性都用于定位和布局。播放器可以被嵌入到任何其他视图中,就像使用正常控件一样,这意味着如果你愿意,可以在 TableView 的行中直接播放视频缩略图。此外,视频播放器可以播放本地和远程视频(使用视频 url 属性)。在本菜谱中,我们正在上传由设备上的摄像机捕获的已保存视频。

你同样可以从 URL 或直接从你的 Resources 文件夹中加载视频。本章源代码中附有一个 10 秒的视频,格式为 mp43gp,你可以用来测试,名为 video-test.mp4。你也可以尝试使用以下网络地址远程加载它:boydlee.com/video-test.mp4

注意

注意,某些网络视频格式,如 FLV,不受支持。

还有更多…

如果你希望视频使用全屏尺寸播放,而不仅仅是视图内,那么你可以将其 fullscreen 属性设置为 true。这将在视频开始播放时自动将其加载到全屏模式。

还有更多…

安全地从文件系统中删除已保存的文件

我们可以创建这些文件并将它们写入我们的本地手机存储,但删除它们怎么办?在本菜谱中,我们将解释如何使用 Titanium.Filesystem.File 命名空间安全地检查和删除文件。

注意

本菜谱的完整源代码可以在 /Chapter 4/Recipe 9 文件夹中找到。

如何操作…

在你的 photos.js 文件中,在文件末尾添加以下按钮代码,并添加一个事件监听器。这将是我们“垃圾桶”按钮,它将在当前选定的图像上调用删除函数。

//create trash button
var buttonTrash = Titanium.UI.createButton({
width: 25,
height: 25,
right: 25,
bottom: 25,
image: 'images/trash.jpg',
zIndex: 2,
visible: false
});
//create event listener for trash button
buttonTrash.addEventListener('click', function(e){
});

在我们现有的 btnSaveCurrentPhoto 点击事件中添加一个额外的行,以便在照片实际上已保存到磁盘后,我们的垃圾桶按钮才可见:

btnSaveCurrentPhoto.addEventListener('click', function(e){
buttonTrash.visible = true;
});

最后,扩展按钮的事件监听器以删除文件,但在添加按钮到窗口之前,确保它已经存在:

buttonTrash.addEventListener('click', function(e){
var photosDir = Titanium.Filesystem.getFile (Titanium.Filesystem.applicationDataDirectory,'photos');
var fileName = 'photo-' + scrollingView.currentPage.toString() + '.png';
var imageFile = Titanium.Filesystem.getFile (photosDir.nativePath, fileName);
if (imageFile.exists()) {
//then we can delete it because it exists
imageFile.deleteFile();
alert('Your file ' + fileName + ' was deleted!');
}
});
win.add(buttonTrash);

它是如何工作的…

文件操作全部是通过文件对象上的方法完成的,与许多其他语言中通常意味着将文件对象传递到删除函数中删除的情况不同。在我们的配方中,你可以看到我们像之前在保存照片到文件系统配方中那样简单地创建文件对象。但是,我们不是将对象写入磁盘,而是检查其存在,然后调用 [file-object].deleteFile()。在 Titanium 中所有文件操作都是以这种方式完成的。例如,如果你想重命名文件,你只需创建对象并调用 rename() 方法,传入新的字符串参数。

你可能也注意到了,我们给垃圾按钮设置了一个名为 zIndex 的参数,并将其设置为 2zIndex 定义了组件的堆叠顺序。zIndex 较高的组件将始终显示在 zIndex 较低的组件之上。在这种情况下,我们给垃圾按钮分配了一个 2 的索引,这样它就会显示在其他默认 zIndex 值为 0 的元素之上。

以下截图显示了新保存的文件上可见的垃圾箱图标,以及确认从文件系统中删除的消息提示:

如何工作…

还有更多...

Titanium.Filesystem.File 方法的完整列表可以在 Appcelerator 的网站上找到,位于当前 API 文档下,网址为:developer.appcelerator.com/apidoc/mobile/latest/Titanium.Filesystem.File-object

第五章:连接你的应用程序与社交媒体和电子邮件

在本章中,我们将涵盖:

  • 编写和发送电子邮件

  • 向电子邮件添加附件

  • 设置自定义 Facebook 应用程序

  • 将 Facebook 集成到你的 Titanium 应用程序中

  • 在你的 Facebook 墙上发布

  • 使用 OAuth 连接到 Twitter

  • 使用 PHP 和 HttpRequest 上传图片

  • 通过 Birdhouse 和 OAuth 发送推文

简介

曾经被认为是古怪 Y 世代的地盘,社交媒体在过去几年里以指数级增长,成为网络最热门的领域。Facebook 现在拥有超过 5 亿用户,是美国人口的两倍!Twitter 曾经是人们分享早餐吃了什么的地方,现在它成为了许多人获取突发新闻的首选。智能手机和移动应用程序的兴起加速了这些社交网络服务的增长,因为在线社交不再局限于桌面。人们可以在火车上、在车里,几乎在任何地方使用 Facebook 和 Twitter 等众多服务。

正因为这些服务如此普遍,现在许多人期望它们能够作为应用程序内的标准服务。一个简单的应用程序,比如列出新闻网站 RSS 源的应用程序,当用户可以一键推文、发布或发送文章时,就变得更加有用。在本章中,我们将从原始的社交沟通媒介电子邮件开始,然后再继续展示如何将世界上最大的社交网络服务 Facebook 和 Twitter 集成到你的应用程序中。

先决条件

你应该已经熟悉 Titanium 基础知识,包括创建 UI 对象和使用 Titanium Studio。此外,为了测试功能,你需要一个 Twitter 账户和一个 Facebook 账户。你还需要在你的 iPhone 或 Android 设备上设置电子邮件账户。

注意

整个章节的完整源代码可以在/Chapter 5/PhotoShare中找到。

编写和发送电子邮件

我们将从这个章节开始,以最简单的社交沟通形式,无论是使用还是开发——电子邮件。

如果你打算跟随整个章节并构建 PhotoShare 应用程序,那么请仔细注意这个菜谱的第一个准备就绪部分,因为它将指导你设置项目。

准备就绪

为了准备这个食谱,打开 Titanium Studio 并登录,如果你还没有这样做的话。如果你需要注册一个新账户,你可以在应用程序内部免费注册。登录后,点击“新建项目”,创建新项目的详细信息窗口将出现。将应用程序名称输入为PhotoShare,并使用你自己的信息填写其余的详细信息。

注意

本食谱的完整源代码可以在/第五章/食谱 1文件夹中找到。

如何操作...

我们现在已经使用 Titanium Studio 创建了项目。让我们开始吧!在你的编辑器中打开app.js文件,并删除所有现有的代码。完成之后,输入以下内容并保存:

// this sets the background color of the master UIView (when there are no windows/tab groups on it)
Titanium.UI.setBackgroundColor('#000');
//this variable will hold our image data blob from the device's gallery
var selectedImage = null;
var win1 = Titanium.UI.createWindow({
title:'Tab 1',
backgroundImage: 'images/background.jpg'
});
var label = Titanium.UI.createLabel({
width: 280,
height: 'auto',
top: 20,
left: 20,
color: '#fff',
font: {fontSize: 18, fontFamily: 'Helvetica', fontWeight: 'bold'},
text: 'Photo Share: \nEmail, Facebook & Twitter'
});
win1.add(label);
var imageThumbnail = Titanium.UI.createImageView({
width: 100,
height: 120,
left: 20,
top: 90,
backgroundColor: '#000',
borderSize: 10,
borderColor: '#fff'
});
win1.add(imageThumbnail);
var buttonSelectImage = Titanium.UI.createButton({
width: 100,
height: 30,
top: 220,
left: 20,
title: 'Choose'
});
buttonSelectImage.addEventListener('click',function(e){
//obtain an image from the gallery
Titanium.Media.openPhotoGallery({
success:function(event)
{
selectedImage = event.media;
// set image view
Ti.API.debug('Our type was: '+event.mediaType);
if(event.mediaType == Ti.Media.MEDIA_TYPE_PHOTO)
{
imageThumbnail.image = selectedImage;
}
},
cancel:function()
{
//user cancelled the action from within
//the photo gallery
}
});
});
win1.add(buttonSelectImage);
var txtTitle = Titanium.UI.createTextField({
width: 160,
height: 35,
left: 140,
top: 90,
value: 'Message title...',
borderStyle: 2,
backgroundColor: '#fff'
});
win1.add(txtTitle);
var txtMessage = Titanium.UI.createTextArea({
width: 160,
height: 120,
left: 140,
top: 130,
value: 'Message text...',
font: {fontSize: 15},
borderStyle: 2,
backgroundColor: '#fff'
});
win1.add(txtMessage);
win1.open();

之前的代码展示了我们的基本应用程序,并集成了一个简单的照片画廊选择器,就像我们在上一章(第四章,使用音频、视频和摄像头增强你的应用)中所做的那样。现在我们将创建一个新的按钮,当点击时将调用一个函数来创建并显示电子邮件对话框:

//create your email
function postToEmail() {
var emailDialog = Titanium.UI.createEmailDialog();
emailDialog.subject = txtTitle.value;
emailDialog.toRecipients = ['info@packtpub.com'];
emailDialog.messageBody = txtMessage.value;
emailDialog.open();
}
var buttonEmail = Titanium.UI.createButton({
width: 280,
height: 35,
top: 280,
left: 20,
title: 'Send Via Email'
});
buttonEmail.addEventListener('click', function(e){
if(selectedImage != null) {
postToEmail();
} else {
alert('You must select an image first!');
}
});
win1.add(buttonEmail);

在完成源代码的输入后,在模拟器或设备上运行你的应用程序。你应该能够从照片画廊中选择一张图片,然后使用文本字段输入电子邮件的标题和消息。这将在点击buttonEmail对象以启动带有你的消息和标题的电子邮件对话框窗口之前发生。请注意,如果你使用的是模拟器并且画廊中没有照片,最佳的方法是通过在移动 Safari 中访问images.google.com并搜索图片来获取一些图片。然后你可以通过在模拟器中点击并按住图片直到出现保存图片弹出窗口来将这些图片保存到照片画廊中。

如何操作...

工作原理...

第一块代码是创建我们的布局视图,包含一个窗口和多个基本组件,这些内容已经在第一章到第四章中介绍过了。

EmailDialog本身是通过Titanium.UI.createEmailDialog()方法创建的,并且只需要提供几个简单的参数就可以发送基本的电子邮件消息。subjectmessageBodytoRecipients参数是标准的电子邮件字段。虽然提供这些字段不是启动电子邮件对话框的必要条件,但通常情况下,你至少会提供其中的一到两个字段。需要注意的是,subjectmessageBody字段都是简单的字符串,而toRecipients参数实际上是一个基本的数组。你可以通过添加另一个数组参数来添加多个收件人。例如,如果我们选择将电子邮件发送给两个不同的用户,我们可以编写以下代码:

emailDialog.toRecipients = ['info@packtpub.com',
'me@boydlee.com'];

您也可以以相同的方式添加 BCC 或 CC 收件人,分别使用电子邮件对话框的ccRecipientsbccRecipients方法。最后,我们使用open()方法启动电子邮件对话框,此时您应该会在应用程序中看到如下标准电子邮件对话框:

如何工作…

还有一件事

您可以使用电子邮件对话框的事件监听器complete来告知电子邮件是否已成功发送。在您的事件处理程序中的result属性将为您提供电子邮件的状态,它将是以下字符串之一:

  • 取消(仅限 iOS)

  • 失败

  • 已发送

  • 保存(仅限 iOS)

向电子邮件添加附件

现在我们已经有一个基本的电子邮件对话框正在运行,但理想的情况是我们想将我们从照片库中选择的照片附加到我们的新电子邮件消息中。幸运的是,Titanium 通过公开一个接受我们想要附加的文件本地路径的方法addAttachment(),使这变得很容易。

注意

完整的源代码可以在/Chapter 5/Recipe 2文件夹中找到。

如何操作...

添加附件通常就像传递电子邮件对话框的addAttachment()方法和您希望附加的文件或 blob 的位置一样简单,例如:

//add an image from the Resource/images directory
emailDialog.addAttachment('images/my_test_photo.png');

尽管我们的情况比这要复杂一些。为了成功附加我们选择的照片,我们需要首先将其临时保存到文件系统,然后将文件系统路径传递给addAttachment()。修改您的postToEmail函数以匹配以下代码:

//create your email
function postToEmail() {
var newDir = Titanium.Filesystem.getFile(
Titanium.Filesystem.applicationDataDirectory,
'attachments');
if(!newDir.exists()) { newDir.createDirectory(); }
//write out the image file to the attachments directory
writeFile = Titanium.Filesystem.getFile(newDir.nativePath,
'temp-image.jpg');
writeFile.write(selectedImage);
var emailDialog = Titanium.UI.createEmailDialog();
emailDialog.subject = txtTitle.value;
emailDialog.toRecipients = ['info@packtpub.com'];
emailDialog.messageBody = txtMessage.value;
//add an image via attaching the saved file
emailDialog.addAttachment(writeFile);
emailDialog.open();
}

如何工作...

如您从代码中看到的,可以将附件作为 blob 对象、文件或从文件路径添加到您的电子邮件对话框中。在我们的示例中,我们在将图片添加到电子邮件对话框之前,首先将其从照片库保存到临时文件中,以便将其作为合适的图片附件显示(如下面的截图所示)。您也可以多次调用addAttachment方法。然而,请注意,目前 iPhone 上仅支持多个附件。

如何工作...

设置自定义 Facebook 应用程序

将 Facebook 集成到您的 Titanium 应用程序中可能一开始看起来是一个令人畏惧的前景。然而,一旦您了解了必要的步骤,您会发现这实际上并不太难!在您允许用户从您的移动应用程序发布或检索 Facebook 内容之前,您首先需要在 Facebook 本身上设置一个应用程序。该应用程序将为您提供在用户授权您的移动应用程序代表他们发布和获取内容之前所需的必要 API 密钥。

如何操作...

首先,你需要使用你注册时使用的电子邮件地址和密码登录到 Facebook。如果你没有 Facebook 账户,你需要首次创建一个。不过别担心,这是完全免费的!然后你需要将开发者应用添加到你的 Facebook 账户。你可以通过在搜索栏中搜索开发者来完成此操作,然后点击直到它被添加到你的账户:

如何操作...

一旦你将开发者应用程序添加到你的账户并加载,只需在开发者主页上点击设置新应用按钮。然后会出现创建应用程序屏幕,允许你给你的应用程序命名,并在继续之前同意 Facebook 的条款和条件。我们称我们的应用程序为PhotoShare Titanium,然而,你可以使用你想要的任何名称:

如何操作...

在出现的下一个屏幕上,给你的应用程序添加一个描述并填写其他所需字段。完成之后,只需保存你的更改。过程中的最后一个屏幕为我们提供了连接我们的 Titanium 应用程序到 Facebook API 所需的所有重要信息。这里有三个重要的值,你将在下一个食谱中使用,所以请确保将它们安全地记录下来!

这些字段是:

  • 应用程序 ID

  • API 密钥

  • 应用程序密钥

如何操作...

将 Facebook 集成到你的 Titanium 应用程序中

现在我们已经设置好了 Facebook 应用程序,我们可以着手将我们的 Titanium 应用程序连接到它。幸运的是,从 1.6.0 版本开始,Appcelerator 已经将 Facebook 的新 Graph API 紧密集成到 Titanium 中,因此连接和发布到 Facebook 平台相当简单!

注意

本食谱的完整源代码可以在/Chapter 5/Recipe 4文件夹中找到。

如何操作...

我们需要做的第一件事是创建一个新的按钮,该按钮将授权我们的 Titanium 应用程序代表用户发布数据。在你的app.js文件中输入以下代码以在现有的电子邮件用户按钮下方创建一个新按钮:

//create your facebook session and post to fb
function postToFacebook() {
//if the user is not logged in, do so, else post to wall
if(Titanium.Facebook.loggedIn == false) {
Titanium.Facebook.appid = '<your app id>';
Titanium.Facebook.permissions = ['publish_stream'];
// Permissions your app needs
Titanium.Facebook.addEventListener('login', function(e)
{
if(e.success) {
alert('You are now logged in!');
} else if(e.error) {
alert('Error: ' + e.error);
} else if(e.cancelled) {
alert('You cancelled the login');
}
});
//call the facebook authorize method to login
Titanium.Facebook.authorize();
}
}
var buttonFacebook = Titanium.UI.createButton({
width: 280,
height: 35,
top: 330,
left: 20,
title: 'Send Via Facebook'
});
buttonFacebook.addEventListener('click', function(e){
if(selectedImage != null) {
postToFacebook();
} else {
alert('You must select an image first!');
}
});
win1.add(buttonFacebook);

现在请选择一张图片并点击 Facebook 按钮。如果你正确地输入了客户端 ID(你应该是通过遵循前面的食谱获得的),那么你应该会看到一个登录窗口打开并连接到 Facebook 网站,显示你的应用程序及其请求的权限,如下面的示例截图所示:

如何操作...

它是如何工作的...

我们正在创建一个函数,为我们的应用程序提供 Facebook 功能,并允许我们登录到 Facebook 并让用户接受我们为了在他们墙上发布所需权限。此函数允许我们使用在先前的食谱中创建的应用程序 ID 对 Facebook API 进行身份验证。

当此授权成功时,允许用户登录并同意您使用其 Facebook 账户的某些权限。成功的授权将返回并保存一个 Facebook 令牌。此令牌本质上是一个包含所有所需用户 ID 和权限数据的随机字符串,我们将使用它来执行针对授权用户账户的 Facebook Graph 请求。成功的登录将设置Titanium.Facebook.loggedIn变量为 true,在下一道菜谱中,我们将扩展我们的postToFacebook函数,使用这个变量作为请求的一部分,将我们选择的图片发布到我们的 Facebook 墙上。

在你的 Facebook 墙上发布

现在我们能够对 Facebook 进行身份验证,是时候从相册中发布一张照片到我们的墙上了!为了实现这一点,我们需要使用 Facebook 的 Graph API,调用正确的 graph 函数,并使用正确的权限。

注意

本菜谱的完整源代码可以在/Chapter 5/Recipe 5文件夹中找到。

如何做…

让我们通过编写一个新的 if-else 语句来扩展我们的新postToFacebook()函数,该语句将接受一些参数并执行对 Facebook API 的 Graph 请求。在app.js中扩展postToFacebook()函数,使其与以下代码匹配:

//create your facebook session and post to fb
function postToFacebook() {
//if the user is not logged in, do so, else post to wall
if(Titanium.Facebook.loggedIn == false) {
Titanium.Facebook.appid = '252235321506456';
Titanium.Facebook.permissions = ['publish_stream'];
// Permissions your app needs
Titanium.Facebook.addEventListener('login', function(e)
{
if(e.success) {
alert('You are now logged in!');
} else if(e.error) {
alert('Error: ' + e.error);
} else if(e.cancelled) {
alert('You cancelled the login');
}
});
//call the facebook authorize method to login
Titanium.Facebook.authorize();
}
else {
//Now post the photo after you've confirmed
//that we have an access token
var data = {
caption: 'I am posting a photo to my facebook page!',
picture: selectedImage
};
Titanium.Facebook.requestWithGraphPath('me/photos',
data, "POST", function(e) {
if (e.success) {
alert( "Success! Your image has been posted to
your Facebook wall.");
Ti.API.info("Success! The image you posted has
the new ID: " + e.result);
}
else {
alert('Your image could not be posted to Facebook
at this time. Try again later.');
Ti.API.error(e.error);
}
});
} //end if else loggedIn
}

现在在您的模拟器或设备上运行应用程序。如果您成功发布了一张图片,您应该在应用程序中看到一个警报对话框出现,在开发人员控制台中显示来自 graph API 的新图像对象的 ID,并且照片作为帖子出现在您的 Facebook 墙上!

如何做…

如何工作…

我们扩展了我们在上一个菜谱中创建的postToFacebook()函数,更新它以首先登录到 Facebook API,然后在随后的帖子尝试中发送一个包含照片的 graph 请求到我们的 Facebook 墙。

我们现在可以使用 Facebook 的 Graph API(封装在我们的新graphRequest函数中)来执行我们的请求,传递我们从上一个菜谱中的身份验证对话框中检索到的会话令牌,以及我们想要调用的 graph 方法名称(me/photos),以及该方法所需的数据属性。在me/photos方法的情况下,这两个属性是:

  • 标题:将伴随我们的图像文件的字符串值

  • 图片:包含我们图像数据的 blob/image

使用 Graph API 和 Facebook 登录及发布函数,我们可以执行 Facebook(以及您的用户权限)允许的任何类型的 graph 请求。以下截图显示了一个示例帖子:

如何工作…

使用 OAuth 连接到 Twitter

Appcelerator 目前没有为 Titanium 应用程序提供连接到 Twitter 的集成方法。然而,有许多其他选项可供选择。其中最好的库之一是由 Joe Purcell 提供的,称为“Birdhouse”。在这个食谱中,我们将在线设置一个 Twitter 应用程序,获取必要的 API 密钥(与 Facebook 类似),然后实现 Google 的 OAuth 库,最后实现 Birdhouse Titanium 模块。

注意

本食谱的完整源代码可以在 /第五章/食谱 6 文件夹中找到。

如何做...

在我们尝试实现鸟屋和 OAuth 库之前,我们首先需要通过 Twitter 创建一个应用程序。如果您没有 Twitter 账户,您将需要首次创建一个。不过,请放心,这是完全免费的!登录到您的 Twitter 账户 dev.twitter.com/apps/new,一旦 Twitter 验证了您的详细信息,您将能够创建一个新的 Twitter 应用程序。

使用您自己的详细信息填写所有字段。出于我们的目的,我们已将 Twitter 应用程序命名为PhotoShare,并将公司和网站设置为 Packt Publishing。确保应用程序类型设置为浏览器,默认访问类型设置为读/写。所有其他字段可以按需创建,或者留空。输入验证码并点击注册应用程序,接受服务条款。

完成这些后,您的应用程序就准备好接收 Titanium 的请求了。但在我们实现 Birdhouse 库之前,我们首先需要从 Google 获取oauth.jssha1.js源文件。您可以从他们的 SVN 仓库 oauth.googlecode.com/svn/code/javascript/ 下载这些文件。下载并将这两个文件保存到现有Resources文件夹中的新文件夹lib中。然后,将您的浏览器导航到 github.com/jpurcell/birdhouse 并下载birdhouse.js模块文件,再次将其保存到您的Resources文件夹中。

如何做...

现在的最终步骤是将birdhouse.js库包含到我们的app.js文件中,并执行 Twitter 授权过程。首先,在您的app.js文件顶部包含该库:

Ti.include('birdhouse.js');

然后,我们需要创建一个新的按钮来授权并最终发布我们的推文。在app.js文件的底部附近输入以下代码,用 Twitter 应用页面提供的consumer_keyconsumer_secret值替换这些值:

//create your twitter session and post a tweet
function postToTwitter()
{
var BH = new BirdHouse({
consumer_key: "your-consumer-key",
consumer_secret: "your-consumer-secret"
});
//call the birdhouse authorize() method
BH.authorize();
}
var buttonTwitter = Titanium.UI.createButton({
width: 280,
height: 35,
top: 375,
left: 20,
title: 'Send Via Twitter'
});
buttonTwitter.addEventListener('click', function(e){
if(selectedImage != null) {
postToTwitter();
} else {
alert('You must select an image first!');
}
});
win1.add(buttonTwitter);

就这样!现在您应该能够选择一张图片,然后点击屏幕底部的新的 发布到 Twitter 按钮。应该会出现带有我们 Twitter 应用程序详细信息的授权屏幕,您可以使用现有的用户名和密码登录到 Twitter,如下面的截图所示:

如何操作...

使用 PHP 和 HttpRequest 上传图片

向 Twitter 发布图片有多种方式,包括使用 yfrog 或 TwitPic 这样的服务。然而,在这个例子中,我们将使用 PHP 将图片发布到我们自己的服务器,并返回 URL。如果您还没有服务器,您需要运行 PHP 并具有 GDImage 才能使本菜谱工作。如果您没有服务器,网上有很多便宜或免费的 PHP 主机服务。或者,如果您精通其他网络语言(如 ASP.NET),您总是可以根据需要重写示例代码。

注意

本菜谱的完整源代码可以在 /Chapter 5/Recipe 7 文件夹中找到。

如何操作...

在您的服务器上创建一个名为 upload.php 的新文件,并保存以下内容:

<?php
$target = getcwd();
$target = $target .'/'. $_POST['randomFilename'];
if(move_uploaded_file($_FILES['media']['tmp_name'], $target))
{
$filename = $target;
// Get dimensions of the original image
list($current_width, $current_height) = getimagesize($filename);
// The x and y coordinates on the original image where we
// will begin cropping the image
$left = 0;
$top = 0;
// This will be the final size of the image (e.g. how many pixels
// left and down we will be going)
$crop_width = $current_width;
$crop_height = $current_height;
// Resample the image
$canvas = imagecreatetruecolor($crop_width, $crop_height);
$current_image = imagecreatefromjpeg($filename);
imagecopy($canvas, $current_image, 0, 0, $left, $top, $current_width, $current_height);
imagejpeg($canvas, $filename, 100);
echo 'http://<mysite.com>/'.$_POST['randomFilename'];
}
else
{
echo "0";
}
?>

现在在您现有的 app.js 文件中的 postToTwitter 函数中,我们将扩展代码以适应将图片发布到我们的服务器。然而,在执行任何发布图片代码之前,我们将执行对 Birdhouse API 的授权方法调用,并确保用户当前已登录到 Twitter。我们还将生成一个随机的 5 个字母文件名。保持它简洁是很重要的,以将我们使用的字符数保持在最低,因为 Twitter 消息有 140 个字符的限制!

function randomString(length,current){
current = current ? current : '';
return length ? randomString( --length , "abcdefghiklmnopqrstuvwxyz".charAt ( Math.floor( Math.random() * 60 ) ) + current ) : current;
}
//create your twitter session and post a tweet
function postToTwitter()
{
var BH = new BirdHouse({
consumer_key: "<your consumer key>",
consumer_secret: "<your consumer secret>"
});
if(!BH.authorized){
BH.authorize();
}
else
{
//create the httpRequest
var xhr = Titanium.Network.createHTTPClient();
//open the httpRequest
xhr.open('POST','http://<mysite>.com/upload.php');
xhr.onload = function(response) {
//the image upload method has finished
if(this.responseText != '0')
{
var imageURL = this.responseText;
alert('Your image was uploaded to ' + imageURL);
//now we have an imageURL we can post a tweet
//using birdhouse!
}
else
{
alert('The upload did not work! Check your PHP server settings.');
}
};
// send the data
var r = randomString(5) + '.jpg';
xhr.send({'media': selectedImage, 'randomFilename': r});
}
}

它是如何工作的...

在这里,我们正在利用我们现有的通过 HttpRequest 发布的知识,并将其扩展到使用 PHP 和 GDImage 发送 blob 数据。然后,我们将这些 blob 数据写入我们远程服务器上的图像文件,并在返回新 URL 之前。您会注意到我们还扩展了 postToTwitter 函数,在执行此发布之前检查用户是否已经对 Twitter 进行了授权。

通过 Birdhouse 和 OAuth 发送推文

在我们的最终菜谱中,我们将把所有内容组合起来,并使用我们之前菜谱的“图片发布”功能与 Birdhouse API 的组合,发布包含来自我们应用程序的消息和伴随图片 URL 的推文。

注意

本菜谱的完整源代码可以在 /Chapter 5/Recipe 8 文件夹中找到。

本章的完整源代码可以在 /Chapter 5/PhotoShare 文件夹中找到。

如何操作...

将您现有的 app.js 文件中的 postToTwitter 函数修改为以下代码,通过添加我们的新 BH.tweet 方法调用。如果您已经集成了之前菜谱中的照片上传代码,那么此源代码片段应出现在 xhr.onload() 事件处理程序中:

BH.tweet(txtMessage.value + ' ' + this.responseText,
function(){
alertDialog = Ti.UI.createAlertDialog({
message:'Tweet posted!'
});
alertDialog.show();
});

现在尝试在模拟器中运行你的应用程序,并在必要时授权给 Twitter 后,你的新推文应该会直接从你的应用程序中发布!前往twitter.com/yourusername查看发布到你的时间线上的推文(如下一页所示)。点击链接后,应该会加载你上传的图片!

如何做…

如何工作…

这个菜谱非常简单,因为连接到 Twitter 并将图片上传到我们的服务器上的所有艰苦工作都已经有人处理了。因此,通过 Birdhouse 将我们的最终消息推送到 Twitter API 实际上非常容易。在这里,我们调用 Birdhouse 的tweet()函数,该函数接受一个消息(作为字符串)和第二个参数,该参数作为 Twitter 响应的事件处理器。你还可以在将图片或文件上传到服务器之外使用这个tweet函数。尝试发布一个不带图片和代码的简单消息推文,你会发现它同样工作得很好!

如何工作…

第六章. 掌握事件和属性

在本章中,我们将涵盖:

  • 读取和写入应用程序属性

  • 触发和捕获事件

  • 使用自定义事件在应用程序和 WebView 之间传递事件数据

简介

本章描述了 Titanium 框架两个基本重要但看似简单的部分的流程。在本章中,我们将解释如何创建和读取应用程序属性,以便你可以存储可以从应用程序任何部分访问的数据。这类似于如果你正在构建基于 Web 的应用程序,会话数据或 cookie 的工作方式。

我们还将进一步详细介绍事件,包括由 Titanium 的各种组件触发的事件的选择,以及你可以自己定义的自定义事件。

应用程序属性用于以键/值对的形式存储数据。这些属性可以在你的应用程序窗口之间持久化,甚至可以跨越单个应用程序会话,就像网站 cookie 一样。你可以在属性名称中使用任意组合的大写字母、小写字母和数字,但你应该谨慎地混合它们,因为 JavaScript 是区分大小写的。在这方面,Myname, mynameMYNAME 将是三个非常不同的属性!

你应该在何时使用应用程序属性?

应当在以下任一点成立时使用应用程序属性:

  • 数据由简单的键/值对组成

  • 数据与应用程序相关,而不是与用户相关

  • 数据不需要其他数据才能有意义或有用

  • 任何给定时间只需要存储数据的一个版本

例如,将字符串/字符串键对data_url[mywebsite.com/data.xml](http://mywebsite.com/data.xml)存储为应用程序属性是使用应用程序属性的有效方式。这个 URL 可以在你应用程序的所有屏幕/窗口中重用,并且与你的应用程序相关,而不是与你的数据相关。

如果你的数据复杂且在检索时需要连接、排序或查询,那么使用用 SQLite 构建的本地数据库会更好。如果你的数据是文件或大型 blob 对象(例如,图像),那么按照我们之前食谱中描述的方式存储在文件系统中会更好。

可以将哪些对象类型存储为应用程序属性?

目前可以在应用程序属性模块中存储五种不同的对象类型。这些包括:

  • 布尔值

  • 双精度(浮点值)

  • 整数

  • 字符串

  • 列表(数组)

在下面的食谱中,我们将创建一些应用程序属性,然后读取它们,在读取过程中将它们打印到控制台。

注意

整个章节的完整源代码可以在/Chapter 6/EventsAndProperties中找到。

读取和写入应用程序属性

无论是读取还是写入值,所有应用属性都是从Titanium.App.Properties命名空间访问的。在这个食谱中,我们将在应用的第一标签窗口上创建多个属性,所有属性类型都不同。然后我们将从第二个标签窗口上的按钮读取它们并将它们的值输出到控制台。我们还将向您展示如何使用hasProperty方法检查属性的存在。

注意

本食谱的完整源代码可以在/第六章/食谱 1文件夹中找到。

准备工作

准备此食谱,请打开 Titanium Studio 并登录,如果您尚未登录的话。如果您需要注册新账户,您可以直接在应用程序内免费注册。登录后,点击新建项目,创建新项目的详细信息窗口将出现。将应用名称输入为EventsAndProperties,并使用您自己的信息填写其余的详细信息。

请注意应用标识符,它以反向域名表示法(即,com.packtpub.eventsandproperties)正常书写。在项目创建后,此标识符不容易更改,您在创建用于分发应用的配置文件时需要精确匹配它。

如何操作…

在您的编辑器中打开app.js文件,保留现有的代码,除了两个标签和标签窗口中添加这些标签的行。在win1对象的声明之后,输入以下代码:

//
//create a button that will define some app properties
//
var buttonSetProperties = Titanium.UI.createButton({
title: 'Set Properties!',
top: 50,
left: 20,
width: 280,
height: 40
});
//create event listener
buttonSetProperties.addEventListener('click',function(e){
Titanium.App.Properties.setString('myString', 'Hello world!');
Titanium.App.Properties.setBool('myBool', true);
Titanium.App.Properties.setDouble('myDouble', 345.12);
Titanium.App.Properties.setInt('myInteger', 11);
Titanium.App.Properties.setList('myList', ['The first value', 'The second value','The third value']);
alert('Your app properties have been set!');
}); //end event listener
win1.add(buttonSetProperties);

现在,仍然在您的app.js文件中,添加以下代码。它应该放在win2对象声明之后。

//
//create a button that will check for some properties
//
var buttonCheckForProperty = Titanium.UI.createButton({
title: 'Check Property?',
top: 50,
left: 20,
width: 280,
height: 40
});
//create event listener
buttonCheckForProperty.addEventListener('click',function(e){
if(Titanium.App.Properties.hasProperty('myString')){
Ti.API.info('The myString property exists!');
}
if(!Titanium.App.Properties.hasProperty('someOtherString')){
Ti.API.info('The someOtherString property does not exist.');
}
}); //end event listener
win2.add(buttonCheckForProperty);
//
//create a button that will read and output some app //properties to the console
//
var buttonGetProperties = Titanium.UI.createButton({
title: 'Get Properties!',
top: 50,
left: 20,
width: 280
height: 40
});
//create event listener
buttonGetProperties.addEventListener('click',function(e){
Ti.API.info('myString property = ' + Titanium.App.Properties.getString('myString'));
Ti.API.info('myBool property = ' + Titanium.App.Properties.getBool('myBool'));
Ti.API.info('myDouble property = ' + Titanium.App.Properties.getDouble('myDouble'));
Ti.API.info('myInteger property = ' + Titanium.App.Properties.getInt('myInteger'));
Ti.API.info('myList property = ' + Titanium.App.Properties.getList('myList'));
}); //end event listener
win2.add(buttonGetProperties);

现在,从 Titanium Studio 中启动模拟器,您应该会看到标准的双标签导航视图,每个视图中都有一个按钮。点击第一个标签上的设置按钮将设置您的应用属性。完成设置后,您可以使用第二个标签视图上的按钮读取单个属性并检查属性的存在。结果将显示在您的 Titanium Studio 控制台中,类似于以下截图:

如何操作…

工作原理…

在这个食谱中,我们通过使用我们的设置属性!按钮设置了一系列应用属性。每个属性由一个键/值对组成,因此需要一个属性名(也称为“键”)和一个属性值。要设置属性,我们使用set方法,其形式类似于Titanium.App.Properties.set<type>(key,value)。然后我们通过使用get方法检索我们的应用属性,其形式类似于Titanium.App.Properties.get<type>(key,value)

应用程序属性在应用程序启动时加载到内存中,并在应用程序的全局范围内存在,直到它被关闭,或者使用Titanium.App.Properties.remove()方法从内存中删除属性。虽然以这种方式使用属性会有内存开销,但这意味着您可以从应用程序的任何部分有效地快速访问它们,因为它们在作用域上是全局的。

您还可以使用Titanium.App.Properties.listProperties方法访问任何给定时间存储的整个属性列表。

触发和捕获事件

大部分 Titanium 都是围绕事件驱动编程的概念构建的。如果您曾经用 Visual Basic、C#、Java 或任何数量的事件驱动、面向对象的语言编写过代码,这个概念对您来说已经很熟悉了。

每当用户与您应用程序界面的某个部分进行交互,或在TextField中输入内容时,都会发生一个事件。事件只是用户执行的动作(例如,点击、滚动或虚拟键盘上的按键)以及它发生的位置(例如,在按钮上,或在这个TextField中)。此外,某些事件可以间接触发其他事件。例如,当用户选择一个打开窗口的菜单项时,它会导致另一个事件——窗口的打开。

在 Titanium 中基本上有两种基本类型的事件;那些您自己定义的(自定义事件),以及那些由 Titanium API 预先定义的(按钮点击事件是一个很好的例子)。

在接下来的菜谱中,我们将在向您展示如何创建可以在您的应用程序和 Webview 之间传递数据的自定义事件之前,探索许多由 Titanium 定义的事件。

如前一个菜谱中提到的,用户也可以间接地导致事件发生。例如,按钮有一个名为'click'的事件,当用户在屏幕上点击按钮时发生。处理事件响应的代码称为事件处理器。

您的 Titanium 应用程序中的每个对象都可能发生许多事件。好消息是您不需要了解所有这些事件,并且那些已经定义的事件列在 Titanium API 中。您只需要了解它们是如何工作的以及如何访问事件数据,这样您就可以找出对象是否能够响应该事件。

在这个菜谱中,我们将探讨从多个常见组件中发生的事件,以OptionDialog为例,并解释如何访问这些事件的属性。我们还将解释如何创建一个函数,该函数将返回的事件传递回我们的执行代码。

注意

本菜谱的完整源代码可以在/Chapter 6/Recipe 2文件夹中找到。

如何做…

在您的编辑器中打开app.js文件,并在您的win1对象声明下方输入以下代码:

//create a button that will launch the optiondialog via
//its click event
var buttonLaunchDialog = Titanium.UI.createButton({
title: 'Launch OptionDialog',
top: 110,
left: 20,
width: 280,
height: 40
});
//create the event listener for the button
buttonLaunchDialog.addEventListener('click',function(e){
Ti.API.info(e.source + ' was tapped, it has a title of: ');
Ti.API.info(e.source.title);
});
//add the launch dialog button to our window
win1.add(buttonLaunchDialog);

现在,在你创建了前面的代码之后,我们将创建一个使用外部函数作为其事件处理程序的事件监听器的OptionDialog。我们将在buttonLaunchDialog的事件处理程序函数中这样做:

//create the event listener for the button
buttonLaunchDialog.addEventListener('click',function(e){
Ti.API.info(e.source + ' was tapped, it has a title of: ');
Ti.API.info(e.source.title);
var dialog = Titanium.UI.createOptionDialog({
options:['More than words can say!',
'Lots!',
'It is okay...',
'I hate ice cream', 'Cancel'],
cancel: 4,
title: 'How much do you like ice cream?'
});
//add the event listener for the option dialog
dialog.addEventListener('click', optionDialogEventHandler);
//show the option dialog
dialog.show();
});

现在剩下的就是为我们的OptionDialog创建最终的事件处理函数。在buttonLaunchDialog的事件监听器之前添加以下函数到你的代码中:

//this is the event handler function for our option dialog
function optionDialogEventHandler(e) {
alert(e.source + ' index pressed was ' + e.index);
}

现在尝试在 iPhone 或 Android 模拟器中启动你的代码。如以下示例截图所示,你应该能够点击按钮并通过按钮的事件处理程序启动OptionDialog的执行,该处理程序反过来可以通过optionDialogEventHandler:执行一个警告。

如何做到这一点…

它是如何工作的…

首先,重要的是要重申事件处理程序和事件监听器之间的区别。监听特定事件(如'click')并相应地附加特定函数的代码称为事件监听器。处理事件响应的代码称为事件处理程序。在本食谱中,我们展示了事件监听器可以直接通过用户交互(如按钮点击)启动,并且事件处理程序可以通过以下两种方式之一执行:

  • 我们的第一种方法是内联的,也就是说,事件处理函数直接在事件监听器中声明,例如buttonLaunchDialog.addEventListener('click', function(e){} );。这对于快速执行可能只用于简单任务的代码来说很棒,但代码复用性不高。

  • 第二种方法,以及使用事件处理程序的一个更受欢迎的方式,是将它编写为一个独立的、自包含的函数,例如:

    function dialogClicked(e) {
    //e.source will tell you what object was clicked
    }
    //create the event listener for the button
    buttonLaunchDialog.addEventListener('click', dialogClicked);
    
    
  • 这种方法允许你获得更高的代码复用性,并且通常被认为是一种更整洁的源代码组织方式。

使用自定义事件在您的应用和 Webview 之间传递事件数据

虽然我们可以使用钛金 API 内置的事件来满足我们 90%的一般需求,但当我们想要启动一个标准钛金组件未涵盖的事件时会发生什么?幸运的是,钛金已经通过我们Titanium.App命名空间中的fireEvent方法为我们解决了这个问题!

FireEvent允许你执行一个任意事件,该事件具有你确定的监听器名称,然后在你的代码中监听该事件。在本食谱中,我们将稍微复杂一些,编写一些代码,将输入字段的 数据复制并显示在我们的应用中的标签上。我们将通过从 Webview 中触发一个自定义事件来完成此操作,然后我们将在钛金窗口中监听并响应该事件。

参见

  • 本食谱的完整源代码可以在/Chapter 6/Recipe 3文件夹中找到。

如何做到这一点…

在你的编辑器中打开app.js文件,并在win2对象的声明下方输入以下代码以创建 Webview:

//create a webview and then add that to our
//second window (win2) at the bottom
var webview = Titanium.UI.createWebView({
url: 'webevent.html',
width: 320,
height: 100,
bottom: 0
});

现在,创建一个新的 HTML 文件,并将其命名为 webevent.html,以下代码。完成之后,将 HTML 文件保存到你的项目 Resources 文件夹中。

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
<head>
<title>EventHandler Test</title>
</head>
<body>
<input name="myInputField" id="myInputField" value="" size="40" />
</body>
<script>
//capture the keypress event in javascript and fire
//an event passing through our textBox's value as a
//property called 'text'
var textBox = document.getElementById("myInputField");
textBox.onkeypress = function () {
// passing object back with event
Ti.App.fireEvent('webviewEvent',
{ text: this.value });
};
</script>
</html>

现在剩下的只是在我们 app.js 文件中创建事件处理程序,它将在你输入时从我们的 HTML 文件中复制输入字段数据,然后将我们的 webview 添加到窗口中。在 app.js 文件中你的初始 webview 对象声明下方写下以下代码:

//create a label and add to the window
var labelCopiedText = Titanium.UI.createLabel({
width: 'auto',
height: 'auto',
value: '',
bottom: 120
});
win2.add(labelCopiedText);
//create our custom event listener
Ti.App.addEventListener('webviewEvent', function(e)
{
labelCopiedText.text = e.text;
});
win2.add(webview);

现在在模拟器中运行你的应用,你应该能够输入位于我们 Webview 内的输入字段,并看到结果在位于其上方的标签上镜像!你应该能看到一个与这里图片中一样的屏幕:

如何操作…

它是如何工作的…

基本上,我们从 Titanium.App.fireEvent 方法触发的事件创建了一个跨上下文的应用程序事件,任何 JavaScript 文件都可以监听。然而,这里有两个注意事项。首先,只有当你的 fireEvent 调用和监听器中使用相同的事件名称时,事件才能被处理。与应用程序属性一样,这个事件名称也是大小写敏感的,所以请确保在应用程序代码的所有部分中拼写完全相同。

其次,即使对象为空,你也必须返回一个对象,并且该对象必须以 JSON 序列化格式存在。这种标准化确保了你的数据负载始终可以在不同上下文之间传输。

还有更多…

如果不再需要,也可以从你的代码中移除事件监听器。你可以通过调用 Titanium.App.removeEventListener 并传递事件名称来实现。请注意,它仍然是大小写敏感的,所以你的事件名称必须完全匹配!对于我们应用中移除 webviewEvent 的一个示例将是:

Titanium.App.removeEventListener('webviewEvent');

第七章:创建动画、变换以及理解拖放

在本章中,我们将介绍:

  • 使用“animate”方法对视图进行动画处理

  • 使用 2D 矩阵和 3D 矩阵变换对视图进行动画处理

  • 使用触摸事件拖动ImageView

  • 使用滑动控件缩放ImageView

  • 使用toImage()方法保存我们的“Funny Face”图像

简介

几乎 Titanium 中的任何控件或元素都可以应用动画或变换。这允许你通过添加交互性和“bling”级别来增强你的应用程序,否则你的应用程序可能没有。

在本章中,我们将创建一个小应用程序,允许用户选择一个“有趣的表情”图像,我们将将其放置在我们自己的照片的顶部。我们将使用过渡和动画来显示有趣的表情图片,并允许用户调整他/她的照片的大小和位置,以便它能够整齐地位于有趣的表情剪影部分内。

最后,我们将使用 Windows 的toImage()方法将我们的“我”的照片和有趣的表情合并成一张完整的图像,让用户可以将生成的图像通过电子邮件发送给他的朋友!

注意

整个章节的完整源代码可以在Chapter 7/FunnyFaces文件夹中找到。

使用“animate”方法对视图进行动画处理

Titanium 中的任何窗口、视图或组件都可以使用animate方法进行动画处理。这允许你快速自信地创建可以给你的应用程序带来“哇”效果的动画对象。此外,你可以使用动画作为在屏幕上保持信息或元素的方法,直到它们实际需要时。一个很好的例子是,如果你有三个不同的 TableView,但只想在任何时候显示其中一个视图。使用动画,你可以根据需要将那些表格滑动到屏幕空间内或滑出屏幕空间,而无需创建额外的窗口。

在下面的菜谱中,我们将通过布局多个不同的组件来构建我们应用程序的基本结构,然后开始对四个不同的ImageView进行动画处理。每个ImageView将包含一个不同的图像,用作我们的“Funny Face”角色。

注意

本菜谱的完整源代码可以在/Chapter 7/Recipe 1文件夹中找到。

准备工作

为了准备这个菜谱,打开 Titanium Studio 并登录,如果你还没有登录的话。如果你需要注册新账户,你可以在应用程序内部免费注册。登录后,点击新建项目,创建新项目的详细信息窗口将出现。将应用程序名称输入为FunnyFaces,并使用你自己的信息填写其余的详细信息。

注意应用程序标识符,它以反向域名表示法(即,com.packtpub.funnyfaces)正常书写。在项目创建后,此标识符不容易更改,您在创建用于分发应用程序的配置文件时需要完全匹配它。

首件事是将所有必需的图片复制到项目“资源”文件夹下的images文件夹中。然后,在您的 IDE 中打开app.js文件,并用以下代码替换其内容。这段代码将成为我们 FunnyFaces 应用程序布局的基础。

// this sets the background color of the master UIView Titanium.UI.setBackgroundColor('#fff');
//
//create root window
//
var win1 = Titanium.UI.createWindow({
title:'Funny Faces',
backgroundColor:'#fff'
});
//this will determine whether we load the 4 funny face
//images or whether one is selected already
var imageSelected = false;
//the 4 image face objects, yet to be instantiated
var image1;
var image2;
var image3;
var image4;
var imageViewMe = Titanium.UI.createImageView({
image: 'images/me.png',
width: 320,
height: 480,
zIndex: 0
left: 0,
top: 0,
zIndex: 0,
visible: false
});
win1.add(imageViewMe);
var imageViewFace = Titanium.UI.createImageView({
image: 'images/choose.png',
width: 320,
height: 480,
zIndex: 1
});
imageViewFace.addEventListener('click', function(e){
if(imageSelected == false){
//transform our 4 image views onto screen so
//the user can choose one!
}
});
win1.add(imageViewFace);
//this footer will hold our save button and zoom slider objects
var footer = Titanium.UI.createView({
height: 40,
backgroundColor: '#000',
bottom: 0,
left: 0,
zIndex: 2
});
var btnSave = Titanium.UI.createButton({
title: 'Save Photo',
width: 100,
left: 10,
height: 34,
top: 3
});
footer.add(btnSave);
var zoomSlider = Titanium.UI.createSlider({
left: 125,
top: 8,
height: 30,
width: 180
});
footer.add(zoomSlider);
win1.add(footer);
//open root window
win1.open();

首次在模拟器中构建和运行您的应用程序,您应该得到一个看起来与以下示例类似的屏幕:

准备中

如何做…

现在,回到app.js文件中,我们将对四个 ImageView 进行动画处理,每个 ImageView 将提供一个搞笑面孔图片的选项。在imageViewFace对象的事件处理程序声明中,输入以下代码:

imageViewFace.addEventListener('click', function(e){
if(imageSelected == false){
//transform our 4 image views onto screen so
//the user can choose one!
image1 = Titanium.UI.createImageView({
backgroundImage: 'images/clown.png',
left: -160,
top: -140,
width: 160,
height: 220,
zIndex: 2
});
image1.addEventListener('click', setChosenImage);
win1.add(image1);
image2 = Titanium.UI.createImageView({
backgroundImage: 'images/policewoman.png',
left: 321,
top: -140,
width: 160,
height: 220,
zIndex: 2
});
image2.addEventListener('click', setChosenImage);
win1.add(image2);
image3 = Titanium.UI.createImageView({
backgroundImage: 'images/vampire.png',
left: -160,
bottom: -220,
width: 160,
height: 220,
zIndex: 2
});
image3.addEventListener('click', setChosenImage);
win1.add(image3);
image4 = Titanium.UI.createImageView({
backgroundImage: 'images/monk.png',
left: 321,
bottom: -220,
width: 160,
height: 220,
zIndex: 2
});
image4.addEventListener('click', setChosenImage);
win1.add(image4);
image1.animate({
left: 0,
top: 0,
duration: 500,
curve: Titanium.UI.ANIMATION_CURVE_EASE_IN
});
image2.animate({
left: 160,
top: 0,
duration: 500,
curve: Titanium.UI.ANIMATION_CURVE_EASE_OUT
});
image3.animate({
left: 0,
bottom: 20,
duration: 500,
curve: Titanium.UI.ANIMATION_CURVE_EASE_IN_OUT
});
image4.animate({
left: 160,
bottom: 20,
duration: 500,
curve: Titanium.UI.ANIMATION_CURVE_LINEAR
});
}
});

现在,从 Titanium Studio 中启动模拟器,您应该看到带有“点击选择图片”视图的初始布局。点击选择 ImageView 现在应该将我们的四个搞笑面孔选项动画化到屏幕上,如以下截图所示:

如何做…

它是如何工作的…

第一段代码创建了应用程序的基本布局,包括几个 ImageView、一个包含“保存”按钮的页脚视图以及我们将用于稍后增加我们自己的照片缩放比例的 Slider 控件。我们的第二段代码是其中最有趣的部分。在这里,我们通过检查用户是否已经使用imageSelected布尔值选择了图片,然后进入名为image1、image2、image3 和 image4的动画 ImageView。

这四个 ImageView 动画背后的概念相当简单。我们本质上只是在毫秒数内改变我们控制的属性,这些毫秒数是我们自己定义的。在这里,我们在半秒内改变所有图片的topleft属性,以便在屏幕上产生它们滑入位置的视觉效果。您可以通过添加更多要动画化的属性来进一步增强这些动画,例如,如果我们想在图片 1 滑入位置时将其不透明度从 50%变为 100%,我们可以将代码更改为类似以下的形式:

image1 = Titanium.UI.createImageView({
backgroundImage: 'images/clown.png',
left: -160,
top: -140,
width: 160,
height: 220,
zIndex: 2,
opacity: 0.5
});
image1.addEventListener('click', setChosenImage);
win1.add(image1);
image1.animate({
left: 0,
top: 0,
duration: 500,
curve: Titanium.UI.ANIMATION_CURVE_EASE_IN,
opacity: 1.0
});

最后,animate()函数的曲线属性允许您调整动画组件的缓动效果。在这里,我们在每个 ImageView 上使用了所有四个动画曲线常量。它们是:

  • Titanium.UI.ANIMATION_CURVE_EASE_IN: 动画开始时缓慢加速

  • Titanium.UI.ANIMATION_CURVE_EASE_OUT: 动画结束时缓慢减速

  • Titanium.UI.ANIMATION_CURVE_EASE_IN_OUT: 动画开始和结束时都缓慢加速和减速

  • Titanium.UI.ANIMATION_CURVE_LINEAR: 使动画在整个动画周期中速度保持恒定

使用 2D 矩阵和 3D 矩阵变换来动画化视图

你可能已经注意到,在前面的菜谱中,我们为每个ImageView都附加了一个click事件监听器,调用名为setChosenImage的事件处理器。这个事件处理器将负责将我们的选择“搞笑面孔”图像设置到imageViewFace控件上。然后,它将使用多种 2D 和 3D 矩阵变换来动画化屏幕区域上的所有四个“搞笑面孔”ImageView对象。

注意

本菜谱的完整源代码可以在/Chapter 7/Recipe 2文件夹中找到。

如何实现…

用以下源代码替换现有的空setChosenImage函数:

//this function sets the chosen image and removes the 4
//funny faces from the screen
function setChosenImage(e){
imageViewFace.image = e.source.backgroundImage;
imageViewMe.visible = true;
//create the first transform
var transform1 = Titanium.UI.create2DMatrix();
transform1 = transform1.rotate(-180);
var animation1 = Titanium.UI.createAnimation({
transform: transform1,
duration: 500,
curve: Titanium.UI.ANIMATION_CURVE_EASE_IN_OUT
});
image1.animate(animation1);
animation1.addEventListener('complete',function(e){
//remove our image selection from win1
win1.remove(image1);
});
//create the second transform
var transform2 = Titanium.UI.create2DMatrix();
transform2 = transform2.scale(0);
var animation2 = Titanium.UI.createAnimation({
transform: transform2,
duration: 500,
curve: Titanium.UI.ANIMATION_CURVE_EASE_IN_OUT
});
image2.animate(animation2);
animation2.addEventListener('complete',function(e){
//remove our image selection from win1
win1.remove(image2);
});
//create the third transform
var transform3 = Titanium.UI.create2DMatrix();
transform3 = transform3.rotate(180);
transform3 = transform3.scale(0);
var animation3 = Titanium.UI.createAnimation({
transform: transform3,
duration: 1000,
curve: Titanium.UI.ANIMATION_CURVE_EASE_IN_OUT
});
image3.animate(animation3);
animation3.addEventListener('complete',function(e){
//remove our image selection from win1
win1.remove(image3);
});
//create the fourth and final transform
var transform4 = Titanium.UI.create3DMatrix();
transform4 = transform4.rotate(200,0,1,1);
transform4 = transform4.scale(2);
transform4 = transform4.translate(20,50,170);
//the m34 property controls the perspective of the 3D view
transform4.m34 = 1.0/-3000; //m34 is the position at [3,4]
//in the matrix
var animation4 = Titanium.UI.createAnimation({
transform: transform4,
duration: 1500,
curve: Titanium.UI.ANIMATION_CURVE_EASE_IN_OUT
});
image4.animate(animation4);
animation4.addEventListener('complete',function(e){
//remove our image selection from win1
win1.remove(image4);
});
//change the status of the imageSelected variable
imageSelected = true;
}

它是如何工作的…

再次强调,我们正在为四个ImageView中的每一个创建动画,但这次的方式略有不同。我们不是使用内置的animate方法,而是在调用ImageViewanimate方法并将此动画对象传递给它之前,为每个ImageView创建一个单独的动画对象。这种方法创建动画可以让你更精细地控制它们,包括使用变换。

变换有一些快捷方式可以帮助你快速轻松地执行一些最常见的动画类型。如前代码所示,image1image2变换分别使用rotatescale方法。在这种情况下,缩放和旋转是 2D 矩阵变换,这意味着它们只在 X 轴和 Y 轴上的二维空间中变换对象。每种变换类型都接受一个单个整数参数;对于缩放,它是 0-100%,对于旋转,数值是 0-360 度。

使用变换进行动画的另一个优点是,你可以轻松地将它们链接起来以执行更复杂的动画风格。在之前的代码中,你可以看到scalerotate变换都在变换image3组件。当你运行模拟器或设备上的应用程序时,你应该注意到这两个变换动画都应用于image3控件!

最后,image4控件也应用了一个变换动画,但这次我们使用的是 3D 矩阵变换,而不是其他三个ImageView使用的 2D 矩阵变换。这些变换与常规的 2D 矩阵变换工作方式相同,除了你还可以在 3D 空间中,沿着 Z 轴动画化你的控件。

需要注意的是,动画有两个事件监听器:startcomplete。这些事件处理器允许你根据动画生命周期的开始或结束执行操作。例如,你可以通过使用 complete 事件在之前的动画完成后添加一个新的动画或变换到对象上,从而将动画链在一起。在我们的上一个例子中,我们就是使用这个 complete 事件在动画完成后从窗口中移除我们的 ImageView。

使用触摸事件拖动 ImageView

现在我们已经允许用户从我们的四个动画 ImageView 控件中选择一个有趣的表情图片,我们需要允许他们调整自己照片的位置,使其适合透明洞,这个洞构成了我们有趣表情的脸部部分。我们将使用 ImageView 控件提供的触摸事件来完成这项工作。

注意

本菜谱的完整源代码可以在 /Chapter 7/Recipe 3 文件夹中找到。

如何操作…

执行此任务最简单的方法是捕获 X 和 Y 触摸点,并将 ImageView 移动到该位置。这段代码很简单。只需在你声明 imageViewFace 控件之后,但在将其添加到窗口之前添加以下代码:

imageViewFace.addEventListener('touchmove', function(e){
imageViewMe.left = e.x;
imageViewMe.top = e.y;
});

现在,在模拟器中运行你的应用,并在选择一个有趣的表情图片后,尝试在屏幕上触摸并拖动你的照片。你应该会发现它工作得很好,但似乎并不完全正确,对吧?这是因为我们是根据对象的左上角位置移动图片,而不是以对象的中心为基准。让我们修改我们的代码,使其基于 imageViewMe 控件的中心点,通过用以下源代码替换之前的代码:

imageViewFace.addEventListener('touchstart', function (e) {
imageViewMe.ox = e.x - imageViewMe.center.x;
imageViewMe.oy = e.y - imageViewMe.center.y;
});
imageViewFace.addEventListener('touchmove', function(e){
imageViewMe.center = {
x:(e.x - imageViewMe.ox),
y:(e.y - imageViewMe.oy)
};
});

再次在模拟器中运行你的应用,并在选择一个有趣的表情图片后,尝试在屏幕上触摸并拖动你的照片。这次你应该会注意到一个更加平滑、更自然的拖放效果!尝试将你的“我”照片定位到其中一个有趣表情的中心,你应该能够复制以下截图:

如何操作…

工作原理…

在这里,我们使用两个独立的触摸事件来变换我们的imageViewMe控件的左和上定位属性。首先,我们需要找到中心点。我们在touchstart事件中通过 ImageView 控件的center.xcenter.y属性来完成这个操作,然后将这些属性分配给几个我们命名为“ox”和“oy”的自定义变量。在touchstart事件中这样做确保了当touchmove事件发生时,这些变量立即可用。然后,在我们的touchmove事件中,我们不是改变imageViewMe的顶部和左属性,而是根据触摸事件的 x 和 y 属性,减去我们保存为对象 ox 和 oy 变量的中心点,将图像的中心属性传递给新的 x 和 y 坐标。这确保了图像的移动既平滑又顺畅!

使用滑动条控件缩放 ImageView

我们现在已经创建了一个代码来选择一个动画搞笑脸,并且我们有能力使用拖放方法移动我们的图像。我们需要能够使用滑动条控件和一个新的变换来缩放我们的“我”照片。

在下面的菜谱中,我们将连接滑动条控件的监听器,并使用另一个 2D 矩阵变换,这次根据用户输入改变imageViewMe控件的缩放比例。

注意

本菜谱的完整源代码可以在/Chapter 7/Recipe 4文件夹中找到。

如何做到这一点…

在你当前的源代码底部附近,你应该已经实例化了一个名为"zoomSlider"的滑动条控件。我们将用稍微更新版本的代码替换它,然后捕获滑动条的更改事件,以便根据所选值缩放我们的imageViewMe组件。将你的zoomSlider组件声明替换为以下代码:

var zoomSlider = Titanium.UI.createSlider({
left: 125,
top: 8,
height: 30,
width: 180,
minValue: 1,
maxValue: 100,
value: 50
});
//create the sliders event listener/handler
zoomSlider.addEventListener('change', function(e){
//create the scaling transform
var transform = Titanium.UI.create2DMatrix();
transform = transform.scale(zoomSlider.value);
var animation = Titanium.UI.createAnimation({
transform: transform,
duration: 100,
curve: Titanium.UI.ANIMATION_CURVE_EASE_IN_OUT
});
imageViewMe.animate(animation);
});
//finally, add our slider to the footer view
footer.add(zoomSlider);

现在尝试在模拟器中运行你的应用程序,并在选择一个搞笑脸图像后,你应该能够使用滑动条控件来缩放“我”照片。尝试结合使用上一个菜谱中的触摸和拖动来将你的脸放入搞笑图片的洞中,如图所示:

如何做到这一点…

它是如何工作的…

我们正在执行与本章第二个菜谱中非常相似的操作。在我们的滑动条控件的改变事件处理程序中,我们正在应用一个 2D 矩阵变换到imageViewMe控件,使用缩放方法。我们的滑动条被赋予了最小值 0 和最大值 100。这些值是我们将要缩放图像的相对百分比。通过在动画中使用非常短的时间(例如,100 毫秒),我们可以使滑动条的移动几乎瞬间与“我”照片的缩放相关联。

使用toImage()方法保存我们的“搞笑脸”图像

对于本应用程序的最后一部分,我们想要将两张图像(我们的“我”照片和选择的有趣表情)合并在一起,并将它们保存到文件系统中作为一个完整的图像。为此,我们将连接保存按钮控件的事件监听器,并使用在几乎所有视图和控制类型上都可以找到的另一个常用方法;toImage。一旦我们将两张图像合并并保存到本地文件系统中,我们将创建一个快速的电子邮件对话框,并将有趣的面部表情附加到它上,使用户能够将完整的图像发送给他的/她的朋友。

注意

本菜谱的完整源代码可以在 /Chapter 7/Recipe 5 文件夹中找到。

如何做到这一点…

在您的 btnSave 对象实例化下面,添加以下事件监听器和处理程序代码:

btnSave.addEventListener("click", function(e){
//hide the footer
footer.visible = false;
//do a slight delay before capturing the image
//so we are certain the footer is hidden!
setTimeout(function(e){
//get the merged blob -- note on android you
//might want to use toBlob() instead of toImage()
var mergedImage = win1.toImage();
writeFile = Titanium.Filesystem.getFile(
Titanium.Filesystem.applicationDataDirectory,
'funnyface.jpg');
writeFile.write(mergedImage);
//now email our merged image file!
var emailDialog = Titanium.UI.createEmailDialog();
emailDialog.setSubject(—Check out funny face!—);
emailDialog.addAttachment(writeFile);
emailDialog.addEventListener('complete',function(e) {
//reset variables so we can do another funny face
footer.visible = true;
imageViewFace.image = 'images/choose.png';
imageSelected = false;
});
emailDialog.open();
}, 250);
});

现在,在模拟器或设备上再次启动您的应用程序,再次执行所有步骤,直到您选择了一个有趣的表情,并相应地调整您的照片布局。完成后,点击 保存 按钮,您应该会看到一个电子邮件对话框出现,您的综合图像作为附件可见。

它是如何工作的…

toImage 方法简单地获取了问题元素的综合截图。在我们的例子中,我们正在对 win1 执行命令,即我们的根 Window 对象。为此,我们只是隐藏了我们的页脚控件,然后设置了一个短暂的超时。当超时后,它使用 toImage 来获取我们的 imageViewMeimageViewFace 控件的综合截图,然后我们将它们保存到文件系统中。

大多数控件还有一个名为 toBlob 的方法,其工作方式与 toImage 非常相似。根据您想要实现的目标,您通常可以使用这两种方法中的任何一种。然而,有时您会发现 Titanium API 中存在错误,并且可能只有其中一种方法可以工作。特别是,toBlob 方法在 Android 设备上比 toImage 方法表现得更好。然而,随着 Titanium 平台的日益稳定,您可以期待这两个 API 调用都能提供更好的性能。此外,您将使用 toBlob 方法来将 blob 数据存储在本地数据库中,使用 SQLite,尽管通常不采用这种方法,因为它非常占用内存。将 blob 对象保存到文件系统是推荐的方法。

以下截图显示了我们的最终综合图像,它已被保存到文件系统中,并附加到一个新的电子邮件对话框中,准备与用户的亲朋好友分享!

它是如何工作的…

第八章.与原生手机应用程序和 API 交互

在本章中,我们将介绍:

  • 创建 Android 选项菜单

  • 访问联系人地址簿

  • 通过剪贴板存储和检索数据

  • 在 iPhone 上创建后台服务

  • 在 iPhone 上显示本地通知

  • 使用意图显示 Android 通知

  • 将您的 Android 应用程序存储在设备的 SD 卡上

简介

虽然 Titanium 允许您创建几乎完全跨平台的本地应用程序,但不可避免的是,某些设备将固有地具有特定于它们的操作系统和硬件差异(尤其是在 Android 和 iOS 之间)。例如,任何使用过 Android 和 iPhone 设备的用户都会立即认识到通知系统设置方式非常不同。然而,还有其他特定于平台的限制,这些限制非常具体于 Titanium API。

在本章中,我们将向您展示如何在您的应用程序中创建和使用一些这些特定于设备的组件和 API。与本书中的大多数章节不同,这一章不遵循一个单一、连贯的应用程序。因此,请随意按您希望的顺序阅读每个菜谱。

创建 Android 选项菜单

选项菜单是 Android 用户界面的重要组成部分。它们是屏幕上菜单项的主要集合,当用户在其设备上按下MENU按钮时出现。在本菜谱中,我们将创建一个 Android 选项菜单并将其添加到我们的屏幕上,为每个选项分配一个具有动作的点击事件。

准备工作

为了准备这个菜谱以及本章中的所有菜谱,打开 Titanium Studio 并登录,如果您还没有这样做的话。您可以使用本章中每个菜谱相同的程序,或者创建一个新的程序,选择权在您手中。

注意

本应用程序的图标和代码可在Chapter 8/Recipe 1文件夹中找到。

本菜谱的完整源代码可以在/Chapter 8/Recipe 1文件夹中找到。

如何操作…

在您的 IDE 中打开app.js文件,并输入以下代码:

//create the root window
var win1 = Titanium.UI.createWindow({
title: 'Android Options Menu',
backgroundColor: '#ccc'
});
if(Titanium.Platform.osname == 'android')
{
//references the current android activity
var activity = Ti.Android.currentActivity;
//create our menu
activity.onCreateOptionsMenu = function(e) {
var menu = e.menu;
//menu button 1
var menuItem1 = menu.add({ title: "Item 1" });
menuItem1.setIcon("item1.png");
menuItem1.addEventListener("click", function(e) {
alert("Menu item #1 was clicked");
});
//menu button 2
var menuItem2 = menu.add({
title: "Show Item #4",
itemId: 2
});
menuItem2.setIcon("item2.png");
menuItem2.addEventListener("click", function(e) {
menu.findItem(4).setVisible(true);
});
//menu button 3
var menuItem3 = menu.add({ title: "Item 3" });
menuItem3.setIcon("item3.png");
menuItem3.addEventListener("click", function(e) {
alert("Menu item #3 was clicked");
});
//menu button 4 (will be hidden)
var menuItem4 = menu.add({
title: "Hide Item #4",
itemId: 4
});
menuItem4.setIcon("item4.png");
menuItem4.addEventListener("click", function(e) {
menu.findItem(4).setVisible(false);
});
};
//turn off the item #4 by default
activity.onPrepareOptionsMenu = function(e) {
var menu = e.menu;
menu.findItem(4).setVisible(false);
};
}
win1.open();

首次在 Android 模拟器中构建和运行您的应用程序。当您在设备/模拟器上按下“菜单”按钮时,您应该看到一个看起来就像以下示例的屏幕。点击第一个菜单项应该执行其点击事件并显示一个警告对话框。以下截图是一个示例:

如何操作…

工作原理…

首先,需要注意的是,这个菜谱中的代码仅适用于 Android。iOS 平台没有像 Android 设备那样的物理菜单按钮,因此也没有选项菜单。在 Android 上,这些菜单有助于方便用户操作。我们可以在第一个菜单项的click事件中看到这一点,在那里我们使用事件处理器来捕获这个事件并显示一个简单的警告对话框。

菜单中的第四个选项通过使用"onPrepareOptionsMenu"事件进行修改,该事件在菜单被添加到屏幕之前执行。您可以在此事件处理器中启用、禁用、添加或删除项目。在本教程中,我们通过将第四个菜单选项的visible状态设置为false来隐藏它,我们可以在第二个菜单选项的事件处理器中再次将其更改为true

Android 设备的菜单图标大小

您的菜单图标应该是平面的,以“正面”呈现,并且是灰度的。根据 Android 指南,所有菜单图标应使用相同的调色板和效果,以保持一致性。以下是 Android 屏幕密度大小的图标大小列表。

  • 高密度(hdpi)屏幕的菜单图标尺寸:

    • 完整资产:72 x 72 像素

    • 图标:48 x 48 像素

    • 方形图标:44 x 44 像素

  • 中密度(mdpi)屏幕的菜单图标尺寸:

    • 完整资产:48 x 48 像素

    • 图标:32 x 32 像素

    • 方形图标:30 x 30 像素

  • 低密度(ldpi)屏幕的菜单图标尺寸:

    • 完整资产:36 x 36 像素

    • 图标:24 x 24 像素

    • 方形图标:22 x 22

访问联系人地址簿

有时候,您可能希望用户访问他们设备中的现有数据,以便在您的应用程序中填充某些字段或数据库。可能最好的例子是使用地址簿和联系人详细信息。例如,如果您构建了一个主要用于通过电子邮件共享数据的应用程序,使用设备上的联系人地址簿将允许用户从选择列表中选择他们已经拥有的联系人(而不是需要单独记住或重新输入数据)。

在本教程中,我们将创建一个基本界面,该界面访问我们的通讯录并检索联系人详细信息,在执行此操作的同时填充我们的界面组件,包括一些文本字段和图像视图。在开始之前,请确保您的设备或模拟器中已通过在 iPhone 上选择通讯录图标或在 Android 上选择联系人图标添加了一些联系人。

注意

本教程的完整源代码可在/Chapter 8/Recipe 2文件夹中找到。

如何操作...

在您的 IDE 中打开app.js文件,并输入以下代码:

//create the root window
var win1 = Titanium.UI.createWindow({
title: 'Android Options Menu',
backgroundColor: '#ccc'
});
//add the textfields
var txtName = Titanium.UI.createTextField({
width: 280,
top: 150,
left: 20,
height: 40,
backgroundColor: '#fff',
borderRadius: 3,
hintText: 'Friend\'s name...',
paddingLeft: 3
});
win1.add(txtName);
var txtEmail = Titanium.UI.createTextField({
width: 280,
top: 200,
left: 20,
height: 40,
backgroundColor: '#fff',
borderRadius: 3,
hintText: 'Contact\'s email address...',
paddingLeft: 3,
keyboardType: Titanium.UI.KEYBOARD_EMAIL
});
win1.add(txtEmail);
//this is the user image
var imgView = Titanium.UI.createImageView({
width: 80,
left: 20,
height: 80,
top: 45,
image: 'no_avatar.gif'
});
win1.add(imgView);
var contactButton = Titanium.UI.createButton({
title: 'Select a contact...',
left: 20,
top: 10,
height: 28,
width: 280
});
contactButton.addEventListener('click', function(e){
//
//if array of details is specified, the detail view will be
//shown
//when the contact is selected. this will also trigger
//e.key, and e.index in the success callback
//
Titanium.Contacts.showContacts({
selectedProperty: function(e) {
Ti.API.info(e.type + ' - '+ e.value);
txtEmail.value = e.email;
},
selectedPerson: function(e) {
var person = e.person;
txtEmail.value = person.email.home[0];
if (person.image != null) {
imgView.image = person.image;
}
else {
imgView.image = 'no_avatar.gif';
avatar = 'no_avatar.gif';
}
txtName.value = person.firstName + ' ' + person.lastName;
}
});
});
win1.add(contactButton);
win1.open();

工作原理...

对地址簿的访问取决于平台。在 Android 操作系统上,您将只能对联系人列表进行只读访问,而在 iPhone 上,您将拥有完整的读写权限。因此,请注意,createPerson等方法在 Android 设备上不可用。

所有对设备通讯录的访问都通过Titanium.Contacts命名空间提供。在本教程中,我们创建了一个包含一些文本字段和图像视图的基本屏幕,我们通过加载联系人 API 并从设备联系人列表中选择一个条目来填充它。为此,我们执行了showContacts()方法,该方法有两个不同的回调函数:

  1. SelectedProperty: 当用户选择一个人的属性而不是单个联系人条目时执行此回调

  2. SelectedPerson: 当用户选择一个人的条目时执行此回调

在我们的示例菜谱中,我们正在使用SelectedPerson函数,并将回调属性(e)分配给一个名为person的新对象。从这里我们可以访问从设备联系人列表中选择的联系人的field属性,例如电话、电子邮件、姓名和联系照片,然后将这些变量分配给我们自己的应用程序中的相关字段。以下截图显示了从设备列表中选择联系人后,联系人的屏幕在空和填写后的样子:

它是如何工作的…

通过剪贴板存储和检索数据

剪贴板用于存储文本和对象数据,以便在设备上的不同屏幕和应用程序之间使用。虽然 iOS 和 Android 都内置了剪贴板功能,但 Titanium 通过允许您以编程方式访问和写入剪贴板数据来扩展了这一功能。在这个菜谱中,我们将创建一个带有两个文本字段和一系列按钮的屏幕,这些按钮允许我们以编程方式从一个文本字段复制数据并将其粘贴到另一个文本字段中。

注意

本菜谱的完整源代码可以在/Chapter 8/Recipe 3文件夹中找到。

如何做到这一点…

在您的 IDE 中打开项目中的app.js文件,并输入以下代码(删除任何现有代码)。完成后,在模拟器中运行您的应用程序以进行测试。

var win1 = Titanium.UI.createWindow({
backgroundColor: '#fff',
title: 'Copy and Paste'
});
var txtData1 = Titanium.UI.createTextField({
left: 20,
width: 280,
height: 40,
top: 10,
borderStyle:Titanium.UI.INPUT_BORDERSTYLE_ROUNDED
});
var txtData2 = Titanium.UI.createTextField({
left: 20,
width: 280,
height: 40,
top: 60,
borderStyle:Titanium.UI.INPUT_BORDERSTYLE_ROUNDED
});
var copyButton = Titanium.UI.createButton({
title: 'Copy',
width: 80,
height: 30,
left: 20,
top: 140
});
var pasteButton = Titanium.UI.createButton({
title: 'Paste',
width: 80,
height: 30,
left: 120,
top: 140,
visible: false
});
var clearButton = Titanium.UI.createButton({
title: 'Clear',
width: 80,
height: 30,
right: 20,
top: 140
});
function copyTextToClipboard() {
Ti.UI.Clipboard.setText(txtData1.value);
copyButton.visible = false;
pasteButton.visible = true;
}
function pasteTextFromClipboard() {
txtData2.value = Ti.UI.Clipboard.getText();
txtData1.value = '';
copyButton.visible = true;
pasteButton.visible = false;
}
function clearTextFromClipboard() {
Ti.UI.Clipboard.clearText();
}
copyButton.addEventListener('click', copyTextToClipboard);
pasteButton.addEventListener('click', pasteTextFromClipboard);
clearButton.addEventListener('click', clearTextFromClipboard);
win1.add(txtData1);
win1.add(txtData2);
win1.add(copyButton);
win1.add(pasteButton);
win1.add(clearButton);
win1.open();

它是如何工作的…

在这个菜谱中,我们正在将简单的字符串复制到剪贴板和从剪贴板中。然而,重要的是要注意,您还可以使用Ti.UI.Clipboard.setObject()方法复制对象。我们正在使用两种方法来复制剪贴板中的数据,setText()getText(),它们确实执行了它们所描述的功能。我们使用复制按钮将文本从第一个文本字段设置到剪贴板中,然后使用粘贴按钮以编程方式将相同的文本粘贴到第二个文本字段中。使用剪贴板有许多用途,但其中最重要的用途是它允许用户将应用程序提供的数据与设备上的其他应用程序共享(如下面的截图所示)。例如,您可以为电子邮件地址提供一个“复制”按钮,然后用户可以将该按钮复制并粘贴到他们的本地电子邮件客户端中,例如 Mobile Mail 或 Google Gmail。

它是如何工作的…

在 iPhone 上创建后台服务

苹果现在支持 iOS 4 及更高版本的背景服务,这意味着您的应用程序现在可以在后台运行代码,就像 Android 应用程序能够做到的那样。在这个菜谱中,我们将创建一个后台服务,它将执行一个名为bg.js的单独文件中的固定代码片段。我们还将记录后台服务周期的每个阶段到控制台,以便您理解每个过程。

注意

本食谱的完整源代码可以在/Chapter 8/Recipe 4文件夹中找到。

如何做到这一点…

在你的 IDE 中打开项目app.js文件,并输入以下代码(删除任何现有代码):

//create root window
var win1 = Titanium.UI.createWindow({
backgroundColor: '#fff',
title: 'Background Services'
});
function validiOSPlatform() {
//add iphone checks
if (Titanium.Platform.osname == 'iphone')
{
var version = Titanium.Platform.version.split(".");
var major = parseInt(version[0],0);
//can only test this support on ios 4+
if (major >= 4) {
return true;
}
}
//either we're not running ios or platform is old
return false;
}
if (validiOSPlatform() == true) {
//register a background service.
//this JS will run when the app is backgrounded
var service =
Ti.App.iOS.registerBackgroundService({url:'bg.js'});
Ti.API.info("registered background service = " + service);
//fired when an app is resuming for suspension
Ti.App.addEventListener('resume',function(e){
Ti.API.info("App is resuming from the background");
});
//fired when an app has resumed
Ti.App.addEventListener('resumed',function(e){
Ti.API.info("App has resumed from the background");
});
//fired when an app is paused
Ti.App.addEventListener('pause',function(e){
Ti.API.info("App was paused from the foreground");
});
}
//finally, open the window
win1.open();

现在创建一个名为bg.js的新文件,并将其保存到项目的Resources文件夹中,然后输入以下代码。这是我们通过后台服务将要执行的代码:

Ti.API.info("This line was executed from a background service!");

完成后,在模拟器中运行你的应用程序以进行测试。

它是如何工作的…

本食谱涉及两个主要步骤。第一步是确保我们正在运行的设备确实是一部 iPhone(iOS)设备,第二步是确保它正在运行所需的 iOS 4+操作系统。validiOSPlatform()函数执行此任务,并返回一个简单的布尔值 true/false 响应,指示是否继续注册我们的后台服务。

第二部分是使用bg.js文件作为代码注册我们的后台服务,当应用程序进入“后台”状态时执行此代码。在这种情况下,后台服务文件中的代码将触发并记录一条信息消息到控制台。本例中还处理了所有应用程序暂停和恢复事件监听器。因此,你可以在模拟器中运行应用程序,打开它,退出它,然后再次打开它,以查看每个事件处理程序触发,并将相应的消息记录到控制台。

如何工作…

在 iPhone 上显示本地通知

另一个 iOS 4+后的特性是本地通知的引入,它允许开发者创建看起来和操作类似推送通知的基本通知警报,但无需创建所有必要的证书和服务器端代码,以便推送工作。在本食谱中,我们将扩展为我们的后台服务编写的先前代码,并在应用程序被推送到系统后台时创建一个本地通知。

注意

本食谱的完整源代码可以在/Chapter 8/Recipe 5文件夹中找到。

如何做到这一点…

打开上一个食谱中的项目bg.js文件,并添加以下代码来扩展它:

var notification = Ti.App.iOS.scheduleLocalNotification({
alertBody: 'Hey, this is a local notification!',
alertAction: "Answer it!",
userInfo: {"Hello": "world"},
date: new Date(new Date().getTime())
});

现在,在你的app.js文件中,创建以下事件监听器和处理程序。它将在后台过程中按下Answer It确认按钮时执行:

//listen for a local notification event
Ti.App.iOS.addEventListener('notification', function(e)
{
Ti.API.info("Local notification received: "+ JSON.stringify(e));
alert('Your local notification caused this event to fire!');
});

完成后,在模拟器中运行你的应用程序以进行测试。你应该能够在应用程序启动后“后台”或暂停应用程序(通过按下 iPhone 上的“主页”按钮)并接收本地通知。点击Answer It将重新加载你的应用程序,并触发我们的“通知”事件监听器(如以下截图所示)!

如何做到这一点…

如何工作…

本地通知由多个参数组成,包括:

  • alertBody: 出现在你的警报对话框中的消息

  • alertAction: 执行您应用的右侧按钮

  • userInfo: 您希望传递回应用的资料

  • date: 何时执行通知

我们的示例使用当前日期和时间,这意味着通知将在应用变为“后台”后立即出现。当通知出现时,用户可以取消它,或者使用我们的自定义“操作”按钮重新启动应用并执行我们的“通知”事件处理器。

使用意图显示 Android 通知

意图是 Android 术语,用于在系统上执行的操作。最重要的是,它用于启动活动。意图的主要参数包括:

  1. “操作”:要执行的一般操作,例如 ACTION_VIEW

  2. “数据”:操作要操作的数据,例如数据库记录或联系人数据

在这个菜谱中,我们将结合使用意图和 Android 的通知管理器来创建一个本地通知,该通知将出现在我们用户的 Android 通知栏中。

注意

本菜谱的完整源代码可以在 /Chapter 8/Recipe 6 文件夹中找到。

如何操作...

您需要包标识符(格式为 com.yourcompany.yourapp—you,您可以在 Titanium Studio 的 编辑 选项卡中找到它)以及您 Android 应用的类名。您可以通过在项目中打开 Build/Android 文件夹,然后打开包含在内的 Android.manifest.xml 文件来找到类名。在 application 节点下,您将找到一个类似以下内容的部分:

<activity
android:name=".NativeapisActivity"
android:label="NativeAPIs"
android:theme="@style/Theme.Titanium"
android:configChanges="keyboardHidden|orientation"
>
...

您的 className 属性是您的应用标识符和之前 XML 中的 android:name 属性的组合。在我们的例子中,这个 className 属性是 com.boydlee.nativeapis.NativeapisActvitity

将这两个值记录下来后,在您的 IDE 中打开项目中的 app.js 文件,并输入以下代码(删除任何现有代码):

//create root window
var win1 = Titanium.UI.createWindow();
if(Titanium.Platform.osname == 'android')
{
var intent = Titanium.Android.createIntent({
action: Titanium.Android.ACTION_MAIN,
className: 'com.boydlee.nativeapis.NativeapisActivity',
packageName: 'com.boydlee.nativeapis'
});
intent.addCategory(Titanium.Android.CATEGORY_LAUNCHER);
var pending = Titanium.Android.createPendingIntent({
activity: Titanium.Android.currentActivity,
intent: intent,
type:
Titanium.Android.PENDING_INTENT_FOR_ACTIVITY,
flags: Titanium.Android.FLAG_ACTIVITY_NEW_TASK
});
var notification =
Titanium.Android.createNotification({
contentIntent: pending,
contentTitle: 'New Notification',
contentText: 'Hey there Titanium Developer!!',
tickerText: 'You have a new Titanium message...',
ledARGB: 1,
number: 1,
when: new Date().getTime()
});
Ti.Android.NotificationManager.notify(1,
notification);
}
//finally, open the window
win1.open();

完成后,在 Android 模拟器中运行您的应用以进行测试。一旦您的应用启动,您应该可以退出并下拉 Android 通知栏以查看结果。

它是如何工作的...

在这个菜谱中,我们结合使用意图、活动和通知消息。notification 对象本身相对简单。它接受包括通知的标题和消息、徽章 numberwhen 参数(通知将显示的 datetime,我们将其设置为默认值 'now')在内的多个参数。ledARGB 参数是设备 LED 闪烁的颜色,我们将其设置为设备默认值。

您会注意到我们还使用 addCategory 方法为我们的意图添加了一个类别,例如 intent.addCategory(Titanium.Android.CATEGORY_LAUNCHER);在我们的例子中,我们使用了 CATEGORY_LAUNCHER,这意味着我们的意图应作为顶级应用出现在启动器中。

与我们的通知一起的是一个名为pending的对象。这是我们意图,这个意图已经被编写来启动一个活动。在我们的案例中,活动是重新启动我们的应用程序。您还可以向意图添加 URL 属性,以便您的应用程序可以在重新进入时启动特定的代码。

以下截图显示了我们的通知消息在实际操作中的示例:

它是如何工作的…

将您的 Android 应用程序存储在设备的 SD 卡上

Titanium 的一个缺点是,由于其编译过程,基于 Titanium 平台构建的应用程序与原生应用程序相比,文件大小往往较大。在 Titanium 上大多数简单应用程序的大小在 4-5 兆字节之间。这对 iPhone 来说并不是真正的问题,但不幸的是,许多 Android 设备使用 SD 卡内存,并且没有太多的本地手机存储空间。

在这个食谱中,我们将向您展示如何配置您的 Android 应用程序,以便它可以在 SD 卡上运行,通过在应用程序设置屏幕中的 Android 的移动到 SD 卡按钮。

注意

本食谱的完整源代码可以在/第八章/食谱 7文件夹中找到。

如何做到这一点…

打开项目根目录下的tiapp.xml文件,并找到 XML 中的<android>节点,该节点位于文件底部附近。修改<android>节点,使其看起来像以下代码:

<android >
<tool-api-level>8</tool-api-level>
<manifest android:installLocation="preferExternal">
<uses-sdk android:minSdkVersion="7" />
</manifest>
</android>

现在构建并运行您的应用程序在您的 Android 设备上。请注意,这可能在模拟器中不起作用。

它是如何工作的…

在理解与这个 XML 配置相关的几个重要部分时,首先要注意的是<tool-api-level>节点值实际上是指所需的 Android 工具的最小版本。版本 8 是启用外部存储功能所需的最小版本。

<android:installLocation>属性指的是应用程序安装时的初始存储位置。在这里,我们告诉 Android 操作系统我们希望它存储在外部 SD 卡上。然而,如果 SD 卡不可用,应用程序将直接存储在手机内存中。您还可以使用internalOnly值,这将不允许应用程序安装在外部存储上。

最后,<uses-sdk>节点指的是所需的 Android 版本。在本例中,版本 7 指的是 Android 2.1 及以上版本,这是执行复制到 SD 卡操作所必需的。

第九章。将您的应用程序与外部服务集成

在本章中,我们将涵盖:

  • 连接到使用基本身份验证的 API

  • 从 Google Places API 获取数据

  • 使用 OAuth 连接到 FourSquare

  • 在 FourSquare 上发布签到

  • 通过 Yahoo! YQL 搜索和检索数据

  • 将推送通知与 UrbanAirship.com 集成

  • 使用 PHP 和 HTTP POST 测试推送通知

简介

许多移动应用程序是自包含的程序(例如计算器应用程序),并且不需要与其他服务或系统交互。然而,您会发现随着您构建的越来越多,开始变得有必要与外部供应商和系统集成,以保持您的用户满意。最近将 Facebook "赞" 按钮集成以及能够在应用程序内推文的趋势是这一点的绝佳例子。

在本章中,我们将集中讨论以多种常见方式与各种不同的服务提供商进行通信,包括基本授权、开放授权以及使用服务提供商(如 Urban Airship),结合一些 PHP 代码,使推送通知在您的 iPhone 上工作。

连接到使用基本身份验证的 API

基本身份验证是一种通过在发送到 HTTP 之前使用 Base64 编码用户名和密码凭据来获取系统访问权限的方法。例如,给定用户名 'Aladdin' 和密码 'open sesame',字符串 'Aladdin:open sesame' 被 Base64 编码,结果为 'QWxhZGRpbjpvcGVuIHNlc2FtZQ=='。这个 Base64 字符串随后被接收服务器解码,结果为用冒号分隔的原始用户名-密码字符串。虽然这不是最安全的身份验证方案,但对于人类眼睛来说是不可读的,并且对于小型 API 或私有系统来说非常容易实现。从 HTTP/1.1 开始,所有网络浏览器都支持基本身份验证,因此可以在 Web 和移动设备上广泛实现,无需担心浏览器支持。

许多外部服务使用基本身份验证和会话密钥,以便您访问和交互它们的 API。在这个例子中,我将向您展示如何使用基本身份验证机制访问 Blurtit API。这个食谱的基本原理也适用于任何其他使用基本身份验证的标准 API。

准备工作

Blurtit 是一个免费的在线问答系统,类似于 Yahoo! Answers,或者许多其他在网上的问答风格论坛。您需要在与 Blurtit.com 的账户设置中注册并申请他们的 API,该 API 位于 api.blurtit.com。注册后,您将获得一个用户 ID、API 密钥、登录名和密码。您需要这四项内容才能连接到 API 并检索数据。

注意

本食谱的完整源代码可以在 /Chapter 9/Recipe 1 文件夹中找到。

如何操作...

在 Titanium Studio 中创建一个新的项目,并打开app.js文件,删除所有现有的代码。首先,我们将创建一些变量,这些变量将包含你的 API 密钥、用户 ID、用户名、密码以及 API 的 URL。确保你将以下代码中的loginNameloginPassword变量值替换为在准备工作部分注册 API 时提供的登录信息:

var win = Titanium.UI.createWindow();
var userid = '123456';
var apikey = 'B_1a0350b39b19a05************';
var loginName = 'b****@gmail.com';
var loginPasswd = '******';
var apiUrl = 'http://api.blurtit.com/users/signin.json';

现在,为了进行基本认证,我们需要创建一个请求头。这个信息在声明xhr httpClient对象之后发送,但在你执行send方法之前:

var xhr = Titanium.Network.createHTTPClient();
xhr.open('POST', apiUrl);
authstr = 'Basic ' + Titanium.Utils.base64encode(userid+':'+apikey);
xhr.setRequestHeader('Authorization', authstr);

接下来,根据 Blurtit API 创建你的参数数组。在这种情况下,我们将login_namepassword变量传递进去以执行signin请求。将params数组附加到你的xhr.send()方法,如下所示:

var params = {
'login_name': loginName,
'password': loginPasswd
};
xhr.send(params);

最后,在xhr.onload()方法中,读取responseText并将其分配给一个 JSON 对象。然后我们可以读取返回的数据(在这个例子中是一个会话 ID),并将其分配给一个标签以供显示:

//create and add the label to the window
var lblsession = Titanium.UI.createLabel({
width: 280,
height: 'auto',
textAlign: 'center'
});
win.add(lblsession);
//execute our onload function and assign the result to
//our lblsession control
xhr.onload = function() {
Titanium.API.info(' Text: ' + this.responseText);
var jsonObject = JSON.parse(this.responseText);
lblsession.text = "Session ID: \n" +
jsonObject.user.session_id;
};
//finally open the window
win.open();

现在我们已经授权并存储了我们的会话变量,我们可以调用 Blurtit API 提供的函数。以下是一个示例,它向 API 提出一个简单的问题,然后将 JSON 响应记录到控制台:

function callAPI(apiFunction, params)
{
var xhr = Titanium.Network.createHTTPClient();
Ti.API.info('Session ID = ' + sessionid);
xhr.onload = function() {
//this is the response data to our question
Titanium.API.info(' Text: ' + this.responseText);
var jsonObject = JSON.parse(this.responseText);
};
xhr.open('POST', apiUrl + apiFunction);
authstr = 'Basic '+
Titanium.Utils.base64encode(userid+':'+apikey);
xhr.setRequestHeader('Authorization', authstr);
xhr.send(params);
}
var params = {
'session_id': sessionid,
'query': "Who is Britney Spears?"
};
//call the api with your session_id and question_id
callAPI('questions/search.json?query=' +
txtQuestion.value, params);

它是如何工作的...

基本认证系统基于认证和接收一个会话令牌的原则,然后这个令牌可以在每个后续的 API 调用中用作识别自己的手段。这个会话变量作为参数传递给你要对系统进行的每个调用。这可以从我们之前的代码中看到,我们正在调用搜索问题方法(questions/search.json?query=xxx)。

注意

应当注意的是,将用户名和密码变量编码为 Base64 字符串的目的并非是为了安全,而是为了确保可能的不兼容 HTTP 字符被编码成 HTTP 兼容的值。基本认证方法目前在互联网上仍然广泛使用。然而,在今天,它在许多情况下正被 OAuth 所取代。我们将在本章的下一个菜谱中查看如何与 OAuth 集成。

从 Google Places API 获取数据

Google Places API 是 Google Maps 的一个新部分,它返回有关地点的信息(例如,银行、自动取款机、服务、机场等)。它标志着 Google 试图直接将用户与其位置附近的商店或感兴趣的项目连接起来,并且高度针对移动使用。在本菜谱中,我们将创建一个新的模块,其中包含连接到 Google Places API 并返回数据的所有代码。

准备工作

为了对 Places API 进行请求,你需要从 Google 获取一个 API 密钥。你可以从这里获取密钥:code.google.com/apis/console

注意

本食谱的完整源代码可以在 /Chapter 9/Recipe 2 文件夹中找到。

如何做到这一点…

在 Titanium Studio 中创建一个新的项目,你可以给它任何你想要的名称。然后,创建一个名为 placesapi.js 的新文件,并将其保存到你的项目的 Resources 目录中。将以下代码输入到这个新的 JavaScript 文件中:

var api = {
xhr: null
};
var places = (function() {
//create an object which will be our public API
//data format must be json or xml
api.getData = function(lat, lon, radius, types, name, sensor, success, error) {
if(api.xhr == null){
api.xhr = Titanium.Network.createHTTPClient();
}
var url =
"https://maps.googleapis.com/maps/api/place/search/json?";
url = url + "location=" + lat + ',' + lon;
url = url + "&radius=" + radius;
url = url + "&types=" + types;
url = url + "&name=" + name;
url = url + "&sensor=" + sensor;
url = url + "&key="
+ Titanium.App.Properties.getString("googlePlacesAPIKey");
Ti.API.info(url);
api.xhr.open('GET', url);
api.xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
api.xhr.onerror = function(e){
Ti.API.error("API ERROR " + e.error);
if (error) {
error(e);
}
};
api.xhr.onload = function(){
Ti.API.debug("API response: " + this.responseText);
if (success) {
var jsonResponse = JSON.parse(this.responseText);
success(jsonResponse);
}
};
api.xhr.send();
};
//data format must be json or xml
api.getPlaceDetails = function(reference, sensor, success,
error) {
if(api.xhr == null){
api.xhr = Titanium.Network.createHTTPClient();
}
var url =
"https://maps.googleapis.com/maps/api/place/details/json?";
url = url + "reference=" + reference;
url = url + "&sensor=" + sensor;
url = url + "&key=" +
Titanium.App.Properties.getString("googlePlacesAPIKey");
//for debugging should you wish to check the URL
//Ti.API.info(url);
api.xhr.open('GET', url);
api.xhr.setRequestHeader('Content-Type', 'application/json;
charset=utf-8');
api.xhr.onerror = function(e){
Ti.API.error("API ERROR " + e.error);
if (error) {
error(e);
}
};
api.xhr.onload = function(){
Ti.API.debug("API response: " + this.responseText);
if (success) {
var jsonResponse = JSON.parse(this.responseText);
success(jsonResponse);
}
};
api.xhr.send();
};
//return our public API
return api;
} ());

现在打开你的 app.js 文件(或你打算从那里调用地点模块的地方),删除所有现有的代码。输入以下示例代码以使用我们的 API 包装器获取数据。请注意,在这个例子中,你可以使用 JSON 格式从该 API 返回 XML 数据,这应该是你移动开发的实际标准。你还需要将 XXXXXXXXXXXXXXXX API 密钥替换为你自己的有效 API 密钥,来自 Google。

//include our placesapi.js module we created earlier
Ti.include('placesapi.js');
//Types array
var types = ['airport', 'atm', 'bank', 'bar', 'parking', 'pet_store', 'pharmacy', 'police', 'post_office', 'shopping_mall'];
Titanium.App.Properties.setString("googlePlacesAPIKey", "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
//fetch banks and atm's
//note the types list is a very short sample of all the types of
//places available to you in the Places API
places.getData(-33.8670522, 151.1957362, 500, types[1] + "|" + types[2], '', 'false',
function(response) {
//getting an item out of the json response
Ti.API.info(response.results[1].name);
Ti.API.info(response.results[1].vicinity);
Ti.API.info(response.results[1].icon);
Ti.API.info(response.results[1].types[0]);
},
function(e) {
Titanium.UI.createAlertDialog({
title: "API call failed",
message: e,
buttonNames: ['OK']
}).show();
});

在模拟器中运行示例应用程序,你应该能够获取一个 JSON 格式的列表返回,并将列表中的第一个项目记录到控制台。尝试扩展这个示例以使用实时位置数据与 Google Maps 集成!你还可以通过调用 API 的 getPlaceDetails() 方法获取更详细的位置信息,例如:

places.getPlaceDetails(response.results[1].reference, 'false', function(response){
//log the json response to the console
Ti.API.info(response);
},
function(e){
//something went wrong
//log any errors etc...
});

它是如何工作的…

小贴士

地点 API 可能是最简单的服务集成类型。使用它,除了需要 API 密钥外,没有其他认证方法,所有参数都通过查询字符串使用 HTTP GET 传递。

请求头是这个方法的一个重要特性。请注意,在我们对 xhr 对象执行 send() 调用之前,我们需要将内容类型设置为 application/json。如果不设置内容类型,你将面临数据以 HTML 或其他格式返回的风险,这些格式可能不是 100%与 JSON 兼容。因此,它可能无法加载到 JSON 对象中。

当地点服务从搜索返回 JSON 结果时,它将它们放置在结果数组中。即使服务没有返回结果,它仍然返回一个空的结果数组。响应中的每个元素都包含一个从你通过纬度和经度输入指定的区域获取的单个地点结果,按突出度排序。许多事情,包括签到次数,都会影响结果的突出度及其受欢迎程度。Google 文档提供了有关每个地点结果返回数据的以下信息(见 [http://code.google.com/apis/maps/documentation/places/]):](http://code.google.com/apis/maps/documentation/places/)

  • name 包含返回结果的易读名称。对于 establishment 结果,这通常是商业名称。

  • vicinity 包含一个附近位置的名称。这个特征通常指的是给定结果中的街道或社区。

  • types[] 包含一个特征类型的数组,描述了给定的结果。

  • geometry 包含关于结果的位置信息,通常包括位置(geocode)和(可选)标识其一般覆盖范围的 viewport

  • icon 包含推荐图标的 URL,当指示此结果时可能会显示给用户。

  • reference 包含一个唯一的令牌,你可以使用它来检索有关此位置的其他信息。你可以存储这个令牌,并在将来任何时间使用它来刷新有关此位置缓存的资料,但同一位置在不同搜索中返回的相同令牌并不保证。

  • id 包含一个唯一的稳定标识符,表示这个位置。

Places API 中有许多其他功能,包括“签到”到位置的能力等。此外,你还应该注意,当将此菜谱集成到实际应用程序中时,谷歌的条款之一是你必须在你的应用程序中显示“由谷歌提供”的标志,除非你显示的结果已经在谷歌品牌的地图上。

使用 OAuth 连接到 FourSquare

开放授权(通常通过其缩写名 OAuth 闻名)是一个为授权而开发的开放标准,它允许用户将存储在一个网站或设备(例如你的手机)上的私有数据与另一个网站共享。OAuth 不使用凭据(例如用户名和密码),而是依赖于令牌。每个令牌都包含了一系列特定网站(例如 FourSquare 或 Twitter)的详细信息,使用特定的资源或权限(例如照片或你的个人信息)在特定时间段内(例如两小时)。

FourSquare 是一个流行的基于位置的社会化网络平台,专门为具有 GPS 功能的移动设备设计。它允许你签到到各种位置,并在这样做的同时,以“徽章”的形式获得积分和奖励。在这个菜谱中,我们将使用 OAuth 连接到 FourSquare 并检索一个访问令牌,我们可以在以后使用这个令牌来使我们的应用程序能够在 FourSquare 社区内的各种位置进行“签到”。

准备工作

为了对 FourSquare API 进行请求,你需要从 FourSquare 获取一个客户端 ID 密钥。你可以在这里免费从开发者网站获取一个密钥:developer.foursquare.com

注意

这个菜谱的完整源代码可以在 /第九章/菜谱 3 文件夹中找到。

如何做到这一点...

在 Titanium Studio 中创建一个新的项目,你可以给它任何你想要的名字。然后,创建一个名为 fsq_module.js 的新文件,并将其保存到你的项目 Resources 目录中。这个文件将包含创建模块所需的所有源代码,我们可以在我们的 Titanium 应用程序中的任何地方包含这个模块。在你的新 fsq_module.js 文件中打开你的编辑器,并输入以下内容:

var FOURSQModule = {};
(function() {
FOURSQModule.init = function(clientId, redirectUri) {
FOURSQModule.clientId = clientId;
FOURSQModule.redirectUri = redirectUri;
FOURSQModule.ACCESS_TOKEN = null;
FOURSQModule.xhr = null;
FOURSQModule.API_URL = "https://api.foursquare.com/v2/";
};
FOURSQModule.logout = function() {
showAuthorizeUI(
String.format('https://foursquare.com/oauth2/authorize?response_type=token&client_id=%s&redirect_uri=%s',
FOURSQModule.clientId,
FOURSQModule.redirectUri)
);
return;
};
/**
* displays the familiar web login dialog
*
*/
FOURSQModule.login = function(authSuccess_callback) {
if (authSuccess_callback != undefined) {
FOURSQModule.success_callback = authSuccess_callback;
}
showAuthorizeUI(
String.format('https://foursquare.com/oauth2/authenticate?response_type=token&client_id=%s&redirect_uri=%s',
FOURSQModule.clientId,
FOURSQModule.redirectUri)
);
return;
};
FOURSQModule.closeFSQWindow = function() {
destroyAuthorizeUI();
};
/*
* display the familiar web login dialog
*/
function showAuthorizeUI(pUrl)
{
window = Ti.UI.createWindow({
modal: true,
fullscreen: true,
width: '100%'
});
var transform = Ti.UI.create2DMatrix().scale(0);
view = Ti.UI.createView({
top: 5,
width: '100%',
height: 450,
border: 10,
backgroundColor: 'white',
borderColor: '#aaa',
borderRadius: 20,
borderWidth: 5,
zIndex: -1,
transform: transform
});
closeLabel = Ti.UI.createLabel({
textAlign: 'right',
font: {
fontWeight: 'bold',
fontSize: '12pt'
},
text: '(X)',
top: 5,
right: 12,
height: 14
});
window.open();
webView = Ti.UI.createWebView({
top: 25,
width: '100%',
url: pUrl,
autoDetect: [Ti.UI.AUTODETECT_NONE]
});
Ti.API.debug('Setting:[' + Ti.UI.AUTODETECT_NONE + ']');
webView.addEventListener('beforeload',
function(e) {
if (e.url.indexOf('http://www.foursquare.com/') != -1) {
Titanium.API.debug(e);
authorizeUICallback(e);
webView.stopLoading = true;
}
});
webView.addEventListener('load', authorizeUICallback);
view.add(webView);
closeLabel.addEventListener('click', destroyAuthorizeUI);
view.add(closeLabel);
window.add(view);
var animation = Ti.UI.createAnimation();
animation.transform = Ti.UI.create2DMatrix();
animation.duration = 500;
view.animate(animation);
};
/*
* unloads the UI used to have the user authorize the application
*/
function destroyAuthorizeUI()
{
Ti.API.debug('destroyAuthorizeUI');
// if the window doesn't exist, exit
if (window == null) {
return;
}
// remove the UI
try
{
Ti.API.debug('destroyAuthorizeUI:webView.removeEventListener');
webView.removeEventListener('load', authorizeUICallback);
Ti.API.debug('destroyAuthorizeUI:window.close()');
window.hide();
}
catch(ex)
{
Ti.API.debug('Cannot destroy the authorize UI. Ignoring.');
}
};
/*
* fires and event when login fails
*/
function authorizeUICallback(e)
{
Ti.API.debug('authorizeUILoaded ' + e.url);
Titanium.API.debug(e);
if (e.url.indexOf('#access_token') != -1)
{
var token = e.url.split("=")[1];
FOURSQModule.ACCESS_TOKEN = token;
Ti.App.fireEvent('app:4square_token', {
data: token
});
if (FOURSQModule.success_callback != undefined) {
FOURSQModule.success_callback({
access_token: token,
});
}
destroyAuthorizeUI();
} else if ('http://foursquare.com/' == e.url) {
Ti.App.fireEvent('app:4square_logout', {});
destroyAuthorizeUI();
} else if (e.url.indexOf('#error=access_denied') != -1) {
Ti.App.fireEvent('app:4square_access_denied', {});
destroyAuthorizeUI();
}
};
})();

现在,回到你的 app.js 文件中,输入以下代码以包含新的 FourSquare 模块并执行登录函数:

function loginSuccess(e) {
alert('You have successfully logged into 4SQ!");
};
FOURSQModule.init('yourclientid', 'http://www.yourfoursquareurl.com');
FOURSQModule.login(loginSuccess, function(e) {
Titanium.UI.createAlertDialog({
title: "LOGIN FAILED",
message: e,
buttonNames: ['OK']
}).show();
});

尝试在 Android 或 iPhone 模拟器中运行您的应用程序。您应该在启动时看到一个登录屏幕出现,其外观类似于以下截图:

如何做…

它是如何工作的…

在本配方中构建的模块遵循与 Web 上其他模块非常相似的图案和风格,包括为 Titanium 构建的针对 Facebook、Twitter 等其他模块。它包括创建一个覆盖现有窗口的模态视图,并包含指向 FourSquare 登录页面的移动版本的网络视图。一旦用户登录到系统,我们就可以从 authorizeCallBack() 方法中的响应中获取访问令牌,并将生成的令牌保存到模块的 ACCESS_TOKEN 属性中。

将签到发布到 FourSquare

现在我们已经创建了用于验证 FourSquare 的基本模块,我们将扩展它,以便用户可以“签到”到特定位置。这是通过发送您当前位置的详细信息(例如,酒吧、电影院、公园或博物馆)以及其纬度和经度值到 FourSquare 服务器来实现的。从那里,您可以告诉哪些朋友在附近,或者,您可以选择将您的位置和活动对所有人公开。

注意

本配方完整的源代码可以在 /Chapter 9/Recipe 4 文件夹中找到。

如何做…

打开您的 fsq_module.js 文件,扩展现有模块,使其具有以下额外方法:

FOURSQModule.callMethod = function(method, GETorPOST, params, success, error) {
//get the login information
try {
if (FOURSQModule.xhr == null) {
FOURSQModule.xhr = Titanium.Network.createHTTPClient();
}
FOURSQModule.xhr.open(GETorPOST, FOURSQModule.API_URL + method + "?oauth_token=" + FOURSQModule.ACCESS_TOKEN);
FOURSQModule.xhr.onerror = function(e) {
Ti.API.error("FOURSQModule ERROR " + e.error);
Ti.API.error("FOURSQModule ERROR " + FOURSQModule.xhr.location);
if ( error ) {
error(e);
}
};
FOURSQModule.xhr.onload = function(_xhr) {
Ti.API.debug("FOURSQModule response: " + FOURSQModule.xhr.responseText);
if ( success ) {
success(FOURSQModule.xhr);
}
};
FOURSQModule.xhr.send(params);
} catch(err) {
Titanium.UI.createAlertDialog({
title: "Error",
message: String(err),
buttonNames: ['OK']
}).show();
}
};

现在回到您的 app.js 文件,我们将扩展之前配方中编写的“登录”调用,现在在成功授权后发布 FourSquare 签到:

FOURSQModule.init('yourclientid', 'http://www.yourcallbackurl.com');
FOURSQModule.login(function(e){
//checkin to a lat/lon location... you can get
//this from a google map or your GPS co-ordinates
var params = {
shout: 'This is my check-in message!',
broadcast: 'public',
ll: '33.7,44.2'
};
FOURSQModule.callMethod("checkins/add", 'POST', params,
onSuccess_self, function(e) {
Titanium.UI.createAlertDialog({
title: "checkins/add: METHOD FAILED",
message: e,
buttonNames: ['OK']
}).show();
});
//now close the foursquare modal window
FOURSQModule.closeFSQWindow();
},
function(event) {
Titanium.UI.createAlertDialog({
title: "LOGIN FAILED",
message: event,
buttonNames: ['OK']
}).show();
});

现在尝试在模拟器中运行您的应用程序。登录到 FourSquare 系统后,您应该自动发布了一个标题为“这是我的签到消息!”的测试签到,FourSquare 系统应该发送一个成功的响应消息并将其记录到控制台。

它是如何工作的…

我们 FourSquare 模块的 callMethod() 函数在这里完成了所有工作。它基本上是接收要调用的方法名称,以及它是 GET 还是 POST 调用,以及使该方法工作所需的参数。我们的示例代码正在调用 checkins/add 方法,这是一个 POST 调用,并通过 shoutbroadcastll 参数传递。这些分别是我们的消息、隐私设置和当前位置。所有授权工作,包括保存我们的访问令牌,都是通过之前的配方完成的。以下控制台输出显示了在成功签到发布后 FourSquare 的响应:

[DEBUG] destroyAuthorizeUI
[DEBUG] destroyAuthorizeUI:webView.removeEventListener
[DEBUG] destroyAuthorizeUI:window.close()
[DEBUG] FOURSQModule response: {"notifications":[{"type":"notificationTray","item":{"unreadCount":0}},{"type":"message"
,"item":{"message":"OK, got your shout (This is my check-in message!)!"}}],"response":{"checkin":{"i
d":"4ebf9a5d7ee54e4cd299b72e","createdAt":1321179741,"type":"shout","shout":"This is my check-in mes
sage!","timeZone":"Asia/Baghdad","location":{"lat":33.7,"lng":44.2}}}}

通过 Yahoo! YQL 搜索和检索数据

YQL 是一种类似于 SQL 的语言,允许您查询、过滤和组合来自 Yahoo!网络和其他公开数据源的多源数据。通常,开发者访问多个资源的数据是分散的,需要从不同提供商的多个 API 中进行调用,通常具有不同的数据格式。YQL 通过提供一个单一的端点来查询和塑造您请求的数据,从而消除了这个问题。您可能还记得,我们在第二章中简要介绍了通过标准 HTTP 请求调用 YQL 的使用,然而,在本章中,我们将利用内置的 Titanium YQL 方法。

钛金属内置了对 YQL 的支持,在这个菜谱中,我们将创建一个简单的应用程序,用于在 YQL 网络上搜索股票数据,然后以简单的标签形式显示这些数据。

注意

注意,当以未认证的方式使用 YQL(如我们在这里所做的那样)时,每天会施加 100,000 次调用的使用限制。对于大多数应用程序来说,这是一个非常慷慨的限制。但是,如果您希望将其增加,您将需要通过 OAuth 对您的调用进行认证。您可以通过在 Yahoo!上注册并注册您的应用程序来完成此操作。

本菜谱的完整源代码可以在/Chapter 9/Recipe 5文件夹中找到。

如何做到这一点...

创建一个新的项目,然后打开app.js文件,删除任何现有内容。现在输入以下代码:

//
// create base UI tab and root window
//
var win1 = Titanium.UI.createWindow({
backgroundColor:'#fff'
});
// This is the input textfield for our stock code
var txtStockCode = Titanium.UI.createTextField({
hintText: 'Stock code, e.g. APPL',
borderWidth: 0,
width: 200,
left: 10,
height: 30,
font: {fontSize: 14, fontColor: '#262626'},
autoCorrect: false,
autocapitalization: Titanium.UI.TEXT_AUTOCAPITALIZATION_ALL,
borderStyle: 1,
top: 5
});
//add the text field to the window
win1.add(txtStockCode);
// Create the search button from our search.png image
var btnSearch = Titanium.UI.createButton({
title: 'Search YQL',
width: 80,
height: 30,
right: 10,
borderRadius: 3,
top: 5
});
//add the button to the window
win1.add(btnSearch);
//This function is called on search button tap
//it will query YQL for our stock data
function searchYQL() {
// Do some basic validation to ensure the user
//has entered a stock code value
if(txtStockCode.value != '')
{
txtStockCode.blur(); //hides the keyboard
// Create the query string using a combination of
//YQL syntax and the value of the txtStockCode field
var query = 'select * from yahoo.finance.quotes where symbol
= "' + txtStockCode.value + '"';
// Execute the query and get the results
Titanium.Yahoo.yql(query, function(e) {
var data = e.data;
//Iff ErrorIndicationreturnedforsymbolchangedinvalid
//is null then we found a valid stock
if(data.quote.ErrorIndicationreturnedforsymbolchangedinvalid
== null)
{
//show our results in the console
Ti.API.info(data);
var lblStockInfo = Titanium.UI.createLabel({
top: 60,
left: 20,
width: 280,
height: 'auto',
text: ''
});
//create a label to show some of our info
lblStockInfo.text = lblStockInfo.text
+ 'Company name: ' + data.quote.Name;
lblStockInfo.text = lblStockInfo.text +'\nDays Low: '
+ data.quote.DaysLow;
lblStockInfo.text = lblStockInfo.text +'\nDays High: '
+ data.quote.DaysHigh;
lblStockInfo.text = lblStockInfo.text +'\nLast Open: '
+ data.quote.Open;
lblStockInfo.text = lblStockInfo.text +'\nLast Close: '
+ data.quote.PreviousClose;
lblStockInfo.text = lblStockInfo.text +'\nVolume: '
+ data.quote.Volume;
win1.add(lblStockInfo);
}
else
{
//show an alert dialog saying nothing could be found
alert('No stock information could be found for ' + txtStockCode.value);
}
});
} //end if
}
// Add the event listener for this button
btnSearch.addEventListener('click', searchYQL);
//open the window
win1.open();

现在,您应该能够在模拟器中运行应用程序,并搜索股票符号(例如,'AAPL'代表苹果公司),并将一些结果列在屏幕上的标签中,如下所示:

如何做到这一点…

它是如何工作的…

searchYQL()函数中实际上发生了什么?首先,我们对文本字段进行非常基本的验证,以确保用户在按下搜索按钮之前已经输入了股票符号。如果找到股票符号,我们使用文本字段的blur()方法来确保键盘被隐藏。代码的核心部分围绕使用正确的语法创建 Yahoo! YQL 查询,并将文本字段值作为符号参数提供。这个 YQL 查询只是一个字符串,使用+符号连接起来,就像您在 JavaScript 中的任何其他字符串操作一样。

我们然后使用Titanium.Yahoo.yql()方法执行我们的查询,该方法返回内联响应函数的'e'对象中的结果。然后我们可以以任何我们希望的方式操纵和使用这些 JSON 数据。在这种情况下,我们将它的一个子部分分配给屏幕上的标签,以便用户可以查看相关股票的每日开盘价和收盘价。

将推送通知与 UrbanAirship.com 集成

推送通知是一个始终开启的 IP 连接,用于将第三方应用的服务器上的通知转发到你的 iOS 设备。它们作为“始终运行”应用的替代品,允许你的设备在应用未运行时接收特定应用的通知。如果你曾在你的 iPhone 上收到过短信,那么你已经知道推送通知的样子了。它们本质上是一个包含标题、消息以及“关闭”按钮和“操作”按钮的消息框。你可以通过代码定义“操作”按钮的外观以及当按钮被点击时传递给应用的底层动作和数据。

准备中

你需要在 Urban Airship 上注册一个账户,网址是go.urbanairship.com/accounts/register/。一旦你注册并通过 Urban Airship 发送给你的电子邮件链接验证了账户,你需要在go.urbanairship.com/apps/的账户中添加一个新的应用。如果你还没有这样做,请从你的 Apple 开发者账户创建并下载一个新的 Apple 推送证书。你可以在 iOS 开发者账户的“配置”下创建一个新的 App ID,然后在应用列表中找到你刚刚创建的应用,点击“配置”链接。

然后应该会出现一个新页面,允许你选择推送通知选项,如下面的截图所示:

准备中

你需要创建一个特定于应用的客户端 SSL 证书,这可以通过密钥链完成。点击“开发 SSL 证书”选项旁边的“配置”按钮,然后按照逐步向导操作。完成之后,你应该能够下载一个新的 Apple 推送通知证书。

将此证书保存到你的计算机硬盘上,然后双击保存的文件在密钥链访问中打开它。在密钥链访问中,点击“我的证书”,然后找到你刚刚创建的新 Apple 推送通知证书,右键单击它,选择“导出”。你需要给你的新 P12 证书命名。点击“保存”后,你还将被要求提供密码,如下面的截图所示。这可以是任何你喜欢的,例如packt

准备中

现在回到你创建新应用的 Urban Airship 页面,上传新的 P12 证书,并在要求的框中提供密码。保存你的应用,你现在就可以发送推送通知了!

注意

本食谱的完整源代码可以在/第九章/食谱 6文件夹中找到。

如何操作...

在开发者网站的配置文件部分为您的应用程序创建一个新的开发配置文件,并将其下载到您的计算机。接下来,创建一个新的 Titanium 项目,并确保您使用的应用程序标识符与您在开发者门户中创建配置文件时使用的标识符相匹配。Urban Airship 已经为您创建了一个基本的注册示例,因此我们也将使用它。

接下来,打开app.js文件,删除任何现有内容。输入以下代码:

//create root window
var win = Titanium.UI.createWindow({
title:'sample',
backgroundColor:'#fff'
});
var key = 'your app key';
var secret = 'your app secret';
Titanium.Network.registerForPushNotifications({
types:[
Titanium.Network.NOTIFICATION_TYPE_BADGE,
Titanium.Network.NOTIFICATION_TYPE_ALERT,
Titanium.Network.NOTIFICATION_TYPE_SOUND
],
success: successCallback,
error: errorCallback,
callback: messageCallback
});
function successCallback(e) {
var request = Titanium.Network.createHTTPClient({
onload:function(e) {
if (request.status != 200 && request.status != 201) {
request.onerror(e);
return;
}
},
onerror:function(e) {
alert("Register with Urban Airship Push Service failed. Error: "
+ e.error);
}
});
// Register device token with UA
request.open('PUT', 'https://go.urbanairship.com/api/device_tokens/'
+ e.deviceToken, true);
request.setRequestHeader('Authorization','Basic ' +
Titanium.Utils.base64encode(key + ':' + secret));
request.send();
}
function errorCallback(e) {
alert("Error during registration: " + e.error);
}
function messageCallback(e) {
var message;
if(e['aps'] != undefined) {
if(e['aps']['alert'] != undefined){
if(e['aps']['alert']['body'] != undefined){
message = e['aps']['alert']['body'];
} else {
message = e['aps']['alert'];
}
} else {
message = 'No Alert content';
}
} else {
message = 'No APS content';
}
alert(message);
}
//finally, open root window
win.open();

现在,为了测试此代码,您必须在设备上运行应用程序。模拟器根本不具有推送功能,因此不会适用于此食谱。转到 Titanium Studio 中的运行在设备上选项卡,并提供您在食谱的第一步中创建的调试配置文件。接下来,点击立即安装按钮,使用 iTunes 编译并将应用程序包推送到您的设备。

一旦您的应用程序在设备上启动并运行,请转到您的网络浏览器,在 Urban Airship 的应用程序页面中,点击推送然后设备令牌。您的新令牌应该列在此页面上。如果不在,请再次检查所有步骤,并确保您使用正确的移动配置文件来构建您的应用程序。现在,您可以点击发送广播,直接从 Urban Airship 网站向您的设备发送示例推送通知。现在尝试一下,您应该在 iPhone 上收到一条看起来非常类似以下截图的消息:

如何做…

它是如何工作的…

确保您在将推送通知与 Titanium 应用程序一起使用时成功,有几个关键因素。请记住以下要点:

  • 记住,您创建的每个应用程序都需要自己的推送证书,在集成推送时不能使用通配符证书。

  • 总是在开发者控制台中的应用程序设置下首先创建推送证书,然后创建您的配置文件。如果反过来操作,意味着您的配置文件将无效,您的应用程序将不接受任何推送通知请求。

  • 推送通知只能在真实的 iPhone 或 iPod Touch 设备上进行测试,它们在模拟器下不会工作。

  • Titanium.Network.registerForPushNotifications方法需要您希望使用的通知类型作为第一个参数。如果您在最初没有请求用户的特定权限,您可能无法在将来发送此类通知。此外,用户必须始终同意允许您向他们的设备发送推送通知。如果他们不允许此过程发生,您将无法这样做。

  • 您需要在 Apple iOS 开发者控制台和 Urban Airship 中为推送通知创建单独的配置文件和证书。在生产环境中不能使用开发配置文件,反之亦然。

使用 PHP 和 HTTP POST 测试推送通知

为了让我们的服务器应用程序能够以编程方式向用户或一组用户推送通知,我们需要创建一个可以将通知推送到 Urban Airship 服务器的脚本。这可以通过多种方法完成(通过桌面应用程序、.NET 应用程序、Web 应用程序等等),但为了本菜谱的目的,我们将使用 PHP,因为它简单、快速且免费可用。

注意

本菜谱的完整源代码可在 /第九章/菜谱 7 文件夹中找到。

如何操作…

首先,我们需要创建一个 PHP 脚本,该脚本将与 Urban Airship 服务器通信以发送推送通知。创建以下 PHP 脚本,将其保存为 airship.php,并将其上传到能够运行 PHP 且已安装 CURL 的服务器上。如果你还没有一个能够执行此操作的 PHP/Apache 主机账户,网上有很多免费的 PHP/Apache 主机账户。

以下示例取自 Urban Airship 网站:

<?php
define('APPKEY','xxxx');
define('PUSHSECRET', 'xxx'); // Master Secret
define('PUSHURL',
'https://go.urbanairship.com/api/push/broadcast/');
$contents = array();
$contents['badge'] = "+1";
$contents['alert'] = "Hello there Titanium Developer!";
$push = array("aps" => $contents);
$json = json_encode($push);
$session = curl_init(PUSHURL);
curl_setopt($session, CURLOPT_USERPWD, APPKEY . ':' . PUSHSECRET);
curl_setopt($session, CURLOPT_POST, True);
curl_setopt($session, CURLOPT_POSTFIELDS, $json);
curl_setopt($session, CURLOPT_HEADER, False);
curl_setopt($session, CURLOPT_RETURNTRANSFER, True);
curl_setopt($session, CURLOPT_HTTPHEADER, array('Content-Type:application/json'));
$content = curl_exec($session);
echo $content; // just for testing what was sent
// Check if any error occured
$response = curl_getinfo($session);
if($response['http_code'] != 200) {
echo "Got negative response from server, http code: ".
$response['http_code'] . "\n";
} else {
echo "Wow, it worked!\n";
}
curl_close($session);
?>

现在剩下的只是在一个浏览器中运行 PHP 脚本,当你这样做时,你应该在浏览器页面上看到一条成功消息被回显出来,你也应该能够看到一条新的推送通知被发送到你在上一个菜谱中设置的设备上,如下面的截图所示:

如何操作…

它是如何工作的…

本菜谱中的 PHP 脚本所做的与实际 Urban Airship 网站在你可以通过他们的控制台进行测试时所做的几乎相同的工作。在这里,我们使用 PHP 构建一个 CURL 请求,以 JSON 格式发送到 Urban Airship 服务器。该请求随后被接收,然后由 Urban Airship 系统将其作为推送通知推送到你的设备或设备上。

在生产环境中,你可能希望扩展你的 PHP 脚本来接收徽章和消息变量作为 POST 变量,或者可能直接将脚本连接到数据库,使用你的应用程序所需的任何业务逻辑。你还应该注意,Urban Airship 为除 PHP 之外的语言提供了示例。所以如果你的系统是用 .NET 或其他平台构建的,发送广播的相同原则仍然适用。

第十章。使用自定义模块扩展您的应用

在本章中,我们将涵盖:

  • 集成现有模块——PayPal 移动支付库

  • 准备您的 iOS 模块开发环境

  • 使用 XCode 开发新的 iPhone 模块

  • 创建一个公共 API 方法

  • 使用测试工具包打包和测试您的模块

  • 打包您的模块以进行分发和销售

简介

虽然 Titanium 允许你创建几乎跨平台的 app,但不可避免的是,某些设备将固有地具有特定于它们的操作系统和硬件差异(尤其是在 Android 和 iOS 之间)。例如,任何使用过 Android 和 iPhone 设备的人都会立即认识到通知系统设置方式非常不同。然而,还有其他特定于平台的限制,这些限制非常具体于 Titanium API。

在本章中,我们将讨论构建和将模块集成到您的 Titanium 应用程序中,以 iOS 平台为例。使用 Java 开发 Android 模块的方法非常相似,然而,为了我们的目的,我们只会集中开发使用 Objective-C 和 XCode 为 iOS 开发的模块。

集成现有模块——PayPal 移动支付库

已经有许多模块是为 Titanium 平台编写的,包括 Appcelerator 本身和整个社区。甚至还有一个全新的开放移动市场,您可以在那里购买(和出售)模块,以将平台扩展到更高、更远的新高度!

准备工作

在您能够下载和使用 PayPal 移动支付库之前,您首先需要注册 Titanium + Commerce 计划。您可以在 www.appcelerator.com/products/titaniumcommerce/ 上免费注册。填写完所需表格后,只需将名为 Titanium+Commerce MPL for Paypal Module for iOS 的 ZIP 文件下载到您的计算机硬盘上即可。

您还必须将您的应用程序注册到 PayPal,注册后您将获得一个应用程序 ID,您必须在您的 Titanium 项目中引用该 ID。您可以从 www.paypal.com 注册应用程序 ID。请注意,应用程序 ID 的注册还要求您成为 PayPal 会员,因此如果您之前尚未注册,您可能需要先注册。

注意

本食谱的完整源代码可以在 /Chapter 10/Recipe 1 文件夹中找到。

如何操作…

首先,您需要将 Paypal 模块文件复制到 Titanium 安装下的 modules 文件夹中。在 OSX 上,这通常位于 /Library/Application Support/Titanium/modules。在模块下应该已经有一个名为 "iphone" 的子文件夹。如果没有,现在创建一个,然后解压模块文件,以便最终得到一个位于 /Library/Application Support/Titanium/modules/iphone/ti.paypalti.paypal 文件夹。快速浏览一下这个文件夹。您应该立即注意到它下面的第一个子文件夹被命名为 "1.0" 或可能是 "1.2"。这是您刚刚安装的模块的版本号。请仔细注意并记下来,因为这将非常重要。

完成上述步骤后,需要编辑您项目的 tiapp.xml 文件,以便模块部分包括我们的 ti.paypal 模块。此引用告诉 Titanium Studio 编译器在构建项目时添加模块。通过在 "guid" 元素下添加以下行来扩展 tiapp.xml 文件。请确保模块版本号与您刚刚安装的 ti.paypal 库的版本号匹配。

<modules>
<module version="1.0">ti.paypal</module>
</modules>

现在,回到您的 app.js 文件中,我们需要在 JavaScript 的顶部包含模块引用,如下所示:

Ti.Paypal = require('ti.paypal');

现在,我们可以使用这个新的变量对象来创建 PayPal 支付按钮对象并将其添加到窗口中。PayPal 库还包括一些事件监听器来处理支付成功、错误和取消事件。以下是如何使用 PayPal 库为美国红十字会进行支付捐赠的示例,该示例取自 Appcelerator KitchenSink 示例:

var ppButton = Ti.Paypal.createPaypalButton({
width: 294,
height: 50,
bottom: 50,
appId: "YOUR_PAYPAL_APP_ID",
buttonStyle: Ti.Paypal.BUTTON_294x43,
paypalEnvironment: Ti.Paypal.PAYPAL_ENV_SANDBOX,
feePaidByReceiver: false,
transactionType: Ti.Paypal.PAYMENT_TYPE_DONATION,
enableShipping: false,
payment: {
amount: win.data.amt,
tax: 0.00,
shipping: 0.00,
currency: "USD",
recipient: "osama@x.com",
itemDescription: "Donation",
merchantName: "American Red Cross"
}
});
ppButton.addEventListener("paymentCanceled", function(e){
Ti.API.info("Payment Canceled");
});
ppButton.addEventListener("paymentSuccess", function(e){
Ti.API.info("Success");
win.fireEvent("completeEvent", {data: win.data, transid: e.transactionID});
});
ppButton.addEventListener("paymentError", function(e){
Ti.API.info("Payment Error");
});

如果您已正确安装模块并正确更新了 tiapp.xml 文件,您应该会看到一个消息说“检测到第三方模块:[模块名称]/[模块版本]”。在我们的情况下,这将表明它已检测到 ti.paypal 模块。

以下是一个示例,展示了红十字会应用程序运行并使用 Titanium 的 PayPal 模块。此示例代码是本食谱的一部分。

如何操作…

它是如何工作的…

一旦您的模块被复制到模块目录并在 tiapp.xml 中引用,您就可以像使用任何其他原生 Titanium JavaScript 一样使用它。所有模块的公共方法和属性都已由模块的开发者提供给您。

更具体地说,针对我们的 PayPal 模块,一旦买家点击您应用中的“Paypal”购买按钮,就会显示支付结账流程。每当发生重要事件(例如,支付成功)时,这些事件都会被 Titanium 通过以下事件处理器抛出和捕获。您的应用程序需要包含这三个处理器:

ppButton.addEventListener("paymentCanceled", function(e){
Titanium.API.info("Payment Canceled");
});
ppButton.addEventListener("paymentSuccess", function(e){
Titanium.API.info("Payment Success. TransactionID: " +
e.transactionID);
});
ppButton.addEventListener("paymentError", function(e){
Titanium.API.info("Payment Error");
Titanium.API.info("errorCode: " + e.errorCode);
Titanium.API.info("errorMessage: " + e.errorMessage);
});

当支付成功传输后,一个交易 ID 将返回到你的 "paymentSuccess" 事件监听器。需要注意的是,在这个例子中我们使用的是 Paypal 沙盒(测试)环境,对于实际应用,你需要将 paypalEnvironment 变量更改为 Ti.PayPal.PAYPAL_ENV_LIVE。在沙盒环境中,不会实际转账。

更多内容…

尝试在 PayPal 模块中实验不同的属性。以下是最有用的属性及其常量值列表:

buttonStyle PayPal 按钮的大小和外观,可用值有:Titanium.Paypal.BUTTON_68x24Titanium.Paypal.BUTTON_118x24Titanium.Paypal.BUTTON_152x33Titanium.Paypal.BUTTON_194x37Titanium.Paypal.BUTTON_278x43Titanium.Paypal.BUTTON_294x43
paypalEnvironment 可用值有:Titanium.Paypal.PAYPAL_ENV_LIVETitanium.Paypal.PAYPAL_ENV_SANDBOXTitanium.Paypal.PAYPAL_ENV_NONE
feePaidByReceiver 这仅在交易类型为 Personal 时适用。可用值有:truefalse
transactionType 正在进行的付款类型(付款是为了什么)。可用值有:Titanium.Paypal.PAYMENT_TYPE_HARD_GOODSTitanium.Paypal.PAYMENT_TYPE_DONATIONTitanium.Paypal.PAYMENT_TYPE_PERSONALTitanium.Paypal.PAYMENT_TYPE_SERVICE
enableShipping 是否选择/发送运输信息。可用值有:truefalse

准备你的 iOS 模块开发环境

在你开始开发自己的自定义 iOS 模块之前,你首先需要正确设置你的环境。这包括在你的 Titanium SDK 路径中设置对 titanium.py 脚本的别名。

准备工作

以下说明仅适用于 Mac OSX。你可以在 Linux、Windows 以及 OSX 上开发 Android 模块。然而,在这个菜谱中,我们将专注于 iOS 模块开发,这需要一个运行 OSX 10.5 或更高版本的 Apple Mac。

如何操作…

  1. 打开 终端 应用程序,你可以在 应用程序 | 实用工具 | 终端 下找到它。

  2. 输入 cd $HOME 并按 Enter 键。

  3. 输入 vi .bash_profile 并按 Enter 键。如果你之前没有创建过 bash_profile,那么现在你会创建一个新文件,否则它将加载你的现有 bash_profile 脚本。

  4. 在你的脚本中添加以下行:"alias titanium='/Library/Application\ Support/Titanium/mobilesdk/osx/1.7.2/titanium.py'"—其中 1.7.2 是你目前安装的 Titanium SDK 的最新版本。请仔细注意并确保你的 Titanium SDK 路径正确,并且路径位置被单引号包围。

  5. 按下 Esc 键保存文件,然后输入 ":wq"。这将保存你的文件并退出编辑器。

  6. 回到终端,输入 "source ~/.bash_profile" 并按 Enter 键。

  7. 现在输入 "titanium" 并按 Enter。如果你已经正确设置了脚本,你将在终端窗口中看到 Appcelerator 的输出,就像以下截图所示。

  8. 保持终端窗口打开,因为它将在下一个菜谱中需要:

如何操作…

为了测试你的环境是否已经正确设置,请在终端窗口中输入以下内容:

titanium create --platform=iphone --type=module --dir=~/tmp --name=test --id=com.packtpub.testmodule

如果一切顺利,你将在终端窗口中看到以下输出。你可以通过在 finder 中导航到 /tmp/test 目录来验证此脚本创建的文件(注意,tmp 目录将在你的用户账户的 Home 文件夹下)。

如何操作…

它是如何工作的…

实际上,我们在这里所做的只是设置一种从控制台使用别名执行 Titanium 脚本的方式。这意味着我们可以使用像 "titanium create" 这样的简单命令,而不是尝试通过在终端中执行冗长的命令来手动完成相同的事情。

使用 XCode 开发新的 iPhone 模块

为 Titanium 开发我们自己的自定义模块使我们能够利用本地代码,并让 Titanium 执行它原本无法执行或至少目前无法执行的事情。在这个菜谱中,我们将开发一个小模块,该模块使用 Bit.ly 来缩短长 URL。你可以在任何 iOS 应用程序中需要创建短 URL 时使用此模块(例如,在 Twitter 上发布链接时)。

准备工作

你首先需要按照前一个菜谱中描述的步骤设置你的 Mac。确保你遵循这些步骤并且系统设置正确,因为这个菜谱在很大程度上依赖于这些脚本的正常工作。你还需要对这个菜谱有一定的 Objective-C 知识。这本书不会以任何方式教授 Objective-C,因为已经有足够多的重量级书籍了。然而,你应该能够跟随这个菜谱中的代码,以便让我们的示例模块正常工作。

如何操作…

首先,让我们使用之前菜谱中使用的相同脚本创建基本模块。在终端窗口中,输入以下内容(将 /Projects 目录替换为你希望创建模块的目录):

titanium create --platform=iphone --type=module --dir=~/Projects --name=BitlyModule --id=com.packtpub.BitlyModule

现在在 Finder 中打开 /Projects/BitlyModule 目录,你将看到主要是标准外观的 XCode 项目文件列表。双击 BitlyModule.xcodeproj 文件以在 XCode 中打开它进行编辑。

它是如何工作的…

以下信息直接来自 Appcelerator 指南(可在 wiki.appcelerator.org/display/guides/Module+Developer+Guide+for+iOS 查找)并且是了解 Titanium 模块架构的良好入门。

模块架构包含以下关键接口组件:

  1. 代理:一个基类,代表你的 JavaScript 代码和本地代码之间的原生绑定。

  2. ViewProxy:一种专门化的代理,知道如何渲染视图。

  3. View:Titanium 可以渲染的 UI 组件的视觉表示。

  4. Module:描述特定 API 集或命名空间的特殊类型的代理。

当构建一个模块时,你只能有一个模块类,但你可以有零个或多个代理(Proxies)、视图(Views)和ViewProxies

对于每个View,你需要一个ViewProxyViewProxy代表模型数据(如果视图需要释放,则数据保存在代理内部),并负责公开视图支持的 API 和事件。

当你想在 JavaScript 和本地之间返回非视觉数据时,你会创建一个代理。代理知道如何处理任何方法、属性分配和事件触发。

创建公共 API 方法

Titanium 在模块创建过程中创建的示例模块代码已经为我们提供了一个公共方法的示例。我们虽然会创建自己的,但这个方法接受一个单个的字符串输入值(即“长”URL),然后通过Bit.ly API 处理短 URL,最后将其返回到我们的 Titanium 应用程序中。

准备工作

在您可以使用模块之前,您需要注册一个 Bitly API 密钥,您可以在bitly.com/a/sign_up?rd=/a/your_api_key免费注册。

此外,我们还需要 Objective-C 的开放源代码 SBJSON 框架,它能够原生地读取和写入 JSON 格式的数据流。您可以从github.com/stig/json-framework/下载 SBJSON 框架。

注意

本食谱的完整源代码可以在/Chapter 10/Recipe 4文件夹中找到。

如何实现…

首先,解压 SBJSON 框架,并将 Finder 中的Classes文件夹中的所有文件拖到您的模块 XCode 项目的Classes文件夹中。

打开ComPacktpubBitlyModuleModule.h文件,确保它看起来如下(忽略文件顶部的头注释):

#import "TiModule.h"
#import <Foundation/Foundation.h>
@interface ComPacktpubBitlyModuleModule : TiModule
{
}
@end

现在打开ComPacktpubBitlyModuleModule.m文件,确保它看起来如下源代码(忽略文件顶部的头注释)。请记住,将 URL 的QueryString部分的loginkey值替换为Bit.ly API 分配给你的值:

#import "ComPacktpubBitlyModuleModule.h"
#import "TiBase.h"
#import "TiHost.h"
#import "TiUtils.h"
#import "SBJson.h"
#import "SBJsonParser.h"
@implementation ComPacktpubBitlyModuleModule
#pragma mark Internal
// this is generated for your module, please do not change it
-(id)moduleGUID
{
return @"a33e440e-ef62-4ec7-89cd-8939d264e46e";
}
// this is generated for your module, please do not change it
-(NSString*)moduleId
{
return @"com.packtpub.BitlyModule";
}
#pragma mark Lifecycle
-(void)startup
{
// this method is called when the module is first loaded
// you *must* call the superclass
[super startup];
NSLog(@"[INFO] %@ loaded",self);
}
-(void)shutdown:(id)sender
{
// this method is called when the module is being unloaded
// typically this is during shutdown. make sure you don't
do too
// much processing here or the app will be quit forceably
// you *must* call the superclass
[super shutdown:sender];
}
#pragma mark Cleanup
-(void)dealloc
{
// release any resources that have been retained by the module
[super dealloc];
}
#pragma mark Internal Memory Management
-(void)didReceiveMemoryWarning:(NSNotification*)notification
{
// optionally release any resources that can be dynamically
// reloaded once memory is available - such as caches
[super didReceiveMemoryWarning:notification];
}
#pragma mark Listener Notifications
-(void)_listenerAdded:(NSString *)type count:(int)count
{
if (count == 1 && [type isEqualToString:@"my_event"])
{
// the first (of potentially many) listener is being added
// for event named 'my_event'
}
}
-(void)_listenerRemoved:(NSString *)type count:(int)count
{
if (count == 0 && [type isEqualToString:@"my_event"])
{
// the last listener called for event named 'my_event' has
// been removed, we can optionally clean up any resources
// since no body is listening at this point for that event
}
}
#pragma Public APIs
-(id)example:(id)args
{
// example method
return @"hello world";
}
///creates the short url from bitly
- (id)getShortUrl:(id)value
{
NSString *baseURLString = @"http://api.bit.ly/shorten?version=2.0.1&longUrl=";
NSString *longUrl = [TiUtils stringValue:value];
longUrl = [longUrl stringByReplacingOccurrencesOfString:@"("
withString:@""];
longUrl = [longUrl stringByReplacingOccurrencesOfString:@—)—
withString:@""];
longUrl = [longUrl stringByReplacingOccurrencesOfString:@"\""
withString:@""];
longUrl = [longUrl
stringByTrimmingCharactersInSet:[NSCharacterSet
whitespaceAndNewlineCharacterSet]];
baseURLString = [baseURLString
stringByAppendingString:longUrl];
baseURLString = [baseURLString
stringByAppendingString:
@"&login=REPLACE_YOUR_LOGIN&apiKey=REPLACE_YOUR_KEY"];
NSURL* baseURL = [[NSURL alloc]
initWithString:baseURLString];
NSMutableURLRequest *req = [[NSMutableURLRequest alloc]
initWithURL:baseURL];
NSHTTPURLResponse* urlResponse = nil;
NSError *error = [[[NSError alloc] init] autorelease];
NSData *data = [NSURLConnection sendSynchronousRequest:req
returningResponse:&urlResponse error:&error];
if ([urlResponse statusCode] >= 200 && [urlResponse
statusCode] < 300)
{
NSLog(@"Got a response from bit.ly");
SBJsonParser* jsonParser = [SBJsonParser new];
NSString* jsonString = [[NSString alloc]
initWithData:data encoding:NSUTF8StringEncoding];
NSDictionary* dict = (NSDictionary*)[jsonParser
objectWithString:jsonString];
[jsonString release];
[jsonParser release];
NSString *statusCode = [dict
objectForKey:@"statusCode"];
if([statusCode isEqualToString:@"OK"])
{
// retrieve shortURL from results
NSLog([dict description]);
NSString *shortURL = [[[dict
objectForKey:@"results"]
objectForKey:longUrl]
objectForKey:@"shortUrl"];
return shortURL;
}
else
{
return @"Unable to shorten this URL,
please check its format.";
}
}
return baseURLString;
}
@end

它是如何工作的…

这里的主函数是我们创建的,称为getShortUrl。模块的所有其他方法和属性都由 Titanium 模块创建脚本为我们自动生成。简而言之,此方法执行对Bit.ly API 的请求,使用我们的密钥和用户名,当收到响应时,使用 SBJSON 解析器进行解析。然后从 JSON 结果的shortURL元素中提取出shortURL变量(NSString类型),并将其返回到 Titanium。

我们在这里想要关注的是 Titanium 公共方法的集成以及“value”参数的转换。在这里,我们使用(id)声明,这使得我们可以轻松地将传入的值转换为 Objective-C 理解的参数类型。在这种情况下,我们将“value”参数转换为NSString类型,因为我们知道传入的参数将是一个格式为网址的字符串值。这个转换过程要归功于TiUtils,我们在文件顶部使用#import "TiUtils.h"命令导入。

一些最常见的转换示例包括:

CGFloat f = [TiUtils floatValue:arg];
NSInteger f = [TiUtils intValue:arg];
NSString *value = [TiUtils stringValue:arg];
NSString *value = [TiUtils stringValue:@"key" properties:dict def:@"default"];
TiColor *bgcolor = [TiUtils colorValue:arg];

我们还返回一个字符串值,它要么是错误消息(如果Bit.Ly转换过程失败),要么是希望中的新短网址,这是Bit.Ly友好地提供的。由于我们返回的是字符串,我们不需要在返回参数之前执行转换。

以下类型可以在不进行类型转换的情况下返回:

  • NSString

  • NSDictionary

  • NSArray

  • NSNumber

  • NSDate

  • NSNull

使用测试工具包打包和测试你的模块

现在是时候构建、打包和测试我们的新模块了!在你继续这个菜谱之前,确保你已经构建了 XCode 项目并且它已经成功。如果没有,你需要在继续之前修复任何错误。

注意

此菜谱的完整源代码可以在/Chapter 10/Recipe 5文件夹中找到。

如何做到这一点...

打开你的模块的app.js示例文件,你可以在模块项目的example目录中找到它。用以下源代码替换现有的示例内容:

// This is a test harness for your module
// You should do something interesting in this harness
// to test out the module and to provide instructions
// to users on how to use it by example.
// write your module tests here
var bitlymodule = require('com.packtpub.BitlyModule');
Ti.API.info("module is => " + bitlymodule);
// open a single window
var window = Ti.UI.createWindow({
backgroundColor:'white'
});
var txtLongUrl = Ti.UI.createTextField({
top: 10,
left: 10,
width: 300,
height: 30,
borderStyle: 1,
hintText: 'Enter your long url...'
});
window.add(txtLongUrl);
var btnShorten = Ti.UI.createButton({
title: 'Shorten it with Bit.ly!',
width: 200,
right: 10,
height: 30,
top: 50
});
btnShorten.addEventListener('click', function(e){
var result = bitlymodule.getShortUrl(txtLongUrl.value);
txtShortUrl.value = result;
});
window.add(btnShorten);
var txtShortUrl = Ti.UI.createTextField({
top: 100,
left: 10,
width: 300,
height: 30,
borderStyle: 1,
hintText: 'Your short url appears here...'
});
window.add(txtShortUrl);
window.open();

现在,回到终端,更改目录,确保你位于你的BitLyModule目录中(假设你仍然从之前的菜谱中打开了终端窗口,你应该已经在那里了)。

在终端中输入./build.py,然后按Enter键执行命令。当它完成后,输入titanium run并按Enter键。如果一切顺利,你应该在 20 或 30 秒后看到 iPhone 模拟器启动,我们的示例 Titanium 应用程序可见,它由两个TextFieldsBitLy转换按钮组成。在第一个TextField中输入一个长网址,然后按使用 Bit.ly 缩短它!,如以下截图所示:

如何做到这一点…

它是如何工作的…

让我们集中精力研究用于通过示例项目构建和启动我们的模块的 Titanium 代码。正如你所看到的,我们样本 JavaScript 中的第一行非常关键:

var bitlymodule = require('com.packtpub.BitlyModule');

此代码实例化我们的模块,并将其定义为名为bitlymodule的新变量。然后我们可以像使用任何其他常规 Titanium 控件一样使用我们的模块,通过调用我们自己的自定义方法,并在将其显示在shortURL文本字段之前返回结果:

var result = bitlymodule.getShortUrl(txtLongUrl.value);
txtShortUrl.value = result;

打包你的模块以进行分发和销售

Titanium 模块是以便于分发和重用的方式创建的,无论是在您自己的应用程序中还是在 Titanium+Plus 市场中。在本食谱中,我们将介绍打包您的模块并将其分发给市场的步骤。

注意

本章的完整源代码可以在/Chapter 10文件夹中找到,包括Bit.Ly模块的编译版本。

如何操作...

第一项要求是编辑在创建模块时自动生成的清单文件。以下是一个从我们的BitlyModule:中摘取的示例:

version: 0.1
description: My module
author: Your Name
license: Specify your license
copyright: Copyright (c) 2011 by Your Company
# these should not be edited
name: bitlymodule
moduleid: com.packtpub.BitlyModule
guid: a33e440e-ef62-4ec7-89cd-8939d264e46e
platform: iphone
minsdk: 1.7.2

# these should not be edited行以下的内容不应被编辑,但请将所有其他键值对替换为您自己的姓名、描述、许可、版本和版权文本。一旦您完成编辑清单文件,请通过在终端中输入./build.py来重新构建您的模块,并按Enter键执行命令。

您的模块现在已准备好用于您自己的项目或手动分发。只需将 ZIP 文件的内容复制到/Library/Application/ Support/Titanium目录中即可安装。当然,您仍然需要在app.js文件中使用require方法调用包含您的模块,并且您需要在tiapp.xml文件中引用它,就像在本章的第一个食谱中为移动 PayPal 库所做的那样。

您可以使用在构建过程中创建的相同 ZIP 文件包将您的模块分发到 Open Mobile 市场。然而,在您能够分发之前,您需要满足以下几个先决条件:

  1. 您必须拥有有效的 Titanium 开发者账户。

  2. 您必须完全填写您的清单值。

  3. 您的项目中LICENSE文件必须包含有效的许可文本。

  4. 您的项目文档目录中的index.md文件必须包含有效的文档文件。

  5. 在上传时,您必须指定一些额外的元数据,例如价格(可以是免费的)。

  6. 如果您对模块收费,您必须与 Appcelerator 建立支付设置,以便您能收到付款。

  7. 您必须接受 Titanium+Plus 市场服务条款协议。

一旦您上传了模块并完成了必要的提交步骤,您的模块将可供市场目录使用。请注意,第一次提交模块时,Appcelerator 将对上述基本要求进行审核。

它是如何工作的...

新的 Appcelerator 市场使开发者能够轻松地构建、销售和分发他们自己的自定义 Titanium 模块,适用于 iOS 和 Android。您需要做的只是为您的产品设置一个配置文件,并提供您的 PayPal 账户详细信息,以便为每次销售获得付款。

开发者通过开放移动市场销售了他们 70%的产品,并且有众多工具可供追踪您的客户、发票和反馈。您今天就可以在marketplace.appcelerator.com/cms/landing注册。

第十一章. 平台差异、设备信息和怪癖

在本章中,我们将涵盖:

  • 收集有关设备的信息

  • 获取设备的屏幕尺寸

  • 理解设备方向模式

  • 在 iOS 和 Android API 之间的差异中进行编码

  • 确保你的设备可以打电话

简介

在本章中,我们将探讨 iOS 和 Android 之间的许多平台差异,并展示如何围绕这些差异进行编码。我们还将突出显示如何收集有关应用程序运行设备的详细信息,包括其屏幕尺寸和能力,例如打电话的能力。

注意

本章的完整源代码可以在/Chapter 11/PlatformDiffs文件夹中找到。

收集有关设备的信息

当前设备的大部分信息都可以通过Titanium.Platform命名空间获取。在这里,我们可以确定一系列与设备相关的数据,包括电池电量、设备操作系统和版本、当前设备语言、屏幕分辨率等等。了解这些信息非常重要,因为它会给你一系列关于物理设备上发生情况的线索。例如,如果电池电量下降到一定百分比以下,你可能希望备份用户的应用数据,以防设备关闭导致数据丢失。更常见的是,你会使用像Titanium.Platform.osname这样的设备属性来确定你的应用程序当前运行在什么操作系统上,比如 iPhone、iPad 或 Android。

准备工作

为了准备这个菜谱,打开 Titanium Studio 并登录,如果你还没有这样做的话。如果你需要注册新账户,你可以在应用程序内部免费注册。登录后,点击新建项目,创建新项目的详细信息窗口将出现。将应用程序名称输入为PlatformDiffs,并填写其他详细信息。打开app.js文件,删除除了根窗口实例化和win1对象的 open 方法之外的所有内容,使其看起来如下所示:

//
// create root window
//
var win1 = Titanium.UI.createWindow({
title:'Tab 1',
backgroundColor:'#fff'
});
//open root window
win1.open();

注意

本菜谱的完整源代码可以在/Chapter 11/Recipe 1文件夹中找到。

如何操作…

现在,回到app.js文件,我们将简单地创建一些标签,并从Titanium.Platform命名空间中可用的属性请求每个标签的值。然后,这些值将作为屏幕上的文本显示:

var labelOS = Titanium.UI.createLabel({
width: 'auto',
height: 30,
top: 0,
left: 10,
font: {fontSize: 14, fontFamily: 'Helvetica'},
color: '#000',
text: 'OS Details: ' + Titanium.Platform.osname + ' (version ' + Titanium.Platform.version + ')'
});
var labelBattery = Titanium.UI.createLabel({
width: 'auto',
height: 30,
top: 40,
left: 10,
font: {fontSize: 14, fontFamily: 'Helvetica'},
color: '#000',
text: 'Battery level: ' + Titanium.Platform.batteryLevel
});
var labelMemory = Titanium.UI.createLabel({
width: 'auto',
height: 30,
top: 80,
left: 10,
font: {fontSize: 14, fontFamily: 'Helvetica'},
color: '#000',
text: 'Available memory: ' + Titanium.Platform.availableMemory + 'MB'
});
var labelArchitecture = Titanium.UI.createLabel({
width: 'auto',
height: 30,
top: 120,
left: 10,
font: {fontSize: 14, fontFamily: 'Helvetica'},
color: '#000',
text: 'Architecture: ' + Titanium.Platform.architecture
});
var labelLocale = Titanium.UI.createLabel({
width: 'auto',
height: 30,
top: 160,
left: 10,
font: {fontSize: 14, fontFamily: 'Helvetica'},
color: '#000',
text: 'Locale: ' + Titanium.Platform.locale
});
var labelModel = Titanium.UI.createLabel({
width: 'auto',
height: 30,
top: 200,
left: 10,
font: {fontSize: 14, fontFamily: 'Helvetica'},
color: '#000',
text: 'Model: ' + Titanium.Platform.model
});
win1.add(labelOS);
win1.add(labelBattery);
win1.add(labelMemory);
win1.add(labelArchitecture);
win1.add(labelLocale);
win1.add(labelModel);

它是如何工作的…

代码示例中的每个标签都代表有关你的设备及其功能的不同信息。这里的代码并没有什么特别复杂的地方,但重要的是这些方法本身。

大多数这些都是相当直观的。电池、内存、架构和型号的方法都为你提供了有关设备和其特定功能的信息。你可以在应用程序的生命周期中的某些时刻使用这些信息,例如,当电池达到某个临界水平时自动保存表单数据。

所有这些方法中最有用的是Titanium.Platform.osname。这是你在开发 Titanium 跨平台应用程序过程中会不断使用的方法,因为你将使用它来检查你是否在 iPhone 或 Android 平台上,如以下截图所示,并运行相应的代码。

如何工作…

获取设备的屏幕尺寸

虽然我们开发者目前对 iPhone 平台及其单一的 320x480 像素分辨率感到非常幸运,但对于 Android 平台来说,情况并非如此。特别是 Android 平台,由于它在众多制造商之间的分布方式,拥有多种不同的屏幕比例和分辨率。不可避免的是,你可能会遇到需要根据当前屏幕大小计算对象位置的情况,而不必不断依赖一系列Titanium.Platform.osname检查。

注意

自从 iPhone 4 的推出以来,所有新的 iOS 设备都配备了视网膜显示屏。本质上,屏幕分辨率仍然是 320x480,但 DPI 翻倍,这意味着有效分辨率实际上是 640x960。针对这两种分辨率进行实现非常简单。只需将所有图像文件命名为正常,然后所有双倍分辨率的文件都使用相同的方式命名,并添加一个@2x标志。所以如果你有一个名为header.png的图像,它是为 320x480 设计的,你可以创建一个大小加倍且命名为header@2x.png的图像,这将自动被所有 iOS 视网膜显示屏识别。

在这个菜谱中,我们将生成三个视图,一个占据屏幕下半部分,另外两个只占据顶部。我们将使用Titanium.Platform.displayCaps函数来实现这一点。

注意

这个菜谱的完整源代码可以在/Chapter 11/Recipe 2文件夹中找到。

如何实现…

在你的app.js文件中,我们将创建三个不同的视图,每个视图占据屏幕的一部分。删除任何现有的代码,并输入以下内容:

//
// create root window
//
var win1 = Titanium.UI.createWindow({
title:'Tab 1',
backgroundColor:'#fff'
});
var windowWidth = Titanium.Platform.displayCaps.platformWidth;
var windowHeight = Titanium.Platform.displayCaps.platformHeight;
var viewBottom = Titanium.UI.createView({
width: windowWidth,
height: windowHeight / 2,
bottom: 0,
left: 0,
backgroundColor: 'Red'
});
win1.add(viewBottom);
var lblDeviceDPI = Titanium.UI.createLabel({
text: 'The device DPI = ' +
Titanium.Platform.displayCaps.dpi,
width: windowWidth,
height: windowHeight / 2,
textAlign: 'center',
bottom: 0,
color: '#fff'
});
viewBottom.add(lblDeviceDPI);
var viewTop1 = Titanium.UI.createView({
width: windowWidth / 2,
height: windowHeight / 2,
top: 0,
left: 0,
backgroundColor: 'Green'
});
win1.add(viewTop1);
var viewTop2 = Titanium.UI.createView({
width: windowWidth / 2,
height: windowHeight / 2,
top: 0,
left: windowWidth / 2,
backgroundColor: 'Blue'
});
win1.add(viewTop2);
//open root window
win1.open();

如何工作…

这里的代码相当简单。简单来说,我们正在将设备的宽度和高度值分配给两个名为windowWidthwindowHeight的变量。为此,我们正在使用Titanium.Platform.displayCaps命名空间中可用的两个属性;即platformWidthplatformHeight。一旦我们有了这些值,就很容易创建我们的视图并使用一些非常简单的计算来布局它们。

以下是在 iPhone 和 Android 上以两种非常不同的分辨率渲染相同屏幕的示例:

工作原理…

理解设备方向模式

对于当前智能手机用户来说,一个巨大的好处是能够以任何可能的方式握持设备,并且屏幕会旋转以适应其方向。Titanium 允许你根据应用中的方向变化来触发事件处理程序。

在这个菜谱中,我们将创建一个事件处理程序,每当设备上的方向发生变化时,它都会触发,并且我们将相应地重新排列屏幕上的某些 UI 组件。

注意

这个菜谱的完整源代码可以在/Chapter 11/Recipe 3文件夹中找到。

如何实现…

打开你的app.js文件,删除任何现有代码,并输入以下内容:

//
// create root window
//
var win1 = Titanium.UI.createWindow({
title:'Tab 1',
backgroundColor:'#fff'
});
//set the allowed orientation modes for win1
//in this example, we'll say ALL modes are allowed
win1.orientationModes = [
Titanium.UI.LANDSCAPE_LEFT,
Titanium.UI.LANDSCAPE_RIGHT,
Titanium.UI.PORTRAIT,
Titanium.UI.UPSIDE_PORTRAIT
];
var view1 = Titanium.UI.createView({
width: Titanium.Platform.displayCaps.platformWidth,
height: Titanium.Platform.displayCaps.platformHeight,
backgroundColor: 'Blue'
});
var labelOrientation = Titanium.UI.createLabel({
text: 'Currently in ? mode',
width: '100%',
textAlign: 'center',
height: 30,
color: '#000'
});
view1.add(labelOrientation);
win1.add(view1);
Ti.Gesture.addEventListener('orientationchange', function(e) {
//check for landscape modes
if (e.orientation == Titanium.UI.LANDSCAPE_LEFT ||
e.orientation == Titanium.UI.LANDSCAPE_RIGHT) {
view1.width =
Titanium.Platform.displayCaps.platformWidth;
view1.height =
Titanium.Platform.displayCaps.platformHeight;
labelOrientation.text = 'Currently in LANDSCAPE mode';
view1.backgroundColor = 'Blue';
}
else {
//we must be in portrait mode!
view1.width =
Titanium.Platform.displayCaps.platformWidth;
view1.height =
Titanium.Platform.displayCaps.platformHeight;
labelOrientation.text = 'Currently in PORTRAIT mode';
view1.backgroundColor = 'Yellow';
}
});
//open root window
win1.open();

如何实现…

现在尝试在模拟器或设备上运行你的应用,并在横屏和竖屏模式之间调整屏幕方向。你应该会看到与之前截图中的变化类似的变化!

工作原理…

我们将一个事件监听器添加到Ti.Gesture中,一旦设备方向发生变化,这个事件处理程序就会被触发,我们可以根据需要重新排列屏幕上的组件。技术上,我们可以在这个处理程序中真正做任何我们想做的事情。一个很好的例子是在纵向模式下有一个TableView,当用户将屏幕旋转到横向模式时,打开一个包含MapView的新窗口。在这里,我们只是改变我们主要视图对象的颜色和其中标签的文本属性,以突出显示设备方向的变化。

在 iOS 和 Android API 之间的差异中进行编码

虽然 Appcelerator Titanium 使开发者对集成多个操作系统和设备的繁琐工作变得不可见,但仍然会有一些时候,你不得不编写一些特定平台的代码。最常见的方法是通过检查Titanium.Platform命名空间中的osname属性来实现。

在这个菜谱中,我们将创建一个简单的屏幕,当设备是 iPhone 时显示自定义活动指示器,当用户在 Android 设备上时显示标准指示器。

注意

这个菜谱的完整源代码可以在/Chapter 11/Recipe 4文件夹中找到。

如何实现…

打开你的app.js文件,删除任何现有代码,并输入以下内容:

// create root window
var win1 = Titanium.UI.createWindow({
title: 'Tab 1',
backgroundColor: '#fff'
});
///this next bit is a custom activity indicator for iphone
///due to too many diffs between android and ios ones
var actIndIphone = Titanium.UI.createView({
width: 320,
height: 480,
backgroundColor: '#000',
opacity: 0.75,
visible: false
});
var actIndBg = Titanium.UI.createView({
width: 280,
height: 50,
backgroundColor: '#000',
opacity: 1,
borderRadius: 5
});
var indicatorIphone = Titanium.UI.createActivityIndicator({
width: 30,
height: 30,
left: 10,
top: 10,
color: '#fff',
style: 1
});
actIndBg.add(indicatorIphone);
var actIndLabel = Titanium.UI.createLabel({
left: 50,
width: 220,
height: 'auto',
textAlign: 'left',
text: 'Please wait, loading iPhone...',
color: '#fff',
font: {fontSize: 12, fontFamily: 'Helvetica'}
});
actIndBg.add(actIndLabel);
actIndIphone.add(actIndBg);
win1.add(actIndIphone);
//the important bit!
//check if platform is android and if so, show a normal dialog
//else show our custom iPhone one
if(Ti.Platform.osname == 'android')
{
var indicatorAndroid = Titanium.UI.createActivityIndicator({
title: 'Loading',
message: 'Please wait, loading Android...'
});
indicatorAndroid.show();
}
else
{
actIndIphone.visible = true;
indicatorIphone.show();
}
//open root window
win1.open();

现在在 Android 和 iPhone 模拟器中运行你的应用程序。你应该能够看出我们编写的代码已经识别出你正在运行的平台,并且在每个平台上以不同的方式显示活动指示器。

工作原理…

这个简单的菜谱展示了如何使用最简单的“if”语句来处理两个平台之间的差异,即通过使用Titanium.Platform.osname属性检查当前设备的osname。我们通过仅在 iPhone 上显示我们的自定义活动指示器视图来充分利用这个检查。在 Android 平台上,这是不必要的,因为活动指示器只需使用其".show()"方法就会作为模态视图出现在屏幕上所有其他视图之上。

您可以使用此属性在需要显示单独的 UI 组件或与平台无关的 API 集成时检查平台。以下截图显示了此菜谱在每种设备上的运行示例:

工作原理…

确保您的设备可以拨打电话

虽然现代智能手机集成了所有技术魔法和触摸屏的便利性,但很容易忘记它们的主要功能仍然是电话,能够进行和接收语音通话。然而,可能会有时候,由于某种原因(网络服务差、iPod touch 用户缺少电话功能等),用户的设备无法进行通话。

在这个菜谱中,我们将尝试通过首先检查设备的性能,并在无法实现时抛出一个错误信息来拨打一个电话。

注意

本菜谱的完整源代码可以在 /第十一章/菜谱 5 文件夹中找到。

如何操作...

打开您的app.js文件,删除任何现有代码,并输入以下内容:

// create root window
var win1 = Titanium.UI.createWindow({
title: 'Tab 1',
backgroundColor: '#fff'
});
//create the textfield number entry to dial
var txtNumber = Titanium.UI.createTextField({
top: 20,
left: 20,
height: 40,
width: 280,
hintText: '+44 1234 321 231',
borderStyle: 1
});
win1.add(txtNumber);
//create our call button
var btnCall = Titanium.UI.createButton({
top: 90,
left: 20,
width: 280,
height: 40,
title: 'Call Now!'
});
//attempt a phone call
btnCall.addEventListener('click', function(e){
if(txtNumber.value != '')
{
if(Titanium.Platform.osname != 'ipad'
&& Titanium.Platform.model != 'iPod Touch'
&& Titanium.Platform.model != 'google_sdk'
&& Titanium.Platform.model != 'Simulator')
{
Titanium.Platform.openURL('tel:' + txtNumber.value);
}
else
{
alert("Sorry, your device is not capable of making calls.");
}
}
else
{
alert("You must provide a valid phone number!");
}
});
win1.add(btnCall);
//open root window
win1.open();

现在运行您的应用程序,无论是在模拟器中还是在无法拨打电话的设备上,例如 iPod Touch。您应该会看到一个弹窗出现,表明该设备无法执行所请求的电话呼叫。

工作原理…

在这里,我们只是使用 Titanium 平台命名空间来确定用户当前正在使用哪种设备,并在设备为 iPod、iPad 或模拟器时提供错误信息,如以下截图所示。如果所讨论的设备能够进行电话呼叫,例如 iPhone 或 Android 智能手机,那么将通过特殊 URL 请求的方式调用设备的电话 API:

//must be a valid number, e.g. 'tel:07427555122'
Titanium.Platform.openURL('tel:' + txtNumber.value);

只要传入的电话号码有效,设备将启动拨号界面,并尝试代表用户进行通话。

工作原理…

第十二章:准备您的应用程序进行分发和发布

在本章中,我们将涵盖:

  • 加入 iOS 开发者计划

  • 安装 iOS 开发者证书和配置文件

  • 使用 Titanium Studio 构建您的 iOS 应用程序

  • 加入 Google Android 开发者计划

  • 创建您应用程序的分发密钥

  • 构建并提交您的应用程序到 Android 市场

简介

我们开发难题的最后一部分是解决如何将我们的 Titanium 应用程序打包和分发到 iTunes 商店和 Android 市场,以便我们的潜在客户下载并享受我们所有的辛勤工作。每个商店都有自己的独立流程、认证和会员计划。

在本章中,我们将向您展示如何设置系统以备分发,以及如何注册每个网站,以及如何打包和提交您的应用程序到 iTunes 和 Android 市场。

加入 iOS 开发者计划

为了提交应用程序到 iTunes 商店,您必须首先付费成为苹果 iOS 开发者计划的会员。会员资格是付费的,起价为 99 美元(或等值货币),每年续费。即使您打算免费开发和分发应用程序,您仍然需要成为 iOS 开发者计划的付费会员。值得事先注意的是,只有 Mac 用户才能遵循和实施 iOS 食谱的步骤,因为 iOS 应用程序的构建和分发仅限于运行 Mac OSX 操作系统的用户。

如何操作…

要注册苹果的 iOS 计划,首先打开一个网页浏览器并导航到developer.apple.com/programs/register,然后点击开始链接。随后加载的页面将询问您是否要创建一个新的 Apple ID 或使用现有的一个。除非您之前已经注册过苹果的一些开发者服务,否则您应该选择创建新配置文件链接。

一旦您进入创建配置文件页面:

  1. 提供您的联系信息,包括您的居住国家。这很重要,因为当您开始销售付费应用程序时,您需要提供一些居住证明。

  2. 在下一页,提供专业配置文件所需的信息。

  3. 最后,仔细阅读并同意苹果设定的条款和条件,并确认您同意,且至少 18 岁(或您所在国家的法定年龄)。点击我同意按钮以完成账户创建。

  4. 苹果将向您发送一封包含确认代码/链接的电子邮件。点击电子邮件中的此链接将在您的浏览器中打开并确认您的电子邮件地址并完成账户设置。

您现在应该能够在浏览器中看到以下页面。从这里开始,我们将注册开发者计划并支付 99 美元(或等值货币)的年度费用。

如何操作…

点击页面菜单左上角的程序和附加组件标签,这将带您到一个显示您当前订阅的会员列表的页面。假设您有一个新账户,那么应该会显示三个开发者计划的列表,每个列表右侧都有一个今天加入按钮。要继续,请按照以下步骤操作:

  1. 点击 iOS 开发者计划的今天加入按钮,它应该出现在列表的顶部。

  2. 在加载的下一页上,点击立即注册,然后继续直到到达逐步向导的步骤。

  3. 从右侧列表中选择我在苹果注册为开发者并希望加入付费的苹果开发者计划。然后按继续

  4. 从这里,您需要提供所有要求您提供的信息以完成账户设置。您应该选择是否注册为商业用户或个人。请注意,无论您选择哪种注册方式,您都需要确保您拥有所有相关的文件。您将被要求提交这些文件以供苹果验证,并且您在收到并批准这些文件之前无法提交付费应用程序。一些信息不能更改,一旦您输入并完成申请,从所有意义上讲,它就固定下来了!

  5. 最后,同意最终的条款和条件,然后在线支付。您需要一张信用卡或借记卡来完成这次购买。

现在,您应该可以通过在浏览器中导航到developer.apple.com/devcenter/ios来登录您的新苹果开发者账户。登录后,您应该在账户的主页上获得一些新的菜单选项,包括配置iTunes Connect。您账户中缺失的任何信息都可以在 iTunes Connect 选项下的合同、税务和银行部分找到。在这个网站的部分,您可能需要不时上传一些文件并同意新的条款和条件。

如何操作…

安装 iOS 开发者证书和配置文件

构建您的应用程序需要两种类型的证书,这两种证书都用于设备调试和 iTunes 商店分发。第一种是您的开发证书。此证书安装在您的 Mac 中的KeyChain中,并用于您将开发的每一个应用程序。当您分发应用程序时,它识别您,即开发者。

第二种是应用程序的配置文件证书。此配置文件证书是针对特定应用程序和特定发布的。这意味着您需要为希望发布的应用程序的每个状态创建一个单独的配置文件(最常见的是开发和分发)。

在这个配方中,我们将通过创建和安装您的开发者证书,然后在 Titanium Studio 中创建和使用特定于应用程序的配置文件的过程。

如何操作...

我们现在将开始介绍安装 iOS 开发者证书和配置文件的步骤。

  1. 设置您的 iOS 开发者证书。

  2. 如果您尚未登录,请登录您的 Apple 开发者账户,访问 developer.apple.com/ios,并点击 iOS 配置文件门户 链接。加载的页面将在左侧菜单中提供多个选项,点击 证书 选项。将加载一个包含一系列步骤的页面,标题为 如何创建开发证书。您需要严格按照描述的步骤进行操作,并且从开始到结束完成这些步骤后,您应该在您的 Mac 上保存一个证书签名请求 (CSR) 文件。对于这个配方,我们假设您已经仔细遵循了这些步骤,并将 CSR 保存到您的桌面。

  3. 点击屏幕底部的 选择 按钮,从您的计算机中选择 CSR 文件并将其上传到网页。上传完成后,选择页面右下角的 提交 按钮。

    当屏幕重新加载时,您现在应该会在网格中看到一个证书出现,其名称与您生成时相同,状态为 待发行。如果您拥有 iOS 账户,您可以简单地等待 20-30 秒,然后刷新页面,状态将变为 已发行,并且在网格的右侧出现一个 下载 操作。如果您使用的账户属于另一方,您将需要等待他们确认此操作,您才能收到已发行的证书。现在下载证书,当下载完成时双击保存的文件。它将在 KeyChain 访问中自动打开,并显示它已被安装。如果您在页面底部有关于需要安装 WWDR 证书的消息,您也可以选择在此处下载并运行它。

  4. 设置您的设备。

    如果您有一部 iPhone 或 iPod touch 并希望用它进行测试,那么您首先需要将该设备注册到您的 iTunes 账户。点击页面左侧的 设备 菜单链接,然后点击 添加设备。出现的屏幕将要求您提供有关设备所有者的信息,以及更具体地,该设备的唯一标识符是什么。您可以通过将设备连接到您的 Mac 并打开 iTunes 来找到此标识符。它将出现在 我的 iPhone/iPod 下方,然后点击出现的 序列号 标签。

    下面的截图显示了唯一标识符的示例:

    如何操作...

    创建您的应用程序配置文件。

    现在你的开发者证书已经设置好了,是时候为我们的应用程序创建配置文件了。在这个例子中,我们将使用我们在 第一章 中构建的 LoanCalc 应用程序的详细信息。然而,你也可以使用你已创建的任何应用程序。

点击左侧菜单中的 App IDs 链接,当屏幕加载时,选择 新建 App ID 按钮。

给你的应用程序一个描述,并保持 Bundle Seed ID 下拉列表设置为 生成新

输入 Bundle 标识符。这是你在 Titanium Studio 中创建应用程序时给出的至关重要的标识符。在这个例子中,我们的 bundle 标识符是 com.packtpub.loancalc

提交 完成流程并生成你的 App ID。

现在点击 配置 并在屏幕加载后选择 新建配置文件。它应该激活了 开发 选项卡。

为你的配置文件选择一个名称。我们将保持简单,将其命名为 LoanCalc 开发配置文件。你现在应该能够勾选你的证书复选框,并从下拉列表中选择 LoanCalc 应用程序标识符,以及你希望用于此开发配置文件的设备。完成表单填写后,请按 提交

你的新配置文件现在应该出现在 开发 选项卡下的配置文件列表中。等待 20-30 秒并刷新页面,其状态应该从 挂起 变为 下载。将证书下载到你的电脑上的某个位置,例如桌面。

重复之前的步骤,但选择 分发 选项卡而不是 开发 选项卡。在创建分发证书时,你还将有一个额外的选项:分发:App Store 或 Ad Hoc。如果你想将你的应用程序分发到 iTunes 商店,确保你选中了第一个单选按钮选项。

注意

如果你不是账户所有者,但被赋予了其他人的 iTunes 账户的成员资格(例如,如果你是大型公司的员工),那么你需要确保你已经获得了管理员访问权限,以便设置你的证书和配置文件。

使用 Titanium Studio 为 iOS 构建你的应用程序

在这个菜谱中,我们将继续之前两个菜谱中开始的过程,并为开发以及分发到 iTunes 商店构建我们的应用程序。

小贴士

记住,如果所有其他方法都失败了,你总是可以在 XCode 中手动构建你的应用程序,方法是通过导航到你的项目中的 build/iphone 文件夹并打开 XCode 项目文件。

如何做到这一点...

  1. 为开发构建你的应用程序。

    i. 在 Titanium Studio 中打开你的项目;我们以第一章中的LoanCalc应用为例,然而,你可以使用任何你喜欢的项目。确保 Titanium Studio 中的应用程序 ID(在编辑选项卡上)与创建配置文件时使用的 ID 相匹配。在我们的例子中,这个 ID 是com.packtpub.loancalc

    ii. 切换到测试与打包选项卡,并选择在设备上运行。Titanium Studio 应该自动突出显示 iPhone 选项。如果它没有,那么你可能不在 Mac 上(必需的)或者可能没有安装必要的 XCode 工具。你可以从developer.apple.com/xcode下载 XCode。

    iii. 在上传配置文件文本框旁边的文件选择图标上,选择为LoanCalc创建的开发配置文件。假设你已按照前面的步骤正确安装了 WWDR 证书和开发证书,你现在应该会看到一个类似于以下截图的屏幕:

    如何操作...

    iv. 点击完成按钮,让 Titanium Studio 启动构建过程。在这个过程中,你的应用程序将被构建并添加到 iTunes 库中的应用列表中。你需要同步你的 iPhone 或 iPod Touch,以便在你的设备上运行你的应用程序。

  2. 构建你的应用程序以进行分发。

    i. 首先,我们需要在 iTunes Connect 中创建你的新应用,在苹果开发者网站上。在浏览器中导航到苹果开发者网站的 iTunes Connect 部分,并点击管理应用。加载的下一屏将列出你当前的所有应用(如果你创建了的话)。要创建一个新的,请点击左上角的添加新应用。添加所需的信息,包括应用名称(在我们的例子中为Packt LoanCalc)并从产品标识符列表中选择LoanCalc应用。SKU 可以是任何你想要的代码,例如 packtpub001。在接下来的两个屏幕上填写有关描述、关键词、截图和应用程序数据的信息。完成之后,你应该会被转发到一个看起来像以下截图的屏幕:

    如何操作...

    ii. 现在,如果你准备好了上传,请点击左下角的查看详情按钮,然后在加载的下一页上,点击屏幕右上角出现的准备上传二进制文件按钮。苹果将向你提出几个安全问题。回答这些问题后,你的应用状态将从准备上传变为准备上传

    iii. 切换回 Titanium Studio,确保你的项目在项目资源管理器页面中被选中,然后选择分发按钮,接着选择分发 - App Store选项。

    iv. 你现在应该面对一个与之前用于开发测试打包应用程序的屏幕相似的屏幕。输入一个分发位置(任何都可以,我们在这个例子中选择了下载文件夹),并选择你从上一个菜谱中保存的分发配置文件。现在你的 Titanium Studio 中的分发屏幕应该看起来像这样:

    如何操作...

    v. 点击完成按钮开始构建过程。如果你在Users/[你的用户名]/Library/MobileDevice/Provisioning Profiles目录中收到关于缺失文件的错误,那么只需将你电脑上保存的分发配置文件复制到这个目录,并将其重命名为错误对话框中缺失文件的名称。

    vi. 当这个过程完成后,XCode 应该加载,并且组织者窗口应该出现在屏幕上,你的应用程序已经存档并准备好提交到 iTunes 商店,如下面的截图所示:

    如何操作...

    vii. 在这个屏幕上点击提交并按照提示操作。如果一切操作都正确(并且符合苹果的规定),你的应用程序应该被上传到 iTunes 服务器,你应在几分钟内收到确认邮件!

注意

你可以通过开发者程序网站上的 iTunes Connect 部分随时检查你的提交进度。通常需要 1-2 周的时间来批准。然而,这往往会根据提交的数量以及你的应用程序是否被拒绝或需要在批准前进行修改而波动。苹果将在提交周期的每个阶段给你发送电子邮件,包括你首次提交应用程序时、他们开始审查时以及他们批准或拒绝时。

加入谷歌 Android 开发者计划

为了将应用程序提交到 Android 市场,你必须首先注册一个谷歌账户,然后注册谷歌支付账户和 Android 开发者账户。所有这些账户都使用相同的用户名和密码组合,并且过程非常直接。会员资格是付费的,起价为 25 美元(或等值货币),并且是一次性支付。

如何操作…

要注册,首先打开一个网页浏览器并导航到market.android.com/publish。你将被要求登录到你的谷歌账户(如下面的截图所示)。如果你还没有账户,那么这就是你需要创建账户的阶段。

如何操作…

完成登录或注册过程后,你将被要求提供你的开发者/出版商详细信息。之后,你将需要支付 25 美元的注册费。就这样——简单直接!你现在可以开始创建和上传应用程序到 Android 市场了。

小贴士

在注册几周后,Google 可能会发送一封电子邮件,要求您提供账户的身份证件,通常是护照或驾照。您可以在规定的时间内将此信息通过电子邮件发送给他们,一切都会顺利。

创建您应用程序的分发密钥

为了构建适用于 Android 市场的应用程序,您需要在您的本地计算机上创建一个分发密钥。此密钥用于对您的应用程序进行数字签名。

如何做到这一点…

如果您使用 Mac 或 Linux,请打开终端;如果您是 Windows 用户,请打开命令提示符。使用cd命令将当前目录更改为以下路径:

cd /<path to your android sdk>/tools
//e.g. cd /Users/boydlee/android-sdk/tools

要创建密钥,我们需要使用位于此目录中的 Java keytool。在命令提示符/终端中,输入以下内容,同时将my-release-key.keystorealias_name替换为您的应用程序的密钥和别名:

Windows: START"Command $ keytool -genkey -v -keystore my-release-key.keystore -alias alias_name -keyalg RSA -validity 10000
Windows: Mac: Terminal $ keytool -genkey -v -keystore my-release-key.keystore -alias alias_name -keyalg RSA -validity 10000

例如,我们的LoanCalc应用程序密钥命令看起来可能如下:

$ keytool -genkey -v -keystore packtpub.loancalc -alias loancalc -keyalg RSA -validity 10000

按下 Enter 并执行命令,您将会被问到一系列问题。您需要为 keystore 提供密码 - 智慧的做法是将其写下来,因为您稍后需要用它来打包应用程序。我们将使用密码packtpub。当您被提示输入二级密钥密码时,只需按下 Enter 使用相同的密码。

现在,您的密钥将被导出并保存到您当前所在的目录。在我们的例子中,这是 Android SDK 文件夹下的tools目录。您需要记住文件位置,以便在下一道菜谱中使用 Titanium Studio 构建 Android 应用程序。

构建并将您的应用程序提交到 Android 市场

在这道菜谱中,我们将继续在前两道菜谱中开始的过程,并构建我们的应用程序以分发到 Android 市场。

如何做到这一点…

在 Titanium Studio 中打开您的项目。我们以第一章中的LoanCalc应用程序为例,然而,您可以使用任何您希望的项目。确保您的项目在项目资源管理器页面中被选中,然后选择分发按钮,然后选择分发 - Android选项,如以下截图所示:

如何做到这一点…

您需要输入分发位置(您希望保存打包的 APK 文件的位置)以及您在上一道菜谱中创建的 keystore 文件的位置,以及您之前提供的密码和别名。输入这些信息后,它应该看起来像以下截图:

如何做到这一点…

如果所有信息都正确,点击完成。几分钟后,APK 文件将被写入你提供的分发位置。返回安卓市场网站,在主页上点击添加新应用。选择上传按钮,选择你保存的 APK 文件(在我们的例子中是LoanCalc.apk),并将其上传到服务器。如果一切顺利,你应该会看到一个类似于下面显示的屏幕:

如何操作…

现在剩下的只是填写剩余信息,包括应用描述和标题,以及一些截图。你还将被要求选择定价信息并同意谷歌的条款和条件。完成这些后,剩下的只是按下提交按钮。几分钟后,你的应用应该开始在安卓市场显示!与苹果不同,提交安卓应用不需要进行审核流程。

你现在应该能够构建并提交应用到苹果和安卓市场。

posted @ 2025-09-28 09:13  绝不原创的飞龙  阅读(1)  评论(0)    收藏  举报