安卓-Web-应用开发入门指南-二-

83 阅读1小时+

安卓 Web 应用开发入门指南(二)

原文:Beginning Android Web Apps Development

协议:CC BY-NC-SA 4.0

七、使用 jQuery Mobile 构建令人印象深刻的用户体验

信不信由你,对于一个母亲获得艺术学校奖学金、父亲从事广告工作多年的人来说,我并没有太多的艺术天赋。我大部分的编程努力都集中在功能上,而不是风格上…结果好坏参半。一方面,许多人称赞这种极简设计——例如,我的一个名为快餐卡路里查询的应用只不过是两个下拉框和几个按钮。它有超过 1000 条评论,在安卓市场上的评分为 4.3 分。然而,对于每一个欢呼“如此容易导航,方便的信息!”(实际回顾),我得到的是不理解为什么看起来不好看的人。

值得庆幸的是,在 web 应用领域,有一些选项可以让像我这样的设计“懒汉”看起来像是我们支付了一些非常高的价格让事情看起来华丽的设计师。事实上,有很多工具可以免费实现这个目标,在这一章中,我们将深入了解其中一个工具:jQuery Mobile(jquerymobile.com)。这绝不是你唯一的选择。其他想到的还有:Wink 工具包([www.winktoolkit.org/](http://www.winktoolkit.org/))、Sencha Touch ( [www.sencha.com/products/touch/](http://www.sencha.com/products/touch/))、Zepto.js ( [zeptojs.com](http://zeptojs.com))、Jo ( [joapp.com/](http://joapp.com/))、xui.js ( [xuijs.com/](http://xuijs.com/))和 JQTouch ( [www.jqtouch.com/](http://www.jqtouch.com/))。然而,就我们的目的而言,jQuery Mobile 在特性和易于配置方面提供了最好的性价比。所以——让我们开始探索吧!

基础知识

如果您熟悉用于桌面浏览器的 jQuery UI 工具包,那么您应该很快学会 jQuery Mobilepretty!jQuery Mobile 是这样一种框架,它试图将开发移动应用的大量繁琐任务从开发人员手中拿走,并将所有繁重的工作交给框架,这样您就可以将更多的时间放在对您的应用更重要的其他项目和功能上。jQuery Mobile 创建了一系列触摸优化的小部件和用户界面元素,正在迅速成长为移动 javascript 框架领域的顶级竞争者之一。

然而,jQuery Mobile 最好的特性之一是它非常简单且易于修改的模板结构。有了一点 CSS 知识,开发人员可以很容易地修改他们的 jQuery Mobile 主题的样式,使其看起来像他们想要的那样。这对于像我这样喜欢弄脏自己的手,在他们接触到的任何一段代码的内部进行修改的人来说是非常棒的。另一方面,正如我们将很快看到的,创建一个自定义主题一点也不困难——事实上,如果我们不想的话,我们根本不需要接触 CSS!

最重要的是,jQuery Mobile 是一个非常容易安装和运行的框架。开始时,只需在文档头中包含 jQuery Mobile 源文件,然后在 HTML 标记中使用特殊的数据属性。数据属性是 HTML5 的有趣补充。在 XML 和以前版本的 HTML 中,正式定义的属性被用在标签中来描述数据或数据的格式。虽然在以前的 HTML 版本中,总是可以向 HTML 代码中添加自定义属性,但这总是不被接受。而且,通常情况下,如果你的代码通过一个验证程序运行,它最终会到处出错。清单 7–1 显示了一个无效的自定义“情绪”属性,而清单 7–2 显示了如何在 HTML5 中使用一个有效的“数据-情绪”属性。

**清单 7–1。**HTML 中一个无效自定义属性的例子

`

Today was an awesome day!

Suspendisse consectetur consequat risus non viverra. Phasellus ligula urna, egestas porttitor facilisis vel, euismod sit...

`

**清单 7–2。**html 5中一个有效自定义属性的例子

`

Today was an awesome day!

Suspendisse consectetur consequat risus non viverra. Phasellus ligula urna, egestas porttitor facilisis vel, euismod sit...

`

让我们看一下前面的代码示例。在编写代码的第一次尝试中,我们创建了一个附加了无效自定义属性的 div 容器,以指示创建该博客条目的个人的心情。虽然没有什么可以阻止我们使用这个异想天开地创建的 mood 属性,但是代码不能用流行的验证引擎进行验证,比如 W3c 标记验证服务([validator.w3.org/](http://validator.w3.org/))。这是因为 mood 不是 HTML 规范认可的属性。经过多年来自各地开发人员的抱怨,权力机构决定对我们开发人员有点怜悯,并给了我们最终创建自定义属性以附加到我们代码上的能力。开发人员可以使用这些新的数据属性向他们的标记代码中添加任何类型的数据,这些数据可以用于我们可以想象的任何目的。在我们的第二个例子(清单 7–2)中,我们使用了data-mood属性来记录我们的心情,以一种取悦验证者的方式,同时仍然允许我们根据需要在脚本中使用这些数据!只要我们将前缀“data-”放在想要存储的数据字符串前面,它就是有效的 HTML5 代码。我们可以将它们用作小型数据容器,如下例所示。"

现在,您已经了解了数据属性是如何工作的,以及如何使用它们来丰富我们的移动应用,让我们来看看一些 jQuery 移动代码,并剖析它是如何工作的。

在清单 7–3 中,我们将使用 jQuery Mobile 框架显示一个非常简单的页面。您会注意到它使用了许多数据角色属性。

清单 7–3。 来自默认 jQuery 移动样板文件的 HTML 标记示例

`

Our Super Rad Demo Page

Well Then, Soldier...

How goes the day?

© 2011 Dodo Men Ply, Inc.
`

只需前面的几行代码,就可以使用普通的老式 web 浏览器创建非常原始的移动应用外观。这个页面非常简单。首先,我们有一个包含数据属性值“page”的主容器,它是内容的包装器。正如所料,在那之后我们有了我们的“header”,它通常包含页面的标题和人们决定放在那里的任何导航元素。在我们传递了页眉之后,我们开始向页面的主体前进,也称为“内容”数据属性,紧接着是我们的“页脚”

正如我之前所说的,这个标记并没有太多的东西。大多数神奇的事情都发生在幕后,在 jQuery 移动应用的内部工作中。在接下来的几个例子中,我们将探索如何使用 jQuery Mobile,从简单到复杂,然后是自定义主题化!让我们打开可信的网络浏览器,看看这个页面在我们的移动设备上是什么样子的(见图 7–1)!

images

图 7–1。 由 jQuery Mobile 创建并显示在 Android 2.3.4 手机上的一个看起来非常本地的用户界面

你看到那有多容易了吗?最好的一点是,我们仅仅触及了 jQuery Mobile 的皮毛。只需一点时间,人们就可以轻松地构建出一个功能齐全的移动 web 应用,其中包括漂亮的用户界面元素、模态窗口、对话框、页面转换、导航工具栏以及其他许多东西!让我们再深入一点。

添加多个页面

通常,应用包含不止一个页面!在清单 7–4 中,我们将创建一个更高级的 jQuery Mobile 文档,它包含三个页面:主页、关于页面和联系页面。在这个过程中,我们将会比平常多加一些注释,这样您就可以看到代码的哪个部分创建了哪个元素(显示在图 7–2、7–3 和 7–4 中)。这样,如果您想要定制某些元素,您将确切地看到需要修改什么。

清单 7–4。 页面上的所有 UI 元素都是由 jQuery 移动应用创建的

`

Our Super Rad Demo Page

Hello World

`

在标题中,我们将在页面顶部的黑条中指定我们想要查看的测试。如果我愿意,我可以包含不止一行文本,黑条会继续增加。然而,这可能会让用户有点分心。

清单 7–4 续。 主页面的内容

`

  • Pellentesque habitant morbi tristique senectud    
  • Morbi ultrices dignissim erat id blandit    
  • Etiam massa quam, tempus quis    
  • Nam laoreet congue aliquet    
  • Morbi et ligula vel ligula lobortis pharetra ut eu massa
  • Nam commodo erat orci.    
`

如果你在家编码,你会注意到每一项都出现在一个按钮式的列表中。你可能想知道我们是否可以让这些列表更有趣一点。事实上我们可以——jQuery Mobile 支持几种类型的列表([jquerymobile.com/demos/1.0.1/docs/lists/index.html](http://jquerymobile.com/demos/1.0.1/docs/lists/index.html))有完整的列表和示例)。我们在这里使用只读列表,因为我们没有将列表项链接到任何特定的页面或动作。但是,也可以使用其他链接选项,例如:

  • 一个嵌套列表(参见 Figure 7–2),其中一个列表项被点击后,会滑开以显示多个列表项。

images

图 7–2。 一个嵌套列表

  • 计数气泡式列表(参见图 7–3),其中每个列表项目的末端都可以显示一个数字,嵌入到项目中。这对于显示新项目、通知、新闻故事等的数量非常有用

images

图 7–3。 一个盘点泡泡榜

  • 缩略图(图 7–4)和图标(图 7–5)列表,在列表文本左侧显示缩略图或图标。这对于显示小照片和项目所用于操作的描述性图标很有用,或者只是为了装饰。

images

**图 7–4:**缩略图列表

images

图 7–5。 一个图标列表。

  • 创建插入搜索字段的几个选项(图 7–6),通常是顶部的一个列表项,让用户稍后过滤列表项。

images

图 7–6。 列表顶端的一个搜索过滤框

  • 最后,可以创建一个带有表单的列表(Figure 7–7),允许您在列表视图中创建一个有序的表单页面。对于结帐页面、设置页面和类似的情况很有用。

images

图 7–7。 表单列表内的元素

就列表的用途而言,天空是无限的!现在,回到代码…

清单 7–4 续。:主页面的页脚。

`

`

您会注意到,在主页的底部,我们创建了一个页脚部分(看起来像另一个黑条),并添加了两个按钮。在 jQuery Mobile 中创建按钮非常简单——基本上,它只是将data-role="button"添加到任何常规的 HTML 链接中。

后两个属性—过渡和主题—指的是页面过渡应该如何发生,以及按钮的颜色和样式。我们将在接下来的几章中详细讨论这两个问题。我们也可以通过指定一个图标来手动地为按钮添加更多的样式。在有意义的地方,jQuery Mobile 会自动向按钮添加图标(例如,通过向对话框关闭按钮添加 X 图标)。但是,如果我们愿意,我们可以手动执行此操作。例如,将前面的“联系人”链接改为

<a href="#contact" data-transition="pop" data-role="button" data-theme="b" data-icon="plus">Contact</a>

会在按钮上添加一个漂亮的+图标。完整的图标列表可以在 jQuery Mobile 的网站([jquerymobile.com/demos/1.0.1/docs/buttons/buttons-icons.html](http://jquerymobile.com/demos/1.0.1/docs/buttons/buttons-icons.html))上找到。默认情况下,图标出现在按钮的左侧;然而,我们可以使用data-iconpos="right""top""bottom"来移动它。我们也可以使用data-iconpos="notext"来显示图标而不显示其他任何东西!

如果你想使用自己的图标呢?嗯,那也是可能的。为此,您需要添加一些自定义 CSS,并创建一个格式正确的图标文件(这两者在前面提供的链接中都有解释)。

最后,通过用<div data-role="controlgroup"></div>标签包围一系列按钮,可以将按钮格式化为同一组的一部分。这将使您可以直观地聚集具有相似含义的按钮(例如,“是”、“否”或“取消”组)。这通常意味着只有顶部和底部按钮的角是圆形的(如果使用默认主题),按钮之间几乎没有空间。这对于给用户一个关于 groupedbuttons 相关性质的微妙提示非常有用。现在第一页已经完成,从页眉到页脚,我们将在同一个 HTML 文档中定义另外两个页面!

清单 7–4 续。:关于和联系页面。

`

About Us

Vivamus sit amet nulla vitae odio ultricies fringilla quis at felis. Integer sagittis eleifend leo, et tempor elit adipiscing in. Pellentesque commodo condimentum pulvinar. Integer vitae tellus ac sapien molestie euismod. Sed a enim ut leo fermentum lobortis ac eget velit. Mauris commodo porta felis id fermentum. Aenean eleifend justo eu sem consectetur auctor. Quisque convallis ullamcorper elementum. Integer hendrerit vehicula nisi eu congue. Integer aliquet quam a arcu cursus ac consequat est pretium. Nam nec pharetra lorem. Maecenas lacinia facilisis eros quis tempor.

Send Us Mail!

Name: Email:
` `
Message:
Cancel
Send
`

现在我们已经讨论了代码是如何工作的,让我们看看主页面在 Figure 7–8 中的样子。

images

图 7–8。 在 Android 2.3.4 移动设备上查看我们的 jQuery 移动主页

我们将快速跳过“关于”页面,因为这里没有太多的操作。不过,有一点我们将要介绍的美味佳肴。我敢肯定,看到这一页的你可能会问自己,“嘿,他为什么不创造某种视觉线索,可以用来导航回我们所在的上一页?!"(即使你没有想到这一点,你现在已经想到了)好吧,你不用担心!如果您在您的 web 浏览器中加载我们刚刚创建的页面,并查看“关于”页面,您会注意到 jQuery Mobile 非常好地在您的页面中为您包含了一个后退按钮(参见图 7–9),甚至包含了我提到的正确图标!

images

图 7–9。 我们的 jQuery Mobile 关于在 Android 2.3.4 移动设备上查看的页面。注意到 jQuery Mobile 自动包含在用户界面中的 back 按钮了吗?

最后,在我们的联系人页面上,我们真正开始享受 jQuery Mobile 提供的一些特性(参见图 7–10)。查看我们的代码,您会看到该页面上有两个数据角色为“fieldcontain”的 div 容器。我相信您现在已经猜到了,这些容器是用来存放和分隔表单输入字段的,这些字段不一定要放在一起。基本上,它是用于我们这些平面设计受损的人的审美。在分离了表单字段之后,我们又有了另一个 div 容器,它保存了 Cancel 和 Submit 按钮。通过将 fieldset 元素分配给 ui-grid-a 的 CSS 类(本例中的“a”代表将用于样式化网格容器的主题),这些按钮以类似网格的格式放置在页面上。最后,值得注意的是,我们这里的代码将电子邮件表单发送到 nothing (#),就像它现在所写的那样。在本章的后面,我们将看到如何使用 JavaScript 来处理表单,或者编写一个简单的 PHP 脚本来接收表单数据并进行处理。

如你所见,jQuery Mobile 允许你在很短的时间内创建一个非常漂亮的用户界面!

images

图 7–10。 我们的 jQuery 移动联系人页面格式整齐,可以在 Android 2.3.4 移动设备上查看

那么,关于这些转变

如果您一直在编写代码,您会注意到上一个示例中的按钮根据我们想要做的事情有不同的转换。动画是移动设计中的一个关键元素,因为它让小屏幕给用户一种三维的感觉,这可以帮助他们理解他们在做什么。默认情况下,jQuery mobile 框架将实现从右到左的滑动过渡,这给用户翻页的感觉。这个框架也很好,当“后退”按钮被按下时,它将反转最初使用的过渡,所以在这种情况下,转向上一页。在清单 7–5 中,我创建了一个简单的页面,展示了从第一页到第二页的所有过渡类型。

清单 7–5。 过渡演示 HTML

`

This is jQuery's Base Template, Showing off The Transitions ` `

The Title

This is where we put what we want to talk about on this page, it's going to be great

Pop Page 2
Slide Page 2
Slideup Page 2
Slidedown Page 2
Fade Page 2
Flip Page 2

The Title of Page 2

This is where we put what we want to talk about on this page, it's going to be great

Back to page 1
`

呈现时,页面看起来像 Figure 7–11,带有几个文本链接,将根据需要显示适当的过渡。

images

图 7–11。 过渡演示页面

点击每个链接查看相应的过渡,然后使用第 2 页上的后退按钮查看相反的过渡。我们在清单 7–4 的 HTML HREF 链接中指定了我们想要的过渡,你可以随心所欲地使用它们。然而,请记住,当处理转换时,应该事先决定每个转换的含义,以免用户感到困惑。如果在数据库中输入内容会导致渐变,而向上滑动会显示消息或错误,自由切换这些内容可能会让用户怀疑他们采取的行动。另请注意,翻转和弹出过渡可能无法在某些版本的 Android 股票浏览器上正确呈现,因此可能需要避免它们。说到向用户显示消息…

让我们来对话吧

就计算机而言,对话框已经存在很长时间了。我们已经在本书前面展示了一些非常简单的例子。然而,jQuery Mobile 有一个非常好的内置方式来显示这些对话框,我们将使用下面的清单 7–6 来展示它。

清单 7–6。 对话框示例

`

This is jQuery's Base Template, Showing off The Dialogs ` `

The Title

This is where we put what we want to talk about on this page, it's going to be great

Open Dialog
Slide Down Dialog
Flip Dialog
Pop Dialog

The Title of Page 2

This is a dialog box

Back to page 1
`

当我们加载清单 7–6 时,我们得到一个包含一系列 3 个按钮的页面(图 7–12),我们可以按下这些按钮来查看弹出的第二页(与我们的过渡演示中的页面相同)。在对话框中,一旦打开,我们还会在右上角看到一个小黑 X 按钮来关闭消息,不需要任何额外的编码。

images

图 7–12。【dialogs.html】对话框演示页面,

当你需要引起用户的注意,并且不想给人一种他们正在进入新页面的印象时,对话框是很有用的。如果使用得当,它们会给你的用户一个很好的,完美的体验。

现在你已经添加了一些个人风格的过渡和对话框,让我们回到我之前提到的主题:自定义主题。

使用主题滚轮滚动您自己的主题

让我们面对现实:创造一个主题并不是一件容易的事情。有很多事情需要考虑,虽然苹果已经做了很多工作来使移动页面看起来有点标准,但你不希望我们的 Android 应用看起来完全像 iPhone 的克隆版,对吗?(事实上,也许你有,但是嘿——有选择总是好的!幸运的是,jQuery Mobile 团队已经创建了一个他们自己的名为 theme roller(Figure 7–13)的令人敬畏的 web 应用,它可以让你快速而轻松地创建一个主题。

images

图 7–13。?? 主启动屏幕

ThemeRoller 允许您调整普通 jQuery 移动主题的每一个元素。在屏幕的右侧,您将看到三个主题(A、B 和 C),每个主题都可以定制,如果需要,您甚至可以添加更多主题。创建之后,我们可以使用data-theme属性指定我们想要在给定页面上显示的主题(在前面的清单中可以看到这样的例子,比如清单 7–4)。

一旦你关闭了“开始滚动”屏幕,你可以开始扩展屏幕左侧的选项,就像我在图 7–14 中所做的那样。

images

图 7–14。 自定义滚轮屏幕左侧的选项

我可以对我的所有主题进行全局更改,或者我可以专门对主题 A、B 或 c 进行更改。在这种情况下,所有的默认设置都提供了非常 iPhone-y 的外观。我将改变它们,以创建如图图 7–15 所示的配置。

images

图 7–15。 改变选项更新右边的预览

你可以看到我已经改变了相当多的设置,这使得界面非常不同。我还没有改变颜色,虽然我可以很容易地做到这一点。事实上,ThemeRoller 甚至会给我一个 Adobe kuler 样本集(图 7–16;互相搭配的颜色集合——([www.adobe.com/products/kuler/](http://www.adobe.com/products/kuler/))帮助我挑选一组让我的用户满意的颜色。

images

图 7–16。 Adobe kuler 样本帮助我找到了我正在寻找的那种配色方案

经过几分钟(或几小时)的定制,我可以通过输入几个选项来下载我的主题,如图图 7–17 所示。这为我提供了一个包含主题目录的 ZIP 文件,一个正确嵌入了我的主题的示例 index.html,所需的图片,以及两个用于我的主题的 CSS 文件,如果需要的话,我可以手工编辑它们。

images

图 7–17。 带说明的下载主题窗口

如果我需要修改我的主题,我可以使用 ThemeRoller 中的“导入”选项将它重新导入到 web 应用中,并再次播放。然后,我可以像导出第一个主题一样导出新主题。想象一下:你可以创建自定义主题(就像我之前创建的那个,显示在图 7–18 中的示例索引文件中),然后为“姐妹”应用修改它们——无需直接接触 CSS!

images

图 7–18。 我的自定义主题应用到 ThemeRoller 提供的示例索引文件

现在我已经让我的应用看起来像我想要的那样,并且我知道如何构建额外的页面、过渡和对话框。我正在创建一个不仅功能强大,而且外观漂亮的应用!

把所有的都放在一起:简单的计算

既然我们已经在 jQuery Mobile 中构建了一些示例页面,那么让我们构建一些有用的东西:一个简单的计算器。我们将构建一个非常简单的计算器,它接受两个数字并对它们执行一些操作,然后返回结果。我还将创建一个自定义主题来使用。最终结果看起来像图 7–19:

images

图 7–19。 简单的 Calc jQuery 手机 App

Simple Calc 是一个极其简单的计算器。你在第一个盒子里放一个数字,在按钮下面的第二个盒子里放一个数字,然后按+或-。会弹出一个小的 JavaScript 警告,告诉你答案是什么。这当然很简单,但是对于展示如何使用 JavaScript 向 jQuery 移动页面添加一些交互性很有用。让我们看看清单 7–7 中的代码。

清单 7–7。 简单计算的代码

`

Simple Calc ` `

Simple Calc

Enter a number in the first box, one in the second, and then press a button in between to get the answer!

Neat!
`

让我们浏览一下这段代码。首先,我们开始添加通常的 HTML 标题和 jQuery Mobile 的链接。您会注意到我的自定义主题也包含在第 7 行(simplecalc.css)。我使用我们在上一节中讨论的 ThemeRoller 构建了这个!

接下来是一组定制的 JavaScript 函数,addnums()subnums().这些函数访问两个数字框(分别声明为 num1 和 num2)并执行所需的操作。然后他们弹出一个带有结果的标准 JavaScript 警告框。如果您想发挥您的 JavaScript 能力,您可以将这两个功能合二为一!

再往下一点,我们可以看到实际内容的布局。我们放入了两个输入,数字 1 和数字 2,以及它们之间的两个按钮。我们使用了一个控件组,这样按钮组合在一起,而不是分开。您会注意到每个按钮都有一个对其对应的 JavaScript 函数的onclick=引用。这就是神奇的地方——也是为什么我们实际上不需要构建表单操作页面——JavaScript 会处理所有的计算。

这就是了——一个简单的计算器!

总结

使用 jQuery Mobile,人们可以快速获得一个基本的应用,并使它看起来像是专业设计的。您还可以制作快速而漂亮的“信息”应用(例如,作为会议程序或建筑物或事件的导游的应用)。我们已经介绍了 jQuery Mobile 的基础知识,但是如果您需要的话,仍然有一些高级的主题和细微差别留给您自己去探索——您的想象力是唯一的限制!

八、构建视觉丰富的互联网应用

既然我们已经更好地掌握了 JavaScript 和各种面向移动的 JavaScript 框架可以做些什么来增强我们的用户体验,那么是时候深入其中,用一点数字手指画来弄脏我们的手了。构建优秀的 web 应用不仅仅是编程和逻辑。相当多的网页开发从设计领域获得了很多线索。以 CSS 为例。它让开发人员疯狂的一个主要原因是因为 CSS 是一个设计工具,而不是真正的开发工具。当然,我们必须编写代码使一切看起来漂亮,但实际上我们正在做的是精心设置边距、填充、颜色、字体等。

记住这一点,在这一章中,我们将探索开发中更多涉及设计的有趣方面。我们将看看 Creative Commons 图标和图形的资源,web 字体的资源,CSS 框架,甚至是一个面向 web 开发人员和 web 设计人员的漂亮的位图和矢量图形编辑器,叫做 Adobe Fireworks ([www.adobe.com/products/fireworks.html](http://www.adobe.com/products/fireworks.html)).

查找和使用图标和库存照片

让网站看起来更有吸引力的最简单的方法之一是在你的项目中添加一些图标或吸引人的图片。有大量的资源,艺术家们乐意提供他们创作的图标集或他们拍摄的宏伟照片,免费使用,不问任何问题。

在这一节中,我们将看看一些更受欢迎的网站,这些网站迎合了设计师、开发人员和所有其他爱好者对有吸引力和有趣的事物的需求。

图标查找器

Iconfinder ( [www.iconfinder.com](http://www.iconfinder.com) /,见 Figure 8–1)是我个人最喜欢的一个,可以在开源图标的知识共享空间中找到。这个搜索引擎可以让你从 150,000 多个不同的图标中挑选出你的下一个项目。

从 12 像素到高达 512 像素的图标和图形,这个搜索引擎几乎可以保证有一些东西可以用来直观地提升你正在进行的任何项目。

虽然此搜索引擎中的图标可以免费使用,但并非所有商业网站(即,为营利团体而非非营利团体建立的网站)上的图标都可以免费使用,因此请确保阅读您可能想要使用的任何图标的许可协议。到目前为止,我们已经提到过几次 Creative Commons 许可,所以花点时间考虑一下它和其他许可是有意义的。首先,你必须记住,无论是谁最初设计了一件艺术品(无论是图标、照片等等),都保留着该艺术品的版权。很容易想到“嘿,这是在互联网上;必须是免费的。”然而,如果主人发现了,这种想法会让你很快陷入麻烦。许多艺术家和设计师不希望为他们的内容创建一个独特的许可证,他们决定使用知识共享许可证([creativecommons.org](http://creativecommons.org)。这些许可证允许艺术家自由使用他们的作品,有或没有某些限制。你不能仅仅假设在知识共享下许可的东西是免费使用的——它可能对商业使用有限制(NC 是知识共享团队使用的简写),或者它可能要求在你的应用中归属(这里使用的简写是将图标或图像归功于所有者)。此外,所有者可能会指定应该知道的其他选项。

总而言之,如果你不确定是否可以使用一个图标,不要害怕接触作者并询问。最坏的情况是他们会说不!

images

图 8–1。 Iconfinder 搜索引擎首页。超过 158,184 个图标,并且还在增长!

查找图标

find Icons(Figure 8–2)是另一个搜索引擎,它迎合了开发人员和设计人员对网站的需求。功能和布局类似于图标查找,所以如果你熟悉一个搜索引擎,你可以很容易地适应使用另一个搜索引擎。

我发现当搜索一个图标时,同时使用两个站点总是好的,因为每个站点都有一系列在另一个站点上没有的图标。您可以在[findicons.com/](http://findicons.com/)浏览查找图标搜索引擎

images

图 8–2。 在查找图标网站上搜索“暂停”图标

使用图标

假设你已经找到了一个你喜欢的图标,或者你已经使用 favicon.ico Generator ([www.favicon.cc/](http://www.favicon.cc/)),这样的服务创建了一个图标,你可以通过在 HTML Header 部分放置以下 HTML 代码来轻松地为你的 web 应用指定这个图标:

<link rel="shortcut icon"  href="http://yourdomain.com/app.ico" />

这一行告诉所有现代浏览器你的应用的图标位于[yourdomain.com/app.ico](http://yourdomain.com/app.ico)(你可以把图标放在你选择的任何地方)。这将导致浏览器显示你的图标(而不是常用的“书签”图标),只要用户将你的页面加入书签或者(在某些浏览器中)浏览到它。图 8–3 和 8–4 分别显示了查看有图标和无图标的网站时 Mozilla Firefox 浏览器的地址栏。

images

图 8–3。 火狐显示一个带有指定图标的网址

images

图 8–4。 火狐显示一个没有图标的网址

您可能还想添加一行:

<link rel="apple-touch-icon" href="/image.png" />

这一行明确告诉移动浏览器(如苹果的 Safari 和安卓的浏览器)应该在锁定主屏幕快捷方式的功能中使用参考图像文件(通常是 57×57 像素图像或 114×114 图像文件(对于较新的系统))。对于 web 应用来说,这可以给用户一个完美的外观,让他们更接近原生应用的体验。

既然我们已经选择并使用了一个图标,让我们来讨论一下如何通过查找和使用库存照片来使我们的应用更加图像友好。我们将从最古老的图像搜索工具之一 deviantART 开始。

猜猜

似乎 deviantART ([www.deviantart.com/](http://www.deviantart.com/),见图 8–5 已经存在了很长时间,从技术角度来说,的确如此。这个 2000 年的古老遗迹经受住了互联网时代的考验,是业余和专业艺术家以及平面设计师创造力的一个闪亮支柱。

除此之外,一些社交媒体功能允许用户评论或与其他用户分享作品或艺术,你会有一个令人上瘾的用户体验,提供了大量的资源...有时并不是很好的资源。

一定要去看看他们的摄影区,那里有很多免费的库存摄影作品,可以在你即将到来的项目中使用。另外,一定要查看提供的库存照片的许可协议。有些艺术家可能有特殊的要求,在你利用他们的作品之前,必须先满足这些要求。这些要求可能相当普通(即无商业用途)或非常具体(即必须用于公共服务公告或慈善事业)。

images

图 8–5。 查看 deviantART 的股票摄影版块

组合拳

另一个更专业也更昂贵的解决方案是 iStockphoto ([www.istockphoto.com/](http://www.istockphoto.com/)),它可以为你的 web 应用提供出色的库存照片。这是一项我已经使用多年的服务,为客户项目购买免版税的图像。

虽然与 deviantART 不同,这种资源不是免费使用的,但您将购买的图像来自专业人士,并且往往比您在其他地方找到的业余人士的图像质量更好。

除此之外,还有一个很大的图像存档,可以很容易地搜索到你想要的东西,你有一个应该总是放在手边的资源!你永远不知道什么时候,有一天,你的一个客户可能会打电话给你说“嘿,我已经决定我们真的要在我们的网站上添加一个巨大的粉红色电话”,尽管这听起来很疯狂和可笑,但你很可能会在 iStockphoto 上找到一个巨大的粉红色电话的图像。

除了 iStockphoto 和 deviantART,你还可以在 Shutterstock ( [www.shutterstock.com](http://www.shutterstock.com))和 Flickr ( [www.flickr.com](http://www.flickr.com)).)找到图片,特别是 Flickr,它有一个非常好的高级搜索功能([www.flickr.com/search/advanced/?](http://www.flickr.com/search/advanced/?)),允许你指定你只想要 Creative Commons 许可的内容(而且,你只想要 CC 许可的内容,这些内容可以免费用于商业用途,并可以进行改编)。这是为您的应用获得非常好的用户提交照片的简单方法。

在我们结束讨论照片之前,讨论一下什么时候应该和不应该选择使用照片可能是有用的。在许多不同的空间中使用图片来推广你的应用和/或让它大放异彩是很有诱惑力的;但是,有一条必须要走的细线。

在 Web 应用中使用照片的指南

在找到我们在前面几节中讨论的服务后,很容易就能找到应用每个部分的照片。例如,你可能在应用的主屏幕上有很大的导航按钮,这些大的正方形在里面放照片会不会比文字或简单的图标看起来更好?也许……但是,我们建议您首先考虑以下几点:

  • 我给每张照片增加了多少加载时间?回想一下,当我们测试时,我们的应用通常驻留在我们的计算机上。这使得加载时间的争论很大程度上没有实际意义——我们只是不知道在特定情况下我们的应用会如何加载。考虑到这一点,漂亮的照片有时会导致应用加载缓慢。我们建议使用适当大小的照片(例如,必要时调整大小;不要简单地指定高度和宽度,这将使它们呈现得更小,但实际上文件大小不会更小(如果可能的话,小于 10 千字节),并在各种条件下测试页面的照片和非照片版本(例如,从生产 web 服务器提供给 WiFi 和蜂窝数据连接上的设备)。
  • 我的图片会分散用户的注意力吗?是的,虽然一张图片抵得上 1000 字,但有些图片在用户中引起的争论更有价值。在某些情况下,停止标志的照片对用户来说可能很容易理解,但在其他情况下可能会令人困惑(例如,在游戏中,停止标志是否意味着一轮结束?退出游戏?阻止我的小男人走路?).图像应该只是帮助用户更快地找到东西,而不是通过分散注意力、混淆视听或质量低劣来迷惑用户。
  • 使用图片会引起用户刻板印象吗?图像可能很强大,但在某些情况下,它们可能与我们熟知的传统刻板印象有关,也可能与一些你可能没有想到的刻板印象有关。例如,使用可靠的“穿西装的男人”和“使用笔记本电脑的女人”可能看起来适合你的应用;然而,用户通常已经看过这些股票图片,可能会认为你的 web 应用和其他使用过相同图片的人一样没有创意。这就是为什么现在许多网站倾向于极简设计趋势——这比“努力工作的主管”或“足球妈妈”更难预测

假设你已经考虑了前面列表中讨论的问题,使用照片可能是增强你的应用吸引力的好方法。注意你的用途!现在我们来看一个更微妙的图像操作:在我们的 web 应用中使用自定义字体。

网络资源

你可能还记得,在第二章中,我们在构建“那条推文是谁?”时,简要地提到了使用网络字体的基础申请。在过去的一年半时间里,所有最流行的桌面浏览器都推出了更新,现在可以在标记中使用网络字体。几乎所有 Android 的移动浏览器都可以使用它们。

将 web 字体添加到您的网站或应用非常简单,如清单 8–1 所示。

清单 8–1。 用一点 CSS 魔法将 Droid Sans 字体加载到我们的网络浏览器中。

` @font-face {     font-family: 'Droid Sans';     font-style: normal;     font-weight: normal;     src: local('Droid Sans'), local('DroidSans'), url('droidsans.woff') format('woff'); }

body {     font-family:'Droid Sans', Arial, Helvetica, sans-serif; } `

让我们快速看一下清单 8–1 中的代码,这样你就能理解我们在这里做什么了。我们将在@font-face代码中定义的第一个属性是font-family属性。在这里,我们可以随意命名字体。我们在这里使用字体的真名,Droid Sans,但是如果我真的想的话,我可以称它为“龟蜡”。如果您希望使用内部命名结构,使用自定义名称可能会有所帮助,尽管如果您希望对两种不同的产品使用相同的字体,这可能会造成混淆。

接下来,我们将定义新字体的font-style。这很重要,因为根据font-style是什么,我们可以有不同的 Droid Sans 字体加载变体。作为一名开发人员,这是你需要小心的地方,确保你不会在不需要的时候加载 10 种不同的字体。这不仅会弄乱你的代码,还会加载不必要的资源,从而降低用户体验。在这种情况下,我们只想使用这种字体的默认或“正常”外观;也就是说,我们不想要斜体。

在我们的font-style之后,我们有我们的font-weight。这就像它前面的属性一样,可以用来加载不同版本的字体,比如 Droid Sans Bold。

最后,但并非最不重要的,我们有我们的src,,它用来告诉你的计算机在服务器上哪里可以找到我们的字体,以及在用户的计算机上该字体可能被称为什么。如果字体在用户的机器上,这可以为我们节省一些带宽,因为不必下载我们已经拥有的文件。虽然在大多数情况下本地加载更可取,但是您可能会遇到希望从另一个位置加载字体的情况,例如当您测试各种字体样式以确定最合适的字体时。

谷歌网页字体

你可能还记得,在我们之前的一章中,我们从谷歌网络字体中抓取了 Droid Sans 字体。当时,我们并没有详细介绍谷歌网页字体,也没有说明它对设计者和开发者来说是多么不可思议的资源。

在你的下一个项目中使用谷歌网络字体(如图 8–6 所示)最有趣的一点是,所有的字体都是开源的,任何人都可以使用,不用担心违反许可和欠别人钱。

images

图 8–6。 新设计的谷歌网页字体网站的登陆页面

我听到的关于反对使用 web 字体的开发人员的最大抱怨之一是,字体往往有点笨重,这确实增加了加载时间。虽然由于有限的带宽,这在移动设备上可能很烦人,但谷歌已经考虑到这一点,并创建了一种简单的方法来缩小加载到用户浏览器中的字体文件的大小。

快速浏览一下清单 8–2。这里我们有了将 Chivo Google Web 字体添加到我们的 Web 应用所需的基本 HTML 标记。

清单 8–2。 将非优化版本的 Chivo 字体加载到我们用户的浏览器中。

<linkhref='http://fonts.googleapis.com/css?family=Chivo' rel='stylesheet' type='text/css'>

现在,如果你看一下清单 8–3,你可以看到我们是如何优化字体大小的,只加载我们需要组成单词“Hello World”的字母如果你需要在你的网站上加载一个只使用一次的字体,比如 logo,并且只使用几个字母,这是非常完美的。

清单 8–3。 加载 Chivo 字体的优化版本,只从字体中加载我们需要的字母

<link href='http://fonts.googleapis.com/css?family=Chivo&text=Hello%20World' rel='stylesheet' type='text/css'>

除了允许你优化你的字体,谷歌还会定期向谷歌网络字体目录添加新的字体。当我无聊的时候,它无疑是我最喜欢浏览的地方之一,因为你永远不知道什么时候你会发现一种神奇的字体,它会激励你坐下来开始编写新的代码。

要考虑的字体问题

就像使用照片一样,使用不同的字体和文本样式时,需要考虑和计划一些注意事项:

  • **不同的风格是什么意思?**我们之前提到过,我们可以指定字体的风格变化,比如将文本变为斜体或粗体,甚至使用不同的字体变体,比如使用印刷体。虽然这些变体提供了很大的灵活性,但是如果没有一致地应用,它们也会使用户困惑。您应该考虑是否希望块文本引用动作(例如,视频或音频剪辑的播放按钮),或者是否希望块文本以斜体显示(尽管如果文本不够大,在移动屏幕上很难阅读斜体)。但是,如果动作是斜体,照片下面的标题应该如何显示?粗体可能看起来太强了,或者可能意味着文本是一个链接。看到这是如何变得有点复杂了吗?为你的应用创建一个非正式的“风格指南”可以创造奇迹,产生一致和漂亮的输出,并有效地使用字体。
  • 风格是否值得用流畅来交换?认知心理学——或大脑如何解读信息的心理学——的研究人员使用术语“流畅性”来指处理信息的难易程度。一个字体的粗细足以保持其行的分离,并且没有衬线,这是非常流畅的,这意味着读者可以很容易地快速解析它并理解你在说什么。另一方面,“优雅”笔迹的波浪形字体或非常方方正正的块状字体(字母开始相互碰撞)非常不流畅。这意味着用户将需要更长的时间来阅读这些部分。在某些情况下,这是一件好事——我们可能希望将注意力吸引到应用的某个部分,并确保用户花足够的时间来考虑它(例如,所有数据都将被删除的通知可能是一个很好的块字体候选,假设它不会如此不流畅,以至于导致人们出于沮丧而绕过它!).然而,在其他情况下,我们可能希望关注速度而不是风格。
  • 颜色不仅仅意味着字体。虽然在应用中使用不同的字体来创建不同的外观很有诱惑力,但是人们应该考虑简单地改变文本的颜色是否足够了。在一个方案中使用颜色搭配得很好(也许使用颜色组合([www.colorcombos.com/](http://www.colorcombos.com/))比改变字体更能创造丰富的外观。前面讨论的规则同样适用于颜色(即,颜色应该一致地向读者传达相同的信息——这是警告、行动还是描述?);但是从加载和配置上来说,成本比使用字体的成本要小。

现在,我们已经讨论了如何使用字体、图像和图标来创建您想要的外观,我们将进入一个更广泛的环境——CSS 框架,在这个环境中,我们可以获得一个完整的 CSS“皮肤”来包裹我们的应用,具有深远的影响和后果!

CSS 框架

不管你喜欢还是讨厌它们,在过去的几年里,CSS 网格框架越来越受 web 设计新手和开发人员的欢迎。我把这一切都归咎于 Blueprint,一个在 2008 年左右变得非常流行的 CSS 框架。Blueprint 的创建是为了减少开发时间,并防止开发人员在忘记边距和填充之间的区别时沮丧地拔掉自己的头发 1 。最后一部分对我来说可能是一个小笑话,但它离真相不远。

正如我们在本章开始时所讨论的,像级联样式表这样的工具被开发人员广泛使用,但实际上是设计人员使用的工具。开发人员可能不会对 CSS 感到舒服——有点类似于让一个木匠来管理家里的电线。他可能知道足够去做,但话说回来,他可能会把事情搞得比他从未开始做时更糟。

虽然这听起来可能有点荒谬,但我已经多次为客户承担了一个项目,处理已经由开发人员或工程师编写的样式,很明显,他们已经用编写代码逻辑的相同方式处理了编写 CSS 的整个方法。将 CSS 视为设计标准,而不是代码中的规则。

如果你听起来像是那些似乎不“理解”CSS 的人之一,那么你可能想看看已经被创建来使你的生活更容易的一些框架。从长远来看,它们将帮助您快速解决开发人员在尝试将他们的内容样式化到网格中时遇到的许多常见问题,从而节省您的时间和金钱。它们也是很好的工具,可以用来如饥似渴地了解它们是如何工作的。


对罗科来说,这不是什么大问题。对乔恩来说,他需要所有他能得到的大块头发!

谁知道呢,在玩了一会儿 CSS 框架并学习了它们是如何操作的之后,你可能最终会对样式表语言产生真正的热情,并且最终会比你实现一个已经做好的解决方案更快地手工编写你自己的 CSS!

在下文中,我们强调了三个框架,从最简单的开始,它为你解决了许多桌面/移动 CSS 难题——1140 像素网格。其次是更少的框架 4 ,它提供了一些支持,但有更多的修改空间。最后,我们讨论一下 320 和更高版本,Less Framework 4 的一个版本,它允许您快速启动并运行,而无需大量的调整!

1140 像素网格

1140px 网格是另一个 CSS 框架,目前在互联网上运行,拥有相当大的追随者。该框架本身是在 Creative Commons 许可下发布的,可以随意使用、复制、分发和操作。

创建该框架是为了适应屏幕分辨率的最新变化,特别是当前 1280 × 800 计算机屏幕的突出地位,同时也是为了适应更小的显示器。直到最近,大多数网站都是围绕这样一个理念设计的,即普通用户的浏览器应该有大约 1024 像素宽和 800 像素高的分辨率。然而,在过去的几年中,许多开发者已经注意到,平均用户屏幕分辨率一直在缓慢地提高。

记住这一点,1140 像素网格的设计是为了利用现在更常见的屏幕分辨率,并使用响应式 web 设计以流畅的方式缩小,正如本书第五章所讨论的。这对开发人员来说意味着,如果您正在为桌面用户体验设计和构建一个 web 应用,那么您只需要为一个更大的显示器设计一次应用。然后,对于任何分辨率较低的浏览器,框架会智能地缩小应用。

然而,这并不总是一劳永逸的过程,有时它确实需要你进去对 CSS 做一些修改,让它看起来完全像你想要的样子。例如,网格将允许你使用许多列;但是,如果您希望您的应用在桌面和小浏览器上都有吸引力(类似于我们在本书前面讨论的,当讨论专用移动版本而不是一个页面同时提供两者时),您可能希望将自己限制在一个网格中。总而言之,如果你必须先做大再做小,你可以在[cssgrid.net/](http://cssgrid.net/)查看 1140px 网格框架,让你的生活变得轻松一点

少框架 4

Less 框架([lessframework.com/](http://lessframework.com/),图 8–7)是 CSS 网格系统中的另一个例子。Less Framework 包含三种不同的排版预设和四种不同类型的布局,比一些竞争对手更强大。与 1140px 网格框架不同,该框架是根据平板电脑和智能手机的想法构建的。如果您正在构建一个只需要在手机或平板电脑上显示的应用或页面,那么 Less Framework 4 是比 1140px Grid 更好的选择。虽然这个框架是为 iPad 和 iPhone 用户开发的,但它仍然可以在基于 Android Webkit 的浏览器上很好地工作。

然而,Less 框架并不像其他一些选择那样是一个为你做任何事情的框架。为了更好地利用这个框架,建议您熟悉 HTML 和 CSS。你们三个不一定要成为最好的朋友,也不一定要邀请对方来吃圣诞晚餐,但是偶尔在饮水机旁聊聊天,看看孩子们过得怎么样,也不会有什么坏处。

作为我们意思的一个例子:考虑 Less Framework 4 有两种移动布局:普通和宽。宽布局适用于横向移动电话。考虑到用户可能会因为各种原因在不同时间切换手机的方向,您可能希望在 JavaScript 或 HTML 中采取措施,根据方向重新呈现或重新组织材料。一个在 wide 上看起来不错的应用在正常情况下可能看起来很糟糕——这是你应该确定、测试和调试的事情。

images

图 8–7。 少了谷歌 Chrome 桌面浏览器中看到的框架 4 主页

320 及以上

另一个知识共享许可框架 320 和更高版本([stuffandnonsense.co.uk/projects/320andup/](http://stuffandnonsense.co.uk/projects/320andup/))从一开始就考虑到了更小的观看门户。正如 320 和 Up 网站所说,这个框架是“小屏幕优先”。由安迪·克拉克和基思·克拉克创建,基于 Less Framework 4,这个 CSS 样式集合带有预设的排版样式和一些额外的 JavaScript 代码,用于调整图像大小和提高网站在旧浏览器中的综合性能。正如您在图 8–8、8–9 和 8–10 中所看到的,它可以很好地适应当今 Android 设备上使用的不同常见方向和屏幕。

当您希望避免桌面和移动版本的应用之间的任何重叠,并且当响应是您的主要目标时,320 和更高版本是一个不错的选择。320 和更高版本还包括比 Less Framework 4 更多的定制,例如用于调整照片大小的内置 JavaScript 库,以及应用适当优化的 CSS。如果 Less Framework 4 的定制让您望而生畏,那么您可能希望从 320 开始,看看您是否真的需要深入了解!

images

图 8–8。 在安卓 2.3.4 姜饼手机上以纵向模式查看的 360 及以上网站

images

图 8–9。 在 Android 2.3.4 姜饼手机上以横向模式浏览 360 及以上网站

images

图 8-10。 在桌面浏览器中用谷歌 Chrome 查看 360 及以上网站。请注意,该图和之前的图都显示了相同的标记代码,但页面看起来却因用户浏览器分辨率的不同而有所不同。

对比框架:关于乔恩!!

选择正确的框架可能很难,所以在这个例子中,我们将采用相同的内容并在所有三个框架中呈现它。材料是我(乔恩的)个人网站上的简历。据推测,这将是我的移动 Jon 应用中的一个页面。首先,让我们看看在桌面网络浏览器中呈现的原始页面(参见 Figure 8–11)——这是我们试图在某种程度上在移动版本中模仿的。

images

图 8–11。 乔恩的 Biosketch 渲染在桌面上。

首先,我们将在 1140 像素的网格中渲染它。回想一下,这个框架的优势是我们可以从桌面到移动设备获得类似的东西。首先,移动视图(图 8–12):

images

图 8–12。 Jon 的 Biosketch 使用 1140px 网格在手机上渲染

现在,让我们看看桌面浏览器中的视图(Figure 8–13):

images

图 8–13。 乔恩的 Biosketch 使用 1140px 网格渲染在桌面上

这并不完全理想——两个单栏布局在 Mobile 中堆叠在一起。然而,在桌面视图中,它们是并排显示的,非常“拥挤”。通过调整 HTML 代码,我们可以缓解这种情况,并使桌面版本看起来更好(见图 8–14),同时也保持移动版本。在这种情况下,问题很简单,我们使用了两个“onecol”框,而不是左边的onecol和右边的elevencol(填满屏幕的其余部分)。1140 像素的网格要求从一开始就知道要填充多少列,并使用适当的 div 语句。有 12 列,所以无论您使用什么组合,总共应该是 12 列(即,在本例中,1 个 onecol 和 1 个 elevencol)。您还会注意到,最后一列的 div 语句中有单词“last ”(参见清单 8–2),它让 1140px Grid 知道它是行中的最后一列,因此应该填充页面的其余部分。

images

图 8–14。 乔恩的 Biosketch 的桌面视图片段,修复

1140px 示例的代码可以在清单 8–2 中看到:

清单 8–2。 乔恩的 biosketch 使用了 1140px 的网格。

`

         The 1140px Grid · Fluid down to mobile                         body {     font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;     }

    .container p {     color: #000;     line-height: 16px;     background: #FFF;     text-align: left;     margin: 20px 0 0 0;     }

    

    
        
            

        
        
            

Jonathan E. Westfall, PhD, born 1982, is a researcher and technologist working in New York City at Columbia Business School. His current research focuses on` `the variables individuals use when making economic and consumer finance decisions, specifically strength of handedness, a variable correlated with a number of decision- making and cognitive psychology tasks and measures. Dr. Westfall also conducts research on consumer financial decision making, and applications to both marketing and public policy. His current appointment is as the Associate Director for Research and Technology at the Center for Decision Sciences, a center within Columbia Business School at Columbia University in New York City. Additionally, he holds an appointment as a Lecturer in the Columbia University Psychology department, periodically teaching a course in Judgment and Decision Making

        

    

`

现在,让我们在 Less Framework 4 中看看这个。首先,你会注意到 Less Framework 擅自应用了一些基本的字体格式,这是 1140px 允许我们手动完成的,或者如果我们愿意的话,根本不用。如果你想要那种“移动”的感觉,这可能会帮助你更快地起床和跑步,如图图 8–15 所示。

images

图 8–15。 乔恩的 biosketch 使用了少框架 4 的框架

代码实现起来也相当简单,主要是因为我们不用担心桌面版本。清单 8–3 给出了完整的代码,引用了 Less Framework 4 包中提供的 CSS。

清单 8–3。 乔恩的 biosketch 使用了少框架 4 的框架

`

                      

                          

        

Jonathan E. Westfall, PhD, born 1982, is a researcher and technologist working in New York City at Columbia Business School. His current research focuses on the variables individuals use when making economic and consumer finance decisions, specifically strength of handedness, a variable correlated with a number of decision- making and cognitive psychology tasks and measures. Dr. Westfall also conducts research on consumer financial decision making, and applications to both marketing and public policy. His current appointment is as the Associate Director for Research and Technology at the Center for Decision Sciences, a center within Columbia Business School at Columbia University in New York City. Additionally, he holds an appointment as a Lecturer in the Columbia University Psychology department, periodically teaching a course in Judgment and Decision Making

    

    

    

`

最后,我们来看看我的 320 以上的 biosketch。它使用背景图像和自定义字体进一步应用格式,如您在 Figure 8–16 中所见。

images

图 8–16。 乔恩的 biosketch 采用 320 及以上框架

与 1140 像素的网格相比,代码更简单,因为我们不关心桌面渲染。参见清单 8–4。

清单 8–4。 乔恩的 biosketch 使用了 320 和 Up 框架

`

320 and Up Version
    

        

Jonathan E. Westfall, PhD, born 1982, is a researcher and technologist working in New York City at Columbia Business School. His current research focuses on the variables individuals use when making economic and consumer finance decisions, specifically strength of handedness, a variable correlated with a number of decision- making and cognitive psychology tasks and measures. Dr. Westfall also conducts research on consumer financial decision making, and applications to both marketing and public policy. His current appointment is as the Associate Director for Research and Technology at the Center for Decision Sciences, a center within Columbia Business School at Columbia University in New York City. Additionally, he holds an appointment as a Lecturer in the Columbia University Psychology department, periodically teaching a course in Judgment and Decision Making

`

如您所见,每个框架的格式略有不同,允许您选择自己喜欢的格式。虽然 Less Framework 4 和 320 及更高版本在外观上看起来非常“移动”,但它们缺乏与桌面版本的任何交叉功能。另一方面,1140 像素的网格在风格上不是很“移动”,这意味着你需要手动添加。然而,作为一个加号,你在一个文件中得到一个桌面和移动版本。

为自己选择合适的 CSS 框架可能会很困难;然而,考虑到我们在这里讨论的框架,它应该给你一个为你的应用选择正确的框架的起点!

土坯烟花

Adobe Fireworks,如 Figure 8–17 所示,可能是我的 Web 开发实用工具带中第二常用的工具,这是有充分理由的。这个非常容易使用的矢量和位图编辑程序非常适合润色或压缩图形,以及模拟设计和新项目的原型。

最初由 Macromedia 创建并拥有,后来于 2009 年被 Adobe 收购,这款面向各地网页设计师的神奇应用没有像 Adobe 的旗舰照片编辑程序 Photoshop 那样受到如此多的关注,但这没关系,因为它有许多精彩的功能,很容易使它成为一个应该包含在您的军火库中的程序。事实上,对于 web 开发人员和设计人员处理的许多任务来说,Fireworks 是更好的产品。

就像 Adobe Photoshop 一样,Fireworks 也布满了键盘快捷键,以满足高级用户的需求,同时保留了一个稀疏的 UI,其中包含易于查找的工具、选项和属性,使 Adobe Fireworks 的任何新用户都可以轻松地找到应用。

images

图 8–17。 运行在 Windows 7 电脑上的 Adobe Fireworks CS5

Adobe Fireworks 最酷的功能之一是能够在其特殊格式的 PNG 文件类型中创建页面,这将允许设计人员非常整洁地创建漂亮的布局,并传递给他们的本地开发人员。这些页面的工作原理是,每当您需要一个新页面时,它都会为您提供一个新的画布。您可以在 Fireworks 中按 F5 键轻松访问您的页面。

查看页面时,如图 8–18 所示,只需点击鼠标,即可选择您正在查看和处理的页面。一旦你进入了新选择的页面,你可以跳到 Layers 标签页,查看与当前选择的页面相关的所有层,而不是在一个画布中处理五个或更多页面的层。

images

**图 8–18。**Adobe Fireworks 页面面板的特写

当终于到了开始写代码的时候,保持你的设计更整洁会对你有所帮助。你必须花越少的时间在无尽的层中滚动,试图从你的模型中找到合适的资源用于你的网站,越好!

最重要的是,当要向客户展示你的原型时,给他们留下你整洁有序的印象总是一个好主意(如图 8–19 和 ?? 图 8–20),并且你将把你在模型和原型上花费的同样的关心和考虑用于构建他们的站点或应用。

images

图 8–19。 Adobe Fireworks CS5 在 Windows 7 机器上查看设计作品的第 1 页

images

图 8–20。 Adobe Fireworks CS5 在 Windows 7 机器上查看设计作品的第 1 页

总结

嘿,看那个!看起来我们又下了一章,你有了更多的知识和其他美味的珍闻在你的脑壳里浮动。现在,您已经有了一个更好的想法,知道如何让您的移动 web 应用长出爵士乐手并在房间里跳舞。

在这个世界上,我们的时装设计师甚至可以让无家可归者的服装看起来很时尚,无论你走到哪里,都有人在称赞他们的新设备有多“漂亮”,应用和网站越来越不可接受这种“新设计”的外观和气味。随着 Google+的发布和新 Google+主题在他们提供的无数产品中的推出,甚至谷歌也清理了其设计丑陋但功能强大的产品的行为!然而,正如我们到目前为止讨论的所有概念一样,权力越大,责任也就越大。通过以一致的方式使用这些工具,您可以生成一个视觉上令人惊叹且有用的应用!

九、基于位置的应用

移动开发者面临的最大机遇之一是手机和平板电脑等 Android 设备的本质所带来的丰富选择:它们是移动的!虽然传统桌面应用的用户位置可能很有趣,但很少有人会拿起一台台式机并整天带着它到处走。但是移动设备会移动,它们的位置(也就是设备用户的位置)会影响一切,从游戏到搜索产品和信息,从语言设置和设备行为等各种事物的细微差别和选项。

在这一章中,我们将探索 Android 设备的 HTML5、CSS 和 JavaScript 中的地理定位。我们将探索一个简单的支持位置的应用,并了解如何利用 HTML5 的特性和功能。所以,让我们开始行动吧!

地理定位的机制

为了让你的地理定位想法成为现实,了解所有移动部件的功能是很重要的(抱歉,我们忍不住开了这个玩笑)。在任何以 Android 为目标的地理定位 web 应用中,有两组功能非常重要。首先,给定的 Android 设备向任何基于 web 的定位应用提供了什么功能?第二,HTML5(以及在较小程度上,CSS3 和 JavaScript)的哪些特性提供了定位功能?最终,一个设备能告诉我们它的位置,一个 Web 应用能利用这些信息做什么,这两者的结合将会为你所能达到的目标设定界限。让我们来看看两者的背景。

了解设备功能

无论你有什么样的设备,它都可以依靠一系列令人惊讶的技术来提供基于位置的数据。即使对于缺乏专用硬件的设备,选项仍然存在。您使用的设备(以及我们将使用的示例)通常可以访问以下一项或多项功能:

  • 全球定位系统(GPS)—设备中接收和解码 GPS 卫星信号(或类似的竞争服务,如伽利略或 Glonass)的硬件。精度可以精确到 1 米,尽管这可能会因缓存和其他影响而降低。
  • WiFi 三角测量—您的设备使用 WiFi 进行网络连接,但它也可以结合使用已知的 WiFi 网络 SSID 名称以及 dBm 中的信号强度来进行三角测量。这往往比 GPS 粗糙,并且在建筑物较少的区域或有许多瞬时热点的区域受到影响。请注意,您的用户可能会因为功耗或节能原因而禁用 WiFi。
  • 蜂窝塔三角测量—与 WiFi 技术类似,您的设备知道它在任何给定时间注册了哪个蜂窝,也知道其他哪些蜂窝是可检测的(以及信号强度和相关定时信息)。即使附近只有一个手机信号发射塔,该信号发射塔服务区的粗略水平仍然可以知道您设备的位置。
  • IP 地址分配—虽然您的设备在连接到 WiFi 或移动网络时很可能会收到一个动态 IP 地址,但您的提供商通常拥有一组已知的 IP 地址块,以及在不同地理区域的已知分布模式。虽然这不会将你的设备定位到最近的米,但它可以很好地确定你是在堪萨斯州还是加德满都。

如您所见,有多种方法支持确定设备位置的实际机制。不管设备支持哪种方法,在编写 web 应用时,您都不必考虑选择给定的机制。您必须考虑的是地理定位应用的另外两个管理约束。

第一个这样的约束是用户对设备范围的位置特征的设置的选择。确切地说,用户是否启用或禁用了 GPS,就此而言,他们是否允许或禁止其他位置确定机制精确定位设备?图 9–1 显示了用户可以控制的正常设置

images

图 9–1。 安卓 4.0 中的安卓设备位置服务设置

这些设置的功能非常明显。关闭 GPS 卫星,它将无法用于任何服务,包括浏览器的位置相关功能。这就是我们第二个约束的来源。仅仅因为 HTML5 规范概述了地理定位的 API,并不意味着用户的浏览器已经实现了所有(或者任何!)的特性。

了解 HTML5 的功能

对于一个给定的设备来说,拥有一系列的定位功能当然很好,但是这些功能如何与您实际的 web 应用联系起来呢?答案来自 HTML5 对文档对象模型(DOM)所做的更改,它现在包含了一个 API 来支持一系列专门用于位置相关活动的 DOM 事件。这是一个相当重要的微妙之处。HTML5 本身并没有实现位置感知特性。相反,它包含了一个 API 规范,用于说明 web 浏览器和 web 服务器或应用如何处理位置数据。这影响到作为开发人员的您,因为您不仅需要考虑 HTML5 规范所说的地理定位活动应该发生什么,还需要考虑每个浏览器是如何实现来实际支持这些功能的。不要想得太远,一个例子是,如果一个 web 应用要求定位,那么一个给定的浏览器可能会使用什么样的第三方地理定位服务。

检测浏览器地理位置支持

浏览器中几乎所有地理定位活动的基础都是navigator.geolocation对象。我们可以使用这个基础作为一个简洁的简写来确定浏览器是否至少实现了 HTML5 地理定位规范的基础。清单 9–1 展示了我们的第一个代码示例:一个确定给定浏览器是否理解navigator.geolocation对象的基本 HTML 和 JavaScript 示例。

清单 9–1。 简单地理定位支持检查

`              Android Web Application Development–Geolocation example 1                        

`

您可以在文件ch09–example01.html中找到这段代码。只需将它置于您选择的 web 服务器的控制之下,然后将您的浏览器(桌面或移动)指向它以查看结果。

images **注:**这个例子,以及本章中的其他例子,通常应该从 web 服务器上运行,而不是直接从桌面或移动浏览器上作为文件打开。这是因为大多数当代浏览器阻止任何基于file://的 URL 访问地理位置 API。简而言之,任何访问一个navigator.geolocation对象、它的方法或者数据成员的尝试都会无声无息地失败。这主要是出于安全考虑,这本身就足以构成一个章节。选择一个你觉得合适的 web 服务器,比如 Apache 或者 Nginx,你就不会看到任何问题。

图 9–2 和 9–3 分别显示了在桌面浏览器和移动浏览器中运行我们的第一个示例时应该看到的结果。

images

图 9–2。 我们简单的桌面浏览器地理定位支持测试结果

images

图 9–3。 我们简单的移动浏览器地理定位支持测试结果

关于我们测试地理定位支持的第一段代码,有两点需要注意。首先,确定给定浏览器的支持真的很简单(尽管参见“什么可能出错?”本章稍后将对支持地理定位的设备实际上何时不合作进行更细致的分析)。第二是在桌面应用中使用 JavaScript alert函数大部分时间都很烦人,在小屏幕移动设备上就更烦人了。你们当中有经验的一般 web 开发人员会知道这一点,而对于新手来说,这是一个考虑将工作代码推送到一个功能中的好时机,这个功能可以方便地重用,而不会打扰好用户。清单 9–2 重新分解我们的检测示例,创建一个单独的geoSupport()函数,我们可以根据需要重用它。

清单 9–2。 简单地理定位支持检查修订版

`              Android Web Application Development–Geolocation example 2                                 

`

当然,您可以更进一步,将这个函数放到一个单独加载的 JavaScript 源文件中。如果你愿意,你可以这样做并修改本章后面的代码。

探索我们的示例应用

是时候介绍我们的示例应用了。除非你一直生活在岩石下,否则你可能知道随着移动设备的爆炸,流行的“签到”风格的应用的范围。无论是 Foursquare、Latitude、TripIt 还是其他类似的应用,这些都允许你向全世界宣布你的位置——大概是为了让你的粉丝和狗仔队可以跟踪你的著名动作。这些应用中有许多是作为给定设备的本机应用实现的,但是没有什么可以阻止我们开发与任何设备的浏览器中可用的 web 应用相同风格的应用。

我们的应用在ch09–example03.html中是完全独立的,您可以从您选择的 web 浏览器中运行它。让我们先从用户的角度来看看它(显然)做了什么。将我们的浏览器指向该页面,会弹出我们新的基于位置的签到风格应用的非常简单但实用的主页,您可以在图 9–4 中看到。

images

图 9–4。 调用地理定位之前的示例应用

目前,这还不完全是平面设计的杰作,但我们会做到这一点。现在,让我们看看当我们点击“地理位置登记”时会发生什么按钮。Figure 9–5 显示了我们的页面动态更新,看起来像是一组纬度和经度坐标。之所以看起来是这样,是因为这正是它的本来面目——而且它们正是来自这个测试进行的地方。

images

图 9–5。 由于我们的示例应用,我们的位置暴露了出来。

构建我们的基本地理定位应用

此时,您可能想知道到底需要多少逻辑和代码才能让这个看似简单的应用工作。让我们看一下清单 9–3,在这里我们将看到 ch09–example03.html 文件的完整代码集。

清单 9–3。 地理位置登记示例

`              Android Web Application Development–Geolocation example 3         

            function changeDiv (name, data) {                 var div = document.getElementById(name);                 if(div)                 {                 div.innerHTML = data;                 }             }

            function checkIn () {                 var geoData = "";                 if (supportsGeo()) {                     navigator.geolocation.getCurrentPosition(function(position) {         geoData = position.coords.latitude + ", " +                         position.coords.longitude;                     });                                      } else {                     geoData = "Your browser does not support HTML5 geolocation";                 }                 changeDiv ("myCheckIn",geoData);             }                        

        
                         

Ready to check in...

        

        

            <input type="button" name="checkInBtn" value="Geolocation Check-In!"              onClick="checkIn()">     

    

`

让我们把它分成四个部分,这样你就可以理解正在发生的事情,也可以自己修补和修改代码,这样你就可以在我们进行的过程中探索各种可能性。

地理定位支持测试,Redux

我们在本章前面介绍的supportsGeo()函数已经被调整为返回一个布尔值,所以我们可以在其他表达式中使用它的结果:

function supportsGeo () {     if (navigator.geolocation) {         **return true;**     } else {         return false;     } }

这比前一个返回字符串值的版本提供了更多的实用工具。

为动态页面更改创建实用函数

接下来,我们引入了一个简单的实用函数来控制代码中任何地方的<div>元素的动态 HTML 行为。changeDiv()函数接受一个<div>元素的名称和所需的文本更改,并执行必要的更改:

function changeDiv (name, data) {     var div = document.getElementById(name);     if(div)     {         div.innerHTML = data;     } }

这与地理定位并不严格相关,但是我们认为您会同意,通过从主逻辑中移除这些变化的机制,这将使我们的代码示例更加清晰。

复习基本的 HTML

在我们的例子中,我们的 HTML 代码几乎是无辜的旁观者。它提供了一个名为<div>的元素myCheckIn,我们的函数将在这个元素上施展它们的地理定位魔法。

`

    
                 

Ready to check in...

    

             <input type="button" name="checkInBtn" value="Geolocation Check-In!"          onClick="checkIn()">

`

表单和输入按钮调用我们的中心函数checkIn(),它将执行实际的地理定位工作。

钻研坐标

好了,别在边缘跳舞了!在我们的小例子中,checkIn()函数执行关键任务。

function checkIn () {     var geoData = "";     if (supportsGeo()) {         navigator.geolocation.getCurrentPosition(function(position) {             geoData = position.coords.latitude + ", " + position.coords.longitude;         });         <!-- alert("Confirm geolocation access before clicking OK"); -->     } else {         geoData = "Your browser does not support HTML5 geolocation";     }     changeDiv ("myCheckIn",geoData); }

geoData变量最终保存我们得到的纬度和经度。我们调用我们的supportsGeo()函数来确保我们的浏览器能够支持我们的意图。然后我们通过调用navigator.geolocation.getCurrentPosition()函数进入正题。这是 HTML 地理定位的核心功能之一,在http:// [dev.w3.org/geo/api/spec-source.html](http://dev.w3.org/geo/api/spec-source.html)有一页又一页的重载定义。

现在,您需要知道的是navigator.geolocation.getCurrentPosition()是一个异步函数,在这里使用的形式中,它传递一个回调函数,一旦浏览器和底层硬件对调用者当前位置的请求作出响应,就调用这个函数。我们的position回调为我们的geoData变量分配了两个数据成员:position.coords.latitudeposition.coords.longitude值,它们相当于调用者的纬度和经度。

剩下的就是我们调用实用程序changeDiv()函数来更新我们的 HTML 页面,瞧!当然,我们也确保不支持地理定位的浏览器有适当的信息,表明不支持我们的地理定位工作。

images **注意:**您可能会注意到我们有一个注释掉的对alert()的调用,要求用户在继续之前确认地理位置访问。如果你不允许网页自由询问你的位置(你没有理由这样做),那么当你的浏览器询问时,你需要确认访问。但是,即使是最快的人也无法在对navigator.geolocation.getCurrentPosition()的调用返回之前做到这一点,即使这是异步的。此时,回调将返回一个我们的代码当前没有捕捉到的错误(但请参阅本章后面的“可能出错的内容”一节),我们的示例将会无声地失败。在测试中取消对此警告的注释,以便更好地控制navigator.geolocation.getCurrentPosition()的异步行为。

应对安卓地理定位世界的四个角落

我们最初的示例应用介绍了几乎所有利用地理定位的 web 应用的一些关键构件。为了完善地理定位的世界,我们需要解决四个问题:我在哪里?,我是什么?还能出什么差错?,还有我要去哪里?

我在哪里?

我们在前面的例子中使用了position.coords.latitudeposition.coords.longitude数据成员,已经触及了确定设备位置的基本原理。您可能会想,除此之外,确定位置没有更多的事情,但是您应该考虑一些额外的数据点。

虽然纬度和经度可以告诉你是在夏威夷还是在喜马拉雅山,但你可能会同意在这两个地方你都关心另一个维度:海拔!HTML5 规范包括一个position.coords.altitude数据成员来提供高于名义海平面的高度。注意,对它的支持是不完整的,甚至 Android 模拟器和它的浏览器在 Android 开发工具(ADT)的许多版本中都不支持它。

除了纬度、经度和高度,你可能还会关心你得到的读数有多准确。我们在本章开始时介绍了四种可能的定位机制,每一种都有不同的精确度。两个额外的数据成员是可用的,position.coords.accuracyposition.coords.altitudeAccuracy,为所提供的任何地理位置数据提供误差容限。

有趣的是,没有直接的方法可以确定哪个定位机制被用来提供坐标。如果您询问另一个数据值position.coords.satellites,您可以推断它是 GPS,如果您的浏览器和设备支持,它将返回用于提供 GPS 定位的卫星数量。唯一的问题是,如果你的设备缺乏 GPS 支持,或者无法获得 GPS 定位,这两种情况都将返回 NULL。因此,对于是什么导致了卫星计数的缺失,你将处于一种模糊的状态。

我是什么?

我们已经在很大程度上讨论了回答*我是什么的最佳方式。*提问。HTML5 地理定位规范没有提供确定设备上存在什么特定硬件的详尽方法。处理这个问题的最好方法是使用supportsGeo()函数,或者类似的函数,我们在上一节中已经介绍过了。这就把我们的问题变成了:我是什么?设备和浏览器组合是否支持地理定位?

有什么可能出错?

想象一下,你编写了一个很棒的签到应用,来处理 Foursquare 和 company 这样的公司。你的用户是快乐的,世界是美好的。不会出什么差错的,对吧?或许我们很快会联系上。

在任何设备上,在任何时间,您都会发现自己不得不处理一系列问题,这些问题会阻止您的地理位置代码返回准确的值,甚至根本不返回任何值。这可能是天气、太阳和行星的排列(实际上,这并不像你想象的那样是个笑话),甚至是用户干预。不管是什么原因,都有明确定义的错误状态,您应该在代码中处理它们。

在 HTML5 规范下可能出现的四种主要错误情况是:

  • 超时:在允许的时间内没有定位机制响应定位数据。
  • POSITION_UNAVAILABLE :您的设备有一个或多个有效的定位机制(如 GPS 或 WiFi),但没有一个能提供有效的位置数据。
  • PERMISSION_DENIED :由于权限的原因,浏览器被阻止访问 HTML5 地理定位 API。用户在提示时阻止了访问,或者浏览器被明确配置为阻止访问。
  • 未知 _ 错误:由于未知原因,位置数据不可用。

知道错误条件是什么当然很好,但是如何利用它们呢?你会记得我们说过getCurrentPosition()函数有无数的重载版本。其中一个重要的子集包括一个常见的模式,接受一个用于报告位置的回调函数作为第一个参数,接受一个用于错误处理的回调函数作为第二个参数。看看我们的示例应用的下一个迭代,在文件 ch09–example03.html 中。关键的变化是我们的checkIn() JavaScript 函数,现在看起来像这样。

function checkIn () {     var geoData = "";     if (supportsGeo()) {         navigator.geolocation.getCurrentPosition(             function(position) {                 geoData = position.coords.latitude + ", " + position.coords.longitude; **              },** **            function(error) {** **                switch(error.code) {** **                    case error.TIMEOUT:** **                        alert ('Geolocation returned a timeout error');** **                        break;** **                    case error.POSITION_UNAVAILABLE:** **                        alert ('Geolocation returned a position unavailable error');** **                        break;** **                    case error.PERMISSION_DENIED:** **                        alert ('Geolocation returned permission denied (did you deny access?)');** **                        break;** **                    case error.UNKNOWN_ERROR:** **                        alert ('Geolocation encountered an unknown error');** **                        break;** **                }** **            }**         );         <! -- alert("Confirm geolocation access before clicking OK"); -->     } else {         geoData = "Your browser does not support HTML5 geolocation";     }     changeDiv ("myCheckIn",geoData); }

在粗体文本中,您将看到我们已经更改了getCurrentPosition()函数的方法签名(重载版本),以注册我们的错误回调,并在返回任何错误条件时提醒用户。我们处于一个幸运的位置(好吧,我们在这里是在讽刺),Android 模拟器在响应地理定位请求时是出了名的慢和不稳定。出于这个原因,在真实设备上测试您的代码,以及在模拟器上测试,总是一个好的做法。在我们的第一次尝试中,作者没有真正尝试就设法触发了一个POSITION_UNAVAILABLE错误。您可以在图 9–6 中看到结果。

images

图 9–6。 地理定位尝试返回 POSITION_UNAVAILABLE 错误

正如我们对supportsGeo()函数所做的那样,您应该将这种错误检查方式转变成一种实用函数,在整个代码中使用,而不是用警告式的错误轰炸用户——尽管在这种情况下,这可能是有保证的。

我要去哪里?

为了完善我们正在探索的功能,我们可以问自己——或者更准确地说是设备——*我要去哪里?*有两种方法值得一提。首先,我们可信的地理位置数据成员包括两个更有用的值,position.coords.speedposition.coords.heading。这些指示设备的最后已知速度和最后已知方向。这些值对于像在地图上绘制运动这样的任务来说听起来很棒,理论上,这正是它们的设计目的。然而,实际上,令人不安的是,大量设备要么不报告这些值的数据,要么使用如此简单的 GPS 芯片组,以至于一开始就不支持收集数据。

这给我们带来了另一种方法。到目前为止,我们一直在展示navigator.geolocationgetCurrentPosition()方法。这个方法有一个对等方法,在确定随时间变化的位置以及随之发生的运动时,这个对等方法稍微复杂一些,也更有用。这就是watchPosition()法。

在各种重载形式中,watchPosition()方法的行为与我们的getCurrentPosition()方法非常相似。它注册了一个回调函数用于正常定位,另一个回调函数用于错误处理,但它也接受一系列选项,包括一个定时器选项,说明它应该多长时间休眠和唤醒一次,以及继续调用您的回调。watchPosition()将继续运行,直到被对clearWatch()的调用停止,或者其父进程终止。清单 9–4 展示了我们正在进行的示例应用的下一次迭代,来自 ch09–example05.html。

清单 9–4。 地理位置追踪随时间(和运动!)

`              Android Web Application Development–Geolocation example 5         

            function changeDiv (name, data) {                 var div = document.getElementById(name);                 if(div)                 {                     div.innerHTML = data;                 }             }

**            function startWatch () {** **                var geoData = "";** **                var watchOptions = { frequency: 5000 };** **                if (supportsGeo()) {                     geoData = navigator.geolocation.watchPosition(onWatchSuccess,** **                                   onWatchError, watchOptions);** **                } else {** **                                   geoData = "Your browser does not support HTML5 geolocation";** **                    changeDiv ("myCheckIn",geoData);** **                }** **            }**

**            function onWatchSuccess(position) {**                 var currentLocation = ""; **                currentLocation = 'Latitude: ' + position.coords.latitude + ', Longitude: ' +** **                                     position.coords.longitude;**                 changeDiv("myLocationWatch",currentLocation); **            }**

**            function onWatchError(error) {** **                var currentError = "";** **                currentError = 'Error code: ' + error.code + ', Error message: ' + error.message;** **                changeDiv("myLocationWatch",currentError);** **            }**                        

        
                         

Ready to check in...

        

                     <input type="button" name="watchBtn" value="Start Watching"              onClick="startWatch()">     

    

`

我们的实用函数supportsGeo()changeDiv()是相同的,HTML 布局只是表面上的改变。关键是按钮现在调用我们的startWatch()函数,它本身最终调用watchPosition()。我们将它传递给常规回调函数和错误回调函数——onWatchSuccess()onWatchError()——以及一个选项构造。在这种情况下,我们的选项如下所示

var watchOptions = { frequency: 5000 };

这有效地用于将 5 秒刷新频率传递给watchPosition()功能。如果您自己运行代码,并选择您看到的开始监视按钮,您的浏览器应该大约每五秒钟更新一次您的位置。这在静态屏幕截图中有点难以展示,所以您必须自己尝试一下。如果你不是真的在移动,不要担心:你仍然应该看到坐标的更新,因为每个读数的精度会略有不同,这取决于用于定位的 GPS 卫星的数量,变化的本地干扰等。即使您正躺在沙发上休息,您也应该看到最低有效位的微小变化!

用地图拓展你的视野

到目前为止,您几乎肯定会同意,我们的示例应用并不完全是一件艺术品。因此,让我们添加一点活力,并介绍几乎无处不在的地理定位附件:地图!

向我们的应用添加地图

我们最新的例子来自 file ch09–example06.html,它使用谷歌地图将我们之前的签到例子提升到一个新的水平。它不只是报告数字坐标,而是将数字坐标与 Google Maps APIhosted at [maps.googleapis.com/](http://maps.googleapis.com/)一起使用来加载地图,并在地图上放置一个标记来突出显示您的位置。清单 9–5 是我们修改后的 JavaScript、CSS 和 HTML5。

清单 9–5。 地理定位和地图协同工作

`              Android Web Application Development–Geolocation example 6                               html { height: 100% }             body { height: 100%; margin: 0; padding: 0 }             #mapContainer { height: 100% }                           

            function changeDiv (name, data) {                 var div = document.getElementById(name);                 if(div)                 {                     div.innerHTML = data;                 }             }

            function mapMe(thisLat, thisLong) {                 var myLatlong = new google.maps.LatLng(thisLat, thisLong);                 var myOptions = {                     center: new google.maps.LatLng(thisLat, thisLong),                     zoom: 12,                     mapTypeId: google.maps.MapTypeId.ROADMAP                 };                 var map = new google.maps.Map(document.getElementById("mapContainer"),                                                myOptions);                 var marker = new google.maps.Marker({                              position: myLatlong,                              map: map,                              title:"Hello World!"                 });             }

            function initialize() {                 if (supportsGeo()) {                     myLatitude = "";                     myLongitude = "";                     navigator.geolocation.getCurrentPosition(                         function(position) {                             myLatitude = position.coords.latitude;                             myLongitude = position.coords.longitude;                         },                         function(error) {                             switch(error.code) {                                 case error.TIMEOUT:                                     alert ('Geolocation returned a timeout error');                                     break;                                 case error.POSITION_UNAVAILABLE:                                     alert ('Geolocation returned a position unavailable                                            error');                                     break;                                 case error.PERMISSION_DENIED:                                     alert ('Geolocation returned permission denied (did                                            you deny access?)');                                     break;                                 case error.UNKNOWN_ERROR:                                     alert ('Geolocation encountered an unknown error');                                     break;                             }                         }                     );                     alert("Confirm geolocation access before clicking OK");                 }                 mapMe(myLatitude, myLongitude);             }                        

             
                     
    

`

您会注意到,我们的代码以一些 CSS 开始,以非常简单的 HTML 结束,它在页面加载后立即调用我们新的initialize()方法。CSS 是 Google 的样板文件,允许 div 充当地图对象的容器。接下来,您将看到对 Google Maps API 的 JavaScript 引用(下面一行修改了格式以更好地适应此页面):

<script type="text/javascript"   src="http://maps.googleapis.com/maps/api/js?     key=AIzaSyBpoxnQbCGPTIcpIJ8YPO3pTNcJX3zbc4c&sensor=false"> </script>

这将调用带有特定 API 键的 Maps API。这是 Google 的技术,用于唯一地识别其 API 的用户,并限制对各种 API 的调用次数。你一定要注册自己的 API 密匙,这样你就可以控制自己的 API 访问,监控使用情况等等。

images **提示:**在印刷的时候,免费的谷歌地图 API 被限制在每天 25000 次调用。要获得自己的地图 API 密钥,请访问developer.google.com/并按照说明进行操作。

我们的initialize()函数与旧的checkIn()函数非常相似,所以我们在这里不再赘述。关键的区别是它填充了纬度和经度两个局部变量,然后调用我们的新函数mapMe(),来完成所有艰难的映射工作。

function mapMe(thisLat, thisLong) {     var myLatlong = new google.maps.LatLng(thisLat, thisLong);     var myOptions = {         center: new google.maps.LatLng(thisLat, thisLong),         zoom: 12,         mapTypeId: google.maps.MapTypeId.ROADMAP     };     var map = new google.maps.Map(document.getElementById("mapContainer"), myOptions);     var marker = new google.maps.Marker({                     position: myLatlong,                     map: map,                     title:"Hello World!"     }); }

mapMe()中,我们完成了四个主要动作来展示我们的地图和位置标记。首先,我们使用从 HTML5 地理定位调用中收到的纬度和经度创建一个google.maps.LatLng对象。接下来,我们构建一个名为myOptions的选项对象,它由一个中心对象组成,用于选择浏览器中显示的地图的中心。zoom值决定了地图中的起始缩放设置——这在移动设备上非常重要,因为默认缩放的比例太高,让用户使用不精确的捏和缩放技术放大几次。

然后,我们得到我们的映射的肉。我们创建一个新的google.maps.Map对象,并传递将保存地图的元素(“mapContainer是我们的 HTML 中的<div>的 ID),以及我们构造的选项对象。这实际上在用户的浏览器中创建和显示了地图。创建之后,我们在地图上的相同坐标处创建一个google.maps.Marker对象,并给它一个有意义的标签。图 9–7 中的结果不言自明。

images

图 9–7。 我们的 HTML 地理定位应用适应使用谷歌地图

其他基于位置的服务

我们已经了解了一种流行的地图服务,但是值得考虑将您的视野扩展到基本地理定位服务的其他提供商,以及地图切片(实际显示的图形“切片”)之类的提供商。

有几个原因可以让您考虑两面下注,至少确保您可以对其他服务执行相同的操作:

  • 免费服务、规模和成本:许多服务,如谷歌、雅虎、必应、MapQuest、诺基亚/Ovi 等。提供某种程度的免费服务。通常的条款规定,在被限制和/或被要求付费之前,你和你的用户每天可以消费一定数量的地图展示。
  • 不同的优势和劣势:一些服务,如谷歌,提供一些有用的东西,如一些地点的方向,公共交通的细节等。但在其他地方,像 Open Street Map 这样的服务提供了更详细、更新的数据,对于 OpenCycleMap 这样的服务,整个地图渲染和数据都专注于专业用途:在这种情况下,就是骑自行车的人。
  • 抽象您的映射意味着您可以混合和匹配以满足您的需求。

提取地图数据和影像提供者的最重要原因可能是该领域正在快速发展和变化,良好的软件工程应该引导您尽可能将所有其他代码与这种易变的依赖关系隔离开来。这种不稳定性也可能是由提供者自己进行版本升级和更改的结果。

以下是一些有用的数据提供程序、地图切片提供程序、一体化服务,甚至是现成的制图抽象 JavaScript 库,您可能也想和通常的怀疑一起考虑:

  • 开放街道地图(openstreetmap . org)-最初的开放数据提供商之一,现在也在众包基础地理位置数据的基础上提供其他服务
  • cloud made(cloud made . com)-用于开放街道地图数据的地图切片提供程序。也是传单的提供者,一个开源的瓦片集合服务。
  • map Nik(map Nik . org)-其他工具中常见的地图渲染器(包括此处列出的一些其他工具)。
  • carto db(carto db . com)——一个基于云的地理空间数据库
  • maps action(maps action . com)-一个现成的 JavaScript 库,提供地图服务和数据提供者的抽象。
  • wax(map box . com/wax)——另一个现成的抽象层。来自同一家公司,TileMill 为瓦代。

这份清单可能会占满几页,但你会明白其中的意思。更令人兴奋的是,我们意识到,每周都有数十家这样的公司和服务提供商涌现出来。

游戏您的位置

即使你正在开发一个移动应用,也不要忘记你可以使用的工具来帮助测试和改进你的地理定位代码。例如,Eclipse 的 Android 开发人员工具插件中的 DDMS 为您提供了为仿真器设备设置任意坐标/位置数据的能力。当模拟器中有一个正在运行的项目时,您可以切换到 DDMS 模式,并打开模拟器控制台选项卡。您将看到设置原始坐标或直接提供 GMX 或 KML 数据的能力,如图 Figure 9–8 所示,您的仿真器浏览器可以使用这些数据。

images

图 9–8。 使用 ADT 和 Eclipse 在 Android 模拟器中设置坐标

如果运行 Eclipse 不是您的首选开发方式,您还可以使用 Android 的 adb 工具发出geo fix命令来提供纬度、经度和高度。最后,如果你在没有连接到电脑的设备上调试应用,你可以使用免费的应用 Fake GPS location ( [market.android.com/details?id=com.lexa.fakegps](https://market.android.com/details?id=com.lexa.fakegps))来设置你的位置。

畅游您的位置——尽情享受!

探索 web 应用地理定位功能的一个很好的方法是制作一个游戏——真的!使用上一节中的简单构建块,您可以构建游戏和活动,包括寻宝、定向活动,甚至驾驶、赛车和航海游戏。你会发现将我们的startWatch()示例与我们的谷歌地图示例结合起来非常简单,可以创建一个“地图轨迹”风格的应用,在地图上实时绘制你的运动,并且根据游戏或比赛,还可以显示其他玩家的运动和位置,甚至整个其他游戏正在哪里进行。

在流行的“地理藏宝”运动中,可以找到地理定位功能的一个有趣的应用,比如我们在这里展示的那些。通过您喜欢的搜索引擎进行搜索以获得更多详细信息,或者继续阅读第十章并使用您自己的搜索界面进行搜索!

天空真的是无限的(事实上,你的地理定位数据也可以在你最喜欢的热气球上工作!).

总结

在这一章中,我们探索了作为移动 Web 开发者的你可以使用的基于位置的特性。我们的示例应用演示了如何使用许多核心地理定位功能,包括您在哪里,您的用户设备可能具有哪些功能,以及如何处理不断变化的位置。在第十章中,我们将探索更多关于地理定位应用开发的内容,也将涵盖其他在线服务和 API。

十、使用云服务:一个运输应用

掌握地理定位的基础知识是很好的,正如我们在第九章学到的那样,我们可以享受使用坐标的乐趣。一旦我们知道我们在哪里,以及有什么其他服务可以将我们的位置信息与其他数据混合,一个巨大的可能性就出现了。在这一章中,我们将利用我们的地理定位技术,并结合一些本地搜索功能,来构建一个个性化的公交应用。

我们的示例交通应用将找到您所在的位置,然后搜索离您最近的交通选项,无论是公共汽车站、地铁或火车站,甚至是机场。有了这些基础,您就可以将我们的 transit 应用扩展到几乎任何搜索加定位问题。

作为一名开发人员,在将基于 web 或基于云的服务与您自己的应用相结合时,您有许多选择来决定需要做多少工作。您可以选择使用公开可用的数据源构建自己的服务,使用完整的服务,或者构建混合服务。使用完整的服务在这一章中没有什么可谈的,所以我们选择了一种混合的方法,在这种方法中,我们将使用我们的地理定位技术来使用标准的搜索服务来提供有用的交通信息。

在我们深入研究代码、示例和解释之前,先花一分钟考虑一下您可能已经使用过,但可能没有积极思考过的各种构件。当构建任何类型的交通或运输应用时,我们希望将知识和数据结合起来,然后向用户提供一些有用的选项。这些将包括:

  • 我是谁?在某种程度上,将“谁”在使用您的应用看作是实际用户、他们正在使用的设备及其功能的组合是很有用的。我们将重用我们在第九章中提到的检测代码来确定一个设备是否支持地理定位。
  • **我在哪里?**从第九章中,我们了解了如何通过用户 Android 设备和浏览器的地理定位功能来确定用户的位置。我们将从这一基本出发点出发,推动其他选择。
  • 我想去哪里?这永远是一个微妙的问题。应用用户心中是否有一个位置,或者一种交通方式,或者实际上对如何在点与点之间旅行有任何偏好。这里要记住的两个关键点是,不要过早地限制应用中可用的选项,当您限制选项时,您是故意这样做的,并且将用户带在身边。例如,让他们知道其他路线或交通方式是可行的。
  • 在线服务对我和我的位置有什么了解?这是一个很大的领域,涵盖了许多通用服务。给定一个位置,以及某种期望的旅程或目的地,考虑一下作为开发人员您可以获得哪些帮助,这样您就不需要自己做所有的工作。例如,Google、Yahoo 和 Microsoft 等公司的通用 web 搜索 API 可以处理多种类型的公交搜索,包括查找公交站点、测量距离和计算公交时间。使用这些工具,这样你就不必亲自动手了(当然,除非你的目标是在那个领域竞争)。
  • 哪些专业服务知道我的位置?要考虑的最大领域,但不要被吓倒。专门的 API 和服务存在于令人眼花缭乱的一系列交通和运输类型中。世界上有大量的公共交通机构提供调度和路线数据,我们将在本章后面讨论。然而,许多还提供了一个直接的 API 来回答下一个服务、最近的服务等等的查询。聚合器的存在是为了提供泛提供商服务。一个例子是 Kayak 的航空运输搜索。通过思考(并询问)用户为什么想要进行一次特定的旅行,您可以进一步使用专门的 API。如果是去看电影,在餐馆吃饭,或者参加一些其他活动,像 Yelp、猫途鹰和其他服务提供 API 来搜索特定的地点和活动。作为应用开发人员,您可以创建有用的应用,将所有这些结合起来,以针对您的用户。
  • 根据这些知识,我能得出什么有用的结论?利用您的应用收集的关于用户在哪里、他们想去哪里以及当他们到达那里时想做什么的数据,您开始描绘您的应用的流程和特性。专注于使用前面段落中描述的构建块,而不是重新发明它们,你将构建出引人注目的突出应用。例如,假设您知道用户想乘船从曼哈顿到布鲁克林去参观一家特定的餐馆。你可以将体验提升到一个新的水平,使用 OpenTable 在线服务来检查可用性,并注意到用户的餐厅已经预订一空,而是建议在哈德逊河上进行晚餐巡航。你让他们省去了一次令人失望的旅行,反而给了他们一次美好的经历。

因此,现在我们有了关于我们想要构建什么,以及为什么用户想要使用这样一个应用的大图。我们开始吧!

介绍“移动我”示例应用

我们的示例应用名为“移动我”我们将使用它来探索如何从任何位置找到附近的交通选项。当然,我们将使用我们在第九章中提到的技术来探测我们的位置。也就是说,我们将使用 Android 设备的地理定位功能来确定我们的纬度和经度,然后使用这些数据来驱动搜索服务,以找到我们首选的交通方式。

检查代码

清单 10–1 显示了第一版 Move Me 的代码。不要被它的长度吓倒:我们将对它进行分解,涵盖各个组件,让您了解它的工作方式,并启发您进一步使用这个示例,并将其扩展到您自己的想法。

**清单 10–1。**Move Me 应用源代码,第 1 版

`

             Move Me! - Transit Search Example                                    

            var resultMarker = new google.maps.MarkerImage(                 "Red_Train_Marker.png",                 new google.maps.Size(20, 34),                 new google.maps.Point(0, 0),                 new google.maps.Point(10, 34)); var youMarker = new google.maps.MarkerImage(                 "You_Marker.png",                 new google.maps.Size(20, 34),                 new google.maps.Point(0, 0),                 new google.maps.Point(10, 34));

            function supportsGeo () {                 if (navigator.geolocation) {                     return true;                 } else {                     return false;                 }             }

            function changeDiv (name, data) {                 var div = document.getElementById(name);                 if(div)                 {                     div.innerHTML = data;                 }             }

            function getLocation() {                 if (supportsGeo()) {                     myLatitude = "";                     myLongitude = "";                     navigator.geolocation.getCurrentPosition(                     function(position) {                         myLatitude = position.coords.latitude;                         myLongitude = position.coords.longitude;                     },                     function(error) {                         switch(error.code) {                             case error.TIMEOUT:                                 alert ('Geolocation returned a timeout error');                                 break;                             case error.POSITION_UNAVAILABLE:                                 alert ('Geolocation returned a position unavailable                                        error');                                 break;                             case error.PERMISSION_DENIED:                                 alert ('Geolocation returned permission denied (did you deny access?)');                                 break;                             case error.UNKNOWN_ERROR:                                 alert ('Geolocation encountered an unknown error');                                 break;                             }                         }                     );                     alert("Confirm geolocation access before clicking OK");                 }                 myLatLong = new google.maps.LatLng(myLatitude, myLongitude);             }

            function prepareMap() { // Union Square test                 myLatLong = new google.maps.LatLng(37.788056, -122.4075);                 //getLocation();

                myTransitMap = new google.maps.Map(document.getElementById("map"), {                     center: myLatLong,                     zoom: 14,                     mapTypeId: google.maps.MapTypeId.ROADMAP                 });

                var yourMarker = new google.maps.Marker({                     position: myLatLong,                     map: myTransitMap,                     icon: youMarker,                     title:"You!"                 });

                myLocalSearch = new GlocalSearch();                 myLocalSearch.setSearchCompleteCallback(null,                                                         processLocalSearchResults);             }

            function execSearch() {                 var searchText = document.getElementById("searchtext").value;                 myLocalSearch.execute(searchText);             }

            function processLocalSearchResults() {                 for (var i = 0; i < myLocalSearch.results.length; i++) {                     searchResults.push(new LocalResult(myLocalSearch.results[i]));                 }             }

            // Google's common example LocalResult object             function LocalResult(result) {                 var me = this;                 me.result_ = result;                 me.resultNode_ = me.node();                 me.marker_ = me.marker();                 document.getElementById("resultlist").appendChild(me.resultNode_);             }

            LocalResult.prototype.node = function() {                 if (this.resultNode_) return this.resultNode_;                 return this.html();             };

            LocalResult.prototype.marker = function() {                 var me = this;                 if (me.marker_) return me.marker_;                 var marker = me.marker_ = new google.maps.Marker({                     position: new google.maps.LatLng(parseFloat(me.result_.lat),                                            parseFloat(me.result_.lng)),                     icon: resultMarker,                     map: myTransitMap});                 return marker;             };             LocalResult.prototype.html = function() {                 var me = this;                 var container = document.createElement("div");                 container.className = "unselected";                 container.appendChild(me.result_.html.cloneNode(true));                 return container;             }

            GSearch.setOnLoadCallback(prepareMap);                            

Move Me! Transit Search Example

        
                                      
                
                
            
            
        
    

`

首先,让我们把简单的事情处理掉。在第九章的中,你应该已经从我们的地理定位介绍中认出了几个 JavaScript 函数。我们的老朋友supportsGeo()changeDiv()都在,分别执行相同的地理位置支持检测功能和<div>内容改变功能。

我们的示例代码稍微改变了我们调用一些我们正在使用的公共可用服务的方式。在这种情况下,因为我们已经选择使用 Google 的地图 API 和它的搜索 API,我们已经重构了加载 Google 的 JavaScript 库,首先加载基本框架,如下所示

<script     src="http://www.google.com/jsapi?key=AIzaSyBU-TWQkYc-ynkeYIrd_aP0UFdyRieCyR0"     type="text/javascript"> </script>

然后,在我们自己的 JavaScript 中,我们使用 Google 的google.load()函数来额外加载地图 API 和搜索 API。如果您选择另一个服务或一组服务,比如 Bing、Yahoo、Baidu 或其他服务,那么加载 API 将会改变,以匹配这些服务的适当调用。对于我们的例子,代码非常简单:

google.load('maps' , '3', {"other_params":"sensor=false"}); google.load('search', '1');

images **注意:**如果你对谷歌地图第二版 API 更熟悉的话,你可以恢复使用它,但当你使用依赖该版本 API 的网站时,你会看到反复的警告,说它已被否决。

处理全局状态

接下来,我们设置一些全局状态变量,让您很好地了解我们要做什么来使我们的 transit 应用工作。

var myLatLong; var myTransitMap; var myLocalSearch; var searchResults = [];

变量名有望描述它们的用途,但是如果有任何疑问,我们将使用myLatLong来保存用户的坐标;myTransitMap表示用户位置的地图,最终还将包括附近的交通选项;myLocalSearch是搜索对象,它将包含我们查找相关交通选项的标准;最后,searchResults数组将保存从我们的myLocalSearch搜索返回的结果。

定制位置标记

如果你曾经使用过在线地图服务(任何种类的),你可能对用于精确定位的虚拟“地图图钉”很熟悉。在我们的例子中,当我们使用谷歌地图时,我们使用google.maps.Marker对象来表示地图上的特定位置。我们没有使用 Google Maps 默认自带的普通红色地图标记图标,而是为 Move Me 应用制作了一些自定义标记。我们定制的google.map.MarkerImage图形的代码如下。

var resultMarker = new google.maps.MarkerImage(     "Red_Train_Marker.png",     new google.maps.Size(20, 34),     new google.maps.Point(0, 0),     new google.maps.Point(10, 34)); var youMarker = new google.maps.MarkerImage(     "You_Marker.png",     new google.maps.Size(20, 34),     new google.maps.Point(0, 0),     new google.maps.Point(10, 34));

参考的两个.png图像文件包含在本书的源代码中。第一个是Red_Train_Marker.png,是谷歌地图使用的普通红色大头针,加上字母 T,代表过境或运输。You_Marker.png地图图钉是一个蓝色的标记,上面有“你”字。我们已经为地图图钉指定了正常大小,以帮助用户点击标记来查看“信息气泡”以了解给定点的更多详细信息。这使得小手机屏幕上的 pin 看起来有些大,但保持正常大小是很重要的,以便用户在按下屏幕时不太可能错过触摸 pin。

您会注意到两个MarkerImage对象都有两个对google.map.Point()的调用。第一个设置图像的原点,用作覆盖图。在我们前面的例子中,我们没有改变默认设置。对google.map.Point()的第二次调用为图像设置锚。默认情况下,当一个 google.map.Marker覆盖在地图上时,任何关联的MarkerImage都会在定位点的图像左上方呈现。我们实际上希望大头针图像的尖端看起来直观地指向我们感兴趣的位置,所以我们希望MarkerImage的中间、底部边缘与我们的位置结果对齐。为了实现这一点,您会看到我们将锚点偏移了大头针图像宽度的一半(10 像素),大头针图像的完整高度为 34 像素。

准备我们的地图

做好这些准备后,我们开始使用驱动 transit 应用的主要函数,prepareMap()函数。

`function prepareMap() {     getLocation();

    myTransitMap = new google.maps.Map(document.getElementById("map"), {         center: myLatLong,         zoom: 14,         mapTypeId: google.maps.MapTypeId.ROADMAP     });

    var yourMarker = new google.maps.Marker({         position: myLatLong,         map: myTransitMap,         icon: youMarker,         title:"You!"     });

    myLocalSearch = new GlocalSearch();     myLocalSearch.setSearchCompleteCallback(null, processLocalSearchResults); }`

prepareMap()功能很容易理解,分为四个主要动作。首先,我们调用我们的getLocation()函数,它主要是我们在第九章的清单 9-5 中的initialize()函数中展示的代码的重新标记版本。为了完整起见,这里再次说明:

function getLocation() {     if (supportsGeo()) {         myLatitude = "";         myLongitude = "";         navigator.geolocation.getCurrentPosition(             function(position) {                 myLatitude = position.coords.latitude;                 myLongitude = position.coords.longitude;             },             function(error) {                 switch(error.code) {                     case error.TIMEOUT:                         alert ('Geolocation returned a timeout error');                         break;                     case error.POSITION_UNAVAILABLE:                         alert ('Geolocation returned a position unavailable error');                         break;                     case error.PERMISSION_DENIED:                         alert ('Geolocation returned permission denied (did you deny access?)');                         break;                     case error.UNKNOWN_ERROR:                         alert ('Geolocation encountered an unknown error');                         break;                 }             }         );     }     myLatLong = new google.maps.LatLng(myLatitude, myLongitude); }

如前一章所述,它使用浏览器的navigator.geolocation对象来确定您的纬度和经度。为了节省空间,我们将省略重复几乎相同的代码。

然后我们创建一个名为myTransitMap的新的google.maps.Map对象,将地图的中心放在我们检测到的位置上。我们将缩放级别设置为 14,因为这对于步行的人来说是一个很好的比例,尽管您可以将其更改为任何所需的缩放设置。我们也选择标准的道路地图风格的地图,而不是卫星视图。

提示:这里有一些缩放级别设置的便利值,在为用户渲染地图时要记住。

缩放级别 12: 适合公路级别驾驶,显示几十英里或几公里以上的主要路线。

缩放级别 14: 适用于市内驾驶,距离超过几十码或几米时的一般步行方向

缩放级别 15: 最适合详细的行走,显示建筑物轮廓,以及显示单行道和其他障碍物的驾驶方向。

在我们的myTransitMap对象上,我们在为手机或设备检测到的相同纬度和经度处放置一个标记。我们使用自定义的蓝色“你”标记,如前所述,甚至提供一些悬停文本来强调这个大头针将你放在地图上。

最后,我们构造一个新的本地搜索对象myLocalSearch,然后注册回调函数processLocalSearchResults()。当这个搜索对象完成搜索执行时,将调用这个函数。回调的机制将在本章的稍后部分展示。随着prepareMap()功能的完成,我们实际上可以看到当用户最初在他们的设备上运行我们的应用时是什么样子,如图 Figure 10–1 所示。

images

图 10–1。【the Move Me 示例应用的初始屏幕

我们对开始的例子做了一些改动,将我们在 Android 模拟器中的坐标设置为一个相当著名的位置:旧金山的联合广场。这并不像听起来那么做作。想象自己是旧金山的游客。你已经看到了这个城市的风景和声音,发现自己在联合广场,想找一辆火车,公共汽车,或渡轮到您的酒店或其他目的地。这就是我们示例应用中其余功能发挥作用的地方。

执行本地交通搜索

我们示例中接下来的两个函数执行我们选择的本地搜索,并处理结果。

function execSearch() {     var searchText = document.getElementById("searchtext").value;     myLocalSearch.execute(searchText); }

execSearch()函数只是读取 HTML 表单上searchtext字段的内容,见 Figure 10–1 的顶部,并用该文本调用myLocalSearch()对象上的execute方法。我们将processLocalSearchResults()方法注册为当myLocalSearch()报告执行完成时的回调函数。

function processLocalSearchResults() {     for (var i = 0; i < myLocalSearch.results.length; i++) {         searchResults.push(new LocalResult(myLocalSearch.results[i]));   } }

processLocalSearchResults()函数遍历我们的结果,实例化一个LocalResult对象,并将它们添加到searchResults数组中。LocalResult对象是 Google 在它的许多 API 示例中使用的常见示例对象,我们在这里复制了它。我们使用它主要是为了方便我们访问本地搜索结果的坐标。它做的工作与我们之前的代码类似,因为它在我们的地图上放置了一个标记,并且它还创建了一个文本结果列表。在我们当前的例子中,我们已经创建了 CSS 来隐藏文本列表,但是我们将很快介绍这方面的选项。

最后,就我们的 JavaScript 而言,我们将prepareMap()函数注册为回调函数,以便在 Google Search API 完成加载后调用。

GSearch.setOnLoadCallback(prepareMap);

剩下的就是实际的 HTML 代码,它控制我们的地图、搜索字段和查找按钮的大小,以及相关的<div>名称和嵌套。我们特意为整个显示选择了明确的像素大小,以及其中包含的地图对象,以适应手机上的垂直显示和平板电脑上的水平显示,同时还显示我们将在本章稍后介绍的文本结果。

`     

Move Me! Transit Search Example

    
                          
            
            
         
         
         
    

`

我们的 HTML 显示了您在 Figure 10–1 中看到的简单表单。

运行我们的代码

那么,当我们实际搜索交通工具时会发生什么呢?到目前为止,在我们的示例中,我们的候选用户是一名游客,他发现自己在联合广场,正在寻找最近的交通工具。如果你发现自己在旧金山,你可能会想到使用湾区交通系统,或 BART。所以让我们以此为标准来显示当我们调用execSearch()时实际发生了什么。图 10–2 显示了通过本地搜索调用返回的结果,使用LocalResult对象绘制在地图上。

images

图 10–2。 我们的 Move Me 应用显示附近的交通选项

瞧,我们找到了四个最近的地铁站,从市中心到恩巴卡德罗。您还可以看到我们在本章开始时描述的自定义 transit MarkerImageMarker对象的最终渲染。

也许,作为一名游客,你真的是在寻找旧金山湾的美景,还有什么比找到最近的渡轮码头来寻找世界上最伟大的水道之一更好的方式呢?图 10–3 显示了我们的应用查找最近的渡轮码头。

images

图 10–3。 找轮渡运输带搬我

我们又一次找到了我们想要的运输方式。在这种情况下,您会注意到几个标记重叠在一个位置上。这是谷歌的搜索,返回了几个不同的渡轮公司和从该位置运营的渡轮选项,而不是我们使用的 HTML 或 JavaScript 中的错误。

改进“移动我”示例应用

到目前为止,您可能已经注意到,我们表单的简单性实际上变得有点麻烦了。当然,你的用户可以在他们的本地搜索中输入任何他们喜欢的东西,但是也许你想让他们不用在小软键盘上打字。我们示例代码的第二个版本,在文件ch10–example02.html中,修改了表单代码,为流行的交通类型提供了简单的按钮,并调用了我们修改后的execSearch()函数。我们的新表单编码如下。

`

                   

`

我们修改后的execSearch()函数不再需要读取文本字段,主要是因为我们已经移除了它!相反,它将我们调用函数时传递的文本作为参数,比如“火车站”或“渡轮码头”,并调用相关搜索的执行。

function execSearch(searchText) {   myLocalSearch.execute(searchText); }

我们的应用现在看起来更容易立即使用,有地铁、火车、公共汽车等等的相关按钮,如图 Figure 10–4 所示。

images

图 10–4。 使用专用中转按钮移动我应用

从这里开始,按下一个按钮会产生和以前一样的结果,显然是为了匹配每个按钮的编码标准而定制的。

处理其他运输可能性

有无数种方法可以让这个例子更进一步。一个明显的例子是增加交通选项按钮的数量。你可以包括出租车,机场,电车,等等。还有其他细微差别需要考虑。旧金山的地铁可以指 BART 系统,但市政系统的大部分也被认为是地铁。在其他城市,地铁有其他的名字,比如伦敦的“Underground”或者巴黎的“Metro”。代码可以扩展为重用您的坐标来确定用户当前所在的城市,并且搜索文本可以相应地本地化。

我们方法的局限性

到目前为止,我们的例子有助于找到我们自己或当地的交通站或车站。这无疑有助于从任何给定的位置制定出可能的交通选择,但就像一次未完成的旅程,它让我们离我们真正想去的地方很近。我们可能知道拐角处就有一个公交车站,或者街道尽头就有一个火车站,但是我们知道从这些地方有哪些服务,以及它们要去哪里吗?我们知道下一次服务预定什么时候到达吗?我们到底知不知道那个车站有没有定期航班?

如果有某种方法可以为我们发现的中转站点找到匹配的中转计划数据就好了!

引入公交数据资源

到目前为止,我们已经探索了如何找到我们自己和我们周围可能的交通选择,并在地图上绘制结果。但是任何一个旅行者都会告诉你,知道何时交通工具运行和知道它们在哪里运行一样重要。如果有办法使用公共汽车时刻表、火车时刻表和其他交通时刻表来扩展我们的交通应用的可能性,那不是很好吗?嗯,有一个办法!

利用交通时刻表和时刻表

尝试使用运输机构的数据的历史是漫长而曲折的。为了避免您之前的开发人员不得不忍受的痛苦,我们将缩短我们的故事:我们将专注于“那时”和“现在”1995 年以前,与各种政府或官僚机构打交道以获取他们的交通数据是一种代价高昂的受虐狂行为。未记录的专有格式、蓄意阻挠的政府官员和扭曲的所有权概念给任何使用“公共”交通数据的尝试蒙上了阴影。

1995 年,美国俄勒冈州主要城市波特兰的 TriMet 运输机构的一名员工对自己可以使用的专有地图和运输数据系统和来源感到沮丧。她写信给当时许多领先的地图和地理定位服务,询问他们有什么计划将公共交通数据纳入他们的服务。只有一家公司回应:谷歌。

从当时开发人员的角度来看,处理保管数据的机构的不稳定——甚至有时反复无常——的态度是非常令人沮丧和复杂的。但 TriMet 的试探性问题揭示了一个想法,即希望访问数据的沮丧的开发人员与希望看到数据更好地用于服务社区的沮丧的数据保管人和公共官员相匹配。

这引发了通用运输馈送规范(GTFS)的兴起,该规范旨在为运输数据格式提供一致的协议。这个时代的任何历史学家都会注意到,GTFS 格式几乎完全是波特兰 TriMet 公司用于传输数据的格式。通常情况下,特定技术领域的先行者是事实上的标准制定者*。很快,更多的政府和运输机构有远见地以这种格式发布他们持有的数据,这已经成为共享运输时间表、路线和相关数据的标准。*

*几乎每个人都曾坐过公共汽车、火车、飞机或渡船,因此我们可以跳过 GTFS feed 的基本概念。相反,让我们从一个 Android 开发者的角度来看一下规范。

检查 GTFS 组件

GTFS 提要就是一堆逗号分隔格式的文本数据,分成几个文本文件,然后打包成一个 zip 文件以便于处理。作为 web 应用或原生 Android 应用的开发人员,您可以获取 GTFS 提要的 zip 文件,然后使用它将交通位置、时间表信息等合并到您的代码中。典型的 GTFS zip 文件由(并且需要)以下文本文件组成:

  • Agency.txt 提供 GTFS 数据包中数据的机构和组织。
  • 基于每周花名册的各种服务的日期。这包括服务的开始和结束日期的详细信息,以及服务可用和不可用的日期。
  • 给定服务的停靠站集合,以及它们之间的路径。这与大多数人认为的单一公交路线、火车旅程等相匹配。
  • 服务停止让乘客上下车的明显位置,包括终点站或终点。
  • Stop_times.txt 给定车辆到达和离开停靠点的时间。这也允许计算“停留时间”,即服务停留在停止状态的时间。
  • Trips.txt 给定路线的行程,其中给定车辆连续停靠。

除了前面的强制文件列表之外,给定的 GTFS 馈送还可以在相应的文本文件中包括以下可选附加数据中的一些或全部

  • Calendar_dates.txt 在 calendar.txt 文件中列出标准排班的服务细节的例外情况。
  • Fare_attributes.txt 由运营服务的机构收取的使用特定服务的实际费用的详细信息。
  • Fare_rules.txt 用于应用在 fare_attributes.txt 中指定的票价的规则。这可以包括诸如高峰和非高峰规则、对学生、老年人的优惠等等。
  • Feed_info.txt 关于此 GTFS 源的附加信息,例如版本号和适用日期。
  • frequency . txt不按固定频率运行的服务的定时信息。
  • Shapes.txt 帮助渲染路线图形的细节,比如如何表示出、回程路径的差异,回程点等。
  • 关于从一个服务转移到另一个服务的任何规则的细节。

单个文件字段、格式要求和其他规则的细节可以在许多与 GTFS 相关的网站上找到,比如[developers.google.com/transit/](https://developers.google.com/transit/)

世界各地的许多运输机构在[www.gtfs-data-exchange.com/](http://www.gtfs-data-exchange.com/)利用 GTFS 数据的中央交换所。该网站不仅充当 GTFS 馈送的中央存储库,而且包括关于规范的基本数据,以及关于如何贡献新 GTFS 数据集的细节。

如果这一切听起来不可思议——近乎乌托邦——有一个小问题总是影响着运输时间表和路线的理想,以及通勤者或游客的现实。当服务没有按时运行,或者根本没有运行时,会发生什么?输入 GTFS 实时标准。

处理时间表现实

GTFS-实时时间是一个相对较新的除了 GTFS 的核心,由一组感兴趣的各方在 2011 年 8 月发布。它旨在以提供实时更新运输服务时间表的形式,解决“理论何时符合实践”的永恒问题。它向订阅数据的任何人实时提供以下类型的更新。

  • **行程更新:**计划服务的任何路线、时间变更或取消
  • **服务警报:**服务级别的变化,例如移动的站、关闭的站,或者甚至是整个网络的问题,例如电源故障或切换问题。
  • **车辆位置:**车辆在交通系统中的位置,以及车辆自身或相关交通基础设施提供的当地拥堵信息。

谷歌是 GTFS 实时系统发展的关键参与者,这在数据格式的选择上是显而易见的。GTFS 实时公司没有遵循基本 GTFS 标准的逗号分隔格式,而是采用了谷歌的协议缓冲格式。这只是另一种数据格式,它是基于高效的平台无关的序列化和反序列化的理念而创建的——基本上是一种与 XML 类似的简洁格式。实际上,这只是意味着需要参考协议缓冲区定义来确定文本/数据中的字段长度和属性。

探索 GTFS 的例子

我们的章节已经很长了,介绍和探索一个小的 GTFS 例子将会涉及数百行 JavaScript、HTML 和 CSS。这并不是因为这些示例很难或很复杂,而是因为它们需要处理我们在本章中已经处理过的所有地理定位和地图渲染样式代码,以及大量文本处理代码和 I/O 代码来处理 GTFS 数据。

就其本身而言,将有足够的材料来单独出版一本书,因此,与其用如此大的篇幅来淹没我们的书,我们不如在网上调出一些优秀的公开可用的例子,你可以下载并探索你内心的内容,以发现更多关于 GTFS 及其可能性的信息。

在线提供的一些著名且非常有用的 GTFS 开源示例包括:

  • 谷歌公交开发者页面作为 GTFS 社区的活跃成员,谷歌在网上有大量的例子。将你的浏览器指向[developers.google.com/transit/gtfs/examples/display-to-users](https://developers.google.com/transit/gtfs/examples/display-to-users)以获得大量的例子和背景信息。
  • One Busway 项目最初由开发人员在普吉特湾创建,[developer.onebusaway.org](http://developer.onebusaway.org)是 One bus way 项目的所在地,该项目已扩展为面向 GTFS 开发人员的全球计划。
  • 社区项目如 UlmApi.de 的 Live Map,这是一次黑客马拉松的成果,它在几个小时内就构建了一个功能齐全的 GTFS 交通应用。完整的项目详情请见[github.com/UlmApi/livemap](https://github.com/UlmApi/livemap)

在图 10–5 中,您可以看到 Ulm 清晨的完整交通系统,由 UlmApi.de Web 应用为 Android 渲染。

images

图 10–5。 安卓浏览器中的 UlmApi.de hackathon GTFS 应用

本着基于公交和运输应用的真正精神,我们邀请您去探索这些和其他 GTFS 网站,并看看乘坐带您去哪里!

总结

在这一章中,我们探讨了 Android 基于位置的 Web 应用的进一步开发。我们的例子已经介绍了如何使用 Google 流行的本地搜索 web 服务来演示如何为脚步匆匆的游客找到火车、地铁、渡轮和其他交通工具。我们只是触及了像 GTFS 这样的交通数据馈送系统的表面,但是你现在已经知道了足够多的信息来探索你可以利用的许多在线选项。我们希望我们的示例应用已经展示了将基于手机的定位功能与基于云的服务相结合来创建有用的 mashups 和非常易于开发的功能是多么容易。*