安卓-UI-设计-全-

安卓 UI 设计(全)

原文:zh.annas-archive.org/md5/6f61008f720fa64e36949247ab482c39

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

你的 UI 是你与用户最直接的沟通方式,但在应用开发中,设计往往被忽视,或者只是“顺便”发生的事情。

为了开发一个出色的应用,你需要认真对待你的 UI 设计。即使你的应用提供了出色的功能,但如果其用户界面笨拙、卡顿、难以导航,或者整体上令人不快,那么没有人会想使用它。

设计 Android 并不是一件容易的事情。创建一个在不同设备上看起来都很棒的应用,这些设备具有不同的硬件、软件和屏幕组合,是一项了不起的成就。但如果你在设计过程中投入一些思考,你可以创建一个在整个 Android 生态系统中提供良好体验的 UI。

本书将帮助你成为一个有设计思维的开发者。在 10 章的内容中,你将学习如何设计、精炼、测试和开发一个有效、吸引人的 UI,人们会喜爱使用,并且无论设备的具体情况如何,都能提供最佳的用户体验。

从布局和视图的基础知识到创建响应式、多面板布局,再到通过绘制你的屏幕,使用 Android Studio 的工具来审查你的视图层次结构并查找内存泄漏,本书涵盖了创建完美 Android UI 所需的一切。

本书涵盖内容

第一章,介绍 Android UI,解释为什么设计和开发一个有效的 UI 对你的应用成功至关重要。

第二章,一个有效的 UI 包含哪些内容?,教你如何掌握每个 Android UI 的构建块:布局和视图。学习如何创建和设计常见的 UI 组件,包括文本、图像和按钮。

第三章,扩展你的 UI – 片段、资源以及收集用户输入,帮助你使用数组、维度、状态列表和 9-patch 图像将你的 UI 提升到下一个层次。使用片段,你将学习如何创建一个灵活的 UI,它能根据用户的特定屏幕尺寸做出反应。

第四章,开始使用 Material Design,教你如何掌握谷歌的新设计原则,并通过一个感觉像 Android 操作系统的无缝扩展的 UI 来取悦你的用户。

第五章,将你的创意转化为详细草图,通过介绍路线图、流程图、屏幕列表和屏幕图到你的 Android 应用“待办事项”列表中,帮助你成为一个有设计思维的开发者。

第六章,将你的草图转换为线框,展示了如何使用纸原型和线框将前一章中的高级计划转换为详细的屏幕设计。

第七章,构建原型,对你的计划进行测试!到本章结束时,你将创建一个完整的数字原型。

第八章,扩大受众范围 – 支持多设备,教你如何通过支持广泛的硬件、软件、屏幕尺寸、屏幕密度、方向、Android 平台版本以及甚至不同语言的 APP 来吸引更广泛的受众。

第九章,优化你的 UI,展示了如何创建一个流畅且响应迅速的 UI,人们会喜欢使用。如果你的应用运行缓慢、容易崩溃、消耗数据内存,或者耗尽用户的电池,那么没有人会想使用它!

第十章最佳实践和安全保护你的应用程序,将指导你完成 UI 的最终润色,包括使用即将发布的 Android N 版本的通知。

阅读本书所需的条件

你将需要 Android SDK 和 Android Studio(首选)或 Eclipse。

适合阅读本书的读者

如果你是对为应用程序构建令人惊叹的 Android UI 有浓厚兴趣的 Java 开发者,目的是保留客户并为他们创造美好的体验,那么这本书就是为你准备的。假设你具备良好的 XML 知识并对 Android 开发有一定了解。

习惯用法

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

文本中的代码词汇、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称将如下显示:“我们可以通过使用include指令来包含其他上下文。”

代码块将如下设置:

<LinearLayout

   android:orientation="vertical"
   android:layout_width="match_parent"
   android:layout_height="match_parent">

当我们希望引起你对代码块中特定部分的注意时,相关的行或项目将以粗体显示:

<LinearLayout 

   android:orientation="vertical"
   android:layout_width="match_parent"
   android:layout_height="match_parent">

任何命令行输入或输出都应如下编写:

cd /Users/username/Downloads/android/sdk/platform-tools

新术语重要词汇将以粗体显示。屏幕上显示的词汇,例如在菜单或对话框中,将以如下方式显示:“点击下一步按钮将您带到下一屏幕。”

注意

警告或重要注意事项将以如下框中显示。

小贴士

小技巧和技巧将以如下方式显示。

对于本书,我们概述了 Mac OS X 平台的快捷键,如果您使用的是 Windows 版本,您可以在 WebStorm 帮助页面www.jetbrains.com/webstorm/help/keyboard-shortcuts-by-category.html上找到相关快捷键。

读者反馈

我们读者的反馈总是受欢迎的。让我们知道您对这本书的看法——您喜欢或不喜欢什么。读者反馈对我们很重要,因为它帮助我们开发出您真正能从中获得最大收益的标题。

要发送一般反馈,请直接发送电子邮件至 feedback@packtpub.com,并在邮件主题中提及本书的标题。

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

客户支持

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

下载示例代码

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

您可以通过以下步骤下载代码文件:

  1. 使用您的电子邮件地址和密码登录或注册我们的网站。

  2. 将鼠标指针悬停在顶部的支持选项卡上。

  3. 点击代码下载与勘误

  4. 搜索框中输入书的名称。

  5. 选择您想要下载代码文件的书籍。

  6. 从下拉菜单中选择您购买本书的地方。

  7. 点击代码下载

文件下载完成后,请确保您使用最新版本的软件解压缩或提取文件夹:

  • 适用于 Windows 的 WinRAR / 7-Zip

  • 适用于 Mac 的 Zipeg / iZip / UnRarX

  • 适用于 Linux 的 7-Zip / PeaZip

本书代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/Android-UI-Design。我们还有其他来自我们丰富图书和视频目录的代码包可供在github.com/PacktPublishing/找到。查看它们吧!

下载本书的颜色图像

我们还为您提供了一个包含本书中使用的截图/图表颜色图像的 PDF 文件。颜色图像将帮助您更好地理解输出的变化。您可以从www.packtpub.com/sites/default/files/downloads/AndroidUIDesign_ColoredImages.pdf下载此文件。

勘误

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

要查看之前提交的勘误表,请访问 www.packtpub.com/books/content/support,并在搜索字段中输入书籍名称。所需信息将在勘误部分显示。

盗版

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

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

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

询问

如果您对本书的任何方面有问题,您可以通过 questions@packtpub.com 联系我们,我们将尽力解决问题。

第一章。介绍安卓 UI

如果你拿起这本书,那是因为你想要开发人们喜欢使用的用户界面。

为安卓平台开发是一个充满激动人心的机会和头痛的挑战的混合体;设计和构建你应用程序的 UI 也是如此。

当你构建自己的 UI 时,你将面临的一些挑战非常精确且技术性强(例如创建一个能够在安卓 5.0 和安卓 5.1 上正确显示的 UI),而其他挑战则更加个人化,比如抵制使用 UI 进行疯狂和不寻常的事情的诱惑,仅仅因为你能够这样做。

这就是这本书的作用所在。

在 10 章的篇幅中,你将获得一些设计技能和所需的技术知识,以抓住所有开发安卓平台 UI 的机会并克服所有挑战。

我们将涵盖从使用计划、草图和线框到将最初的灵感转化为构建完美用户界面的逐步蓝图的设计和开发全过程,最后通过构建、测试和改进你的 UI 来实施这个计划。在这个过程中,我们将涵盖所有最新的最佳实践,包括材料设计原则以及安卓 N 中即将推出的某些新 UI 功能。

由于创建一个用户喜欢的 UI 还不够好,我们将更进一步,使用一系列工具和技术来分析和优化我们用户界面的每一个部分。到这本书结束时,你将知道如何开发一个用户会喜爱的 UI。

让我们从基础知识开始。

无论如何,什么是用户界面呢?

这可能看起来像是一个显而易见的问题。毕竟,我们每天都在与 UI 交互,无论是在我们的电脑、手机、平板电脑或其他电子设备上。但有时,最简单的问题最难回答。

用户界面的技术定义是用户与应用程序或计算机程序之间的接口。用户界面是用户可以看到并与之交互的一切,除非你正在开发一种非常特殊(或非常不寻常)的安卓应用程序,否则你开发的每个应用程序都将以某种形式拥有用户界面。

当涉及到创建你应用程序的 UI 时,安卓平台给你带来了将你的愿景变为现实的空间。只需翻阅一下你安卓设备上安装的应用程序,你可能会遇到看起来彼此非常不同的 UI。

尽管这些 UI 在表面上看起来可能不同,但它们确实有很多共同的元素,无论是那些在幕后默默工作的布局,还是可见的元素,如按钮、菜单和操作栏。

这里有一些来自不同安卓应用的 UI 示例。尽管它们各自都有独特的风格和感觉,但它们也有很多共同的 UI 元素:

什么是用户界面?

Google Photos 不仅仅是一个相册,它还通过根据位置、日期和主题等因素组织照片和视频,为喜欢快拍的 Android 用户提供了享受媒体的新方式。由于照片应用希望鼓励你花时间探索和享受其照片和视频内容,因此最突出的 UI 元素是位于右下角的浮动搜索按钮,它允许用户根据位置和主题等因素搜索他们的媒体。

用户界面究竟是什么?

在 2014 年的 Google I/O 大会上宣布,后来出现在 Android 5.0 中,Material Design 是一种新的设计语言,它为 Google 产品(包括 Android)提供了一致的用户体验。借鉴纸张和墨水的灵感,Material Design 使用阴影、边缘、尺寸和材料层的概念来创造引人注目且简约的体验。

许多应用现在实现了 Material Design。这有助于提供更流畅的 UI 体验,即使在用户在由完全不同的开发者创建的应用之间切换时也是如此。上一屏显示了 Dashlane 密码管理器应用,它采用了 Material Design 主题。

用户界面究竟是什么?

在地图应用中你会发现的设计元素被精心设计,以避免干扰,这样主要内容(即地图的部分)就可以清晰地显示且不受干扰。这是一个 UI 在保持不显眼的同时,确保所有可能的 UI 元素始终容易触及的好例子。这种类型的 UI 的另一个例子是 YouTube 应用。

开发 Android UI 真的与其他平台有很大不同吗?

如果你有过在除 Android 以外的平台上开发的经验,那么你可能自信地认为自己可以制作出相当不错的用户界面(UI),无论是 iPhone 应用的 UI 还是注定要在最新版本的 Windows 上运行的软件。

有两个因素使得开发 Android UI 与其他你可能习惯的平台有所不同:

  • 这是一个开放的平台。正如之前提到的,Android 赋予你表达自己的自由,创建你想要的任何 UI。即使是最新的 Material Design 原则也仅仅是建议,而不是你必须遵循的严格规则,如果谷歌要允许你的应用进入 Play Store(尽管强烈建议你遵循这些设计原则!)。

    这意味着你可以自由地做出一些用户会喜爱的创新 UI 决策,但也意味着你可以自由地做出一些用户会讨厌的创新设计决策(有时,你从未见过某些事情的原因可能是因为它是一个糟糕的想法)。自我约束是设计和发展 Android 平台有效 UI 的一个重要部分。

  • 这是一个多样化的平台。当你发布 Android 应用时,你无法预测它最终会出现在哪里!你的应用可能会出现在各种不同的设备上,包括不同的硬件、软件和屏幕,包括 Android 操作系统的不同版本。

设计一个能够处理所有这些变量的 UI,你的应用就有可能给广泛的受众留下深刻印象。设计一个无法应对挑战的 UI,你的用户将会有不一致的体验,你的 UI 在某些设备上看起来很棒,功能完美,在其他设备上运行则不太完美。一个设计糟糕的 UI 甚至可能使你的应用在某些设备上看起来完全崩溃。

一个有效的 UI 有哪些特点?

在这本书的整个过程中,你将学习如何创建一个有效的 UI,但在我们深入探讨这个话题之前,有一个明确的目标是有帮助的。

一个成功的 Android UI 应该如下所示:

  • 清晰...

  • 你的 UI 是你应用与用户沟通的方式。确保你的应用想要表达的内容是清晰的!

  • 如果用户不能迅速浏览你应用中的每一个屏幕并立即知道屏幕要求他们做什么,那么你的 UI 就不够清晰。

  • ....但不要太清晰!

  • 用户界面怎么可能会太清晰呢?

  • 想象一个需要解释每个 UI 元素的屏幕。当然,这样的 UI 会清晰,但你愿意坚持使用这样的应用吗?或者你会寻找一个替代应用,一个不是充满了不必要的、很可能还令人烦恼的文字的应用,比如点击提交结果按钮提交你的结果??

  • 一个有效的 UI 需要在清晰和简洁之间取得平衡。如果你设计得好的 UI,用户会立即知道你音乐播放应用中的+图标意味着添加新曲目到播放列表,而无需你在 UI 中添加额外的文字。

  • 响应式

  • 无论你的应用内容多么出色,如果 UI 卡顿且容易冻结,没有人会想使用它。

  • 一个有效的 UI 是响应式的、流畅的,就像用户与应用之间的对话。例如,想象你刚刚下载了一个新应用,你的第一个任务是创建一个新账户。你填写表格并点击提交。然后,突然你被带到了应用的一个全新的部分,没有任何解释发生了什么。你刚刚创建了一个账户吗?你已经登录到你的新账户了吗?发生了什么?

  • 在这种情况下,一个有效的 UI 会在你点击提交按钮时做出响应,即使它只是一个简单的感谢您注册详细信息 - 您现在已登录到您的账户弹出窗口。

  • 眼睛容易接受

  • 在理想的世界里,你的 UI 是否吸引人(或不吸引人)并不重要。毕竟,如果你的内容很好,按钮是否完美对齐又有什么关系?或者如果你的图片没有达到尽可能高的清晰度呢?

  • 简单的事实是,这确实很重要。非常重要。

  • 你的 UI 不仅要清晰、一致和响应,还要看起来很漂亮。

值得注意的是,创建一个吸引人、专业-looking 的 UI 可能意味着放弃你自己的品味,坚持安全选项。也许你非常喜欢霓虹色,一直认为橙色和绿色是一组被低估的颜色搭配,但请记住,你正在尝试吸引尽可能广泛的受众;这种美学真的会吸引大众吗?

为什么 UI 对你的应用成功至关重要?

为了完全理解掌握有效 UI 艺术的技巧为什么如此重要,让我们看看如果你的应用 UI 做得正确,它可以帮助你实现的一些事情。

立即熟悉感

如果你遵循最佳实践和 Material Design 指南,你的应用将反映出用户在其他 Android 应用中多次遇到过的 UI 原则。你的用户会立刻感到宾至如归,并且也会理解如何与你的应用中的许多元素进行交互,即使他们这是第一次启动应用。例如,一旦用户在一个应用中遇到了一个浮动操作按钮,他们就会知道点击这个按钮将使他们能够访问重要操作(也称为推荐操作)。

易于使用且令人愉悦

你的应用 UI 决定了用户多容易就能让应用做他们想要的事情。创建一个帮助用户快速且以最少的努力从你的应用中获得价值的 UI,你就朝着获得那些 5 星 Google Play 评论迈出了重要的一步。

虽然提供有价值的内容仍然是开发有效应用的关键部分,但请记住,你的应用 UI 同样重要。如果你的应用的有用功能和优秀内容被隐藏在一个笨拙且通常令人不快的 UI 后面,那么没有人会待足够长的时间来发现你的应用实际上能提供多少价值。

确保你在设计和开发你的 UI 上投入的努力,与你在制作应用的内容和功能上投入的努力一样多。

一致性

一个好的 UI 在早期就确立了应用规则,并始终如一。一旦用户感到舒适地与你的应用中的一个屏幕进行交互,他们应该能够找到他们在整个应用中的路径。

防止用户沮丧

你的 UI 应该确保用户永远不会因为你的应用而感到困惑或沮丧,通过温和地引导他们完成下一个任务,以便从你的应用中获得价值。

不论你的 UI 是采用微妙的方法(例如,使用大小和颜色使某些 UI 元素突出)还是更明显的方法(例如,突出用户需要完成的下一个文本字段),你应该确保用户永远不会坐下来问,“我接下来该做什么呢?

帮助用户纠正错误

好的 UI 就像一个有帮助的、不评判的朋友,轻轻指出你做错了什么,并给你提供如何修复的建议。

想象一下,你的应用中有一个用户需要填写并在点击提交之前完成的表单。用户完成这个表单并点击了提交按钮,但什么都没有发生。在这种情况下,你的应用可以让他们挠头并怀疑是否提交按钮坏了,或者你的 UI 可以介入并显示他们忘记填写的一个文本字段,以告知他们出了什么问题。

提供更好的整体 Android 体验

因为这并不完全是关于你的应用。

如果你精心设计你的 UI 并遵循 Material Design 原则,你的应用将感觉像是 Android 平台的无缝扩展,也是用户在其设备上安装的其他应用的扩展。

通过在 UI 设计上投入精力,你实际上可以改善用户的整体 Android 体验。别担心,然后!

UI 案例研究 – Google+

在为 Android 开发应用时,你永远不必从头开始,因为 Android 平台做得很好,提供了一系列预构建的 UI 组件,你可以直接将其添加到你的 UI 中。

这意味着相同的 UI 元素在所有 sorts of Android 应用中反复出现。因此,花些时间看看我们能从其他 Android 应用中学到什么是非常值得的——尤其是那些做得好的 UI 应用。

在本节中,我们将分析预装在许多 Android 设备上的 Google+应用,并且可以从 Google Play 免费下载。我们将逐步分析 Google+应用中 Google 使用的所有核心 UI 组件,并看看是什么使它们如此有效。

操作栏

操作栏沿着屏幕顶部运行,通常包含操作和导航元素:

操作栏

Google+的操作栏包含三个重要的 UI 元素,你通常希望在自己的 Android UI 中包含这些元素。

导航控件

操作栏通常在整个应用中保持不变,这使得它们非常适合为用户提供一种一致且易于访问的方式来导航 UI。

导航控件可以是直接的,例如标准的返回按钮,也可以是更复杂的,例如视图切换控件。

主要的 Google+操作栏包含一个导航控件,使用户能够在其 Google+账户之间切换,这对于拥有多个账户的人来说很有用,例如工作 Google+账户和个人账户。

导航控件

操作按钮

操作栏还非常适合根据用户的当前上下文提供对应用最重要的操作的便捷访问。出现在操作栏中的操作被称为操作按钮

在下面的屏幕截图中,Google+操作栏包含两个操作按钮:

操作按钮

你通常会使用图标和/或文本来表示操作按钮。Android 提供了一系列现成的操作按钮,你应该尽可能使用它们,因为它们确保普通用户至少会熟悉你 UI 中使用的某些操作按钮。

操作溢出

根据应用和可用屏幕空间的不同,可能所有操作按钮都不会总是适合在操作栏中。如果情况如此,Android 系统会自动将一些操作按钮移动到操作溢出中,这是在 Google+ 应用程序整个右上角出现的点状图标:

操作溢出

动作按钮最终隐藏在操作溢出中的数量取决于用户的屏幕。在刻度的一端,横向模式下的平板电脑将比纵向模式下的智能手机有更多的空间用于操作栏。

当设计你的操作栏时,确保将最重要的操作按钮放置在屏幕的左侧,这样它们就不太可能出现在操作溢出中。用户不太可能需要的操作可以放置在操作栏的末尾,这样它们有更大的风险出现在操作溢出中。

虽然操作溢出确实有助于减少操作栏的杂乱,但在某些情况下,你可能希望显示大量操作,而不会让任何操作在较小屏幕上出现在操作溢出中。如果是这种情况,你可能想使用分割操作栏:

操作溢出

在前面的屏幕截图中,Google+ 应用程序使用分割操作栏来减少操作栏的杂乱,同时不会将任何操作降级到操作溢出。分割操作栏也有助于防止操作溢出失控并变得难以管理。

浮动操作按钮

Google+ 的主屏幕是主页,它恰好包含了一种作为 Google 材料设计改造一部分引入的特殊类型的操作按钮。

这被称为浮动操作按钮FAB),它突出显示在 Google+ 主页的右下角:

浮动操作按钮

如其名所示,FABs 是漂浮在主用户界面之上的圆形按钮。这些浮动按钮为你提供了一个方便的方式来突出显示当前上下文中的主要操作。

Google+ 使用其突出的浮动操作按钮来鼓励用户通过撰写状态更新加入对话。

菜单

菜单是导航应用程序的主要方式之一,并且是大多数 Android UI 的基本组成部分。

菜单向用户展示一系列选项,这些选项通常由一个单词或最多一行文本表示:

菜单

Google+ 有很多不同的菜单,但值得特别一提的是上下文菜单。

上下文菜单是特殊的,因为它们会动态更新,只包含与所选内容相关的操作。当您长按屏幕上的元素时,上下文菜单会出现。您可能最常遇到的上下文菜单是当您长按一些文本时出现的全选/剪切/复制/粘贴菜单:

菜单

设置

设置是许多 Android 应用程序的基石,因为它为用户提供了一种调整应用程序行为以更好地适应他们的方式。如果用户可以以任何方式修改您的应用程序,您将需要为他们提供某种形式的设置屏幕。

创建设置屏幕时的主要挑战是确保您的用户能够一目了然地理解所有可用选项及其当前值。这意味着以这种方式设计您的设置,即每个屏幕只包含可管理的选项数量(通常少于10)。

如果您想在应用程序的设置中包含大量选项,请抵制将它们显示为长列表的诱惑。相反,将它们分成相关选项的组。然后您可以将设置显示为单个屏幕,格式化为多个相关项目的列表,或者您可以借鉴 Google+的做法,将每个相关选项组移动到其自己的页面。

如果您选择后者,您的主要设置屏幕可以充当索引,将用户链接到每个子页面,这正是 Google+对其主要设置屏幕采取的方法:

设置

为了便于使用,您应该优先考虑您的设置,这样用户最可能需要的选项始终位于设置屏幕的顶部易于访问。

这也可以在 Google+的设置中看到,其中一些晦涩的选项,例如删除 Google+个人资料,被限制在设置屏幕的底部。

对话框

有时,您可能需要真正抓住用户的注意力,而可以帮助您做到这一点的 UI 元素是对话框。

对话框是一个可以包含文本和按钮的小窗口,通常用于以下目的之一:

  • 向用户提供一些重要信息,例如条款和条件,或者在他们可以进入下一个任务之前需要阅读的免责声明

  • 请求额外信息,例如,提示用户输入密码的对话框

  • 要求用户做出决定;例如,如果您草拟了一个 Google+更新,然后尝试在不发布该更新的情况下导航,Google+会使用对话框来检查您是否真正想要丢弃您的帖子

对话框

吐司

吐司是一个小型的弹出窗口,为用户提供简单的反馈。

与对话框不同,在吐司(toast)可见的整个时间内,位于其下的应用程序保持活跃。当您想向用户传达简短且简单的信息时,选择吐司而不是对话框,并且至关重要,您不需要任何用户输入——吐司不支持任何类型的用户输入。

在 Google+ 中,您偶尔会看到当主屏幕上有可查看的新帖子时出现的提示。此提示将提示您滑动屏幕查看更新。

搜索

Android 用户通常期望能够搜索应用程序中包含的所有数据,因此大多数 UI 都包含某种形式的搜索功能。

好消息是,Android 平台有一个现成的搜索框架,您可以在您的 UI 中实现。使用这个标准搜索框架不仅对您来说更容易,而且对您的用户来说也更简单,因为大多数 Android 用户都会立即知道如何与 Android 的标准搜索功能交互,因为他们之前已经多次遇到过这个底层框架(即使他们没有意识到这一点):

搜索

您可以使用 Android 的标准搜索框架以以下方式之一实现搜索:

  • 搜索对话框:此对话框默认隐藏。当用户激活时,它将作为当前活动顶部的浮动搜索框出现。搜索对话框组件由 Android 系统控制。

  • 搜索小部件:这是一个可以放置在应用程序 UI 中的 SearchView 实例。如果您决定使用搜索小部件,您需要做一些额外的工作,以便 Android 系统处理通过此小部件发生的搜索事件。

输入控制

输入控制是 Android 平台提供的各种组件,用户可以与之交互。启动您设备上的任何 Android 应用程序,很可能会包含某种形式的输入控制——无论是按钮、复选框、文本字段还是其他什么。

由于您的平均 Android 用户之前已经多次遇到过这些标准输入控制,因此在您的 UI 中包含它们可以保证大多数用户会立即知道如何与至少 一些 您应用程序的核心 UI 元素交互。

注意

输入控制几乎是任何 Android UI 的关键部分,因此我们将在下一章中更详细地介绍它们。

样式和主题

我们已经查看了很多可以在 Google+ 应用程序中找到的不同 UI 元素,但是什么将它们联系在一起并确保您的 UI 感觉一致?

答案是样式和主题。

虽然 Android 确实赋予了您自定义 UI 中每个屏幕每个部分的视觉和感觉的能力,但您必须权衡这一点,以提供对用户来说一致的 UI 体验。主题和样式是强大的工具,可以帮助您提供整个应用程序的一致性。

样式和主题允许您定义一组属性一次,然后在您的应用程序中重复使用它们。因此,您可以创建一个样式,指定文本应为蓝色并具有等宽字体,然后您可以在整个应用程序中应用此样式。立即,所有文本都将具有相同的格式。

样式和主题可以是简单的,例如我们的文本示例,或者你可以创建一系列属性,这些属性决定了你的 UI 中每个元素的外观和感觉,从操作栏的色调到布局中使用的填充量。

虽然它们经常一起讨论,但样式和主题之间有一个关键的区别:

  • 样式:这是一组你可以应用于整个活动或应用,或者应用于单个视图的属性

  • 主题:这是一种你可以应用于整个活动或应用,但不能应用于单个视图的样式

Android 平台提供了大量的预定义主题和样式,你可以在你的 Android 应用中使用它们(包括一些关键的 Material 主题),或者你可以创建自己的。

虽然你可以从头开始创建主题和样式,但你通常会希望以 Android 的现成主题和样式作为起点,然后定义你想要添加、更改或删除的任何属性。这被称为继承,这是创建感觉熟悉且专业设计的 UI,但仍然微妙地独特于你的应用的最快和最简单的方法之一。

摘要

在本节中,我们探讨了为什么用户界面的质量对于创建一个能够轻松赢得广泛受众的应用至关重要,然后更详细地查看了一个有效且设计良好的 Android UI 的实际例子。

在下一章中,我们将更详细地研究每个 Android UI 的构建块:视图、ViewGroups 和布局。虽然我们在本章中提到了输入控件,但在下一章中我们将更详细地探讨它们,包括如何在你的用户界面中实现一些最常用的输入控件。

第二章:有效的 UI 包含哪些内容?

在本章中,我们将查看每个 Android 用户界面的核心组件:布局和视图。您将学习如何将这些元素添加到您的 Android 项目中,以及您可以根据自己的设计需求自定义这些 UI 构建块的所有不同方式。我们还将探讨如何使用字符串、颜色资源和状态列表来增强我们的 UI,并开始探讨如何创建一个无论在哪个屏幕上显示都清晰明亮的用户界面。

虽然我们将在本章中更详细地探讨布局和视图,但这两者是内在联系的。在您向您的应用添加视图之前,您需要一个布局,而没有视图的布局不太可能赢得您的应用粉丝。

因此,在我们深入 UI 设计的细节之前,让我们先概述一下视图和布局是如何结合在一起创建有效的 Android UI 的。

什么是视图?

如您所知,Android 应用由 Activity 组成。通常,一次只显示一个Activity,并且这个 Activity 占据整个屏幕。

每个 Activity 都由视图组成,视图是用户界面的最基本组件。视图总是占据一个矩形区域,尽管视图可以显示任何形状的内容:

什么是视图?

最常用的视图示例包括TextViewEditTextImageViewButtonImageButton

什么是布局?

ViewGroup是一个容器,它将其他子视图和 ViewGroup 对象组合在一起。

ViewGroup 最常见的一个例子是布局,这是一个负责在屏幕上定位子元素的不可见容器。例如,LinearLayout是一个 ViewGroup(有时也称为布局管理器),它将子元素(视图或 ViewGroup)排列成垂直或水平行:

什么是布局?

在本章中,我将主要关注布局管理器,因为这些是您通常最常使用的 ViewGroup;但请注意,也存在其他类型的 ViewGroup。

构建你的 UI——XML 还是 Java?

定义用户界面(以及它包含的视图、ViewGroup 和布局元素)的最简单方法是通过您项目的 XML 文件。

使用 XML 声明你的 UI

Android 提供了一种直接的 XML 词汇表,为您的用户界面提供了一个可读的结构,并在定义 UI 的代码和控制应用行为的代码之间创建了一个分离。您在专门的布局资源文件中定义布局。这有助于保持两组代码的整洁,并让您能够在不触及应用底层代码的情况下调整和优化您的 UI。例如,您可以在不触及先前测试的代码的情况下更新布局以支持额外的语言。

在 XML 中声明 UI 也使得提供备用布局变得更容易;例如,在某个时候,您可能希望创建一个针对横屏模式优化的布局的替代版本。如果您在 XML 中声明原始布局,提供横屏优化的布局就像创建一个res/layout-land目录,然后在这个文件夹中填充定义您的应用横屏优化布局的 XML 文件一样简单。

当您在 Eclipse 或 Android Studio 中创建 Android 项目时,IDE 的项目创建向导会自动为应用程序的主活动生成一个布局资源文件:

使用 XML 声明 UI

您可以在项目的res/layout文件夹中找到此布局资源文件:

使用 XML 声明 UI

每个布局资源文件必须恰好包含一个根元素,它可以是视图或ViewGroup。例如,您可以使用垂直的LinearLayout元素作为布局的根元素:

<?xml version="1.0" encoding="utf-8"?> 

<LinearLayout        android:orientation="vertical"   android:layout_width="fill_parent"   android:layout_height="fill_parent"> 

</LinearLayout> 

注意

您还可以使用<merge>元素作为根元素。我们将在第九章优化您的 UI 中介绍合并。

一旦您定义了布局资源文件的根元素,您就可以通过添加TextViewsButtonsImageViews等对象来构建视图层次结构。

要加载布局资源文件,您需要从应用程序的onCreate()回调实现中引用它。例如,打开您的项目MainActivity.java文件,您将看到如下内容:

public void onCreate(Bundle savedInstanceState) { 
  super.onCreate(savedInstanceState); 
  setContentView(R.layout.main_layout); 
} 

在这里,您的项目调用setContentView()并将项目自动生成的资源文件的引用传递给它;在这个例子中,它是R.layout.main_layout。当用户加载您的应用程序时,MainActivity将读取这个引用的布局资源文件并向用户显示其内容。

以编程方式声明 UI

第二种选择是在运行时以编程方式创建 UI。这种方法通常不推荐,因为它意味着您的应用程序的底层代码和 UI 代码被混合在一起。因此,调整应用程序 UI 的元素变得比必要的要困难得多。

然而,有时您可能需要以编程方式定义用户界面的某些方面,有时甚至可能需要用 Java 定义整个界面。

正如我们已经看到的,当您在 XML 中定义布局时,您从应用程序代码中加载它:

setContentView(R.layout.activity_main); 

这里,您正在告诉您的 Activity 加载main_activity.xml布局资源文件,但如果您以编程方式创建布局,则需要删除此段代码,这样您的 Activity 就不会寻找布局资源文件。

例如,看一下以下内容:

@Override 
protected void onCreate(Bundle savedInstanceState) { 
super.onCreate(savedInstanceState); 

  // Create a LinearLayout object that will contain all the views in this Activity// 

    LinearLayout linearLayout = new LinearLayout(this); 

//Set the LinearLayout's orientation. In this instance, it's set to horizontal// 

  linearLayout.setOrientation(LinearLayout.HORIZONTAL); 

  // Create a LayoutParams object to be used by the LinearLayout// 

    LayoutParams linearLayoutParam = new      LayoutParams(LayoutParams.MATCH_PARENT,      LayoutParams.MATCH_PARENT); 

    // Set LinearLayout as the screen's root element// 
    setContentView(linearLayout, linearLayoutParam); 
  } 

使用编程和 XML 布局

有时候,最好的解决方案可能是同时使用编程和 XML 布局。这种方法通过在 XML 中定义大部分 UI 来帮助你保持 UI 和应用程序代码之间的分离,同时允许你在运行时通过修改一些屏幕元素的状态来创建一个更动态的用户界面。

例如,你的应用可能包含一个由单个按钮控制的幻灯片。当用户点击按钮时,会出现一张新图片。为了创建这种效果,你可以在 XML 中定义按钮,然后每当用户点击按钮时,以编程方式添加一张新图片。

由于 XML 是定义 UI 最简单和最有效的方式,因此本章主要关注在 XML 中创建和自定义视图、ViewGroups 和布局。然而,偶尔你可能需要以编程方式定义 UI 的一部分,所以在这个过程中我也会包括一些 Java 代码片段。

深入了解 - 探索布局

现在你已经了解了视图、ViewGroups 和布局如何结合在一起创建用户界面,以及如何以编程方式和在 XML 中创建用户界面,是时候更详细地查看 Android 最常用的 UI 组件了。我们将从基本上为任何用户界面奠定基础的那个组件开始:布局容器。

Android 平台支持一系列布局,因此你的第一个任务是决定哪种布局最能满足你的设计需求。如果你在做出这个决定时遇到困难,请记住,你可以在布局之间嵌套以创建你完美的布局容器。

注意

不要过度嵌套,因为这可能会对你的应用程序性能产生负面影响。如果你发现自己嵌套了多个布局,那么这可能是一个迹象,表明你正在使用错误的布局类型!

在你创建任何类型的布局之前,这里有一些规则、规定和属性,你需要掌握。

定义布局的大小

每当你创建一个布局时,你需要告诉 Android 系统这个布局应该有多大。

你用来定义布局大小的 XML 属性是android:layout_heightandroid:layout_width。正如它们的名称所暗示的,这些属性分别设置布局的高度和宽度。

两者都接受以下值:

一个支持的关键字

Android 屏幕有各种不同的尺寸。确保你的用户界面足够灵活,能够应对所有这些不同尺寸的屏幕的最简单方法之一是将布局的宽度和高度设置为以下支持的其中一个关键字:

  • match_parent:这使得高度或宽度扩展以完全填充可用的屏幕空间

  • wrap_content:这设置高度或宽度为适应元素内容所需的最小尺寸,且不更大

注意

你还可以使用match_parentwrap_content来设置其他屏幕元素的大小,包括视图和 ViewGroups。

尺寸值

或者,你可以使用 Android 系统支持的测量单位之一来设置你的布局大小:

  • 密度无关像素 (dp):这是一个基于屏幕物理密度的抽象单位。dp 单位相对于 160 点每英寸屏幕上的 1 个物理像素。在运行时,Android 会根据当前屏幕的 dp 自动调整绘制 1 dp 所使用的像素数。使用密度无关的测量是创建能够自动适应不同屏幕大小的 UI 的简单解决方案。

  • 绝对单位:Android 支持多种绝对测量单位(特别是像素、毫米和英寸),但你应避免使用绝对大小来定义你的布局,因为这会使你的 UI 非常僵硬,并可能阻止它根据不同屏幕大小进行缩放。除非你有 非常好的理由 不这样做,否则请坚持使用相对测量,如 dp、match_parentwrap_content

程序化设置布局大小

你也可以通过创建 LayoutParams 对象来程序化地设置布局的大小:

LayoutParams linearLayoutParam = new LayoutParams 

你可以按照以下方式设置其宽度和高度:

LayoutParams linearLayoutParam = new LayoutParams (LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 

探索不同的布局

现在你已经知道了如何在 XML 和 Java 中创建布局,以及如何设置它们的高度和宽度,你可以更深入地了解两种最常用的布局:简单易用的 LinearLayout 布局和极其灵活的 RelativeLayout 布局。

关于 LinearLayout 你需要知道的一切

LinearLayout 将其所有子元素对齐在单个水平或垂直行中,一个接一个地堆叠。

你可以使用以下任一方式设置你的 LinearLayout 布局的朝向:

  • android:orientation="horizontal.": 视图被放置在彼此旁边的 中。水平 LinearLayout 布局始终只有一行高。

  • android:orientation="vertical.": 视图被放置在彼此下面的 中。垂直 LinearLayout 布局始终每行只有一个子元素。

下面是一个具有水平方向的简单 LinearLayout 布局:

<LinearLayout  

  android:orientation="horizontal" 
  android:layout_width="match_parent" 
  android:layout_height="match_parent"> 
  <Button 
      android:id="@+id/okButton" 
      android:text="Ok" 
      android:layout_width="wrap_content" 
      android:layout_height="wrap_content" /> 
  <Button 
      android:id="@+id/cancelButton" 
      android:text="Cancel" 
      android:layout_width="wrap_content" 
      android:layout_height="wrap_content" /> 

</LinearLayout> 

这就是这种水平 LinearLayout 布局在 Android 设备上的显示方式:

关于 LinearLayout 你需要知道的一切

下面是具有 android:orientation="vertical" 朝向的相同 LinearLayout 基础 UI:

关于 LinearLayout 你需要知道的一切

关于 RelativeLayout 你需要知道的一切

RelativeLayout 是你可用布局中最灵活的一种,它让你可以根据每个子元素与其任何其他子元素的关系,以及与其父容器的关系来定位每个子元素。

例如,你可以灵活地定位 TextView 以使其与 RelativeLayout 容器的边缘对齐,然后你可以将一个 Button 放置在 TextView 上方 100 密度无关像素的位置。

小贴士

使用 RelativeLayout 优化你的 UI

除了让您控制每个元素在屏幕上的位置外,RelativeLayout还可以通过减少嵌套来提高您应用程序的整体性能。如果您发现自己正在使用多个嵌套的LinearLayout,您可能可以通过用单个RelativeLayout替换它们来简化布局层次结构。

由于RelativeLayout主要是关于给您提供灵活性,以便将 UI 元素定位到您想要的位置,因此它支持一系列属性,这些属性允许您将 UI 元素相对于其父容器和彼此进行定位,这并不令人惊讶。

相对于父容器

所有以下属性都接受true值;例如,android:layout_alignParentTop="true"android:layout_alignParentStart="true.":

  • android:layout_alignParentTop: 这会将视图的顶部与其父视图的顶部对齐

  • android:layout_alignParentBottom: 这会将视图的底部与其父视图的底部对齐

  • android:layout_centerInParent: 这将在父视图内水平和垂直居中视图

  • android:layout_alignParentRight: 这会将视图的右侧与其父视图的右侧对齐

  • android:layout_alignParentLeft: 这会将视图的左侧与其父视图的左侧对齐

  • android:layout_centerHorizontal: 这将在父视图内水平居中视图

  • android:layout_centerVertical: 这将在父视图内垂直居中视图

  • android:layout_alignParentStart: 这会将视图的起始边缘与其父视图的起始边缘对齐

  • android:layout_alignParentEnd: 这会将视图的末端与其父视图的末端对齐

  • android:layout_alignWithParentIfMissing: 如果视图引用了一个缺失的元素,则此属性将使视图与父视图对齐

相对于父容器

相对于其他元素

您还可以将 UI 元素相对于屏幕上的其他元素进行定位;例如,您可能希望将back_button视图放置在forward_button的左侧,并将titleTextBox放置在subheadingTextBox上方。

所有以下属性都应该引用您用作参考点的元素的 ID(我们将在稍后更详细地介绍 ID,但基本上,视图的 ID 是您布局资源文件中android:id元素的值,例如,android:id="@+id/viewName"的 ID 是viewName):

  • android:layout_above: 这会将视图放置在指定的元素上方;例如,android:layout_above="@+id/subheadingTextBox"将 UI 元素放置在subheadingTextBox上方

  • android:layout_below: 这会将视图放置在指定的元素下方

  • android:layout_toLeftOf: 这会将视图放置在指定元素的左侧

  • android:layout_toRightOf: 这会将视图放置在指定的元素右侧

  • android:layout_toStartOf: 这会将视图的末端与指定元素的起始边缘对齐

  • android:layout_toEndOf: 这会将视图的起始边缘与指定元素的结束边缘对齐

相对于其他元素

与其他元素对齐

您还可以通过指定 UI 元素如何与其他屏幕元素对齐来定位 UI 元素。同样,以下所有属性的值都是您用作参考点的元素的 ID:

  • android:layout_alignBottom: 这会将视图元素的底部与指定屏幕元素的底部对齐。例如,android:layout_alignBottom="@+id/back_button"会将 UI 元素的底部与back_button的底部对齐。

  • android:layout_alignLeft: 这会将视图的左侧边缘与指定元素的左侧边缘对齐。

  • android:layout_alignRight: 这会将视图的右侧边缘与指定元素的右侧边缘对齐。

  • android:layout_alignTop: 这会将视图的顶部边缘与指定元素的顶部边缘对齐。

  • android:layout_alignStart: 这会将视图的起始边缘与指定元素的起始边缘对齐。

  • android:layout_alignBaseline: 此属性略有不同。基线是排版术语,指的是文本所在的无形线。因此,此属性将视图的基线与指定元素的基线对齐。例如,如果您有两个TextView,您可能想使用alignBaseline来产生这样的印象,即两个视图中的文本都写在同一无形线上。

创建视图

视图是用户界面的基本构建块。

大多数情况下,您将通过将它们添加到您的 Activity 对应的布局资源文件中来创建视图对象。您可以直接编辑 XML 代码,或者您可能想从您的 IDE 调色板中拖动 UI 元素并将它们拖放到 UI 预览中:

创建视图

您还可以通过编程方式创建视图。例如,如果您想通过编程方式实例化一个TextView,您可以将TextView添加到您的 Activity 的onCreate()方法中:

   @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);  
       LinearLayout linearlayoutLayout =            (LinearLayout) findViewById(R.id.rootlayout);  
//Create a new TextView and assign it the ID txview// 
       TextView txView = new TextView(this); 

//Set the text programmatically// 

       txView.setText("Hello World"); 

//Add the txView TextView object to your layout// 

       linearLayout.addView(txView); 
   } 
} 

分配 ID 属性

ID 为您在布局中识别单个视图提供了一种方式。例如,如果您创建了两个按钮,您可以通过分配它们 ID yesButtonnoButton来区分它们。

您可以使用android:id属性为视图分配一个 ID:

<Button android:id="@+id/backButton" 

然后,您可以使用 ID 来引用这个特定的视图:

android:layout_below="@id/backButton" 

您还可以使用 ID 通过findViewById(id)编程方式定位视图:

findViewById(R.id.myButton) 

一旦此方法返回所需的视图,您就可以通过编程方式与视图交互。

设置视图的大小

与您需要设置布局容器的大小一样,您还需要设置添加到布局资源文件中的所有视图的大小。

好消息是,您可以使用完全相同的属性和值,这意味着您可以将android:layout_widthandroid:layout_height添加到您的布局资源文件中,然后从以下值中选择:

  • wrap_content: 这将视图的高度或宽度设置为容纳视图内容所需的最小尺寸。例如,如果您将 wrap_content 应用到包含文本标签的按钮上,系统将调整按钮的大小,使其刚好足够容纳按钮的文本标签。

  • match_parent: 这将视图的高度或宽度扩展以填充所有可用空间。

  • 密度无关像素(dp): 当您分配一个 dp 测量值时,Android 系统会根据用户屏幕的具体密度来放大或缩小视图。

  • 绝对单位:尽管不推荐这样做,但您也可以使用绝对单位进行测量:像素、毫米或英寸。

Android 重力布局和布局重力

重力属性指定对象在其封装对象内沿 XY 轴应该如何定位。这听起来可能很简单,但有一个问题:您将在 Android 中遇到两个不同的重力属性。尽管它们看起来很相似,但它们实际上可以产生非常不同的结果:

  • android:gravity: 这将定位视图内的内容,例如,TextView 内的文本。

  • android:layout_gravity: 这将子视图定位在其父容器内,例如,一个 TextViewLinearLayout 内。

两者都接受广泛的值,包括几个不会改变应用重力属性的物体大小的值:

  • top: 这会将对象定位在其父容器的顶部。例如,android:gravity="top" 将将文本定位在 TextView 的顶部,而 android:layout_gravity="top" 将将 TextView 定位在 LinearLayout 的顶部。

  • Left: 这将对象定位在其父容器的左侧。

  • center_vertical: 这会将对象定位在其父容器垂直中心。

  • Start: 这将对象定位在其父容器的开始处。

两个 gravity 属性也支持几个会改变对象大小的值,包括以下内容:

  • fill_vertical: 这将对象垂直扩展,使其完全填充其父容器。例如,android:gravity="fill_vertical" 将将图像垂直扩展以填充其 ImageView 容器,而 android:layout_gravity="fill_vertical" 将将 ImageView 垂直扩展以填充其 RelativeLayout 容器。

  • fill_horizontal: 这将对象水平和垂直扩展,使其完全填充其父容器。

对于支持的完整值列表,请参阅 developer.android.com/reference/android/widget/LinearLayout.html#attr_android:gravity

您可以使用 setGravity 属性编程式地分配重力,例如,Gravity.CENTER_HORIZONTAL。您还可以使用 setHorizontalGravitysetVerticalGravity

设置背景 – 与颜色一起工作

当涉及到背景时,一些视图具有完全透明的背景,例如TextViews,而其他视图则具有标准的背景颜色,例如按钮,默认为灰色。

如果您不喜欢视图的默认背景,您总是可以更改它。Android 为您提供了多种选项,以在您的 UI 中添加一抹色彩。

首先,Android 系统确实支持一些开箱即用的颜色,所以如果您想使用以下任何一种色调,那么您很幸运,因为 Android 系统已经为您做了所有艰苦的工作:

  • black

  • white

  • holo_blue_bright

  • holo_blue_dark

  • holo_blue_light

  • holo_green_dark

  • holo_green_light

  • holo_orange_light

  • holo_orange_dark

  • holo_purple

  • holo_red_dark

  • holo_red_light

  • darker_gray

要将任何这些现成的颜色资源应用到视图中,请添加android:background属性,但将其值设置为"@android:/color"后跟您选择的颜色:

android:background="@android:color/holo_red_light"  

注意

Android 还支持primary_text_darkwidget_edittext_dark等特定 UI 元素的值。您可以在官方 Android 文档中找到预定义颜色的完整列表,网址为developer.android.com/reference/android/R.color.html

然而,这个列表相当有限!迟早 Android 的预定义颜色将不再适用,您将需要创建自己的颜色资源,这意味着使用十六进制代码。

如果您有一个特定的色调在心中,您通常可以通过快速谷歌搜索找到它的十六进制代码,例如,通过搜索青色十六进制代码浅粉色十六进制代码。或者,您可以通过浏览 Android 风格指南来进行“橱窗购物”,该指南包含广泛的颜色及其相应的十六进制代码。这可在www.google.com/design/spec/style/color.html#color-color-palette找到。

一旦您有了十六进制代码,您可以直接将代码输入到您的布局资源文件中:

android:background="#0000ff" 

这可能看起来很快,但长期来看,这可能会让您花费更多的时间。由于一致性是提供良好用户体验的重要组成部分,您可能会在应用程序的多个地方多次使用相同的颜色,每次使用该颜色时都输入整个十六进制代码可能会真正增加工作量。

虽然这可能需要一些初始的努力,但大多数时候,在项目的res/values/colors.xml文件中定义颜色资源是有意义的。然后您可以多次引用这些颜色资源,而无需输入整个十六进制代码。

如果您的项目不包含colors.xml文件,您可以通过在项目的values文件夹上右键单击,选择新建,然后选择值资源文件来创建一个。将您的文件命名为colors.xml

设置背景 – 使用颜色

打开colors.xml文件,并使用以下格式定义你想要在应用中使用的所有颜色:

<?xml version="1.0" encoding="utf-8"?> 
<resources> 
  <color name="cyan">#00FFFF</color> 
</resources> 

然后,你可以在你的应用中的任何地方使用这个颜色资源,包括在你的视图的背景中:

android:background="@color/cyan" 

你还可以通过在你的布局容器中添加android:background属性来更改你整个 UI 的背景,如下所示:

<LinearLayout  
  android:orientation="vertical" 
  android:layout_width="match_parent" 
  android:layout_height="match_parent" 
  android:background="@color/cyan" /> 

或者,你也可以使用图像作为视图的背景,无论是简单的纹理背景图像还是高清晰度的照片。当我们在查看ImageViews时,我们将更详细地介绍图像,但作为一个快速概述,你只需要将图像添加到你的项目res/drawable文件夹中,然后在你的布局文件中引用该图像:

android:background="@drawable/imagename" 

如果你想要以编程方式设置你应用的背景,请使用setBackgroundResource方法。

分配权重值

当你在LinearLayout内部定位视图时,你可以通过为其分配一个权重值来控制每个视图在屏幕上占据的空间大小。

当你为你的视图分配权重值时,布局中剩余的空间将按其声明的权重比例分配给你的视图。例如,假设你的布局包含三个具有不同权重值的按钮:

<LinearLayout  
      android:layout_width="match_parent" 
  android:layout_height="match_parent" 
  tools:context=".MainActivity"> 

  <Button 
      android:layout_width="wrap_content" 
      android:layout_height="wrap_content" 
      android:text="A" 
      android:id="@+id/button1" 
      android:layout_weight="1"/> 

  <Button 
      android:layout_width="wrap_content" 
      android:layout_height="wrap_content" 
      android:text="B" 
      android:id="@+id/button2" 
      android:layout_weight="1"/> 

  <Button 
      android:layout_width="wrap_content" 
      android:layout_height="wrap_content" 
      android:text="C" 
      android:id="@+id/button3" 
      android:layout_weight="2"/> 

</LinearLayout> 

Button3声明它比Button1Button2更重要,因此它将被分配剩余空间的一半,而Button1Button2必须平均分配剩余的可供空间:

分配权重值

只要注意,其他属性可能与你的layout_weight值交互。例如,假设你的布局包含三个TextViews,并且它们都被设置为android:layout_width="wrap content"。在这种情况下,Android 系统会计算每个TextView需要多宽才能容纳它们的文本,然后才会分配剩余的空间。所以,如果一个TextView需要容纳比其他两个TextView更多的文本,这将影响你的权重结果。

注意

所有视图都有一个默认的权重值为 0,除非你指定了其他值。

添加和自定义视图对象

向你的布局添加视图对象的最简单方法是使用项目res/layout文件夹中的布局资源文件,尽管你也可以根据需要以编程方式添加视图。

在接下来的几节中,我将向你展示如何创建 Android 中最常用的视图之一,特别是TextViewsEditTextImageViewsButtonsImageButtons。一旦创建了每个视图,我将向你展示如何配置该视图,使其看起来和功能与你期望的完全一致。

TextView

这可能不是你用户界面中最令人兴奋的部分,但绝大多数 Android 应用都包含某种文本。你通过TextView向用户显示文本。

要创建TextView,将<TextView>标签添加到您项目的布局资源文件中,然后告诉TextView它应该显示什么文本,可以通过以下方式:

  • 直接将文本添加到布局中:这很简单。只需将android:text属性和您的文本添加到TextView XML 代码中,例如,android:text="Hello world!"

  • 引用字符串资源:大多数情况下,如果您的应用程序需要显示文本,那么这些文本属于您的项目资源,而不是您的实际应用程序代码。这种分离有助于保持您的应用程序代码干净和可读,并且这也意味着您可以在任何时间点调整和更改应用程序的文本,而无需触及应用程序的底层代码。要创建字符串资源,打开您的项目res/values/strings.xml文件,并按以下格式添加您的文本:

<resources> 
    <string name="helloWorld">Hello world!</string> 
</resources> 

然后,您可以从您的布局文件中引用这个字符串资源:

<TextView 
  android:layout_width="wrap_content" 
  android:layout_height="wrap_content" 
  android:text="@string/helloWorld" 
  android:id="@+id/textView" /> 

这就是您需要知道的所有内容,以便显示基本文本,有时这也许就足够了。然而,文本确实有可能显得有点枯燥和无聊!如果您想创建更具视觉吸引力的文本,您有几种选择。

使文本更加明亮

您可以使用android:textColor属性来更改TextView内文本的颜色。要引用 Android 系统支持的默认颜色之一,请使用以下格式:

android:textColor="@android:color/"holo_green_dark" 

如果您正在引用您在项目res/values/colors.xml文件中定义的颜色,其值布局略有不同:

android:textColor="@color/mycustomcolor" 

如果您想以编程方式设置TextView的颜色,请使用setTextColor()方法。

设置文本大小

您可以使用android:textSize来增大或减小文本。

再次提醒您,Android 屏幕有各种不同的尺寸,并且无论在哪个屏幕上显示,您的文本都需要易于阅读。

要进一步复杂化问题,Android 用户实际上可以通过打开他们的设备设置,点击显示,并选择字体大小来更改显示在他们的设备上的字体大小。这对于有视力问题的人来说是一个非常实用的功能。

确保您的文本足够灵活,能够适应用户的字体偏好和屏幕大小,最简单的方法是使用无缩放像素(sp)单位:

android:textSize="30sp" 

Android 还支持三种相对字体大小样式,您可能想在您的应用程序中使用:

  • TextAppearance.Small,例如style="@android:style/TextAppearance.Small."

  • TextAppearance.Medium

  • TextAppearance.Large

强调您的文本

您可以使用android:textStyle属性为您的文本添加粗体或斜体强调。可能的值是正常、粗体、斜体,或者您可以通过管道字符(android:textStyle="bold|italic")将两个值分开来组合粗体和斜体:

强调您的文本

设置字体样式

默认情况下,Android 应用正常字体到您的文本,但系统也支持 sansmonospaceserif 字体,您可以使用 android:typeface 设置这些字体:

android:typeface="monospace" 

设置字体

要程序化设置字体,您需要使用 setTypeFace 方法。

有多少行?

默认情况下,您的 TextView 的内容将根据需要显示的文本量自动换行。如果您想对 TextView 的显示范围有更多控制,您有以下几种选择:

  • android:lines:这使得 TextView 精确为 X 行高,例如,android:lines="2"

  • android:minLines:至少,TextView 将有这么多行高

  • android:maxLines:这限制 TextView 的高度为这么多行

EditText

虽然 TextViews 在显示文本方面很出色,但如果您希望用户能够输入文本,您应该使用 EditText

EditText 在实际应用中最常见的例子之一是请求用户数据的表单。在这个例子中,每个输入字段都是一个单独的 EditText

<LinearLayout  

  android:layout_width="match_parent" 
  android:layout_height="match_parent" 
  android:orientation="vertical" 
tools:context=".MainActivity"> 

  <TextView 
      android:layout_width="match_parent" 
      android:layout_height="wrap_content" 
      android:text="Please complete this form:" 
      android:textSize="20sp" 
      android:id="@+id/TextView" /> 

  <EditText 
      android:layout_width="match_parent" 
      android:layout_height="wrap_content" 
      android:inputType="textPersonName" 
      android:hint="Name" 
      android:id="@+id/editText" /> 

  <EditText 
      android:layout_width="match_parent" 
      android:layout_height="wrap_content" 
      android:inputType="textEmailAddress" 
      android:hint="Email address" 
      android:id="@+id/editText2" /> 

  <EditText 
      android:layout_width="match_parent" 
      android:layout_height="wrap_content" 
      android:inputType="textPostalAddress" 
      android:hint="Postal address" 
      android:id="@+id/editText3" /> 

  <EditText 
      android:layout_width="match_parent" 
      android:layout_height="wrap_content" 
      android:inputType="textPassword" 
      android:hint="Password" 
      android:id="@+id/editText4" 
      android:layout_gravity="center_horizontal" /> 

  <Button 
      android:layout_width="wrap_content" 
      android:layout_height="wrap_content" 
      android:text="Sign up" 
      android:id="@+id/button" /> 
</LinearLayout> 

下面是这种简单表单在用户设备上的样子:

EditText

由于 EditText 类是从 TextView 类派生出来的,因此许多 TextView 属性也适用于 EditText,包括 textColortextSizetextStyletypeface。但是,EditText 也支持一系列特定于 EditText 类的 XML 属性。

控制键盘行为 - 设置输入类型

默认情况下,当用户点击 EditText 字段时,将出现标准键盘,他们可以输入他们喜欢的任何字符。但是,您可能希望使用 android:inputType 属性限制用户可以输入到您的 EditText 字段中的数据类型。

这种限制可以防止用户输入无效数据,但某些 inputType 值也会提示 Android 系统针对特定类型的内容优化虚拟键盘。例如,如果您指定 EditTextinputType 值为电话号码 (android:inputType="phone"),Android 系统将显示数字键盘,这使得用户输入所需数据变得更容易。

一些 inputType 值也会触发键盘的其他有用行为;例如,如果您将 inputType 设置为 textCapWords,键盘将自动将每个新单词的首字母大写。当您要求用户输入应始终大写的数据时,这很有用,例如他们的名字或地址。

下面是您可以使用的不同 inputType 值:

  • text:这显示标准文本键盘

  • textEmailAddress:这显示带有 @ 字符的标准文本键盘

  • textUri:这显示带有 / 字符的标准文本键盘

  • number:这显示基本的数字键盘

  • phone:这显示电话风格的键盘

所有以下值都显示标准文本键盘,但修改其他键盘行为:

  • textCapSentences: 这会自动将每个新句子的第一个字母大写。

  • textCapWords: 这会自动将每个单词的首字母大写。

  • textAutoCorrect: 这会自动纠正常见的拼写错误。

  • textPassword: 这通过将每个输入的字符转换为点来隐藏用户的密码。这是你在 Android 设备上输入密码时通常遇到的行为。创建此隐藏效果的另一种方法是使用 XML 属性android:password="true."

  • textMultiLine: 默认情况下,EditText字段被限制为单行。此属性允许用户输入多行文本。

你也可以使用管道字符组合多个值。例如,如果你要求用户创建一个密码,你可以自动将每个新单词的首字母大写,同时隐藏他们的输入:

android:inputType="textCapWords| textPassword" 

要以编程方式指定键盘行为,请使用setRawInputType方法。

注意

当我们谈论控制用户输入时,你可以使用android:digits (android:digits="12345.")来限制用户可以输入到EditText中的数字。

android:imeOptions

一旦用户在EditText字段中输入了信息,他们通常会通过点击键盘上通常的回车键替代的操作键来确认他们的输入。

如果你没有指定系统应显示哪个操作键,Android 系统默认为以下:

  • actionNext: 如果至少有一个可聚焦字段可以移动到,系统将显示下一步操作键。

  • actionDone: 如果没有后续的可聚焦字段,系统将显示完成键。在我们的form示例中,完成键会在用户完成最后的EditText字段后出现。

有时,你可能想覆盖此默认行为并指定键盘应显示哪个操作键。你可以使用android:imeOptions属性和以下值之一来完成此操作:

  • actionGo: 这将显示前往键(android:imeOptions="actionGo"))

  • actionNext: 这将显示下一个

  • actionDone: 这将显示完成

  • actionSearch: 这将显示搜索

  • actionSend: 这将显示发送

提供用户提示

尽管EditTexts旨在收集用户输入,但你可能希望通过在EditText字段中显示临时、灰色文本来提示用户进行特定输入。这些提示在用户不清楚应输入什么信息时非常有用。

你可以使用android:hint属性来显示提示信息:

  • 直接将提示信息输入到你的布局资源文件中(android:hint="请输入您的密码")

  • 创建并引用一个字符串资源(android:hint="@string/messageHint.")

ImageView

图片是向用户传达信息的一种便捷方式,无需强迫他们阅读大量屏幕上的文本。尽管您可以在应用中的许多不同区域添加图片(例如布局的背景和屏幕元素如按钮的背景),但 Android SDK 提供了一个专门的视图来显示图片,称为ImageView

我们已经讨论了如何使用密度无关和其他相对单位来创建可以在不同屏幕上正确显示的用户界面。然而,确保您的图片在不同屏幕尺寸上看起来清晰并不那么简单。

这使得ImageView成为更复杂的视图之一,但由于图片是大多数 Android UI 的核心部分,因此花时间正确掌握 Android 的ImageView是非常值得的。

在本节中,我将向您展示如何将可绘制物添加到您的 UI 中,以及您应采取的步骤以确保这些图片在所有可能的屏幕尺寸范围内正确显示。

注意

可绘制物仅意味着“可以在屏幕上绘制的东西”,通常用来描述您应用的图形内容。

支持多屏幕

让我们先解决一些棘手的问题:如何创建可以在广泛不同屏幕上正确显示的图片。

虽然 Android 系统会自动缩放您的内文以适应当前的屏幕配置,但您不应依赖系统为您完成所有繁重的工作,尤其是当涉及到ImageView时,因为这可能导致模糊或像素化的图片。

为了提供最佳的用户体验,您需要为应用中使用的所有图片提供备用版本。这些版本应针对不同的屏幕密度进行优化。

好消息是,您不必为所有可想象的屏幕密度提供图片,因为 Android 系统将所有可能的屏幕密度分组到通用的密度范围内。只要您为每个密度范围提供版本,Android 系统就会选择最适合当前屏幕配置的版本。

Android 支持五种主要的通用屏幕密度:

  • 低分辨率: ldpi 120dpi

  • 中等分辨率: mdpi 60dpi

  • 高分辨率: hdpi 240dpi

  • 超高分辨率: xhdpi 320dpi

  • 超超高分辨率: xxhdpi 480dpi

注意

Android 实际上支持第六种屏幕密度:超超超高分辨率,也称为xxxhdpi。这个640dpi的密度范围与其他的不同,因为它适用于您应用程序的启动器图标。一些设备,如平板电脑,可能在启动器中显示超大型应用图标。为了确保您的应用图标在具有xxxhdpi显示的大屏幕上不会看起来模糊,您应该提供您应用图标的超超超高密度版本。您不需要为任何其他 UI 元素提供xxxhdi版本。

支持不同的屏幕密度

那么,你是如何让 Android 知道哪个图像是针对hdpi显示屏优化的,哪个图像是针对xhdpi显示屏优化的呢?答案是创建带有ldpimdpihdpixhdpixxhdpixxxhdpi限定符的目录。Android 会识别这些目录包含针对特定屏幕密度的资源,然后根据当前的屏幕配置从适当的目录中选择图像。

当你在 Eclipse 或 Android Studio 中创建 Android 项目时,项目通常只包含默认的res/drawable目录,因此你需要手动创建以下目录:

  • drawable-ldpi

  • drawable-mdpi

  • drawable-hdpi

  • drawable-xhdpi

  • drawable- xxhdp

  • drawable-xxxhdpi

记住,这个目录应该只包含应用启动器的额外额外额外高密度版本。

要创建这些密度特定的目录,执行以下步骤:

  1. 右键点击你的项目res文件夹,选择新建,然后选择Android 资源目录

  2. 在出现的窗口中,打开资源类型下拉菜单,选择Drawable

  3. 可用限定符部分,将密度添加为选择的限定符

  4. 打开密度下拉菜单,从列表中选择所需的密度,例如,如果你正在创建drawable-ldpi目录,则选择低密度;如果你正在创建drawable-xxxhdpi目录,则选择XXX-高密度。你会注意到,当你选择密度时,目录名称会自动更新:支持不同的屏幕密度

  5. 当你对输入的信息满意时,点击确定。然后你的 IDE 将创建新的目录。

  6. 重复!你通常希望为每个通用的屏幕密度创建一个目录。

如果你正在使用 Android Studio 中的项目,有时你可能创建了所有密度特定的目录,然后意识到它们在 Android Studio 的项目视图中都没有出现。如果发生这种情况,问题可能在于你选择了Android视图而不是项目视图。要切换视图,请点击 Android Studio 的项目视图中的Android标签,然后从下拉菜单中选择项目

支持不同的屏幕密度

你会注意到你的项目结构已经改变。打开app/src/main/res文件夹,你会看到你之前创建的所有密度特定的目录。

创建密度特定图像

现在你已经更新了项目结构,是时候实际创建那些优化图像并将它们添加到你的项目中。

Android 支持多种不同的图像类型,但通常你会使用以下之一:

  • 位图:Android 支持三种格式的位图文件:.png(首选),.jpg(可接受),或者如果你真的必须.gif(不推荐)。

  • Nine-patch 文件:这是一个.png文件,但有所不同!Nine-patch 文件允许你定义可拉伸的区域,这有助于你的图像更平滑地调整大小。我们将在后面的章节中更详细地探讨 nine-patch 图像。

创建替代位图和 nine-patch 文件的关键是遵守 3:4:6:8:12:16 的缩放比例。例如,如果你有一个 68 x 68 像素的图像,目标是中等密度的屏幕,你需要创建以下替代版本:

  • LDPI: 51 x 51 像素(原始大小的 0.75%)

  • MDPI: 68 x 68 像素(原始大小)

  • HDPI: 102 x 102 像素(原始大小的 150%)

  • XHDPI: 136 x 136 (原始大小的 200%)

  • XXHDPI: 204 x 204 (原始大小的 300%)

  • XXXHDPI: 272 x 272 (原始大小的 400%)

当你创建替代图像时,必须为每个图像版本使用相同的文件名。如果 Android 系统要识别这些文件为同一图像的替代版本,这是必不可少的。

最后一步是将每个文件放入适当的目录中。

添加 ImageView

创建正确的drawable目录结构和提供相同图像的多个版本可能感觉像是一项大量工作,但一旦你完成了所有这些基础工作,在ImageView中显示drawable内容就相对简单直接。

大多数情况下,你将通过向 Activity 的布局资源文件添加<ImageView>元素来创建你的ImageView

<ImageView 
     android:id="@+id/imageView1"        android:layout_width="wrap_content"        android:layout_height="wrap_content" 
     android:src="img/myImage" 
/ > 

注意android:src属性。这是你告诉ImageView显示哪个drawable的方式。假设你已经提供了几个版本的myImage文件,Android 系统会检查每个drawable目录以找到最合适的版本,然后显示此图像。

如果你更喜欢以编程方式设置ImageView内容,可以使用setImageResource()方法代替。

按钮和 ImageButtons

Buttons(以及由此扩展的ImageButtons)是响应用户触摸屏幕的 UI 组件。每次你在 UI 中添加按钮时,用户应该立即清楚这个按钮在被触摸时将执行什么操作。

虽然你可以通过伴随的TextView(例如点击下面的按钮进入下一屏幕)来告知用户按钮的目的,但这很啰嗦且效率低下。大多数情况下,你将通过向此按钮添加标签来传达按钮的目的。

Android 为你提供了几个标签选项:

  • 文本标签,例如下一步提交取消

  • 图像图标,例如勾选标记或交叉图标。

  • 两者都要!如果按钮代表不寻常、意外或复杂的操作,你可能希望通过在按钮上添加文本和图像来消除任何潜在的混淆:

按钮和 ImageButtons

根据你希望按钮显示文本、图标还是两者,你可以通过以下三种方式之一将按钮添加到布局资源文件中。

创建带有文本标签的按钮

要创建一个带有文本标签的基本按钮,请将<Button>元素插入到您的布局资源文件中:

<Button 
android:layout_width="wrap_content" 
android:layout_height="wrap_content" 
android:text="@string/button_text" /> 

您可以使用android:text属性设置按钮的文本标签。与TextViews一样,您可以直接将文本插入到布局中(android:text="Submit")或您可以在项目的res/values/strings.xml文件中创建一个字符串资源,然后引用该字符串资源(android:text="@string/submitText")。

要以编程方式设置按钮的文本,请使用setText

      button.setText("Submit!"); 

创建带有图像标签的按钮

如其名所示,ImageButton是一个带有图像标签的按钮。

要将ImageButton插入到您的 UI 中,您需要在布局资源文件中添加<ImageButton>标签:

<ImageButton 
  android:layout_width="wrap_content" 
  android:layout_height="wrap_content" 
  android:id="@+id/imageButton" 
  android:src="img/send" /> 

认识到android:src属性吗?它引用了一个drawable资源,与ImageView引用drawable资源的方式完全相同。

创建带有文本和图像标签的按钮

如果您想确保用户在触摸按钮时完全清楚会发生什么,您可以使用文本和图像标签该按钮。这涉及到使用带有android:drawableLeftButton类:

<Button 
  android:layout_width="wrap_content" 
  android:layout_height="wrap_content" 
  android:id="@+id/imageButton3" 
  android:text="Send" 
  android:drawableLeft="@android:drawable/send" /> 

小贴士

更改按钮的背景

如果您觉得这三个选项还不够,您还可以使用android:background更改按钮的背景,然后引用一个颜色值或一个图像。

状态列表资源

您典型的按钮有三个状态:

  • Default:此按钮既未被按下也未被聚焦

  • Pressed:此按钮处于按下状态

  • Focused:此按钮当前处于焦点状态

我们之前讨论的所有按钮属性(文本标签、图像标签和背景)都适用于按钮元素,无论其当前状态如何。但有时您可能希望按钮向用户提供关于其当前状态的视觉提示。例如,当用户轻触按钮时,您可能希望它在按下状态下短暂显示较深的颜色,这样用户就知道他们的设备已成功注册了触摸事件。

如果您希望按钮对其当前状态做出反应,您需要创建一个状态列表资源,这是一个定义每个按钮状态使用不同图像或颜色的 XML 文件。

要创建此状态列表资源,请执行以下操作:

  1. 创建三个可绘制对象作为按钮的背景。这些可绘制对象代表按钮的每个状态。

  2. 给每个drawable一个反映其状态的名称,例如button_pressedbutton_default

  3. 将这些可绘制对象添加到适当的res/drawable目录中。

  4. 在您的项目res/drawable目录中创建一个新的 XML 文件,通过在drawable目录上右键单击并选择新建,然后选择可绘制资源文件。给 XML 文件一个描述性的名称,例如button_states,然后点击确定

打开你的新 drawable 资源文件,通过在单个 <selector> 元素内添加单独的 <list> 元素来定义你想要为每个状态使用的所有 drawable

<?xml version="1.0" encoding="utf-8"?> 
<selector > 
<item android:drawable="@drawable/button_pressed" 
        android:state_pressed="true" /> 
   <item android:drawable="@drawable/button_focused" 
        android:state_focused="true" /> 
   <item android:drawable="@drawable/button_default" /> 
</selector> 

在前面的代码中,我们定义了按钮在每种状态下应该使用哪个 drawable

  • pressed = true 时,按钮应该使用 button_pressed drawable

  • focused = true 时,按钮应该使用 button_focused drawable

  • 如果按钮没有被按下或聚焦,它应该使用 button_default drawable

在你的状态列表资源中 <item> 元素的顺序很重要,因为当你引用这个状态列表时,系统会按顺序遍历 <item> 元素,并使用第一个适用于按钮当前状态的 <item> 元素。由于默认状态总是适用的,你必须始终将默认 drawable 放在列表的末尾,以确保它只有在系统首先检查并丢弃 android:state_pressedandroid:state_focused 后才会被使用。

要将状态列表资源应用到按钮上,你需要在 Activity 的布局资源文件中将其引用为一个单独的 drawable

<Button 
   android:id="@+id/button_send" 
   android:layout_width="wrap_content" 
   android:layout_height="wrap_content" 
   android:text="@string/button_send" 
   android:background="@drawable/button_custom" /> 

你还可以创建使用颜色而不是图像的状态列表资源。这类状态列表被称为 颜色状态列表资源。你使用相同的 <selector><item> 元素;唯一的不同是每个 <item> 元素引用的是颜色而不是 drawable

<item android:drawable="@android:color/holo_red_light"  
        android:state_pressed="true" /> 

或者

<item android:drawable=""#0000ff"  
        android:state_pressed="true" /> 

摘要

在本章中,我们介绍了最常用的视图和布局,并探讨了所有不同的自定义方式,以满足我们的特定设计需求。

尽管我们还没有介绍过一些视图和布局,但本章中我们探讨的许多属性都适用于我们在后续章节中遇到的视图和布局,例如 layout_widthandroid:srcandroid:id 以及相对度量单位。

在下一章中,我们将扩展使用资源,如字符串、九宫格图像、颜色和状态列表,并查看一些新的资源,包括数组和维度。

最后,你将学习到 UI 设计的一个重要方面,这是我们之前还没有涉及到的:片段

第三章。扩展你的 UI – 片段、资源以及收集用户输入

在上一章中,我们专注于为你的应用程序用户界面创建一个坚实的基础。在本章中,我们将使用额外的资源,如数组、尺寸和 9-patch 图像,在此基础上构建。

一旦我们完善了 UI 的 外观,我们将探讨如何使 UI 对用户输入做出反应,然后再看看一个可以帮助我们充分利用大屏幕设备的 UI 组件:片段。由于片段不是利用额外屏幕空间的 唯一 方法,我们还将看看 Android N 中即将推出的多窗口和画中画模式。

更多资源类型

如果你的应用程序有任何文本,那么一般来说,文本应该属于你项目中的 res/strings.xml 文件作为字符串资源,而不是在应用程序代码中。尽管我们在上一章中提到了字符串资源,但它们是绝大多数 Android 应用程序的一个基本组成部分,因此有必要更详细地研究它们,特别是关于你可以使用字符串资源执行的一些更复杂的任务,例如创建字符串数组。

创建和样式化字符串资源

字符串是一个简单的资源,你只需在项目中的 res/values/strings.xml 文件中定义一次,然后在整个项目中多次使用它。

你可以使用以下语法定义字符串资源:

<resources> 

<string name="string_name">This is the text that'll appear whenever you reference this string resource.</string> 

</resources> 

你还可以选择为字符串资源添加样式属性,这样每次你在应用程序中使用字符串资源时,它都有完全相同的样式。Android 支持以下 HTML 标记:

  • <b> 用于加粗文本

  • <i> 用于斜体文本

  • <u> 用于下划线文本

将你选择的 HTML 标记包裹在你想样式的文本周围:

<?xml version="1.0" encoding="utf-8"?> 
<resources> 

<string name="Hello World">Hello world, <b>welcome</b> to my app!</string> 

<string name="click">Please click the <i>button</i> to continue</string> 

</resources> 

虽然你通常会在应用程序的布局资源文件中引用字符串资源,但你也可以通过 Java 代码引用字符串:

String string = getString(R.string.string_name);

创建字符串数组

字符串数组正如其名:字符串资源数组。

当你有多个相关的字符串总是同时出现时,字符串数组非常有用,例如在重复出现的菜单中的选项列表。

虽然你可以将每个项目定义为单独的字符串资源,然后单独引用每个字符串,但这非常耗时,而且你还需要记住一个长长的不同字符串 ID 列表!通常,将所有字符串资源添加到单个数组中更有意义,这样你只需通过引用单个字符串数组就可以显示所有这些字符串。

你通常在专门的 res/values/arrays.xml 文件中创建字符串数组。例如,以下 XML 定义了一个名为 ingredients 的字符串数组,它包含五个字符串资源:

<?xml version="1.0" encoding="utf-8"?> 
<resources> 

   <string-array  

//Create your string array// 

name="ingredients"> 

//Give your array a descriptive name// 

      <item>Self-raising flour</item> 

//Add each string to your array// 

      <item>Butter</item> 
      <item>Caster sugar</item> 
      <item>Eggs</item> 
      <item>Baking powder</item> 
   </string-array> 

</resources> 

要加载这个字符串数组,请使用 Resources 类的 getStringArray() 方法:

Resources res = getResources(); 
String[] ingredients = res.getStringArray (R.array.ingredients); 

字符串数组对于快速填充旋转器控件也非常有用。假设旋转器控件的选项是静态和预定的,您可以在字符串数组中定义所有这些选项,然后加载数组到旋转器控件中。

注意

旋转器是一个用户界面元素,用户可以从选项列表中选择一个值。当用户触摸旋转器控件时,会弹出一个下拉菜单,显示所有可用选项。用户可以从列表中选择一个选项,然后旋转器控件将显示所选选项在其默认未打开状态下。

要使用字符串数组填充一个旋转器,请将旋转器控件添加到您的活动布局资源文件中,然后使用android:entries:引用数组

<Spinner 
    android:layout_height="wrap_content" 
    android:layout_width="match_parent" 
    android:id="@+id/spinnerOfIngredients" 
    android:entries="@array/ingredients"> 
</Spinner> 

dimens.xml中定义维度

Android 支持多种不同的度量单位,您可以使用 XML 或 Java 将其硬编码到项目中,例如android:textSize="20sp"。然而,您也可以在项目中的res/values/dimens.xml文件中预先定义您想要使用的维度:

<?xml version="1.0" encoding="utf-8"?> 
<resources> 
  <dimen name="textview_width">26dp</dimen> 
  <dimen name="textview_height">35dp</dimen> 
  <dimen name="headline_size">41sp</dimen> 
  <dimen name="bodytext_size">20sp</dimen> 
</resources> 

然后,您可以通过引用dimens.xml文件中的相应值来设置您的 UI 组件的大小:

<TextView 
   android:layout_height="@dimen/textview_height" 
   android:layout_width="@dimen/textview_width" 
   android:textSize="@dimen/headline_size"/> 

您还可以使用 Java 应用dimens.xml文件中的值:

textElement.setWidth(getResources().getDimensionPixelSize(R.dimen.headline_size)); 

但是,为什么还要费劲使用dimens.xml文件,当您可以直接在布局中添加维度信息时?

虽然这似乎是最快的选择,但将维度与布局和应用程序代码混合并不是一个好的实践。类似于字符串资源,dimens.xml文件提供了一个单一、专门的地点,您可以在这里更改项目维度,而无需触及您的其他代码。它还有助于创建一致的用户界面,因为它鼓励您定义一组值一次,然后在您的整个应用程序中使用这些相同的值。

其次,您可以使用多个dimens.xml文件来创建一个更灵活的用户界面。以这种方式使用dimens.xml文件确实需要一些准备工作,因为您需要创建多个针对 Android 不同通用密度的res/values文件夹。同样地,您也需要创建多个drawable文件夹来管理您项目中的图片。

打开您项目的res文件夹,并创建以下文件夹:

  • values-ldpi:针对 120dpi 设备

  • values-mdpi:针对 60dpi 设备

  • values-hdpi:针对 240dpi 设备

  • values-xhdpi:针对 320dpi 设备

  • values-xxhdpi:针对 480dpi 设备

然后,在每个文件夹中创建一个dimens.xml文件:

在中定义维度

这可能看起来工作量很大,但一旦您建立了这种结构,您就可以使用您的dimens文件来定义针对每个屏幕密度类别的优化值集。然后,当 Android 系统加载您的布局时,它将选择最适合当前屏幕配置的dimens.xml文件,并将文件中的维度应用到您的布局上。

小贴士

下载示例代码

您可以从www.packtpub.com的账户下载本书的示例代码文件。如果您在其他地方购买了此书,您可以访问www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给您。您可以通过以下步骤下载代码文件:

  1. 使用您的电子邮件地址和密码登录或注册我们的网站。

  2. 将鼠标指针悬停在顶部的支持选项卡上。

  3. 点击代码下载与勘误

  4. 搜索框中输入书籍名称。

  5. 选择您想要下载代码文件的书籍。

  6. 从下拉菜单中选择您购买此书的来源。

  7. 点击代码下载

您还可以通过点击 Packt Publishing 网站上书籍网页上的代码文件按钮来下载代码文件。您可以通过在搜索框中输入书籍名称来访问此页面。请注意,您需要登录您的 Packt 账户。

文件下载完成后,请确保使用最新版本的软件解压缩或提取文件夹:

  • 适用于 Windows 的 WinRAR / 7-Zip

  • 适用于 Mac 的 Zipeg / iZip / UnRarX

  • 适用于 Linux 的 7-Zip / PeaZip

颜色状态列表

每当用户与 UI 元素交互时,您的应用程序都会发出信号,表明它已记录此交互。有时,此信号是内置的:新屏幕加载,弹出窗口打开,或复选框中出现勾选。然而,如果无法立即明显地看出 UI 元素已记录用户的输入,您应提供视觉提示。一种可能的方法是使用颜色状态列表

颜色状态列表定义了一系列状态,并为这些状态中的每一个分配了一种颜色。当您将颜色状态列表应用于视图时,视图将根据其当前状态显示列表中的不同颜色。

颜色状态列表最常应用于按钮。例如,一个灰色按钮在按下状态时可能会短暂地变成更深的灰色,这样用户就不会对按钮是否已记录他们的交互产生任何疑问。

注意

颜色状态列表类似于我们在上一章中查看的状态列表资源——我们只是使用颜色而不是图像来表示状态的变化。

要创建颜色状态列表,打开您的项目res/drawable文件夹,并创建一个新的 XML 文件;给文件起一个表明其用途的名字,例如res/drawable/button_background.xml。然后,在此文件中填写所有想要触发颜色变化的不同状态以及您想要使用的颜色。

可能的状态包括以下内容:

  • android:state_pressed="true/false."

  • android:state_focused="true/false."

  • android:state_selected="true/false."

  • android:state_checkable="true/false."

  • android:state_checked="true/false."

  • android:state_enabled="true/false."

  • android:state_window_focused= "true/false."

在这个例子中,我们将向颜色状态列表中添加两个状态:

<selector  > 

//A color state list must be inside a single selector element// 

<item 

//Add each state and color as a separate <item>// 

android:state_pressed="true" 

//If the current state is pressed...// 

android:color="@color/green" /> 

//....Apply the color green to this view// 

<item 

android:color="@color/blue/> 

//If the View is in its default state, apply the color blue instead. We're using blue as the default that'll be applied to the view when none of the above states are relevant// 

注意

你在选择元素内放置<item>元素的顺序至关重要,因为系统会按照顺序遍历颜色状态列表,并选择第一个适用于视图当前状态的项。正如前一个示例中看到的,你可以创建一个默认颜色,当其他状态都不适用时,这个颜色将被应用到视图中。如果你创建了默认状态,那么你必须始终将其放置在颜色状态列表的末尾,作为最后的手段。

剩下的唯一事情就是将颜色状态列表资源应用到你的视图中:

<Button 
   android:layout_width="match_parent" 
   android:layout_height="wrap_content" 
   android:text="@string/button_text" 
   android:background:="@drawable/button_background" /> 

你也可以使用R.drawable.button_text在 Java 中引用你的颜色状态列表资源。

与 9-patch 图像一起工作

9-patch 图形是一种可拉伸位图,允许你在需要调整图像大小以适应当前屏幕时,定义系统可以和不可以拉伸的区域。

当你将普通图像转换为 9-patch 图像时,你会在图像的顶部和左侧添加一个额外的 1 像素宽的边框。这个 1 像素的边框精确地指出了系统在需要创建拉伸效果而不是简单地调整整个图像大小时应该复制的像素:

与 9-patch 图像一起工作

你的边框不必是连续的线条;如果你有任何不想让系统拉伸的区域,只需在该区域留出空白即可。在先前的示例中,系统可以通过复制带有黑色线条标记的像素来水平或垂直拉伸可绘制元素。然而,角落没有被标记,所以这些角落将保持相同的大小,从而创建一个更尖锐、更清晰的角落。

注意

将图像转换为 9-patch 格式会在你的图像外围添加一个额外的像素。当你创建图像的目的是将其转换为 9-patch 时,请记住这一点!

虽然你的图像可以包含多个可拉伸部分,但你的线条必须正好是 1 像素宽,Android 系统才能正确识别这些线条;相应地拉伸你的图像(如果适用),然后从完成的图像中移除线条。如果你添加任何超过一个像素宽的线条,系统将把这些线条视为图像的另一个部分。

如果你确实使用了 9-patch,你仍然需要为 Android 的通用屏幕密度(ldpimdpihdpixhdpixxhdpi)中的每一个提供这些图像的备用版本。当系统加载你的应用时,它会选择最适合当前屏幕密度的 9-patch 图像,然后如果需要,它会拉伸图像的可拉伸部分。

注意

9-patch 图像可以拉伸,但不能缩小。在创建针对不同密度文件夹的 9-patch 图像时,为了获得最佳效果,你应该针对每个密度类别选择最低的共同分辨率。

我该如何创建 9-patch 图像?

有很多不同的 PNG 编辑器,但我会使用 Draw 9-patch,因为这个编辑器包含在 Android SDK 中,所以您很可能已经在计算机上安装了它。

您可以在计算机的sdk/tools文件夹中找到Draw9patch.bat程序。启动编辑器,通过将其拖动到 Draw 9-patch 窗口中导入您的图像。

9-patch 工作空间包括以下内容:

  • 左侧面板:这是绘图区域,您在这里定义图像的可拉伸部分。

  • 右侧面板:这是预览区域,它显示了图形拉伸后的预览效果。当您在左侧面板中编辑图像时,请确保您关注这个预览。

如何创建 9-patch 图像?

要定义可以水平拉伸的区域,通过点击在图像顶部绘制一条线。每次点击都会在您的线上添加一个新像素。

如何创建 9-patch 图像?

如果您犯了错误,可以在点击每个要删除的像素时按住Shift键来删除像素。

要定义可以垂直拉伸的区域,点击以在图像的左侧边缘绘制一条线。

一旦您对结果满意,通过选择文件 | 保存 9-patch来保存您的 9-patch 图像。这将使用.9.png扩展名保存您的图像。然后您可以将此图形资源添加到您的项目中。

注意

如果您不喜欢 Draw 9-patch,或者您只是想尝试一个替代方案,有几个免费的在线工具可以帮助您创建 9-patch 图像,包括draw9patch.com/

注册用户输入

输入控件是您 UI 的交互组件,例如按钮、EditText字段和复选框。

我们已经看到了如何将输入控件拖放到您的 UI 中,但输入控件默认不注册用户输入。

要将 UI 组件,如按钮和EditText字段,转换为完全功能的交互组件,您需要编写一些额外的代码。

处理点击事件

点击事件是您的 UI 必须处理的最常见的输入事件之一。点击事件简单地说是用户触摸屏幕上的元素,例如点击按钮或复选框。

按钮不能单独处理点击事件;您需要创建一个监听器并将其分配给按钮。当用户点击按钮时,监听器会注册点击并执行您的onClick方法中的代码。

让我们假设您的布局中只有一个按钮,并且您希望该按钮能够注册点击事件。与 Android 中的许多事物一样,您可以通过 XML 或通过您的应用程序代码创建相同的效果。

通过 Java 处理 onClick

您可以使用Java来处理这些事件。

Button button = (Button) findViewById(R.id.button1

//Get a reference to the view you want to assign the listener to// 
button.setOnClickListener(new View.OnClickListener() { 

//Assign the setOnClickListener listener to this view// 

                @Override 
                public void onClick(View view) { 

//Override the onClick method//  

.... 
.... 

// This is where you'd implement the code that tells your app what action it needs to perform whenever it registers a click event, for example you might want your app to launch a new activity, open a menu, or start playing a video// 

} 
  }); 

通过 XML 处理 onClick

您可以通过在布局资源文件中添加android:onClick属性到您的视图,然后添加相应的 Java 文件中的onClick方法来创建相同的功能:

<Button 
      android:id="@+id/button1"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:onClick="buttonClicked"
      android:text="Click me" /> 

无论用户何时点击此按钮,都会执行 buttonClicked 方法,因此下一步是将此方法添加到您的 Java 文件中:

public void buttonClicked(View v) { 
Toast.makeText(this, "The button has been clicked!", Toast.LENGTH_LONG).show(); 

//So you can test whether the buttonClicked method is working correctly, we'll tell the app to display a "The button has been clicked" message whenever buttonClicked is executed//  

  } 

注册 EditText 输入

当您将 EditText 添加到 UI 中时,用户可以在字段中输入文本,但默认情况下,EditText 无法读取或使用这些信息。

为了让 EditText 获取用户输入,您需要执行以下操作:

  1. 使用 findViewById 获取 EditText 字段的引用。

  2. 使用 getText()EditText 字段获取文本。

您可能还希望使用 setText() 在 UI 的其他地方显示此文本。

现在您已经知道如何注册点击事件并从 EditText 获取用户输入,让我们看看一个结合所有这些功能的示例应用。

示例应用

我们将创建一个简单的应用,要求用户在 EditText 中输入他们的名字,然后轻触 提交 按钮。应用将然后从 EditText 字段中检索用户的姓名,并在 TextView 中作为欢迎信息的一部分显示它。

创建一个包含这三个屏幕元素的简单布局:

<LinearLayout 

  android:orientation="vertical" 
  android:layout_width="match_parent" 
  android:layout_height="match_parent" > 

      <TextView 
          android:id="@+id/textView1" 
          android:layout_width="wrap_content" 
          android:layout_height="wrap_content" 
          android:text="@string/form" 
          android:textColor="@color/blue" /> 

//Add the TextView. Initially this view will display instructions, but once the user has submitted their name it'll update to display our welcome message instead// 

       <EditText 
          android:id="@+id/editText1" 
          android:layout_width="wrap_content" 
          android:layout_height="wrap_content" 

//Add the EditText// 

          android:background="@color/grey" 

//Depending on your app's color scheme, an empty EditText may blend into the background, so you may want to give the EditText its own background color//  

          android:hint="@string/yourName" 
          android:ems="10" > 

//Make it clear what information the user needs to enter, using android:hint// 

          <requestFocus /> 
      </EditText> 

      <Button 
          android:id="@+id/button1" 
          android:layout_width="wrap_content" 
          android:layout_height="wrap_content" 
          android:background="@color/blue" 
          android:text="@string/submit" 
          android:textColor="@color/white" /> 

//Add the Submit button// 

</LinearLayout> 

接下来,打开 res/values/strings.xml 并创建字符串资源:

<resources> 
<string name="app_name">Form</string> 
  <string name="form">Please complete the form below</string> 
  <string name="yourName">Enter your name</string> 
  <string name="submit">Submit</string> 
</resources> 

示例应用

现在您已经有了 UI,是时候让这些屏幕元素能够注册并处理用户输入了。

在此示例中,我们创建了一个事件监听器并将其分配给我们的 submitButton。当用户轻触 提交 按钮时,应用会注册此交互并检索 EditText 字段中当前的所有文本。然后它将此值设置为 TextView,用 EditText 的值替换默认的 请完成以下表格 文本,并添加其他两段文本,以创建我们的完整欢迎信息:

package com.example.jessica.myapplication; 

import android.os.Bundle; 
import android.app.Activity; 
import android.view.View; 
import android.widget.Button; 
import android.widget.EditText; 
import android.widget.TextView; 

public class MainActivity extends Activity { 

  Button submitButton; 
  EditText nameEdit; 
  TextView welcomeText; 

  @Override 
  public void onCreate(Bundle savedInstanceState) { 
      super.onCreate(savedInstanceState); 
      setContentView(R.layout.activity_main); 
      submitButton = (Button)findViewById(R.id.button1); 

  submitButton.setOnClickListener(new View.OnClickListener() { 
          public void onClick(View view) { 

//Assign the setOnClickListener to your submitButton// 

              nameEdit = (EditText) findViewById(R.id.editText1); 

//Get a reference to the EditText// 

              welcomeText = (TextView)                findViewById(R.id.textView1); 

//Get the text from nameText and set it to the welcomeText TextView. At this point, I'm adding a bit of extra text (Welcome, and !) to create a nicer greeting//  

              welcomeText.setText("Welcome " +                nameEdit.getText().toString() + "!"); 
          } 
      }); 
  } 

} 

就这样——启动您的应用并尝试与不同的 UI 元素进行交互。

示例应用

使用片段

当您为预 Android N 设备开发 UI 时,您将遇到的一个主要限制是,在任何时候您只能显示屏幕上的单个活动。片段为您提供了一种克服这种限制的方法;尽管技术上您仍然一次只能显示一个活动;每个活动可以由多个片段组成。

注意

Android N 引入了多窗口模式,这使用户能够同时显示多个应用,尽管它们来自不同的应用,但可以一次性查看多个活动!我们将在本章后面更详细地探讨多窗口模式。

一个片段是您应用用户界面中的一个独立、模块化的部分,您可以在一个活动中嵌入它。您不能将片段作为一个独立的应用程序元素实例化。将片段视为一种具有自己的生命周期、行为和(通常是)自己的用户界面的子活动

我们为什么需要片段?

Android 团队在 Android 3.0 版本中引入了片段,也称为蜂巢,主要是为了帮助开发者更好地利用大设备(如平板电脑)上可用的额外屏幕空间。使用片段,您可以把每个活动分成不同的组件,然后分别控制每个部分。

您还可以创建多个布局,根据当前屏幕配置以不同的方式组合您项目中的片段。例如,您可以创建一个多栏布局,在单个活动中组合多个片段;您还可以创建一个单栏布局,单独显示每个片段,这更适合较小的屏幕。然后,您的应用程序可以根据当前设备选择最合适的布局(多栏或单栏)。

以这种方式,片段对于充分利用大屏幕设备非常有用,同时也能为在较小屏幕上查看您的应用的用户提供良好的用户体验。

注意

您还可能想使用片段来创建针对横向和纵向握持优化的布局。例如,您可以为横向握持设备创建一个布局,在该布局中,多个片段并排显示,而当设备处于纵向模式时,您可以创建一个单栏布局,一次显示一个片段。

使用片段的最后一个主要好处是,当片段的主活动正在运行时,您可以独立地添加、删除和替换每个片段,以创建一个真正动态的用户界面。

让我们看看一个提供单栏和多栏布局的应用程序可能的工作方式示例。

想象一个包含两个片段的活动(活动 1):片段 A片段 B片段 A 显示项目列表。当用户在片段 A中选择一个项目时,片段 B 会更新以显示与所选项目相关的信息。

此应用程序包含两个不同的布局,并根据设备屏幕的大小选择要显示的布局。如果屏幕足够大,可以容纳片段 A片段 B,则应用程序在活动 1中以多栏布局并排显示这些片段。

为什么我们需要片段?

如果没有足够的空间容纳两个片段,应用程序将分别显示每个片段,就像单栏布局中的不同屏幕一样。

在这种情况下,活动 1仅显示片段 A。当用户从片段 A中选择一个项目时,屏幕更新以显示片段 B。这意味着您的应用程序创建了一个全新的活动(活动 2),它仅仅是为了托管片段 B

为什么我们需要片段?

注意

虽然片段是重要的工具,可以帮助您创建更灵活的布局,但片段并不是万能的解决方案。即使您在用户界面中包含了片段,您仍然需要遵循所有常规的指南和最佳实践以及使用片段

片段生命周期

尽管片段有自己的生命周期,但它的生命周期会直接受到宿主活动生命周期的直接影响。宿主活动的每个生命周期回调都会导致其所有片段产生类似的回调;例如,当宿主活动的onStop()方法被调用时,活动中的所有片段也会收到onStop()的调用,当活动被销毁时,其所有片段也会被销毁。

您只能在宿主活动处于恢复状态时独立操作片段的生命周期。此时,您可以添加、移除和替换片段。然而,一旦宿主活动离开恢复状态,所有片段都会失去独立性,再次依赖于宿主活动的生命周期。

就像活动一样,片段可以存在于三种状态:

  • 恢复状态:片段在运行的活动中可见。

  • 暂停状态:另一个活动在前台并且有焦点,但片段的宿主活动仍然可见。

  • 停止状态:要么是宿主活动已被停止,要么是片段已被从活动中移除并添加到返回栈中。停止状态的片段仍然存活,但对用户来说不再可见。

活动生命周期和片段生命周期的最大区别在于如何恢复每个生命周期并获取其状态:

  • 活动:当一个活动被停止时,它会被放入由系统管理的活动返回栈中。

  • 片段:当一个片段被停止时,它只会被放入由宿主活动管理的返回栈中如果您明确请求保存该实例。为了进行此请求,在移除片段的事务中调用addToBackStack()

注意

什么是返回栈?为什么它如此重要?

返回栈会跟踪用户可以通过点击设备的返回按钮撤销的所有操作,无论是物理返回键还是 Android 的背光软键。如果您将片段添加到返回栈中,那么用户就可以回退到该片段。如果您没有将片段添加到返回栈中,用户就无法恢复该片段。

创建片段

在本节中,我将向您展示如何创建一个简单的片段。第一步是定义片段的 UI 组件:

  1. 打开您项目的res文件夹。

  2. 右键点击layout文件夹,选择新建,然后选择布局资源文件

  3. 给您的布局资源文件起一个描述性的名称,例如list_fragment

  4. 选择您想要使用的根元素选项。

  5. 请确保目录名称选项设置为布局

  6. 点击确定

打开您的新布局资源文件并定义您的片段 UI:

<?xml version="1.0" encoding="utf-8"?> 
<LinearLayout     
android:orientation="vertical"  
android:layout_width="match_parent" 
android:layout_height="match_parent"> 

 <TextView 
      android:layout_width="wrap_content" 
      android:layout_height="wrap_content" 
      android:text="This is a fragment" 
      android:id="@+id/textView" /> 

</LinearLayout> 

在我们创建实际的片段之前,让我们花一点时间来探讨向后兼容性问题;具体来说,如何在运行 Android Honeycomb 之前版本的 Android 设备上也能享受到片段的好处。

片段和向后兼容性

当你开发应用程序时,你应该尽可能支持尽可能多的 Android 版本,因为这将为你提供最广泛的潜在受众。由于片段直到 Android 3.0 才被引入 Android,如果你的应用程序需要与运行 Android Honeycomb 之前版本的设备兼容,你需要在项目中添加 Android 支持库v4 版本。

注意

Android 支持库是一组方便的代码库,它使你能够使用在 Android 平台的早期版本中不可用的特性和 API。

一旦将 v4 库添加到你的项目中,你就可以使用片段,同时保持与运行 Android 1.6 及以上版本的设备的向后兼容性。

要将此库添加到你的项目中,请启动 Android SDK 管理器,打开 Extras 文件夹并下载 Android 支持库(如果你使用 Eclipse)或 Android 支持存储库(如果你使用 Android Studio)。

如果你正在使用 Android Studio,请通过打开其模块级别的 build.gradle 文件并将支持库添加到依赖项部分来将库添加到你的项目中:

dependencies { 
.... 
.... 
.... 
  compile 'com.android.support:support-v4:23.1.0' 
} 

如果你正在使用 Eclipse 进行开发,请执行以下操作:

  1. 在你的项目根目录下创建 libs 目录。

  2. 在你的 Android SDK 目录中定位名为 JAR 文件的支持库(例如,<sdk>/extras/android/support/v4/android-support-v4.jar)。将此 JAR 文件复制到你在上一步中创建的 libs 目录中。

  3. 右键单击 JAR 文件,选择 Build Path,然后选择 Add to Build Path

你还需要从 v4 支持库中导入片段类(import android.support.v4.app.Fragment),并使用 FragmentActivity 而不是常规的 Activity 类来扩展(public class ListFragment extends FragmentActivity)。

注意

关于应该支持哪些版本的 Android 系统并没有硬性规定,尽管你通常会希望尽可能多地支持版本,同时不牺牲你的应用程序功能。如果你决定不支持 Android 平台的早期版本,那么你不需要使用支持库。如果你不确定,查看 Google 关于当前运行每个 Android 平台版本的 Android 设备百分比的统计数据可能会有所帮助,这些数据可在 developer.android.com/about/dashboards/index.html 找到。

创建你的片段类

除了创建布局之外,你还需要有一个与你的片段关联的类。这个类必须扩展 FragmentFragmentActivity

要将新类添加到你的项目中,请执行以下操作:

  1. 打开你的Java文件夹,然后右键单击你的项目包名。

  2. 选择新建然后Java 类

  3. 给你的类一个描述性的名称,例如,ListFragment

打开你的新ListFragment类;它将看起来像这样:

package com.example.jessica.myapplication; 

public class ListFragment { 
} 

你需要做出以下更改:

package com.example.jessica.myapplication; 

import android.os.Bundle; 
import android.support.v4.app.Fragment; 
import android.view.LayoutInflater; 
import android.view.View; 
import android.view.ViewGroup; 

public class ListFragment extends Fragment { 

//Remember, if you want your app to work on devices running anything earlier than Honeycomb, you need to extend FragmentActivity rather than extending the Fragment class// 

    @Override 

//To inflate your fragment's layout inside the current activity, you need to override the onCreateView() method//  

    public View onCreateView(LayoutInflater inflater, ViewGroup container,  

//Your implementation of onCreateView() must return a View, which is the root of your fragment's layout// 

Bundle savedInstanceState) { 

///savedInstanceState is a Bundle that passes data about the previous instance of the fragment, just in case this fragment is being resumed//  

      View view = inflater.inflate(R.layout.list_fragment, 
              container, false); 
      return view; 

//Inflate a new view hierarchy from the specified layout resource file. In this example, that's list_fragment.xml//   

  } 
} 

inflate()方法(在前面代码中)接受以下参数:

  • 正在填充的布局文件(R.layout.list_fragment)。

  • 应将填充的片段布局插入的父ViewGroupcontainer)。

  • 表示填充的布局应在填充期间附加到ViewGroup上的false布尔值。

下一步是将片段添加到你的活动中。你有两种选择:

  • 在你的活动对应的 XML 布局文件中嵌入片段

  • 通过你的应用程序代码在运行时添加片段

最直接的方法是将你的片段嵌入到布局文件中;然而,这确实有一个很大的缺点——当你声明性地添加片段时,片段是静态的,将一直保留在宿主活动上,直到它被销毁。你无法在宿主活动的生命周期中添加或删除此片段。

通过应用程序代码向活动添加片段提供了更多的自由和灵活性,但以编程方式实现片段会更困难。

由于这是最直接的方法,让我们先看看如何通过你的布局资源文件添加片段。

声明性地向你的活动添加片段

你可以使用与声明视图相同的方式使用<fragment>向活动添加片段:

<?xml version="1.0" encoding="utf-8"?> 
<LinearLayout  
android:orientation="vertical"  
android:layout_width="match_parent" 
android:layout_height="match_parent"> 

   <fragment 
      android:id="@+id/listFragment" 
      android:layout_width="match_parent" 
      android:layout_height="match_parent" 

//Add the fragment// 

     class="com.example.jessica.myapplication.ListFragment" /> 

//Identify the fragment you want to instantiate, using the class attribute// 

</LinearLayout> 

或者,你可以使用android:name来识别片段:

<fragment android:name="com.example.jessica.myapplication.ListFragment" 
android:id="@+id/listFragment" 
android:layout_width="match_parent" 
android:layout_height="match_parent" /> 

当创建此活动的布局时,系统将实例化指定的片段,检索其布局,然后将其显示在原始<fragment>标签的位置。

在运行时向活动添加片段

如果你想在宿主活动的生命周期中添加、删除或替换你的片段,事情会变得稍微复杂一些,因为你需要在运行时将这些片段放置在活动中,这意味着需要深入研究你的应用程序代码。

在这个例子中,我们将使用相同的list_fragment.xml文件和之前创建的ListFragment.java类。然而,我们将使用FrameLayout而不是<fragment>占位符,这是一个特殊的容器视图,指示片段最终将在布局文件中显示的位置:

<?xml version="1.0" encoding="utf-8"?> 
<LinearLayout  
android:orientation="vertical"  
android:layout_width="match_parent" 
android:layout_height="match_parent"> 

  <FrameLayout  

      android:id="@+id/fragment_container" 
      android:layout_width="match_parent" 
      android:layout_height="match_parent" /> 

</LinearLayout> 

然后你需要告诉你的活动在运行时用你的片段替换FrameLayout容器:

package com.example.jessica.myapplication; 

import android.support.v7.app.AppCompatActivity; 
import android.os.Bundle; 
import android.support.v4.app.FragmentActivity; 

public class MainActivity extends FragmentActivity { 

  @Override 
  public void onCreate(Bundle savedInstanceState) { 
      super.onCreate(savedInstanceState); 
      setContentView(R.layout.activity_main); 

       if (findViewById(R.id.list_fragment) != null) { 

          if (savedInstanceState != null) { 
              return; 
          } 

           // Create a new Fragment// 
          ListFragment firstFragment = new ListFragment(); 

          firstFragment.setArguments(getIntent().getExtras()); 

// If this activity was started with special instructions from an Intent, pass the Intent's extras to the fragment as arguments// 

           getSupportFragmentManager().beginTransaction() 

// Call the beginTransaction() method on the fragment manager instance// 

                  .add(R.id.list_fragment, 

//Call the add() method of the fragment transaction instance, and pass it the resource ID of the view that'll contain the fragment (R.id.list_fragment) and the fragment class instance(firstFragment)// 

firstFragment).commit(); 

//The final piece of the above code calls the commit() method of the fragment transaction// 

        } 
    } 
} 

当你在运行时添加片段时,你可以自由地根据需要添加、删除和替换此片段。这些更改被称为片段事务

片段事务和返回栈

片段事务是对活动做出的响应用户交互的更改。

每当您执行片段事务时,您都有选择将此事务保存到回退栈中的选项。如果您确实将事务添加到回退栈,用户可以通过按设备的物理 返回 按钮或软键来导航回此片段状态。

如果您执行的事务移除或替换了一个片段,并且没有将事务添加到回退栈,当您提交该事务时,该片段将被销毁,用户无法导航回它。

如果您想要将片段事务添加到回退栈中,请确保在活动 onCreate() 方法期间将片段添加到宿主活动中。然后,在提交将移除片段的事务之前,您可以通过调用 transaction.addToBackStack 来将片段添加到回退栈。

在下一节中,您将学习如何添加、移除和替换片段。无论您执行的是哪种片段事务,您都需要从 FragmentManager 类获取 FragmentTransaction 实例:

import android.support.v4.app.FragmentTransaction; 
import android.support.v4.app.FragmentManager; 

//Add the FragmentTransaction and FragmentManager import statements// 

FragmentManager fragmentManager = getSupportFragmentManager(); 
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); 

//Get the FragmentTransaction instance//  

您可以执行以下片段事务。

添加片段

您可以使用 add() 方法将片段添加到活动中。

add() 方法传递给您想要放置片段的 ViewGroup,识别您想要添加的片段,然后提交事务。例如,看看这里:

fragmentTransaction.add(R.id.fragment_container, firstFragment).commit(); 

移除片段

要从活动中移除一个片段,您需要使用 remove() 方法。此方法需要一个引用到您想要移除的片段实例,以及前面提到的 commit() 方法。

在这个例子中,我们正在移除一个名为 previousFragment 的片段:

fragmentTransaction.remove(previousFragment).commit(); 

替换片段

要在运行时用一个片段替换另一个片段,您需要调用片段事务实例的 replace() 方法。

以下示例展示了如何用一个片段替换另一个片段(newFragment),以便用户可以选择导航回上一个片段。我们还将替换的片段添加到回退栈中:

Fragment newFragment = new Fragment(); 

// Create a new fragment// 

FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); 

//Create a new transaction// 

transaction.replace(R.id.list_fragment, newFragment); 

//The replace() method takes two arguments: the id of the view containing the fragment that's being replaced, and an instance of the new fragment// 

transaction.addToBackStack(null); 

//Give the user a way of reversing the transaction by adding the replaced fragment to the back stack. Note that addToBackStack() takes an optional String parameter that identifies this fragment state on the back stack. If you don't need this parameter, you can just pass null, similar to what we're doing in this example// 

transaction.commit(); 

// Commit the transaction// 

注意

如果您在事务中添加了多个更改,然后调用 addToBackStack(),则在您调用 commit() 之前应用的所有更改都将作为一个单独的事务添加到回退栈中。

Android N 中的多窗口支持

从 Android N 开始,Android 操作系统在平板电脑和智能手机上都原生支持多窗口。

这种新的多窗口模式为用户提供了在分屏环境中同时显示多个应用的选择,可以是并排排列或上下排列。用户可以通过拖动分隔线来调整这些分屏应用的大小,使一个应用更大,另一个应用更小。

给用户同时查看多个应用的能力对生产力是个好消息,为多应用多任务处理铺平了道路,例如在 Google Chrome 中打开餐厅的地址,然后直接将地址输入到 Google Maps 中,或者在观看 YouTube 视频时无需放弃回复即将到来的短信。

多窗口支持的另一个主要好处是,用户可以直接从一项活动拖动数据并将其放入另一项活动中,只要这些活动共享同一屏幕。由于这种直接拖放对于各种日常任务都很有用,如果你的应用尚未支持拖放,那么你应该为 Android N 启用它。

多窗口模式是如何工作的?

安卓智能手机和平板电脑用户可以通过以下方式切换到多窗口模式:

  • 这也可以通过打开概览屏幕(也称为最近应用屏幕任务列表)并长按活动标题来实现。然后用户可以将活动拖动到屏幕的高亮部分以在多窗口模式下打开该活动。

  • 这可以通过打开他们想要在多窗口模式下查看的活动,然后按下概览按钮来实现。然后设备将当前活动置于多窗口模式,并打开概览屏幕,以便用户选择另一个活动来共享屏幕。

在多窗口模式下,任何给定时间只有一个活动是活跃的;这是用户最近与之交互的活动,也称为最顶层活动。所有其他活动都处于暂停状态,尽管它们对用户仍然可见。这意味着某些活动即使在暂停时可能也需要继续运行;例如,视频播放活动即使在它们不是最顶层活动时也应该继续播放视频内容。

注意

如果你正在开发一个视频播放应用,解决方案是在onStop中暂停视频,并在onStart中恢复播放,而不是在你的应用onPause处理程序中。

当用户与暂停的活动交互时,该活动将被恢复,而其他应用则被置于暂停状态。

为多窗口模式准备你的应用

如果你的应用针对 Android N 或更高版本,并且没有指定你的应用是否支持多窗口模式,Android 系统会假设你的应用包含多窗口支持。

然而,明确指出你的应用或活动是否支持多窗口模式,通过将新的android:resizeableActivity属性添加到项目Manifest文件的<activity><application>部分是一个好的做法:

  • android:resizeableActivity="true.":此应用或活动可以在手机和平板电脑上以多窗口模式启动。

  • android:resizeableActivity="false.":此应用或活动不能以多窗口模式启动。如果用户尝试以多窗口模式启动此活动,应用将接管整个屏幕。如果您想确保系统始终以全屏模式显示您的应用,您需要使用android:resizeableActivity="false"明确禁用多窗口支持。

您还可以设置最小允许尺寸,以便用户无法将您的 UI 缩小到指定的尺寸;您可以使用android:minimalSize属性这样做。如果用户尝试调整活动的大小,使其小于android:minimalSize,系统将裁剪活动到用户请求的大小,而不是缩小您的内容。

由于您的应用在多窗口模式下可能需要以不同的方式行为,Android N 扩展了Activity类,以便您可以查询活动以确定它是否处于多窗口模式:

  • Activity.inMultiWindow:此方法用于确定当前活动是否处于多窗口模式。此方法的Fragment版本是Fragment.inMultiWindow

  • Activity.onMultiWindowChanged:每当活动切换到或退出多窗口模式时,都会调用此方法。此方法的片段版本是Fragment.onMultiWindowChanged

测试您的应用的多窗口支持

如果您的应用针对 Android N 或更高版本,并且您没有明确禁用多窗口支持,那么您需要以多窗口模式测试您的应用,以确保您提供了最佳的用户体验。

特别是,您应该检查以下内容:

  • 您的应用在全屏和多窗口模式之间平滑切换。以全屏模式启动您的应用,然后切换到多窗口模式。检查此操作是否快速、平滑,并且不会导致您的应用延迟。

  • 您的应用在多窗口模式下正确调整大小。以多窗口模式启动您的应用,打开另一个应用,然后拖动分隔线以测试您的应用在不同尺寸下的表现。特别是,检查所有 UI 元素是否保持可见和可访问,检查触摸目标是否不会缩小到难以交互的程度,并检查您的应用文本是否可读。测试您的应用在与其他应用共享空间时,在并排和一上一下配置下的调整大小处理方式。您还应该检查连续执行多次调整大小操作不会导致延迟或使您的应用崩溃。

  • 系统尊重您的应用的最小尺寸。如果您已指定最小尺寸,请检查系统是否会通过拖动分隔线防止用户将您的应用缩小到android:minimalSize值以下。

  • 您的应用在可见但非活动状态下的行为符合预期。例如,如果您开发了一个视频播放应用,您应该验证当它不是最顶层活动时,您的应用是否继续按预期播放视频。

如果你明确禁用了多窗口支持(通过在Manifest中包含android:resizableActivity="false"),那么你也应该在 Android N 设备上安装你的应用,并验证是否无法以多窗口模式查看你的应用。

逐帧模式

Android N 并没有将多任务功能限制在智能手机和平板电脑上!Android 7.0 还引入了专为 Android TV 用户设计的多任务功能。

这种新的画中画PIP)模式为 Android TV 用户提供了在屏幕角落观看固定窗口的能力,同时另一个活动在后台运行。用户可以在这两个模式之间切换。如果用户尝试在主屏幕上播放另一个视频,PIP 窗口将自动关闭。

要在你的 Android TV 应用中使用此功能,你需要通过在Manifest中添加android:resizeableActivity="true"android:supportsPictureInPictur="true"来注册你的应用的视频活动。然后,你可以通过调用getActivity().enterPictureInPicture来决定触发你的应用中 PIP 模式的事件。

当你的活动切换到 PIP 时,系统认为活动处于暂停状态,并调用你的活动的onPause方法。然而,PIP 的整个目的就是你的应用继续在屏幕角落播放视频。因此,确保你的应用检查活动是否因为处于 PIP 模式而暂停是至关重要的。如果是,你的应用将继续播放其视频内容:

@Override 
public void onPause() { 

   // If onPause is called due to PIP, do not pause playback//  

if (inPictureInPicture()) { 
       // Continue playback// 

       ... 
   } 

   // If the activity isn't paused due to PIP mode, then pause playback if necessary//   

   ... 
} 

请记住,在 PIP 模式下,你的视频内容在一个小的叠加窗口中显示。这意味着用户无法清楚地看到小细节或与任何 UI 元素交互。因此,如果你的视频活动具有这些功能之一,你应该在活动进入 PIP 时移除它们。然后,当你的活动切换回全屏模式时,你可以恢复这些 UI 元素。

例如,看看这个:

@Override 
public void onPictureInPictureChanged(boolean inPictureInPicture) { 
   if (inPictureInPicture) { 

       //This is where you'd hide the controls in PIP mode//  

       ... 
   } else { 

       //This is where you'd restore any controls that are hidden when the activity enters PIP mode// 

       ... 
   } 
} 

摘要

在本章中,我们看到了如何使用数组、维度、9-patch 图像和颜色状态列表将基本 UI 提升到下一个层次。我们还探讨了如何通过将片段和多窗口模式纳入你的设计来创建一个更灵活的用户界面。

现在我们已经花费了几章来探讨构建有效 UI 的机制,现在是时候转换一下思路,看看创建出色的 Android 用户界面的理论了。

随着 Android 平台不同版本的推出,已经出现并消失了大量的最佳实践和指南,但在 5.0 版本中,Android 团队宣布了 Android UI 的全新方向。

它被称为 Material Design,这是我们下一章的主题。

第四章:开始使用材料设计

在 2014 年 Google I/O 大会上宣布,并在 Android Lollipop 中首次亮相,材料设计是来自 Google 的新设计语言。

这对 Android 来说尤其是个大新闻,因为材料设计的整体目的是提供更一致的用户体验;作为一个开放平台,Android 特别容易受到不一致性的影响。打开您的 Android 设备的应用程序抽屉,花些时间翻阅您的应用程序,您可能会遇到至少几个看起来和感觉与其他应用程序非常不同的应用程序。

材料设计旨在通过提供所需的工具和指南,改变这一切,以提供更统一的用户体验。

通过遵循材料设计原则,您可以创建外观美观、运行流畅,并感觉像是 Android 平台无缝扩展的应用程序。谁会不想要这样的呢?

然而,材料设计不仅仅是关于如何给按钮添加阴影、UI 的主要文本应该有多不透明的技术规范。因此,在我们深入探讨如何创建一个与 Google 新设计方向完美匹配的 UI 之前,让我们通过了解其背后的理论来更好地理解材料设计是什么。

材料设计的理念

材料设计基于将现实世界材料的物理属性转化为虚拟屏幕的想法,并且从纸张、墨水和印刷技术中汲取了许多灵感。

材料设计鼓励设计师和开发者创建屏幕上的对象,这些对象似乎具有现实世界对象的相同特性。这意味着使用阴影、光线和高度等技术来创造深度和边缘感。

材料设计对象的移动方式也模仿了现实世界中物体的移动方式;例如,两个现实世界物体不能同时占据同一空间或相互穿过,因此您的屏幕对象也不应该如此。

为了帮助您创造这种幻觉,材料设计引入了模拟 3D 空间的概念,其中所有 UI 对象都拥有XYZ坐标。Z坐标尤为重要,因为正Z轴向外延伸至用户,创造出对材料设计至关重要的深度感。

注意

在材料设计中,每个对象都占据Z轴上的一个位置,每个对象都有标准的 1dp 厚度。

所有操作都在材料设计的模拟 3D 环境中进行;物体出现、消失,并转化为新的物体,而不会打破连续 3D 环境的幻觉。

当物体在 Material Design 空间中移动时,它们模仿纸张如何被洗牌和装订在一起。例如,你可以沿着一个共同的边缘或 接缝 将两片材料绑在一起,使它们一起移动。相比之下,当两片材料重叠但沿着 Z 轴占据不同的位置时,它们 没有 被绑定在一起,因此可以独立移动。

这些设计原则可能会给你的用户界面带来独特的视觉和感觉,但 Material Design 不仅仅是关于你的 UI 看起来如何。你可以使用 Material Design 元素,如深度和阴影,为用户提供视觉线索,关于你的界面层次结构,微妙地引导他们走向他们需要与之交互的 UI 元素。如果使用得当,Material Design 可以帮助确保你的用户本能地知道如何导航和与你的应用程序的用户界面交互。

掌握 Material Design 的最有效方法之一是查看一些做得很好的 Material Design 示例,这样你就知道你的目标是什么。

由于 Material Design 是 Google 的设计语言,还有什么地方比 Google 自己的应用程序更好的地方去寻找指导呢?

案例研究 – Hangouts

Hangouts 应用程序已经进行了重大改造,以符合 Material Design。

最显著的 UI 变化之一是“创建新消息”按钮,现在它以浮动操作按钮的形式出现,也称为 FAB。作为最重要的操作,这个 FAB 在主 Hangouts 屏幕的右下角显著且方便地定位,因此当用户需要创建新消息时,总是触手可及。

案例研究 – Hangouts

Hangouts 应用程序使用了两个 Material Design 的基本元素——高度和阴影——来营造出 FAB(浮动操作按钮)似乎漂浮在其他所有 UI 元素之上的感觉。这自然将用户的注意力引向屏幕上最重要的任务;在这个例子中,最重要的任务是创建一条新消息。

案例研究 – Google Calendar

Material Design 鼓励使用鲜艳的颜色和大图像。你会在更新的 Google Calendar 应用程序中找到这两者。

案例研究 – Google Calendar

Google Calendar 是一个很好的 UI 示例,其中颜色和图像不仅使应用程序看起来很有趣,而且通过帮助用户一眼就能看到他们的日程安排中的重要信息而发挥作用。例如,只需快速浏览一下之前的日历图像,就可以看到我周一晚上有一个跑步俱乐部,多亏了那双大号、色彩鲜艳的运动鞋图片。

日历也是一个很好的 Material Design 动画示例。花些时间在日历应用程序中四处移动,你会遇到不同的动画装饰,例如屏幕上的元素在屏幕上移动进出,组装成新的视图。

这些简短动画使在 Google Calendar 应用中导航变得更加流畅、自然,并且通常更加愉快。

案例研究 – Google Maps

Google Maps 使用 Material Design 的底部面板概念来创建沉浸式体验,用户可以选择一个位置,然后探索与该位置相关的所有信息,而无需离开地图环境。

打开 Google Maps 应用并选择一个位置(无论是大型旅游景点、著名地标,还是您当地的酒吧),您会注意到从屏幕底部升起的一个底部面板。在其默认状态下,此底部面板显示了一些关于您选择位置的事实,但当您将面板向上拖动时,它会扩展以填充整个屏幕。这个扩展的面板包含更多信息,包括营业时间、联系方式、照片以及您选择位置的用户评价:

案例研究 – Google Maps

底部面板使用阴影和提升来营造一些组件位置高于其他组件的印象。在我们的 Google Maps 截图中,照片组件被样式化,因此看起来比底部面板的其他部分低。

读完底部面板提供的一切后,您可以通过将面板从屏幕上拖动来关闭它。底部面板将折叠起来,露出主要的 Google Maps 屏幕,给您留下整个时间主要屏幕都隐藏在底部面板后面的印象。

开始使用 Material Design

现在我们已经探讨了 Material Design 背后的主要概念,并看到了一些做得很好的 Material Design 示例,是时候看看您如何可以将一些核心的 Material Design 原则应用到您自己的 Android 应用中了。

接下来的几节将向您展示如何使用视觉技术,如阴影和提升,为您的应用进行 Material Design 改造。您还将学习如何通过添加 FAB、卡片和RecyclerView等元素,对应用的功能进行一些更基本的更改。让我们首先确保您的应用看起来符合要求。

将 Material 主题应用到您的应用

应用 Material 主题是使整个应用具有一致 Material Design 外观最快、最简单的方法。

Android 提供了浅色和深色变体供您选择:

  • Theme.Material: 这是 Material 主题的深色版本。这被认为是默认的 Material 主题。

  • Theme.Material.Light: 这是 Material 主题的浅色变体。

  • Theme.Material.Light.DarkActionBar: 这是主题的浅色版本,但具有深色操作栏。

要将 Material 主题应用到您的应用中,您需要创建一个新的样式,该样式继承自您想要使用的主题版本(Theme.MaterialTheme.Material.LightTheme.Material.Light.DarkActionBar)。打开您的项目res/values/styles.xml文件,并创建一个新的样式,继承自您选择的主题:

<resources> 
  <style name="AppTheme" parent="android:Theme.Material"> 

//Inherits from the standard Theme.Material// 

  </style> 
</resources> 

为了在保持 Material Design 外观和感觉的同时赋予您的应用自己的身份,您可能希望向继承的 Material Design 样式添加自己的自定义设置。最常见的自定义之一是更改主题的基本颜色;例如,您可能希望将操作栏的颜色更改为与您的应用主颜色相匹配。

Material Design 使用两种类型的颜色:主色和强调色。正如其名所示,主色是在整个应用中使用的主体颜色——在 Google Hangouts 中,主色是绿色。

强调颜色是一种更亮的色调,您可以使用它来突出显示应用最重要的元素,例如浮动操作按钮或标题。通过使用一致的基色和偶尔的更醒目的强调颜色,您可以创建一个色彩丰富且充满活力的用户界面,同时不会分散用户对应用内容的注意力。

当您需要自定义继承的 Material Design 主题中使用的颜色时,有多个可用的属性可供使用:

  • colorPrimary: 这设置操作栏背景的颜色。这是您应用的主颜色。

  • colorAccent: 这是您应用的强调颜色。这应该与您应用的主颜色相协调,并且是吸引用户注意重要 UI 元素的好方法。

  • colorControlNormal: 这设置应用框架控制在其默认的非激活状态下的颜色。

  • colorControlActivated: 这设置框架控制在其激活状态下的颜色。此属性覆盖colorAccent

  • android:textColorPrimary: 这设置控件上的文本颜色。在运行 Lollipop 版本之前的 Android 设备上,此属性设置溢出菜单和操作栏标题的颜色。

以下属性仅在运行 Android 5.0 或更高版本的设备上有效:

  • colorPrimaryDark: 这是您应用主颜色的深色变体。此属性设置导航栏(通过navigationBarColor)和状态栏(通过statusBarColor)的颜色。

  • colorControlHighlight: 这是应用于应用框架控制高亮显示的颜色,例如涟漪动画。您可以使用此属性提供与您的应用配色方案相协调的视觉反馈。但请勿过度使用——过多的视觉反馈可能会让用户感到不知所措。

  • colorSwitchThumbNormal: 用户通过拖动开关的Thumb部分来回切换。此属性设置Thumb元素在关闭位置时的颜色。

  • android:colorButtonNormal: 这设置按钮在其默认的非按下状态下的颜色。

  • android:colorEdgeEffect: 这设置应用滚动超出内容边界时的滚动效果颜色。

  • android:navigationBarColor:这设置了导航栏的颜色,即出现在设备底部并包含  Back,  Home, 和  Recent 软键的栏。

要创建材料主题的自己的变体,请将任何前面的属性添加到我们之前创建的样式中:

<resources> 

  <style name="AppTheme" parent="android:Theme.Material"> 

    <item name="android:colorPrimary">@color/blue</item> 

//Specifies that the theme's primary color should be blue// 

    <item name="colorPrimaryDark">@color/darkblue</item> 

//Specifies that the navigation bar and status bar should be dark blue// 

    <item name="colorAccent">@color/white</item> 

//Specifies that the app's accent color should be white// 

<item name="colorSwitchThumbNormal">@color/white</item> 

//All the switches should have a white thumb element// 

  </style> 

</resources> 

现在,您已经知道如何自定义受材料启发的主题中的颜色,但您应该使用什么颜色呢?再次强调,材料设计有答案。

选择您的配色方案

选择您的配色方案是您需要做出的最重要的 UI 决定之一,因为您选择的颜色将影响用户界面的每一个部分。

为了帮助您做出这个关键的设计决定,Android 团队发布了一套主色和强调色调色板,这些颜色设计得相互补充。您可以在 www.google.com/design/spec/style/color.html# 找到完整的材料设计调色板。

在选择您应用的配色方案时,您应该从主调色板中选择三种色调(即任何标记为 500 的颜色)和从辅助调色板中选择一种强调色(即任何不是 500 的颜色)。

小贴士

需要一些帮助选择您的调色板吗?

在众多颜色和阴影的选择中感到困惑?您可能想查看许多可以为您生成完整材料设计调色板的网站之一。只需选择两种颜色,网站就会根据您的选择生成一个包含主色和强调色的完整调色板。网上有很多调色板生成器,但其中最简单、最容易使用的是 Material Palette,网址为 www.materialpalette.com/

向后兼容性

材料主题仅在 Android 5.0(API 级别 21)及以上版本中可用,因此在其默认状态下,您无法在运行早期版本 Android 的设备上使用材料主题或任何由此派生的自定义主题。

然而,您可以使用 AppCompat 库 使材料主题对 API 7 及以上版本的运行用户可用。

要将此库添加到您的项目中,请确保您已下载 Android Support Library 的最新版本(如果您使用 Eclipse)或 Android Support Repository(如果您使用 Android Studio)。AppCompat 依赖于 v4 支持库,因此请确保您已将此库添加到您的项目中。

Android Studio 用户需要在他们的模块级 build.gradle 文件中添加 AppCompat 作为依赖项:

dependencies { 
    ... 
    compile 'com.android.support:appcompat-v7:23.1.0' 
} 

Eclipse 用户需要在他们的 Android SDK 目录中找到 AppCompat 库,将其复制到项目 libs 目录中,右键单击 JAR 文件并选择 Build Path,然后选择 Add to Build Path

要使用 AppCompat,请确保您项目中的所有活动都扩展了 AppCompatActivity

import android.support.v7.app.AppCompatActivity; 

public class MainActivity extends AppCompatActivity { 

您的主题还必须继承自 Theme.AppCompat

<style name="AppTheme" parent="Theme.AppCompat"> 

在此之后,您可以像平常一样自定义材料主题。

创造深度感

材料设计结合了三种视觉技术来创造深度感:

  • 光源:在材料设计的模拟 3D 环境中,虚拟光源照亮屏幕上的对象,并允许它们投射阴影。照明可以是主光源(投射方向性阴影)或环境照明(从所有角度创建柔和阴影)。

  • 阴影:这些为用户提供关于每个对象深度的关键视觉线索。当对象移动时,阴影会继续提供重要信息,包括对象移动的方向,以及此对象与其他屏幕上对象之间的距离是增加还是减少。对象投射的形状由对象的背景定义,而不是其内容,因此,无论按钮图标形状如何,圆形按钮都会投射圆形阴影。

  • 高度:每个 UI 元素都有自己的高度,这是对象沿 Z 轴的高度。高度可以帮助您传达每个屏幕上不同 UI 元素的重要性,自然地将用户的注意力吸引到屏幕上最重要的元素。

您可以通过指定对象的高度来创建阴影。当您添加高度时,框架会自动在对象后面的项目上投射阴影。对象的高度决定了其阴影的外观;具有更高 Z 值的视图将投射更大、更柔和的阴影。

要设置视图的高度,请使用 android:elevation 属性:

<TextView 
  android:layout_width="wrap_content" 
  android:layout_height="wrap_content" 
  android:elevation="20dp" 
  android:text="Hello down there!" /> 

小贴士

只需记住,所有材料元素都具有 1dp 的厚度,因此高度是从一个表面的顶部到另一个表面的顶部的距离。

如果您想以编程方式设置视图的高度,请使用 View.setElevationView.getElevation 方法。

每个对象都有一个默认的 静止 高度,应在您的应用中保持一致。例如,如果您在一个屏幕上将浮动操作按钮定位在 10dp,那么它应该在 每个 屏幕上定位在 10dp。

对象还可以具有响应式高度,这是指它们根据用户操作临时改变其高度。例如,如果用户在图库应用中选择了一张图片,这张图片可能会临时增加其高度以指示其选中状态。

响应式高度也应跨您的应用保持一致,因此,如果图库应用中的图片通过 5dp 改变高度,所有其他具有响应式高度的其他图片必须显示完全相同的 5dp 行为。

如果对象改变高度,它应尽快返回其原始高度——通常是在输入事件完成或取消后。

当添加具有响应式高度的组件时,请确保没有可能发生一个组件在改变高度时遇到另一个组件的情况。请记住,在 Material Design 中,对象不能相互穿过!如果空间紧张,那么一个解决方案是使用动画暂时将对象移开;例如,您可以将一个对象向右移动几个像素,以便为正在改变高度的另一个对象腾出空间。

创建 Material Design 结构

在这个阶段,您已经选择了您的调色板,创建了一个定制的 Material 主题版本,并且知道如何为用户界面添加高度。

下一步是查看您可以添加到您的应用中的新结构元素,这样它不仅看起来像 Material 应用,而且也表现得像 Material 应用。

让我们从最熟悉的 Material Design 功能之一:浮动操作按钮开始。

浮动操作按钮

FAB 是一张看似漂浮在用户界面之上的圆形材料片(因此得名)。如果您有一个需要用户随时可用的持久操作,您应该考虑将其显示为 FAB。

浮动图标按钮代表一个单一推荐的操作,并使用熟悉的系统图标。

注意

您可以在www.google.com/design/icons/找到所有系统图标。

通过 XML 将 FAB 添加到项目中相对简单,因为它使用了您已经熟悉的许多属性,例如android:idlayout_width。然而,在我们的 FAB 示例中,我们将使用一个名为CoordinatorLayout的新元素。这个属性允许您控制 UI 元素之间的交互方式,并且对于告诉 FAB 在用户滚动屏幕时应该如何反应特别有用;是移动还是保持在同一位置?

在这个例子中,我们将 FAB 放置在CoordinatorLayout内部,并告诉它保持在工具栏的底部:

<?xml version="1.0" encoding="utf-8"?> 
<android.support.design.widget.CoordinatorLayout 
......... 
......... 
.......... 

<android.support.design.widget.FloatingActionButton 
  android:id="@+id/myfab" 
  android:layout_width="wrap_content" 
  android:layout_height="wrap_content" 
  app:layout_anchor="@id/actionbar" 

//The FAB should stay anchored to the action bar//  

  app:layout_anchorGravity="bottom|right|end" 
  android:layout_margin="@dimen/fab_margin" 
  app:elevation="20dp" 

//Set the button's elevation so it appears to hover above the rest of the UI, and casts a shadow across the items behind it//   

  android:src="img/ic_dialog_email" /> 

//This references the icon that the FAB should display. In this example, I'm using the create new email icon//  

</android.support.design.widget.CoordinatorLayout> 

您可能会注意到我们没有指定 FAB 的背景颜色;这是因为 FAB 默认使用您主题的colorAccent属性,除非您指定了其他颜色。

您可以像往常一样添加点击事件。所以,为了使事情更有趣,我将加入另一个来自 Material Design 的新元素:Snackbar

Snackbar 与 Toast 类似,但关键区别是用户可以与之交互。用户通过从屏幕上滑动来关闭 Snackbar。Snackbar 出现在屏幕底部。因此,它们非常适合显示与我们的 FAB 相关的消息,而 FAB 恰好位于屏幕底部:

fab.setOnClickListener(new View.OnClickListener() { 
    @Override public void onClick(View v) { 
        Snackbar.make(content, "The FAB has been clicked!",          Snackbar.LENGTH_SHORT).show(); 

//Create a snackbar and display the message "The FAB has been clicked!"// 

    } 
}); 

在创建 FAB 时,有一些指南您应该记住:

  • 保持积极:仅使用 FAB 进行积极操作,例如创建喜欢分享导航,永远不要用于破坏性操作,例如存档垃圾箱

  • 使用一致的间距:在移动设备上,你应该将 FABs 放置在边缘 16dp 或更多的地方。在平板尺寸的设备上,浮动动作按钮应至少距离边缘 24dp。

  • 避免自定义 FABs:始终使用标准圆形图标,并且不要被给动作按钮额外尺寸的诱惑所吸引。如果你确实想给 FAB 添加自己的风格,你总是可以动画化按钮内的图标。

  • 不要包含溢出动作:溢出菜单属于工具栏,而不是 FABs。

小贴士

用 FAB 动画给人留下深刻印象

作为突出的 UI 元素,FABs 是让用户通过动画装饰感到惊喜和愉悦的绝佳机会。例如,你可以设计你的“创建新电子邮件”FAB,使其在点击时变成一封新电子邮件。尝试不同的动画和过渡效果,但不要过分!动画应该是细微的,是最后的润色,永远不要阻碍用户的操作或分散他们对应用实际内容的注意力。

底部面板

底部面板是一张从屏幕底部滑动的材料面板,是对用户操作的响应,例如在谷歌地图中选择兴趣点时出现的面板。

底部面板的初始高度相对于它包含的列表项的高度,但底部面板的初始高度不应超过其 16:9 的比例。

底部面板最初仅覆盖屏幕的一部分,但当用户向上滑动时,它们会扩展以填充整个屏幕。当底部面板扩展到其完整高度时,用户可以滚动查看其内容;再次,谷歌地图应用是这种功能的完美示例。

底部面板最适合显示三个或更多不需要描述的动作。如果你想要显示少于三个动作,或者你想要包含详细描述,那么你应该考虑使用对话框或菜单。

底部面板有两种类型:

  • 持续底部面板:

    这些是贯穿整个应用的结构性元素。它们显示补充主视图的应用内内容。即使它们未被积极使用,持续底部面板仍然可见,并且它们与应用其余部分处于相同的高度。持续底部面板通过以独特的方式呈现内容,有助于将用户的注意力吸引到重要内容上。

  • 模态底部面板:

    这是一个临时材料面板,其高度高于其他内容。

    你可以使用模态底部面板以列表或网格格式展示操作,作为菜单和简单对话框的替代方案。用户不可能忽略模态面板;当一个活动的模态底部面板滑入屏幕时,屏幕的其他部分会变暗。用户必须先关闭模态面板,才能与底层内容进行交互。如果你需要向用户展示一系列操作,且用户界面中没有合适的位置插入菜单按钮时,模态底部面板非常方便。

然而,在屏幕空间较少的大屏幕上,对话框和菜单等组件可能比模态底部面板更合适。这是因为,正如其名所示,底部面板总是出现在屏幕底部。对于在较大设备(如横握模式的平板电脑)上与你的应用交互的用户来说,模态面板可能出现在用户触发面板的显著距离之外。这可能听起来不是什么大问题,但这样的小烦恼真的会累积起来,逐渐削弱用户对你的应用的整体体验。

Bottom sheets

上一张截图显示了模态底部面板在展开前的默认状态。

CardView

卡片为你提供了一个方便且一致的方式来显示相关内容,特别是包含多种数据类型的内容。例如,你可以创建一个包含关于特定主题的图片、链接、文本或视频的卡片。

当你想在交互式功能(如+1、评论和用户评价)旁边显示数据时,卡片也很有用。只是要注意不要让卡片上信息过多。

卡片具有恒定的宽度和可变的高度,可以根据可用空间临时扩展。

每张卡片由内容块组成。一张典型的卡片通常包括以下内容:

  • 标题或主要标题。这应该表明卡片是关于什么的。

  • 富媒体,例如图片或视频。包括富媒体可以帮助用户一眼就能从你的卡片中获取有价值的信息;例如,如果你正在设计一个天气应用,每张卡片上都有一个图片,意味着用户只需瞥一眼那张卡片就能对天气情况有一个大致的了解。

CardView

  • 辅助文本。提供有关卡片的重要信息的文本。

  • 主要操作。这是用户可以在卡片上下文中执行的最重要操作。将其视为卡片上的 FAB(浮动操作按钮)的等价物。

  • 可选的辅助操作。这些可以是图标、文本,甚至是 UI 控件,使用户能够更改卡片的内容。在我们的天气示例中,你可以包括一个滑块,允许用户滚动查看每个小时的天气预报。

当您设计卡片的内容层次结构时,您应该将最重要的内容放置在顶部,而补充图标通常属于卡片的最底部。

使用 CardView 将卡片添加到您的布局中。以下代码展示了如何通过 XML 添加一个空白的卡片到您的布局中:

<android.support.v7.widget.CardView 

     android:layout_width="match_parent" 
     android:layout_height="wrap_content"> 

</android.support.v7.widget.CardView> 

您以与添加常规布局内容相同的方式向 Cardview 添加内容。以下示例演示了如何创建一个包含 Contacts 卡片的 LinearLayout。此 Contacts CardView 显示每个人的姓名和头像:

<?xml version="1.0" encoding="utf-8"?> 

<LinearLayout   

  android:layout_width="match_parent" 

  android:layout_height="match_parent" 
  android:padding="20dp"  > 

  <android.support.v7.widget.CardView 
      android:layout_width="match_parent" 
      android:layout_height="wrap_content" 
      android:id="@+id/contacts" > 

   <LinearLayout  

    android:orientation="vertical" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent"> 

<TextView 
     android:id="@+id/contactName" 
     android:layout_width="wrap_content" 
     android:layout_height="wrap_content" 
     android:text="@string/contactName" /> 

 <ImageView 
     android:layout_width="wrap_content" 
     android:layout_height="wrap_content" 
     android:id="@+id/contactphoto" 
     android:src="img/avatar" /> 

</LinearLayout> 

  </android.support.v7.widget.CardView> 

</LinearLayout> 

您可以使用 CardViews 在运行 Android 2.1(API 级别 7)及更高版本的设备上,通过将 v7 cardview 库添加到您的项目中来实现。如果您正在使用 Eclipse,您需要将此库添加到项目中的 libs 目录,然后选择构建路径,接着选择添加到构建路径。Android Studio 用户需要在他们的模块级 build.gradle 文件中将此库添加为依赖项:

dependencies { 
     compile 
     'com.android.support:cardview-v7:21.0.+' 
} 

在您的布局中添加一个标准的 CardView 非常直接,但如果您想自定义 Android 的标准 CardView,您可以做出以下更改:

  • 使用 cardView:cardCornerRadius 更改卡片的角度半径,例如,card_view:cardCornerRadius="10dp"。或者,您可以通过使用 cardView.setRadius 方法通过您的应用程序代码设置角度半径。

  • 使用 card_view:cardBackgroundColor 更改卡片背景颜色。

  • 使用 card_view:cardElevation 为您的卡片添加一个高度并创建一个阴影。

列表和 RecyclerView

列表为您提供了一种以一致、易于阅读的格式展示相关数据类型的方法。

列表由连续的列和行组成,这些列和行作为瓷砖的容器。您应该在每个瓷砖中优先考虑您最重要的内容;想象您正在设计一个电子邮件应用程序,其中每个电子邮件都由一个瓷砖表示。通常,每个瓷砖会以较大的字体显示发件人的姓名和主题标题,因为这是最重要的信息,然后您会在较小的字体中提供电子邮件文本的预览。

典型的列表瓷砖包含以下内容:

  • 文本:在单行列表中,每个瓷砖包含一行文本。在双行列表中,每个瓷砖最多包含两行文本。如果您需要显示超过两行的文本,请考虑使用卡片。同一列表中的瓷砖之间的文本量可能会有所不同。

  • 主要操作:这些在列表中的每个瓷砖上都是一致的。在我们的电子邮件示例中,主要操作可能是打开电子邮件,并且此主要操作将出现在列表中的每个瓷砖上。

  • 可选补充操作:这些通常以图标或辅助文本的形式出现,并且应放置在每个瓷砖的右侧。

使用 RecyclerView 创建列表,RecyclerView 是用于显示大量数据集的容器。与 ListView 相比,RecyclerView 提供了改进的性能,因为它直接回收视图。当项目视图对用户不再可见时,RecyclerView 会自动用数据集中的不同元素替换它们的内 容,从而实现更平滑的滚动。

RecyclerView 还提供了对常见操作(如从列表中删除项目)的默认动画,并提供布局管理器来帮助您在列表中定位项目。

RecyclerView 为您提供了三个内置布局管理器供您选择:

  • LinearLayoutManager 以水平或垂直滚动列表的形式显示项目

  • GridLayoutManager 以网格的形式显示项目

  • StaggeredGridLayoutManager 以交错网格的形式显示项目

要在项目中使用 RecyclerView,您需要执行以下步骤:

  1. RecyclerView 支持库添加到项目的 Gradle 构建文件中 (com.android.support:recyclerview-v7:23.1.0)。

  2. 定义您的数据源。

  3. RecyclerView 添加到布局文件中,就像添加一个常规视图一样:

            <android.support.v7.widget.RecyclerView
            android:id="@+id/recylerview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scrollbars="vertical" /> 
    
    
  4. 指定您想要使用的布局管理器:

            mLayoutManager = new LinearLayoutManager(this);
            mRecyclerView.setLayoutManager(mLayoutManager); 
    
    
  5. 创建一个适配器。要使用 RecyclerView,您需要创建一个扩展 RecyclerView.Adapter 类的适配器:

            public class MyRecyclerAdapter extends
            RecyclerView.Adapter<MyRecyclerAdapter.ViewHolder> { 
    
    
  6. 创建 ViewHolderRecyclerView 适配器依赖于 ViewHolder 对象,该对象存储了对所有视图的引用,因此您不需要使用多个 findViewById() 方法。

            public class ViewHolder extends
            RecyclerView.ViewHolder { 
    
    
  7. 通过 setAdapter 方法将适配器分配给您的 RecyclerView

动画和过渡

材料设计的两个更多关键特性是新动画和过渡。当正确使用时,这些视觉效果不仅看起来很棒,而且还服务于两个主要目的。

加强材料设计的错觉

材料设计的一个关键方面是屏幕上的元素具有与物理对象相同的特性,动画是帮助您真正强调这一点的强大工具。

在现实世界中,物体的运动方式取决于它们的物理特性。通过改变动画,您可以创造出不同的屏幕对象具有不同的物理特性的感觉。例如,您可以通过使物体移动得更慢来暗示物体比其他物体更重。如果用户看到物体快速移动并迅速加速,他们会认为该物体较轻。

材料设计的另一个基本方面是所有屏幕上的对象似乎都在一个连续的 3D 空间中出现、消失和转换的错觉。使您的应用程序感觉像真实的三维环境的最强大方法之一是在应用程序的活动之间创建视觉连续性。

传统上,Android 应用程序被设计为一系列屏幕,其中每个活动都是一个单独的屏幕。材料设计通过使用过渡来模糊这些边界,从而使用户能够轻松地从活动切换到下一个活动。

例如,当一个新的活动开始时,之前活动的元素可能会淡出,而新活动的元素则通过动画方式进入屏幕。如果这两个活动有共同元素,你可以对这些元素进行动画处理,使它们看起来重新排列成一个新的布局,这实际上是一个全新的活动。这样,你可以创建一个更加流畅和沉浸式的用户体验,让用户感觉就像是在你的应用环境中移动,而不是从一个屏幕切换到另一个屏幕。

小贴士

进入和退出

在你的对象进入和退出屏幕的方式上多花些心思,因为这些是加强 Material Design 连续 3D 环境幻觉的理想机会。如果一个对象以相当快的速度进入屏幕,用户就会认为这个对象已经在屏幕外移动了一段距离,并且在移动过程中加速了。如果一个对象在退出屏幕时减速,用户就会认为这个对象将在屏幕外停下来。

你还应该寻找机会利用这些假设来为你带来优势。例如,如果你知道某个对象将会再次出现,你可以让它以缓慢的速度退出屏幕,然后在适当的时候慢慢回到屏幕上,给用户一种这个对象一直在屏幕外悬浮的印象。

提供用户视觉反馈

无论用户何时与 UI 元素交互,你的应用都应该提供某种形式的视觉确认,表明它已成功记录了他们的交互。

注意

最常见的用户交互是触摸事件,因此在本节中,我将重点关注这种类型的事件。只是要注意,你的应用可能需要处理其他形式的用户输入,例如用户在虚拟键盘上打字或通过设备麦克风发出指令。

在 Material Design 中,触摸波纹是提供这种视觉确认的主要机制。

触摸波纹效果是一种特别有用的动画,你可以用它来传达关于触摸事件的额外信息,例如事件的持续时间、施加的压力量以及触摸事件发生的位置(触摸波纹从输入点向外扩散)。

你可能还想让材料动画响应用户输入,例如,当用户长按照片时,让它看起来稍微抬起。

动画——一个警告

动画可能是你拥有的最有力量或最具破坏性的工具之一。

我们的眼睛擅长检测和跟踪运动,但如果多个元素同时移动,或者以随机模式移动,那么用户将无法跟上!糟糕的动画比没有动画还要糟糕。

你还应该避免使用耗时且没有其他目的,仅仅是为了看起来漂亮的动画。理想情况下,你应该旨在通过微妙、意外的动画使用来让用户感到意外,从而以某种方式增强他们的体验,而不是仅仅为了使用动画。

完成细节

现在你已经创建了一个具有材料设计外观和感觉的用户界面,并添加了一些结构元素,如浮动操作按钮和卡片,现在是时候给你的材料设计应用添加完成细节了。

设计你的产品图标

你的产品图标应该传达你的应用身份,同时表明你的应用目的和核心功能。

小贴士

建立你的品牌

如果你正在创建多个应用,每个产品图标都应该独特,但你也应该利用每个图标作为机会来创建和加强一个跨越所有 Android 提供的更广泛品牌。在相关的产品图标之间追求一致性。

产品图标应该反映我们在本章中探讨的相同材料设计原则,因此,再次提醒,你应该将纸张和墨水的物理特性作为你的主要灵感来源。你的产品图标应该看起来就像其他材料设计对象一样被切割、折叠和照亮,但真正的挑战是在传达所有这些信息的同时,创建一个干净、简单的产品图标。

两个特别有效的材料设计图标示例是 Google 日历和 Gmail 产品图标。这两个图标都使用简单的形状来创造阴影、深度和边缘感,同时清楚地传达了应用的主要目的和功能。

下面是 Gmail 的图标:

设计你的产品图标

下面是 Google 日历的图标:

设计你的产品图标

为了使设计你的产品图标更容易,材料设计引入了产品图标解剖学的概念。这些是你可以纳入你设计的标准化形状,以帮助在产品图标之间促进一致的外观和感觉。你可以在www.google.com/design/spec/style/icons.html#icons-product-icons查看这些标准化形状。

每个产品图标都应该包含相同的结构元素。这些元素总是从正上方观看,并且每个组件都位于前一个组件的顶部:

  • 完成:这是一种柔和的色调,你应该用它来突出所有产品图标元素的上边缘。你不应该将这种色调应用到图标的左侧、右侧或底部元素上。

  • 材料背景:这是一张作为你图标背景的材料。

  • 材料前景:这是一个位于材料背景之上的元素,并在背景上投下阴影。

  • 阴影:这是一种出现在所有凸起材料元素边缘的柔和阴影。这种阴影应该沿着产品图标的右侧和底部线条略微加重。

注意

创建完美的产品图标

虽然分层纸张元素对于创建深度感是有效的,但请注意不要通过添加过多的层来使您的应用过于复杂。您应该追求一个具有所有阴影、深度和边缘的产品图标,但它应该仍然提供简单流畅的外观。

您应该提供 48dp 大小的图标,边缘为 1dp。

系统图标

在上一节中,我们深入探讨了产品图标,这些图标是您应用程序独有的,但系统图标又如何呢?

好消息是,Android 团队已经为所有系统图标进行了材料设计的改造。除非您有非常,非常充分的理由不这样做,否则您应该使用这些标准系统图标。

您可以从www.google.com/design/spec/style/icons.html#icons-system-icons获取整个包。

字体和写作

在本节中,我们将探讨创建符合材料设计原则的文本所需了解的所有内容,从一般写作建议到关于文本不透明度的具体指南。

字体

作为一名 Android 开发者,您需要了解两种主要的字体:

  • Roboto:自 Android 4.0 以来,Roboto 一直是 Android 的标准字体。

  • Noto:自 Android 2.2 发布以来,Noto 一直是所有未覆盖 Roboto 字体的语言的标准字体。

材料设计同时使用这两种字体。

文本透明度

您可以使用不同级别的透明度来向用户传达每段文本的重要性。

您应使用的透明度级别将根据您的文本颜色和应用程序背景颜色而有所不同。当您向深色背景添加浅色文本时,您应该使用以下:

  • 主要文本:100%透明度

  • 次要文本:70%透明度

  • 提示文本、禁用文本和图标:30%透明度

当您向浅色背景添加深色文本时,您应该使用以下:

  • 主要文本:87%透明度

  • 次要文本:54%透明度

  • 提示文本、禁用文本和图标:38%透明度

当您向 UI 添加文本时,请检查您的背景颜色是否会使您的文本难以阅读。此外,请注意,过多的对比度会使您的文本同样难以阅读;理想情况下,您的文本应保持 7:1 的对比度比。

写作指南

现在您已经知道您的文本在屏幕上应该是什么样子了,是时候将我们的注意力转向您的文本实际上要表达的内容了。

为了确保您的用户界面尽可能用户友好,您应该创建以下文本:

  • 清晰: 用户会欣赏简单、直接的言语,不需要重复阅读。仔细选择你的词汇,并寻找传达信息的最简单方式;例如,说转到问题 2比说导航到问题 2更好。

  • 易于理解: 在撰写文本时,要考虑到有些人可能在使用你的应用时使用的是第二语言。即使你将你的应用翻译成其他语言,文化特定的短语和俚语可能无法在翻译过程中保留。你的目标应该是写出对所有人都易于理解的文本。相关的是,当你创建字符串资源时,通常是一个好主意包括详细的描述,甚至可能还有额外的注释,这样如果应用最终被翻译,你就有更大的机会确保其被准确翻译。

  • 必要: 文本的全部目的是帮助用户导航并从你的应用中获得价值,所以每次你想要在 UI 中添加文本时,问问自己—用户真的需要知道这个吗? 例如,如果你创建了一个包含表单和提交按钮的屏幕,你的用户可能不需要你添加请完成表单然后点击提交按钮的免责声明,因为他们可能已经知道该怎么做。

  • 简洁: 你决定包含的文本应该是简短、清晰、直接。为了将文本保持在最短,尽可能使用缩写(比如用can't代替can not)。

  • 缺少标点符号: 标点符号会增加视觉杂乱,所以你应该尽可能省略标点符号。特别是你应该避免使用感叹号。每次你想要添加感叹号时,问问自己—我真的想对用户大喊吗?

  • 使用现在时态: 大多数 UI 事件都发生在现在,所以除非你有一个非常充分的理由不这样做,你应该使用现在时态。

  • 使用主动动词: 通过选择主动动词而不是被动动词来使你的写作更具吸引力。唯一的例外是,如果被动形式比主动形式更短、更简单。

  • 使用正确的语气: 正确的语气是友好、尊重的,并且坚定地关注用户。你应该直接称呼用户为,并避免将你自己和用户一起称为我们

摘要

在本章中,我们探讨了材料设计的核心原则,包括创建具有独特材料设计外观和感觉的用户界面,并介绍了如何将一些新的结构特性纳入我们的应用中。

在过去几章中,我们主要关注了创建优秀用户界面的技术方面。在接下来的几章中,我们将转变焦点,探讨如何通过草图、线框和原型来捕捉灵感的火花。

第五章。将您的创意转化为详细草图

Google Play 商店中有许多应用程序,并非所有应用程序都拥有五星评级。

尽管有各种原因可能导致有人给应用程序低评分和负面评论(可能是存在错误、难以导航、缺少用户期望的功能,或者用户界面杂乱无章、令人困惑),但通常在所有负面的 Google Play 评论背后隐藏着一个普遍的真理——应用程序提供了糟糕的用户体验。

移动用户是一群不可饶恕的人,在当今竞争激烈的市场中,仅仅有一个有趣的想法、不寻常的概念或深思熟虑的功能是不够的,您的应用程序还需要易于使用。

这就是为什么提前花时间规划您的应用程序是至关重要的

奇怪的是,尽管设计师和开发者有相同的目标(创建人们喜欢使用的出色应用程序),但我们往往将设计和开发视为彼此分离的,但如果你要创建一个出色的应用程序,那么你需要了解两者的点点滴滴。

无论您是否有为生产力应用程序的伟大创意,它能为您节省大量时间,或者是一个人们愿意花几个小时玩耍的谜题应用程序,设计都是一切。您的应用程序设计将对其实用性、有用性和最终普及度产生巨大影响;设计糟糕的应用程序难以理解,使用起来也不太有趣!

为移动设备设计可能很困难,但这也是最具挑战性的开发平台之一,因为它是一个快速发展的行业,新硬件不断发布。这意味着有很多机会创造出完全独特的东西,让您的用户感到震惊。

本章将向您展示如何成为一名具有设计思维的开发者,了解如何设计开发出令人惊叹的应用程序,这些应用程序在 Google Play 商店上获得好评将毫无问题。

头脑风暴 - 利用移动硬件

当您设计移动应用程序时,不要忘记典型的 Android 设备拥有许多独特的硬件,这些硬件是移动平台独有的。只需想想您的移动应用程序可以因为这种独特的硬件做出哪些反应:触摸手势、倾斜、音频输入和输出、地理位置,等等。

如果您希望您的应用程序在众人中脱颖而出,您需要寻找以新颖和引人入胜的方式使用这些硬件功能的方法。

在我们开始规划我们的应用程序之前,让我们看看一些您可能希望将其融入移动应用程序中的最有趣的 Android 硬件功能。

触摸和手势

与应用程序互动的最常见方式是通过设备的触摸屏,因此您肯定希望将手势集成到设计中。

需要记住的一件事是,用户对与 Android 应用交互的手势确实有期望。他们期望能够通过滑动、拖动、捏合和点击屏幕元素来执行某些操作。虽然你希望你的应用因其独特的触摸和手势使用而脱颖而出,但你还需要确保你的用户能够直观地了解如何与你的应用交互。

不要试图仅仅为了创新而重新发明常见的手势。例如,大多数用户都期望能够通过点击或拖动来移动滑块——那么重新发明已经完美运行的东西有什么意义呢?

正如你将在本章中发现的,设计完美的 Android 应用是在创造独特和创新的东西与感觉熟悉的东西之间取得平衡的行为。

GPS

位置感知并不仅限于 Google Maps 风格的 APP!

你可以用很多不同的方式使用 GPS 坐标;例如,你可能想创建一个应用,根据用户的当前位置建议附近的活动和旅游景点,为照片标记拍照位置,或者向可能想知道他们认识的人是否附近的朋友广播用户的坐标。

备注

不要陷入认为位置感知仅用于获取方向的陷阱。你可以用很多方式使用这个硬件特性来增强你的 Android 应用。

震动

当音频警报变得分散注意力或令人烦恼时(无论是令用户烦恼还是令周围的人烦恼!),你应该使用震动。尽管这个硬件特性容易被忽视,但请花一点时间思考一下你的应用如何可能利用震动作为与用户沟通的额外方式。

一个例子可以是冥想应用,它会播放舒缓的音乐或放松的海浪声。想象一下,当你想要微妙地通知用户他们已经冥想了整整 15 分钟时,以防他们过于投入而忘记了时间。在这种情况下,音频通知会显得突兀,并完全破坏氛围。用微妙的震动来提醒用户不是更好吗?

音频输入/输出

当你想到音频输入时,你可能会想到和朋友交谈或向如 Google 搜索等应用发出语音命令。当涉及到输出时,你可能会想到音频通知、听朋友说话,或者听音乐和有声读物。

这些都是有效的例子,但音频是一个具有无限可能性的特性,所以考虑一下你的应用是否可以用更创造性和不寻常的方式使用音频输入和输出。

尤其是如果你的应用程序将在可能不便或不适通过其他方式与应用程序交互的情况下使用,那么你将希望大量使用音频输入和输出。向我们的智能手机发出快速语音命令通常比长时间在键盘上敲击更符合社会规范,后者往往被视为不礼貌。

在极端情况下,音频为用户提供了一种与其他方法可能危险时与你的应用程序沟通的手段。如果你正在创建一个提供驾驶指南的应用程序,那么鼓励驾驶员为了阅读基于文本的指示而将视线从道路上移开,这绝对是一个坏主意。

与其他设备交互

没有哪个 Android 设备是孤岛!Android 智能手机和平板电脑可以通过蓝牙、近场通信(NFC)、Wi-Fi 和移动网络等渠道连接,这为你的应用程序开辟了一个全新的可能性世界。

更令人兴奋的是,Android 智能手机和平板电脑还可以与不太传统的 Android 设备和谷歌服务连接,例如可穿戴设备、Android 电视和信标。如果你的应用程序能够从额外的设备发送和接收信息,它是否能够提供更好的用户体验?

注意

谷歌的信标平台允许你的应用程序与被称为信标的简单、低功耗设备交互。附近的信标对于为用户提供创新的位置和邻近体验非常有用。它们超出了本书的范围,但你可以在developers.google.com/beacons/上了解更多信息。

这些只是你在应用程序头脑风暴早期阶段可能想要考虑的一些与硬件相关的功能。

如果你目前头脑中充满了想法,那么这是一个好事!记下你所有异想天开的想法,这样你就可以在你对应用程序有更多计划后重新审视它们。即使这些想法最终不适合你最终创建的应用程序,那么谁知道呢,它们可能最终非常适合你的下一个 Android 项目!

小贴士

不要过分热情!

当你在头脑风暴所有可以包含在你应用程序中的酷炫和创意功能时,黄金法则不是仅仅为了加入某个功能而将其塞入你的应用程序。

即使你有一个你认为绝对是天才的想法,如果它与你的当前项目不匹配,那么不要浪费一个好主意在一个不适合它的应用程序上。记下你的想法,并记住,即使你目前没有在项目中实施它,这并不意味着它不会在下一个项目中找到自己的位置。这是一个在整个规划、设计和开发过程中都值得记住的规则。

用户体验(UX)与用户界面(UI)的区别

尽管用户体验(UX)和用户界面(UI)经常被互换使用,但它们并不完全相同。

UX 代表用户体验。你写的所有代码都贡献于用户体验,无论是 UI 代码(例如创建按钮并将其放置在屏幕上的 XML 代码)还是非视觉代码,例如记住用户的地址并使用它来自动填写你应用中的所有表单的代码。

非视觉代码和视觉代码结合在一起来提供你的应用程序的用户体验,但如果单独看待,视觉代码提供你的应用程序的 UI。

小贴士

市场调研

在本章中,我将鼓励你检查其他应用程序如何提供有效的 UI 和 UX。但作为一般规则,如果你发现自己难以克服特定的设计问题,翻阅一下你智能手机或平板电脑上安装的几个 Android 应用程序,看看它们如何处理类似的设计挑战可能会有所帮助。如果你需要一般的灵感,那么查看一些提供有效 UI 和 UX 的 Android 应用程序的例子通常是激发创造力的最佳方式。

头脑风暴你的应用

当你有一个新的 Android 项目想法时,第一步是将你的所有想法都记录在纸上。

写一个概念

一个经过深思熟虑的应用程序解决了一个具体(理想情况下是独特)的问题,而捕捉这种信息的最佳方式是写一个清晰的概念。

一个概念应该简短而直接,所以尽量用一句话来表达你的应用,例如,“我想为高中生创建一个简单的笔记应用。”

创建一个终极功能列表

现在是时候让你的想象力自由驰骋,写下你希望你的应用拥有的所有功能。这个列表可能包括不切实际的功能,但没关系。把这看作是你梦想中的功能列表,如果你不考虑时间、金钱和技术限制,你会在你的应用中包含所有这些功能。

确定你的应用程序的主要任务

最有效的应用程序都有一个明确的初级任务。一个笔记应用可能具有社交功能,如标记笔记中的人、录制视频的能力以及图像支持,但其主要任务是快速创建基于文本的笔记。其他所有都是额外的加分项。

要找到你的应用程序核心的一个好方法就是快速解释你的应用程序的目的,这被称为产品声明

使用以下模板来编写你的产品定义声明:

(是什么使你的应用程序与众不同) (你的应用程序是什么)

以下是一些产品声明的例子:

  • 一个简洁简单的笔记应用

  • 一个安全加密的短信应用

  • 一个漂亮的锁屏替换应用

在你整个项目开发过程中,这个产品声明都应该放在你的脑海中,所以你可能想要把它写在便利贴上。

恭喜你,你刚刚将你的灵感火花转化为关于你想创建的应用类型的一个明确的声明。但在你继续这个想法之前,是时候问自己一些艰难的问题了。

这个应用程序适合移动设备吗?

现在,我们几乎可以在我们的移动设备上做和我们在家用电脑上一样的事情。我们可以在智能手机和平板电脑上发送电子邮件、观看 YouTube 视频、撰写长篇文本文档、编辑照片和检查 Facebook。

移动设备也接管了许多其他电子和非电子物品的角色。我们可以在 Google 日历应用中添加约会,而不是在纸日历上乱写,我们可以将所有联系人存储在数字电话簿中,而不是实体电话簿中,随着相机手机的日益强大,我们不再需要携带笨重的数码相机来拍摄高质量的照片。

每当你想到一个软件点子时,你可能会自然而然地认为它必须以移动应用的形式出现。但即使是功能最强大的移动设备也有其局限性,所以总是要问自己你的想法是否可能在不同的平台上表现得更好。

想象一下,如果你有一个软件点子,可以帮助专业摄影师对他们的照片进行详细和复杂的编辑。你可能自然会认为这必须是一个移动应用程序;毕竟,我们用手机拍照的数量比以往任何时候都要多。但考虑你的目标受众;专业摄影师可能会用他们的相机手机拍很多照片吗?他们不是更有可能拥有专业的摄影设备吗?此外,考虑深入图像编辑是否适合你典型移动设备上的小触摸屏。

将这个特定的想法作为桌面计算机上的软件来开发可能更有意义,也许还有一个配套的移动应用程序,它提供了一些更适合移动环境的功能。这样,用户可以在他们的相机手机上拍照,并在路上做一些简单的调整。然后,如果他们决定想要进行更复杂的编辑,他们总是可以在回家后启动他们的电脑。

你有预算吗?

在开始一个新项目之前,重要的是仔细审视你的预算,看看这个特定的项目是否可行。

虽然“预算”这个词意味着金钱,但预算也可以与时间相关。如果你是一个独自热爱 Android 的爱好者,在业余时间为了乐趣而开发应用程序,那么你最大的挑战可能是找到空闲时间来投入到你的项目中,而不是寻找资金。估算你可以实际投入到项目中的时间,然后利用这些信息来计算你需要多长时间才能完成。

对自己诚实,关于你能够实现什么以及你可以在项目中投入多少时间和金钱。

无论你的主要限制是金钱还是时间,你可能会意识到这个特定的项目实际上并不实用;也许你太忙了,无法在合理的时间内完成项目,你知道你会在到达终点线之前耗尽精力;或者也许你的项目需要更多的帮助,而你的预算不足以雇佣更多的人。

如果是这样,你可能需要重新考虑你的项目,使其更具实用性。也许你可以缩减功能列表,或者通过暂停一项爱好来腾出更多时间。虽然由于时间或财务限制而削减功能可能会令人沮丧,但与继续进行项目并在几个月后发现,从现实的角度来看,你永远无法达到终点线相比,这要令人沮丧得多。

在这个阶段告诉自己一些艰难的真相将帮助你避免在某种不切实际的事情上投入时间、精力和金钱。

规划你的应用

计划是一个容易跳过的步骤,尤其是如果你是独自一人,但花些时间正确地规划你的项目不仅会增加你创建更有效应用的机会,还会增加你真正完成项目的可能性,而不是半途而废。

不要冒险跳过这个阶段!

确定你的目标受众

在你规划应用的早期阶段,你可能会告诉自己,“这个应用是个好主意,每个人都会想使用它!”

如果你认同这个说法,那么你的应用将会完美——但仅限于你自己。由于只为你自己开发应用(尽管,如果你碰巧喜欢最终产品,那将是一个额外的加分项),在规划你的应用时,你首先需要回答的问题是,“我是为谁构建这个产品的?”

太常发生的情况是我们直到计划过程的后期才确定我们的目标受众,或者更糟糕的是,我们过于冲动,在没有真正了解我们为谁构建应用的情况下就开始构建应用。你的目标受众应该影响你应用的每一个部分:从外观和感觉,到你想要包含的功能,甚至如何营销和推广最终产品。

希望你已经对你要针对的目标有了一个大致的了解,但为了确保你创建出人们真正想要使用的东西,你需要更深入地了解你确切的目标受众。实现这一目标的一种方法是通过开发用户画像和用例:

  • 用户画像:这是你创建为你构建应用的人的具体模型的地方。一个用户画像是一个代表你的目标受众的个体用户。为了鼓励你将这个用户画像视为一个真实的人,你应该给你的用户画像起一个名字。

  • 用例:这是你的用户画像可能如何、何时以及在哪里使用你的应用的场景。

创建用户画像

为了帮助你创建详细的用户画像,向你的用户画像提出以下问题:

  • 你多大了?这可能是一个确切的年龄,比如 18-25 岁,或者可能更普遍,比如儿童或年轻人。

  • 你的兴趣和爱好是什么?你喜欢足球吗?烘焙?购物?锻炼?

  • 你认同自己是男性还是女性(尽管并非所有应用都会针对特定的性别)?

  • 你住在哪里?这可能是一个特定的国家、城市,或者是一个地区类型,如乡村或海边。

  • 你有孩子吗?

  • 你有工作吗?如果有,你做什么?

  • 你目前是否在学校、大学或大学?

  • 你有没有任何特定的专业领域?

  • 你最常使用哪种类型的应用?

  • 你还使用其他类型的应用吗?

  • 你永远不会使用哪种类型的应用?

  • 什么影响了你的决策?

  • 你对 Android 有多熟悉?你是专业用户,还是仅仅知道足够的知识来应付?

  • 在情感层面上什么能触动你?什么让你生气、快乐或悲伤?

注意

这绝对不是一个详尽的列表,但这些问题应该足以让你更详细地思考你针对的是哪类人。

让我们看看用户画像如何帮助你规划应用程序的例子。想象一下,你有一个食谱应用的创意,并已经列出了一长串你可能想要包含的功能:

  • 能够根据特定的饮食需求(无麸质、纯素食、素食、低脂、非乳制品)、供应人数以及烹饪难度来筛选食谱。

  • 每顿饭的估计卡路里数。

  • 每顿饭的估计成本。

  • 社交媒体集成,让用户可以与朋友和家人分享他们最喜欢的食谱。

  • 能够对每个食谱提供一系列反馈;无论是发表评论、上传他们的尝试照片,还是给食谱打星。

  • 一个在线剪贴簿,用户可以保存他们最喜欢的食谱。

  • 能够通过应用程序购买食材。

  • 视频教程,让用户可以选择阅读说明或跟随视频进行操作。

  • 一个内置的计时器。

你记下的想法越多,你就会越意识到,即使你的所有想法都经过深思熟虑、实用,并且能为你的应用增加真正的价值,但试图将每一个想法都融入一个单一的应用程序中,这根本是不切实际的。

这不仅会给你带来在时间和精力上实现这么多功能的噩梦,而且对于打开你的应用的每一个用户来说,也会是一个噩梦,他们立刻就会面对一个详尽、混乱的功能列表——而且可能还有一个杂乱无章、令人困惑的用户界面!

你应该始终致力于为一个非常具体的受众设计完美的应用,而不是一个试图吸引所有人的通用应用;这种应用很可能会最终让所有人都感到不满意。

那么,你如何决定你最初列表中的哪些功能应该被保留呢?答案是创建一个用户画像,然后挑选出所有能吸引这个特定用户画像的功能。

让我们为我们的食谱应用创建一个用户画像。想象一下,我们的目标受众主要是面临首次自己做饭挑战的大学生。为了帮助我们让这个目标受众栩栩如生,让我们创建一个单名的用户画像——认识妮可:

  • 她今年 18 岁

  • 她是一名大学生

  • 她和另外五个学生住在学生宿舍里

  • 她没有车

  • 这是她第一次离开家庭住所生活,所以她几乎没有为自己做饭的经验

  • 她非常注重金钱

移动用户画像

当你设计一个移动应用时,你也需要考虑移动用户画像。这是一组人们在使用移动应用时所假设的额外特征。

如果你像许多喜欢通过 Facebook 移动应用随时随地跟踪朋友和家人动态的 Facebook 用户一样,那么想想你使用移动应用的方式与你在笔记本电脑或电脑上使用 Facebook 的方式有何不同。你可能会自动避免那些你知道会耗尽设备电池或消耗数据套餐的活动,比如观看或上传视频。但当你通过桌面访问 Facebook 时,这大概不是你特别担心的事情。

在设计你的移动应用时,请记住,典型的移动用户画像具有以下特征:

  • 有限的数据套餐。

  • 有限的电池电量。

  • 通常在移动中,所以他们可能有不稳定的互联网接入和移动网络信号。

  • 很少将全部注意力放在他们的 Android 设备上。移动用户是世界上最好的多任务处理者之一,他们在酒吧等待朋友时、在公交车站消磨时间时,或者在沙发上半看电视时都会瞥一眼他们的设备。典型的移动用户不想长时间盯着你的应用 UI;如果他们这样做,那么这通常是一个明确的信号,表明你做错了什么,用户正在试图弄清楚他们接下来应该做什么。

  • 在小屏幕上查看你的应用。即使你最大的 Android 平板电脑,其屏幕也比其他电子设备,如电视和笔记本电脑小得多。

  • 对他们的 Android 设备有情感上的联系。我们与智能手机的关系,特别是,与我们与电脑等电子设备的关系非常不同。我们的智能手机总是陪伴在我们身边——无论是在我们手中、口袋里、包里,还是在工作中桌子上,我们用它们来完成非常个人化的任务,比如与家人和朋友保持联系,以及拍摄我们疯狂夜晚外出和度假的照片。我们在移动设备上存储了大量的敏感信息,所以我们自然会对它们产生保护欲!你还没有对我们的手机有特殊的感情吗?只需想想当你伸手进口袋或包里找手机,却发现它不在那里的那一刻的盲目恐慌。这是我们每个人都经历过(虽然希望你的手机最终在另一个外套口袋里,并没有真正丢失)的感觉。

如果你忽视这些属性中的任何一个——例如,你开发了一个消耗大量移动数据的应用程序,或者请求对大量敏感信息的非必要访问——那么你的用户将会有糟糕的体验。你的挑战是在尊重移动用户角色的所有特性的同时,为你的特定目标受众创造一个引人入胜的用户体验。

创建使用案例

妮可什么时候可能会使用我们的食谱应用?是什么促使她启动你的应用程序?她通过与应用程序互动希望实现什么?

这里有一些妮可的使用案例:

  • 虽然是周末,但也是妮可学生贷款到账前的最后几天,所以她通常的周末外卖美食计划已经取消。她需要做些既美味又符合她当前预算的东西。

  • 为了节省金钱,妮可和她的室友们决定轮流准备公共餐食。今天轮到妮可,因此她需要一个能够喂饱六个饥饿学生的食谱——如果还有剩余的话,那就更好了!

  • 妮可很匆忙;她起床晚了,在她下午的讲座前只有一个多小时的时间。她需要一个超级简单、超级快就能做好的食谱。

确定功能列表

再看看我们食谱应用的初始功能列表。现在,我们有一个非常具体的用户角色和一些使用案例;我们应该挑选哪些功能包含在我们的应用中?哪些功能会吸引妮可?

我将选择以下内容:

  • 根据烹饪难度过滤食谱的能力:这对我们的初学者厨师来说是一个首要任务,尤其是当她需要赶在下一堂课之前匆忙吃饭时。

  • 根据可以喂多少人过滤食谱的能力:如果妮可要为她的室友做饭,那么她需要能够轻松访问可以喂六个人的食谱。

  • 每餐的预估成本:作为一名预算意识强的学生,妮可不想把她的学生贷款全部花在昂贵的食材上。她希望能够清楚地看到每餐烹饪的成本——尤其是在她下一次学生贷款分期付款到来之前的最后几天!

  • 视频教程:妮可是一个不熟练的厨师,所以她肯定会欣赏观看食物准备过程而不是仅仅阅读说明的选项。她现在 18 岁,这意味着她已经习惯了在 YouTube 上观看视频教程,所以用智能手机观看烹饪视频的想法对她来说具有很大的吸引力。

  • 内置的计时器:这是妮可第一次离开家庭居住,所以她还没有时间积累烹饪用具。她也不想把学生贷款浪费在像计时器这样的无聊东西上,所以如果您的应用可以提供这种功能,那就更好了!

  • 通过应用购买食材的选项:作为一名 18 岁的年轻人,妮可是一位在线购物的高手,所以将所需的食材直接送到她家门口是她(亚马逊)愿望清单上的首要事项。妮可也没有车,所以去超市或当地的农贸市场并不是世界上最容易的事情。理想情况下,我们的食谱应用能够交叉检查多个供应商,并建议她可以以最低成本购买每个食材的地方,以节省更多的学生贷款。

我们列表中还有其他一些功能可能也会吸引妮可;例如,她可能是一个素食主义者,或者有几个经过验证的家庭食谱想要上传到应用中,但要注意不要对您的用户画像过于具体。

虽然根据饮食需求筛选食谱或允许用户将他们自己的食谱添加到应用中可能会为某些用户增加价值,但请自问这些功能是否真的与您的目标受众相关,而不仅仅是您的用户画像?如果您不确定,那么尝试将功能添加到您的列表中,并从上到下阅读,这些新功能是否合适?或者它们是否像 sore thumb 一样突出?

注意

即使您现在决定不添加某些功能到您的应用中,它们也可能在后续版本中找到它们的位置。如果您觉得某个功能有潜力,但在这个早期阶段并不是您应用的绝对关键,那么记下它;您可以在以后的时间回到它。

仔细审视您的目标受众

您已经确定了目标受众,但在我们进一步推进之前,退一步仔细审视您选择的受众。针对这个特定群体的人进行目标定位是否真的合理?

您的目标受众需要一款移动应用吗?

您的应用是否为您的目标受众提供了他们通过其他平台无法获得的东西?

假设你有一个关于跟踪用户预约的应用程序的想法;他们是否已经在其他地方获得了这种功能?越来越多的地方,如牙医、医生和发型师,通过短信提醒客户即将到来的预约,或者如果你针对的是不太懂技术的用户,他们可能更喜欢在纸日历上记下他们的日程。

虽然假设所有事物作为移动应用都更好是有诱惑力的,但如果他们的需求已经通过其他媒介得到满足,你的目标受众不太可能费力去下载移动应用。

你的目标受众是否使用 Android?

那些严肃的商业用户选择 BlackBerry,而所有酷炫的孩子都选择 iPhone 的日子已经过去了。如今,界限变得模糊,在多个平台上发布应用已成为常见做法。但在你得出结论之前,检查是否有证据表明你的目标受众更倾向于某个移动平台而不是另一个。快速进行一次谷歌搜索,查找任何关于移动人口统计的最新研究,看看是否有任何数据对你的计划中的 Android 应用提出警告。

好的,所以你的目标受众使用的是 Android 设备,但它们是否拥有合适的 Android 设备?

不要假设你的目标受众自动拥有足够强大的 Android 设备来运行你的应用程序,尤其是考虑到 Android 平台如此碎片化。

如果你设计的应用依赖于用户访问最新的 Android 操作系统版本,那么你已经在限制你的受众。如果你正在为更可能在其设备上安装最新版本 Android 的先进 Android 用户开发应用,这可能是有意义的,但如果你的目标是普通非 Android 用户、老年用户或预算紧张的人,他们可能还没有投资于最新的 Android 设备,比如精打细算的学生,这可能不是一个好主意。

他们是否使用这种 "类型" 的应用?

即使你的应用将具有独特的目标受众、功能、UI 和 UX 组合,仍然可以识别出应用程序的 类型。值得注意的是,这些类型并不对应于 Google Play 商店中的类别,后者要具体得多。

他们是否使用这种 "类型" 的应用?

应用程序 类型 可以分为四个类别:

  • 实用工具:这些包括天气、新闻、旅行和生产力应用

  • 娱乐工具:例如壁纸、社交媒体、图像编辑和摄影应用

  • 游戏:这一点相当直观,Google Play 有一个专门的游戏类别,这使得识别这类应用变得更加容易

  • 娱乐:这包括音乐、视频、绘画、有声读物、播客和漫画书应用

在类别之间有一些交叉和灰色区域(例如,购物应用是娱乐工具还是有用的工具?),但你在规划你的应用和确定目标受众上花的时间越多,就越明显你的应用属于哪个类别。我们的食谱应用是一个有用的工具,因为它帮助学生自己烹饪,可能是有史以来第一次。然而,如果我们的应用包含大量以圣诞节为主题的食谱并针对喜欢烘焙节日美食的熟练厨师,那么它更有可能属于娱乐工具类别。

确定你计划开发哪种类型的应用,并确定你的目标受众是否可能使用这种类型的应用。

你的目标受众愿意为这个应用付费吗?

你可以选择以某种方式让你的应用盈利。流行的方法包括要求用户在下载应用之前支付一次性费用;发布免费和付费版本的应用;或者销售人员所说的升级机会,这可能包括解锁特殊专业功能或应用内额外内容,如新关卡、角色、游戏内货币或额外生命。你甚至可以选择提供月度或年度订阅。

如果你选择让你的应用盈利,那么考虑对你为目标受众开发的应用类型最合适的盈利方式。对于一些预算更紧张的人来说,为移动应用付费可能感觉像是一种浪费,或者年轻的目标受众可能没有使用诸如信用卡和借记卡这样的东西的机会。

你还应该考虑你应用的目的。我们食谱应用吸引人的部分之一是它能够通过教授学生如何为自己烹饪来节省他们的钱,而不是依赖外卖和外出就餐,因此要求他们付费下载我们的应用实际上并没有太多意义。哪个学生愿意为本来应该节省他们钱的应用付费呢?

另一个选择是免费发布你的应用,但要求可选的捐款来支持你作为 Android 开发者的工作。这对于为更高级的 Android 用户或可能也是开发者的用户创建应用的开发者来说是一个流行的选择,因为他们更有可能欣赏创建出色的 Android 应用所付出的辛勤工作。

另一种让你的应用盈利的方式是通过包含广告,但如果你选择走广告这条路,那么确保它们尽可能不引人注目。在我们的食谱应用示例中,广告可能是一个可行的选择,因为它不需要我们精打细算的学生直接掏钱。

注意

在你的应用中包含广告

如果你决定包含广告,那么这将为你的应用打开一个额外的收入渠道:如果用户升级到你的应用的付费版本,你可以提供移除广告的服务。然而,如果你针对的是更高级的移动用户,那么你应该考虑你的目标受众使用诸如广告拦截器等工具的可能性。在这种情况下,通过请求可选的捐款来支持你作为 Android 开发者的工作可能会得到更好的回应。

如果你决定让你的应用盈利,它应该始终反映你的应用为你的目标受众提供的价值。如果你的用户决定在应用上投资金钱,那么你应该提供一些值得他们辛苦赚来的现金的东西。

你的目标受众中是否有人足够多,以至于你的项目可以盈利?

虽然具体化是好事,但你的应用必须吸引足够多的人,才能使你的项目有利可图。

注意事项

一个有利可图的项目不一定意味着它能为你赚钱(尽管那总是令人愉快!),对你来说成功意味着什么?可能意味着有X数量的人下载了你的应用,或者在 Google Play 上获得了 3 星以上的评分。

我们已经决定,我们的食谱应用将针对那些不擅长烹饪且希望获取快速、简单、便宜食谱的学生。我们可以通过针对那些对健康敏感的学生进一步缩小我们的目标群体,他们感兴趣的食谱快速、简单、便宜,而且低热量,富含维生素和矿物质。虽然对健康敏感的学生确实存在,但这更是一个细分的目标受众,如果你决定走这条路,你应该进行一些额外的市场研究,以了解是否真的有一个显著的市场需求这种非常具体的应用。

希望在仔细审查你的目标受众后,你会得出结论,你的目标受众确实迫切需要你心中所想的那种应用。如果不是这样,那么现在回到本节的开头,探索你的最初想法是否更适合不同的目标受众,正是时候。

小贴士

是时候认真了

在本章的目的下,我们创建了一个快速且通用的用户画像和一些可能的使用案例。然而,当你创建一个真实的 Android 应用时,你通常会想要进行某种形式的市场研究,这意味着与你要为你的应用创建的人建立联系,并获取他们对你的项目的看法。

好消息是,如果你认为站在当地商业街中间拿着记事本就是地狱(同感),那么互联网是市场调研的理想之地。例如,论坛和社交媒体可以帮助你与可能有一天会使用你的应用程序的人建立联系,或者你可以尝试联系相关机构,看看他们是否愿意将你与目标受众联系起来。在我们的食谱应用程序示例中,你可以尝试联系当地大学,看看他们是否愿意帮助你进行市场调研——尤其是既然你正在开发一个免费应用程序,可以帮助学生克服第一次自己做饭的挑战。

实际市场调研没有替代品,因此你应该始终努力与你的目标受众取得联系,因为他们的反馈将对你创建现实的人物角色和用例非常有价值,同时也有助于你确定你的真实目标受众希望在应用程序中看到哪些功能。

确定用户和产品目标

目标是那些如此明显以至于实际上很容易被忽视的事情之一。你为什么要发布这个应用程序?对你来说有什么好处?为什么有人会下载你的应用程序?这些问题可能很难回答,但它们对于整个规划过程来说可能是至关重要的。

当你有一套明确定义的目标时,你可以在回答设计相关问题时随时参考它们。你是否应该包括一个“分享到 Facebook”的功能?为了找到答案,只需查阅你的目标——这个功能将如何帮助用户实现他们的目标,或者帮助你实现你的目标?

你需要确定两种类型的目标。

产品目标

这并不只是关于用户!创建一个惊人的应用程序需要时间、努力,甚至可能还需要金钱,即使你只是为了乐趣而创建应用程序,你也应该从中获得一些东西!所以,你希望通过创建这个惊人的应用程序实现什么目标?

你的产品目标可能包括以下内容:

  • 你希望达到的下载量

  • 你希望在 Google Play 上获得的评论数量

  • 在 Google Play 上的最低星级评分

  • 传播你公司、产品或服务的信息

  • 通过用户升级到应用程序的专业版本、应用内购买、广告或你选择的额外收入渠道产生的财务回报

用户目标

这些是选择下载和使用你应用程序的人的目标。他们希望实现什么?在我们的食谱应用程序示例中,主要用户目标之一是找到经济实惠的食谱,这些食谱不会让你破产。

在这个阶段,您不需要定义如何实现这些目标,因为可能存在多种实现相同目标的方法。例如,我们可能在我们应用程序的主屏幕上添加一个搜索框,以便用户可以根据食材的成本来筛选食谱;或者您可能决定将您的食谱分为类别,然后在用户屏幕顶部显示这些类别作为标签——每人头下£1 的食谱; 每人头下£2 的食谱,等等。

目前,请专注于确定用户目标,并将定义实现这些目标所需的功能留到以后。

有时,聚焦于有意义的用户目标可能并不简单。也许您只能想到一些宽泛的、一般化的目标,这些目标并不特别有帮助。例如,想象一下您有一个关于文档编辑应用程序的想法,您确信它可能是下一个 Google Drive,但您唯一能想到的目标是“我想能够创建和编辑文档。”这并不具体!关键是把这个宽泛的、一般化的目标分解开来。用户将如何创建这些文档?他们可能想要进行哪些编辑?他们可能想要对完成的文档做些什么?头脑风暴您的答案。

在头脑风暴会议之后,您应该有一份更具体的用户目标列表。在我们的文档编辑应用程序中,这些可能包括“我想能够……”

  • 快速轻松地创建新文档,这样我就可以随时记笔记

  • 使用各种格式化技术使我的文档更具视觉吸引力

  • 通过社交媒体与他人分享我的文档

  • 与朋友和同事协作编辑文档

创建路线图

路线图是您将应用程序从初始发布版本发展到您在编写最终功能列表时心中所想的版本的关键。实际上,现在正是仔细审视这个列表并决定哪些功能应该包含在您应用程序的第一个版本中的最佳时机。

这个初始版本应包括所有满足您应用程序主要任务所需的功能(想象一下发布一个带有大量滤镜但缺乏拍照功能的相机应用程序!)。它还应包括其他高优先级功能,这些功能通常是在您进行市场研究或创建用户画像和用例时反复出现的功能。

如果您不确定某个功能是否重要到足以包含在您应用程序的第一个版本中,请问自己:“没有这个功能,我的应用程序是否仍然可用?”您的应用程序的第一个版本应仅包含必需的功能*。

注意

我们在谈论的是您应用程序的初始版本,而不是初始发布。这是因为您不一定必须将初始版本发布给公众。

在我们完成识别目标受众的所有工作之后,你最终功能列表中的一些项目可能已经不再相关,因此现在是时候再次审视我们的列表,并划掉任何不必要的功能。

你应该剩下的是一个列表,其中包含你的应用无法正常运行的必备功能,以及一些非必需但会为你的应用增加价值的额外功能。后者将成为你路线图的基础。

到目前为止,路线图更像是一个指南而不是严格的日程安排,但创建一个粗略的路线图有助于你可视化项目随时间可能的发展。它还为你提供了一个大致的想法,即你何时可能准备好发布不同的功能。

创建你的路线图时,查看你想要的功能列表,并寻找它们之间的关系。例如,你可能会将所有与社交媒体相关的功能或所有允许用户上传自己内容的特征分组在一起。

你通常会希望将相关功能一起发布,所以一旦你组成了这些组,你应该根据它们的重要性顺序对它们进行排名。这将是你发布这些功能的顺序,所以下一步是将这些组分配给应用的不同版本;例如,如果你决定社交媒体功能比上传新内容的能力更重要,你可能会选择在版本 2.0 中发布这些功能,而后者可能成为版本 3.0 的基础。

小贴士

小批量、频繁发布

不要试图将大量功能压缩到单个发布中。小批量、频繁发布比每次更新包含大量新功能但让用户等待数月数月要好得多。

你的典型 Android 用户很快就会忘记一个很少更新的应用。即使他们没有忘记你的应用,他们可能仍然难以适应引入太多变化的更新。

小批量、频繁发布也能让你更好地控制开发过程,并使你更容易满足这些截止日期。大量发布很难正确规划,而且通常压力很大,所以请为自己和你的用户做点好事,创建一个包含大量小型、频繁发布的路线图。

你还应该尝试在你的路线图中添加截止日期,即使它们是推测性的截止日期,因为它们会促使你开始思考每个功能开发需要多长时间。这些截止日期可能是模糊的,例如,“二月份,我希望发布版本 1.0”,或者“在 1.0 版本发布后四周内发布版本 2.0。”或者,它们可能与年度事件相对应;例如,如果你正在开发购物应用,你可能会决定在圣诞节前夕或在一月销售旺季期间发布此应用。

至少,你应该设定一个希望完成应用第一个版本的截止日期。

一些需要考虑的最终事项

你几乎完成了你的应用规划!在我们进入设计阶段之前,还有一些最后的细节需要考虑。

你应该支持哪些设备?

你应该支持哪些类型的设备?你的应用应该与哪些版本的 Android 兼容?这些问题很棘手,不幸的是,每次你开始一个新的 Android 项目时,你都必须面对这些问题。

考虑到 Android 应用可以在任何设备上运行,除非你明确设置它不运行,可能会诱使你这样想:“我会等等看我的应用在哪些设备上运行良好”,但这确实是一个最终导致应用在你的典型 Android 手机上运行良好,但在其他所有设备上运行不佳的方法。

取决于你提供一种适用于各种平板电脑、大屏手机和横屏或竖屏模式的 Android 智能手机的体验。创建一个能在多种设备上运行的应用并不容易,但你在规划如何支持不同的设备和屏幕配置上投入的精力越多,实施起来就越容易。除非你有非常好的理由不这样做,否则现在就是开始思考如何支持尽可能多的不同 Android 设备的时候了。

如果你决定不支持某些设备,那么你的用户会感激你提前告知,而不是等到他们第一次启动你的应用时发现它完全无法在他们设备上运行。至少,确保在应用 Google Play 描述中提前告知用户不支持哪些设备。

你应该如何推广你的应用?

大多数时候,你推广应用的对象将与你的目标受众相同,但这并不总是如此。例如,如果你正在开发一个教育应用,帮助幼儿学习字母表,这个应用可能对家长更有吸引力,而不是你的目标用户(在这种情况下,还在学习阅读的儿童不太可能搜索 Google Play,寻找可以下载的应用)。

设计你的应用

到目前为止,你应该有一个关于你想要创建的应用的详细计划。你已经完成了以下工作:

  • 一个书面概念

  • 一个最终的功能列表

  • 识别了你的应用的主要任务

  • 编写了一个产品定义声明

  • 通过创建用户画像和一些用例来识别你的目标受众,并将这些信息纳入你的计划中

  • 开始思考一些棘手的问题,包括盈利模式、市场研究和推广你的应用

现在,是时候开始探索你的应用应该如何看起来了。设计应用 UI 的想法可能看起来令人畏惧(尤其是如果你没有设计经验的话),但投入额外的精力进行 UI 设计可以将一个不错应用变成一个优秀的应用;难道我们不是都希望创造优秀应用吗?

在本章的最后部分,我将向你展示如何迈出设计一个有效且吸引人的用户界面(UI)的第一步。

高级流程

制定你的应用程序结构的第一阶段是创建一个高级流程图。这是你绘制用户将通过你的应用程序完成不同任务的不同路径的基本计划的地方。

如你所猜测的,表达应用程序高级流程最流行的方式是使用流程图。你如何创建这个流程图取决于你。你可能想使用你最喜欢的图像编辑软件创建一个数字图表,或者你可能想用纸和笔回到过去。选择对你来说最有效的方法。

高级流程

你通常用形状表示屏幕,用线条或箭头表示导航。你可能还想要给你的流程图中的屏幕编号,尤其是如果你正在规划大量屏幕。这样,当是时候创建线框图时,你可以在线框图中为屏幕编号,以与流程图中的屏幕相对应。

开始寻找改进用户体验的方法永远不会太早,而在这一早期阶段评估你的应用程序用户体验的最简单方法之一是查看用户完成不同任务需要执行多少次交互。

你的流程图非常适合识别任何减少或简化完成每个任务所需步骤数量的机会,无论是删除屏幕、重新排序屏幕还是更改应用程序的导航。例如,你可能会决定在屏幕 A 上包含一个菜单,这样用户就可以直接跳转到屏幕 E 和屏幕 F。

你还应该确定哪个屏幕应该是你的应用程序的主屏幕或首页。首页应该帮助用户完成应用程序的主要任务。由于我们的食谱应用程序旨在帮助用户找到快速、便宜且易于制作的食谱,我们可能会决定选择搜索食谱屏幕作为我们的首页。

此流程图也是确定你可能会用太多选项让用户感到不知所措的区域的完美机会。如果你打算在首页上包含一个搜索框,是否明智地在同一页面上显示所有用户保存的食谱以及应用程序的主要设置?你可能决定将其中一些内容移动到不同的屏幕,或者暂时将其隐藏在侧菜单或单独的标签页中。

或者,你可能决定,如果你足够仔细地设计你的应用程序,你可以通过在单个屏幕上显示所有这些内容来避免让用户感到不知所措。如果你选择后者,那么请注意,你应该为这个屏幕额外分配一些时间,以确保用户体验尽可能简单明了。

小贴士

成为批评家

在处理你的高级工作流程时,审视其他 app 如何以尽可能少的摩擦将用户从第一个屏幕引导到他们想要的位置可能会有所帮助。

查看你 Android 设备上安装的应用,如果你特别喜欢某个应用,为这个应用快速制作一个流程图。尝试确定这个流程为什么如此有效。绘制你不太喜欢的 app 的流程图也可能有所帮助,然后尝试找出为什么这个流程不适合你。这些练习将帮助你养成对应用流程进行批判性思考的习惯,这对于你有效地分析和优化自己的 app 流程图至关重要。

创建屏幕列表

一旦你创建了高级流程,列出所有你需要创建的屏幕,以便用户能够完成他们的各种任务和目标。

我们的食谱 app 的屏幕列表看起来可能像这样:

  • 主屏幕

  • 类别列表

  • 某个类别的食谱列表

  • 已保存食谱列表

  • 详细食谱视图

  • 搜索框

  • 搜索结果

创建屏幕地图

现在是时候将我们的流程图和屏幕列表结合起来,创建一个屏幕地图,以表达构成我们 app 的所有不同屏幕之间的导航关系。

如果有疑问,请参考你的流程图并回顾用户可能通过你的 app 采取的不同路径。

以下是我们食谱 app 的屏幕地图的一个示例:

创建屏幕地图

注意

一个屏幕不一定等于一个活动。一个屏幕也可能等于一个作为多窗格布局一部分显示的碎片。

将屏幕分组为多窗格布局

我们在设计屏幕地图时没有针对任何特定的 Android 设备,但 Android 应用必须足够灵活,以适应广泛的设备,从小型手机到大型平板电脑。

为了以最有效的方式向用户展示内容,你可能想要将相关的屏幕分组在一起。然后,你可以根据用户的设备和屏幕配置以不同的组合展示这些碎片

较小的屏幕通常只适合一次显示一个内容窗格,因此我们地图中的每个屏幕通常相当于较小设备上的一个屏幕。

然而,有时会有更多的屏幕空间可用,例如,当用户在平板电脑、大屏手机或甚至横屏模式下的 Android 智能手机上查看你的 app 时。当有更多空间可用时,你的 app 应该充分利用,而使用碎片就是一种方法。

碎片可以帮助你创建一个能够适应多种不同设备类型、屏幕尺寸和屏幕方向的 app。将屏幕组合成多窗格布局也有助于最小化用户完成每个任务所需进行的交互次数。

我们如何将我们的食谱应用程序中的不同屏幕分组,以便我们能够有效地在多面板布局中显示多个片段,并且在单面板布局中显示单个片段?

在较小的屏幕上,我们可能希望将食谱应用程序的主屏幕显示为一个单独的、独立的片段。然而,如果用户在较大屏幕上查看我们的应用程序,那么显示在主屏幕片段旁边的其他信息可能有什么意义?

让我们回顾一下用户在主屏幕上可能想要执行的所有任务。他们可能想要执行以下操作:

  • 访问类别列表

  • 访问他们保存的食谱

  • 进行搜索

  • 访问食谱

允许用户从主屏幕执行这些任务中的任何一项都会增加价值,但我会选择进行搜索,因为我感觉这是用户在我们食谱应用程序中最常想要执行的任务。因此,在较小的屏幕上,我希望用户看到主屏幕;但在较大设备上,我希望用户看到主屏幕片段搜索片段的多面板布局。

逐步浏览你的屏幕图,寻找将你的屏幕分组到多面板布局的任何其他方法,以便用户在较大设备上访问你的应用程序。

注意

当你显示多个面板的内容时,接受的惯例是从左到右按详细程度递增的顺序排列你的内容。

导航

你的应用程序的导航应该感觉直观且毫不费力,以至于即使是新用户也应该能够轻松地导航你的应用程序。由于你已经创建了一个屏幕图,确定你的应用程序的导航应该是一件轻而易举的事情。

应该很容易导航到应用程序中最重要目的地;例如,在我们的食谱应用程序中,主屏幕将是最突出和最易访问的屏幕。

当你通过在导航层次结构中将其放置得更高来优先考虑一个屏幕时,这个屏幕被称为父级。那些不太重要的屏幕,你通常将它们放置在父级屏幕之下,被称为子级。继续这个主题,具有相同父级的屏幕被称为兄弟,它们通常具有相同的优先级。你也可能听到集合这个词,有时用来指代具有相同父级的多个屏幕。

导航可以分为以下几类:

  • ** descendant navigation**:当用户深入导航到你的应用程序时,他们是从较高层次结构下降到较低层次结构。从父级屏幕移动到子级屏幕是下降导航的一个例子。

  • 横向导航:这是用户在兄弟屏幕之间移动的地方。

导航

为了帮助你确定最适合你应用程序的导航类型,问自己以下问题。

你的应用程序最重要的任务是什么?

识别所有用户可能想要执行的任务,然后为这些任务中的每一个分配一个优先级。你应该通过在你的应用导航层次结构中突出显示这些任务,让用户能够轻松完成高优先级任务。例如,如果你正在设计一个音乐应用,你应该让用户能够轻松播放音乐(一个高优先级任务),而像能够推文分享你正在收听的歌链接这样的低优先级任务,可以放在导航层次结构的较低位置。

有没有可以组合在一起的任务?

你应该在相关功能之间创建某种导航关系;例如,你可以将它们包含在同一个菜单中,同一个屏幕中,或者在同一个父屏幕中的标签行中。

在我们的音乐应用示例中,能够搜索特定艺术家的更多专辑、查看本周的前 10 首热门歌曲、浏览新发行的音乐以及查看你的朋友目前正在收听的歌曲等所有功能都有助于你发现新的音乐,因此你可能会决定将这些功能放在你的应用导航结构中一起放置。

我的导航是否一致?

一致性至关重要。即使你设计的导航尽可能简单,如果导航在不同屏幕之间有所不同,那么你最终会得到一些非常沮丧和困惑的用户。

常见导航模式

在规划你的应用导航时,你应该专注于如何最好地满足目标受众的需求以及他们将在你的应用中执行的任务。然而,有一些常见的导航模式可以帮助你在构建适合特定应用的独特导航结构时取得先机。

嵌入式导航

如果你的应用结构简单,你可能选择将导航嵌入到你的内容中。这种方式的缺点是,将导航嵌入会减少显示应用内容的空间,因此可能不适合屏幕较小的设备。

按钮 和 简单目标

最直接的导航模式之一是在应用的父屏幕中包含可触摸的目标,例如按钮。当用户触摸这些目标之一时,会打开一个子屏幕,该屏幕可能包含更多的可触摸目标。

这种导航易于理解,但请注意,在单个屏幕上包含大量可触摸目标可能会使你的应用在较小的设备上难以导航。

列表

如果你需要显示大量文本,那么垂直滚动列表(如经典的基于文本的菜单)可以提供一种简单直观的导航应用的方法。

只要注意不要创建一个导航层次结构,其中列表导致更多的列表,因为这种导航可能会导致完成每个任务所需的触摸次数失控。

网格、卡片和轮播

当你需要显示大量视觉内容,如视频或照片时,你可能想使用垂直滚动网格、水平滚动轮播或卡片:

网格、卡片和轮播

基于网格的导航示例

标签页

虽然 Android 的传统导航模型是分层的(从父屏幕移动到子屏幕),但在某些情况下,水平导航可能更有意义,尤其是如果你的应用具有许多兄弟屏幕。

标签页是水平导航的一个常见例子。如果你的应用包含大量分组内容、兄弟屏幕、或同等重要的分类数据集或内容,那么标签页是理想的导航形式,因为它们允许你在单个父容器中嵌入多个屏幕。标签页为用户提供了一种直接访问所有这些内容的方式,而无需每次想要访问不同的兄弟屏幕时都返回到父视图。

标签页

标签页也是确保用户意识到你应用所有兄弟屏幕的有用方式。有了标签页,用户无需探索任何隐藏菜单,因为所有应用兄弟屏幕都整齐地排列在屏幕顶部。

如果你确实选择基于标签页的导航,以下是一些你应该记住的最佳实践:

  • 当用户选择标签页时,可能会出现新内容,但标签页本身应保持不变。

  • 标签标签应仅由图标或文本组成。如果你确实选择文本标签,你应该尽量使它们尽可能短。

  • 不要将标签页之间的切换视为历史记录。如果用户从标签页 A 切换到标签页 B,按下他们的设备上的返回按钮不应该将他们带回到标签页 A。

  • 将标签水平排列在屏幕顶部作为单行。标签页永远不应该在屏幕底部运行。如果你确实想在屏幕底部显示操作,请使用分割操作栏。

  • 将滑动手势保留用于在标签页之间导航。不要包含任何支持滑动手势的标签页内内容,因为用户可能会意外地在标签页之间滑动。

  • 不要在标签页内包含进一步的分栏内容。还记得那部电影《盗梦空间》吗?没有人想要一个分栏盗梦空间

  • 如果你怀疑你的用户可能会频繁地在标签页之间切换以比较内容,那么这是一个你应该将内容合并到更少标签页的信号。或者,也许标签页根本不是你应用的最佳导航解决方案?

  • 标签页非常适合显示多个兄弟视图,但你应该将标签页限制在每个父屏幕不超过四个。如果你确实需要显示超过四个兄弟视图,那么再次提醒,标签页可能不是你特定应用的正确导航解决方案。

水平分页(滑动视图)

水平分页是另一种在兄弟屏幕之间导航的流行方法,有时也称为滑动视图。像标签页一样,当你的应用具有多个兄弟屏幕时,水平分页非常有用。

如你所猜,从名字中可以看出,滑动视图是一种导航形式,一次只显示一个屏幕,用户通过从右滑动从左滑动的手势在屏幕的兄弟之间导航。

当你的应用有少量兄弟屏幕,并且这些屏幕之间存在有序关系时,水平分页效果最佳;例如,日历应用通常使用水平分页在月份之间导航。

社交层

在我们的食谱应用中,我们简要地提到了让用户通过社交媒体与朋友和家人分享他们最喜欢的食谱的想法。即使这样的简单社交功能也能为应用增加真正的价值,因此考虑你的应用是否可能从一些社交功能中受益是值得的。至少,给你的用户提供通过社交媒体分享应用内容的选项是一种快速简单的方式,可以推广你的应用。

备注

将社交方面添加到你的应用不是强制性的!你应该只在社交功能以某种方式为你的应用增加价值时才包含它们;不是每个应用都必须有社交方面。

当你在决定是否包含社交层时,请自问以下问题:

  • 你的用户可以从建议的社交功能中获得什么?

  • 社交层会如何提升用户体验?

  • 哪些具体的社交功能可能为你的目标受众带来最大的价值?

  • 哪些社交功能可能有助于营造社区感?

  • 建议的社交功能需要用户付出多少努力?潜在的回报是什么?从用户的角度来看,这些功能值得吗?

  • 添加社交层到你的应用有哪些优点?

  • 潜在的缺点是什么?

当你考虑社交功能时,社交媒体应用,如 Facebook 和 Twitter,可能会立即浮现在你的脑海中。然而,有无数种方法可以巧妙地将社交功能整合到你的应用中,而不会将其变成一个社交媒体应用。

一些具有有效社交功能的非社交媒体应用示例包括 Google 日历和 Spotify。

社交层

每次你在 Google 日历中创建新事件时,应用都会提示你查看你的设备联系人,并邀请朋友、家人、工作同事和其他联系人参加你的活动。然后,每个联系人都可以接受或拒绝这个邀请,而你也会得到额外的安全保障,知道接受你邀请的每个人在活动临近时都会收到自动的 Google 日历提醒。

社交层

Spotify 使用社交功能帮助你找到新的音乐来听。如果你打开 Spotify 并导航到活动,你会看到一张列表,列出了你的 Spotify 朋友一直在听的音乐,如果你没有 Spotify 好友,应用会为你推荐一些可以关注的人。

摘要

在本章中,我们通过记录我们的用户和产品目标以及确定我们的目标受众,制定了一个新的 Android 项目的详细计划。我们创建了一个概念、产品定义声明、功能列表,并考虑了我们的应用盈利选项。

然后,我们开始绘制我们的应用草图,通过创建路线图、流程图、屏幕列表和带有完整导航的屏幕图。在下一章中,您将看到如何将这个粗糙的草图转化为详细的文档或数字线框,并用虚拟内容填充这个线框。

第六章:将你的草图转换为线框图

在上一章中,我们通过创建屏幕图和流程图等东西,在非常高的层面上集中规划我们的屏幕。在本章中,我们将从高级规划转向开始设计将构成我们的应用屏幕。

在屏幕设计过程中,你通常会完成以下步骤:

  • 初稿线框图:这是你开始通过使用纸张和铅笔快速草图来布局屏幕的地方。线框图是你应用布局的低保真度、视觉表示;有时,它被称为骨架、轮廓、页面示意图或屏幕蓝图。为了帮助你专注于每个屏幕的布局、功能和可用性,线框图通常会去除所有外观和感觉元素,至少最初是这样,大多数内容和文本。

  • 数字线框图:一旦你对铅笔和纸张的线框图感到满意,就是时候使用数字软件来细化这些初稿了。这一步也是你检查你正在形成的设计是否适合你想要展示的内容类型的地方,通过开始添加每个屏幕最终将显示的内容和文本。

  • 原型制作:下一步是基于你的数字线框图使用纸张和铅笔创建原型。

  • 可用性测试:这是你在实际用户身上测试你的纸质原型的地方。这种测试对于突出显示你导航中的任何问题,并帮助你了解用户界面对用户理解有多容易至关重要。

注意

在设计中,低保真度指的是概念、设计替代方案和屏幕布局的粗略表示。通过创建低保真度原型和线框图,你可以在不投入太多时间和精力到设计过程中去的情况下测试各种想法。高保真度设计是指填补设计低保真度前身所缺失的所有细节。在本章中,我们将使用纸张和铅笔创建低保真度线框图,然后把这些初步想法发展成为更详细、高保真度的数字线框图。

什么是线框图?

将线框图想象成屏幕布局的骨架,它描述了主要 UI 组件在屏幕层次结构中应该如何排列。线框图的目的在于关注诸如功能、可用性、行为以及内容的定位和优先级等问题。线框图不是关于图形设计的。实际上,在这个阶段,最好省略所有与设计相关的元素。你可能甚至故意让你的线框图看起来非常粗糙和草图般,以帮助你专注于每个屏幕做什么,而不是它看起来像什么。

你的线框图通常不会包括颜色,除非你使用颜色来区分 UI 中的某些元素;例如,你可能想拿一支彩色铅笔在相关内容组周围画一个框,或者添加没有屏幕上物理存在的元素,如交互和运动。

你可以使用铅笔和纸、在白板上涂鸦或使用广泛的免费和商业软件应用来创建线框图。然而,对于初稿来说,使用铅笔和纸快速草图线框图是有意义的。

线框图的优点是什么?

线框图听起来很简单,但这些看似简单的草图是设计更有效应用屏幕的重要步骤。以下是几个值得你拿出铅笔盒、拿起笔记本并线框图你的应用的理由:

  • 线框图非常适合快速测试不同的布局。

    制作草图既快又简单,这意味着线框图是探索多种不同潜在布局的完美机会。

    线框图鼓励你发挥创意,尝试不同的 UI 元素的位置和尺寸以及不同的导航。它们也是映射 UI 元素之间关系的一种简单方法。

    通过线框图多种不同的可能性,你可以发现构成你应用的每个屏幕的最佳布局。

    虽然从技术上讲,你可以在设计过程的每个阶段进行实验,但你在项目上投入的时间和精力越多,你就越不愿意尝试新的想法和不同的布局。

    在线框图多种不同的布局时,你应该注意的一点是,线框图是屏幕的静态表示。线框图并不擅长显示交互细节或移动元素。你需要发挥想象力来更好地了解你的布局中任何交互或移动元素的工作方式,或者花更多的时间原型设计和测试你的布局,这将在本章后面讨论。

  • 它们帮助你早期在设计过程中识别问题。

    就像生活中的一切一样,有时候理论上的事情比实际操作中做得更好,所以你在屏幕地图或脑海中看似不错的想法,一旦尝试实施可能会立刻出现问题。

    无论你的应用经历了多次广泛的设计变更还是仅仅是一些微调,设计变更都是创建移动应用不可避免的一部分,所以不是尽早发现潜在问题更好吗?线框图是快速发现设计问题的最快、最简单的方法之一,而且修改线框图大约比修改高保真原型或更糟糕的,已经处于开发中的用户界面要容易 1 亿倍。

    在线框图阶段发现问题也意味着你更有可能做出必要的更改,以正确解决这些问题,即使这意味着彻底改变你的设计。当你发现后续问题,你自然会倾向于寻找解决方案和快速修复,以修补问题而不会对你的设计造成太大干扰。这种做法可能短期内让你生活更轻松,但长期来看,可能不会让最终用户感到非常满意。

  • 它们鼓励你捕捉一个完整的设计,而不是随意发挥。

    你是否觉得你对应用的外观有一个清晰的心理图像?也许你在开始规划过程之前就有了这个愿景。如果是这样,那很好,但你仍然需要将这个愿景记录在纸上!

    心理图像的边缘往往有点模糊,有时,直到你试图在纸上捕捉它们,你甚至都没有意识到它们有多模糊。一旦你开始尝试重现你心中的景象,你可能会意识到,你的设计中仍有一些部分悬而未决。

    随着你进入设计流程,在面前实际存在的某个东西上进行重绘和细化,远比在虚空中飘浮的图像要容易得多。

  • 它为每个人提供了一个共同的设计语言。

    在本质上,线框图基本上只是标注和注释的草图,因此它们是每个人都能理解的东西。如果你的应用是一个团队项目,这是一个好消息,因为线框图有助于弥合开发应用和设计应用之间的差距,而无需使用任何开发人员或设计师的专业术语。

    如果你正在为某个客户开发应用,线框图也是确保客户对其项目进展有一个清晰了解的绝佳方式。然后你可以获取客户的反馈,并希望在他们进入项目下一个阶段之前,获得他们对设计的认可。

    人们自然不愿意对某人显然花费数小时,甚至数天时间完美化设计提出批评。虽然想要照顾团队成员的感情是好事,但这从长远来看并不能帮助你创建出最好的应用。

    确保你的线框图看起来像是一个快速草图,即使是最有礼貌的团队成员也应该感到舒适地指出其缺陷。

  • 它们鼓励你更详细地思考导航问题。

    过度估计设计不良导航的负面影响是不可能的。如果一个用户无法轻松地在你应用中导航,他们很可能会直接退出你的应用。

    我们在上一个章节中提到了导航,但到目前为止,导航在屏幕地图上不过是一组向上、向下和侧向的箭头。线框设计是开始思考如何实现你的应用导航的完美机会。用户究竟将通过标签、操作栏还是他们的设备的返回按钮从主页移动到设置页?线框设计是设计有效导航的关键步骤。实际上,每次你创建线框时,你首先需要决定屏幕上应该出现哪些导航元素,正如你将在本章后面看到的。

创建线框

创建线框可能看起来相当直接。任何人都可以随意堆砌一堆草图,对吧?虽然线框设计应该是快速和简单的,但如果你想要从你的线框设计会议中获得最大价值,那么你将想要尽可能多地思考你的线框。

在早期草稿中,最有效的线框设计方法是简单地使用纸张和铅笔绘制你的屏幕。理想情况下,你应该为每个屏幕制作几个版本,纸张草图是测试几个想法的最快方式。一旦完成草图,只需将草图并排放置,并决定哪个设计最有效,或者你也可以甚至决定从多个草图中选择元素来创建完美的布局。

即使你更喜欢数字线框设计,并迫不及待地想坐在电脑前启动你最喜欢的图像编辑程序,你也应该先花时间创建粗略草图,因为这正是让你的想法流动起来,并在进入耗时更长的数字线框设计过程之前快速测试多个设计的方法。

创建你的第一个线框

在你的线框设计会议期间,将你的屏幕地图放在手边是有帮助的,这样你可以随时参考它。当你开始一个新的项目时,通常从用户启动你的应用时将看到的第一个屏幕开始是有意义的。在我们的食谱应用中,这恰好就是主页。

在你的屏幕上绘制一个粗略的轮廓,然后确定你需要包含哪些导航元素。对于我们的食谱主页,这些元素如下:

  • 用户可以从哪些屏幕到达主页。本质上,你需要考虑用户如何导航回到上一个屏幕。很多时候,这可以通过设备的返回按钮自动处理,但你也可以通过动作栏中出现的左指箭头或一个 UI 元素,如返回上一个屏幕按钮来提供向后导航。为了简化问题,让我们假设向后导航是由设备的返回软键自动处理的。

  • 用户可以导航到的屏幕。在我们的食谱示例中,这是已保存食谱列表、类别列表和搜索屏幕。为了以紧凑的方式展示所有这些选项,我打算将它们添加到一个菜单中,该菜单将出现在屏幕的左侧:

创建你的第一个线框图

接下来,决定你需要显示哪些内容。首先添加最重要的内容,如图片、标题、副标题和其他大型内容块,然后按内容层次结构逐步进行。

对于我的主页,我想显示应用的标题和标志,以及一些诱人的用户可以通过遵循此应用中包含的食谱制作的菜肴的图片:

创建你的第一个线框图

小贴士

感觉灵感枯竭了吗?

如果你正为灵感而苦恼,那么批判性地审视其他应用是一个很好的启动创意过程的方法。从评分高的 Google Play 商店或 Google 自己的应用套件中汲取灵感是最佳选择。后者尤其有用,因为它们通常是最新最佳实践和设计原则的绝佳示例,尤其是 Material Design。

花些时间浏览不同的屏幕,问问自己你喜欢每个屏幕布局的哪些方面,以及是否有可以改进的地方。创建你特别喜欢的屏幕的线框图可能会有所帮助,然后寻找任何可以用于你自己的线框图的相似之处、元素或一般设计原则。

探索更多线框图

每次你创建线框图时,过程都会因你创建的应用类型、你想要显示的内容、屏幕的目的以及你的目标受众等因素而略有不同。然而,可以识别出某些在多个应用中出现的类型的屏幕,因为这些屏幕通常具有相似的特征和 UI 元素。

以我们的主页线框图为例。许多应用都有某种形式的主页,这些屏幕通常包括标题、标志和主菜单等元素——就像我们的食谱应用主页一样。除了主页之外,这个应用还是几个其他常见类型的屏幕的绝佳示例。由于你很可能在自己的项目中遇到这些屏幕,让我们看看如何为每个这些屏幕创建线框图的方法。

我将从我称之为细节屏幕的内容开始。这是一个当用户在前一个屏幕上选择一个项目时出现的屏幕,应用会显示一个包含有关所选项目更多信息的新屏幕。在我们的食谱应用中,包含每个食谱的屏幕就是一个细节屏幕;用户浏览食谱列表,找到一个他们喜欢的,然后点击食谱。这时,他们会进入一个单独的屏幕,包含他们需要重新制作这道菜所需的所有信息。

备注

在我们的屏幕图中,我们将这个屏幕称为详细食谱视图

细节屏幕的线框设计

如其名所示,细节屏幕都是关于展示信息,这通常是文本和媒体(如图片、视频或音频)的混合。

让我们遵循我们用于线框设计主页的过程,首先确定我们需要添加的所有导航元素。看一下屏幕图,我发现我需要添加以下内容:

  • 用户可以从哪些屏幕到达详细食谱屏幕。由于我们在主页线框设计中忽略了向后导航,让我们在这里更详细地探讨它。根据屏幕图,用户可以从搜索结果屏幕、特定类别的食谱列表和保存的食谱列表到达详细食谱屏幕。尽管我们可能依赖于用户点击设备上的返回软键返回上一个屏幕,但线框设计完全是关于探索不同选项,所以我要尝试一些不同的事情,并在这个特定的线框中添加一个返回上一个屏幕按钮。

  • 用户可以导航到的屏幕。在我们的简单屏幕图中,详细食谱屏幕看起来像是应用程序中的最后一个屏幕,但在现实中,你很少在移动应用程序中遇到这种死胡同。当用户将这个食谱添加到他们保存的食谱列表中时,我认为如果应用程序将用户带到他们的在线剪贴簿,以便他们可以查看所有保存的食谱,这将是有帮助的。为了创建这种效果,我将添加一个添加到剪贴簿按钮,当按下时,执行此操作将用户带到他们的在线剪贴簿。

现在,是内容展示的时候了。不分先后顺序,我希望这个屏幕显示以下内容:

  • 食谱的标题

  • 制作这个食谱有多难

  • 烹饪所需时间的估算

  • 成本估算

  • 这个食谱可以供多少人食用

  • 一张诱人、令人垂涎的成品照片

  • 本身就是食谱

小贴士

你真的需要一个细节屏幕吗?

创建细节屏幕的黄金法则就是它必须包含足够的信息,以证明其作为独立屏幕的地位。例如,想象你刚刚下载了一个应用程序,你可以用它来查看你当地电影院正在上映的电影。

第一屏幕是一个时间表,显示了所有电影及其放映时间。你可以点击时间表中的每部电影,应用程序将加载一个新屏幕,专门用于所选电影。你可能会在这个屏幕上找到哪些新信息?也许是一个剧情简介或主演女演员和男演员的列表?或者是一个星级评分或链接到评论,这样你可以决定这是否是一部你需要带着爆米花在大银幕上观看的电影,或者你是否应该等待它在 Netflix 上播出?

现在,假设你没有看到任何这些额外信息,详情屏幕只是重复了你在主屏幕上已经看到的信息。你会觉得这是一个设计精良、提供良好用户体验的应用吗?答案是响亮的

如果你确实决定包含一个详情屏幕,那么请确保它包含一些新信息。如果没有,那么它就没有价值,因此,它就没有在应用中的位置。

总是首先在你的线框图中添加最重要的内容。在这个例子中,那就是标题、成品图片以及食谱本身:

绘制详情屏幕的线框图

当你沿着内容层次结构向下工作时,你可能会遇到可以在屏幕布局中多个位置工作的内容。如果是这样,那么这是通过创建多个线框图来尝试不同布局的理想时机。然后你可以将这些草图并排放置,并决定你更喜欢哪种布局,或者你可以从多个线框图中组合元素来创建你完美的布局。以下是我为详细食谱屏幕创建的线框图选择:

绘制详情屏幕的线框图

这个线框图包含了一些可能在不同的位置工作的元素——特别是包含估计时间、估计成本和这个食谱可以喂多少人以及食谱的星级评分的文本框。线框图为你提供了一个快速简单的方法来测试这些元素在多个不同位置的效果。

绘制详情屏幕的线框图

在这些元素的不同位置绘制线框图时,我决定在所有这些线框图中保持主要的内容区域相同:

绘制详情屏幕的线框图

备注

我们还可以在这个屏幕上添加更多内容;例如,可能让用户能够发表评论、上传照片或为每个食谱打分(满分 10 分)会很好。你可能还希望包含多张成品照片,让用户可以滚动查看这些照片。为了使这一章简短,我不会绘制所有这些选项的线框图,但如果你有创意,你可能想拿一张纸和一支铅笔,创建包含所有这些额外内容的自己的线框图。

绘制搜索屏幕的线框图

许多应用都有某种形式的搜索功能,我们的食谱应用也不例外,所以让我们看看如何绘制搜索屏幕的线框图。

你现在应该知道了!看看屏幕图,并确定你需要包含的所有导航元素:

  • 用户可以从哪些屏幕到达搜索屏幕。根据屏幕图,用户只能从主页屏幕到达搜索屏幕。

  • 用户可以导航到的所有屏幕。用户只能从搜索屏幕到达的屏幕是搜索结果屏幕。

在之前的线框中,我们通过依赖设备的返回软键和创建一个返回上一屏幕按钮来处理向后导航,所以这次让我们尝试一些不同的方法!我打算在操作栏中使用一个向左的箭头。

注意事项

在现实生活中创建 Android 应用时,你通常会选择一种导航方式并在整个应用中使用它,而不是我们在本章中使用的混合方式。尽管我在尝试很多不同的导航选项,但请记住,在实际项目中,你会选择一种导航形式并在整个应用中应用它,以避免让用户感到困惑和沮丧。

让我们创建我们的搜索功能。我会保持简单,并使用以下内容:

  • 一个EditText,用户可以输入他们想要搜索的关键词。

  • 用户按下以提交他们的搜索词的按钮。我将使用标准的搜索系统图标来标记此按钮,以确保用户立即知道此按钮执行的操作。

你可能已经注意到,在之前的线框中,我们使用的是一般术语,如标题框和图像,而现在,我们使用的是技术术语EditText。这是因为线框设计没有严格的规则,所以如果你已经有了创建某些 UI 元素的想法,并且想要将此信息添加到你的线框中,那么请继续!

注意事项

唯一的例外是当你打算与他人分享你的线框,尤其是如果这些人本身不是 Android 开发者时!如果你与非 Android 类型的人一起创建线框作为协作努力,那么你应该坚持使用民用术语,如文本框和图像。

绘制搜索屏幕线框

虽然这种布局没有问题,但不可否认的是,它相当基础。如果我们的用户能够根据几个不同的标准搜索食谱会更好:

  • 该食谱制作有多简单

  • 购买食材的成本

  • 该食谱可以供多少人食用

实现此功能的方法有很多,但我想到的两种方法是这里:

  • 通过在现有的搜索屏幕线框中添加复选框,让用户能够根据不同的标准过滤食谱。用户可以输入他们想要搜索的任何关键词,并选择相关的复选框;例如,他们可能会搜索包含单词prawn madras(美味,美味)的食谱,然后选择标记为成本 £2-£3烹饪时间 15-20 分钟的复选框。

  • 坚持使用我们简单的搜索屏幕,但在搜索结果屏幕中添加过滤器。用户像平常一样搜索关键词,然后被带到搜索结果屏幕,该屏幕包含额外的过滤器,使用户能够缩小搜索结果。

这两种方案都是可行的,所以我迅速地为每种方法制作了线框图。这是一个很好的例子,说明了你可以如何使用线框图快速测试各种布局:

线框图搜索屏幕

在这个线框图中,用户像往常一样进行搜索,但搜索结果屏幕包含三个新的按钮(价格时间订阅...),他们可以使用这些按钮根据不同的标准过滤搜索结果。

下面的线框图是搜索屏幕的新版本,用户像往常一样在EditText中输入搜索词,但随后使用各种复选框来更具体地说明他们想要看到的搜索结果:

线框图搜索屏幕

搜索屏幕作为一个片段

无论你在设计和开发过程的哪个阶段,你都应该始终考虑如何支持尽可能多的不同 Android 设备。

当你忙于制作线框图时,很容易陷入只针对一种设备进行设计的陷阱。虽然这确实让线框图制作变得容易,但从长远来看,这并不会对 UI 设计产生很大的效果。

小贴士

横屏和竖屏线框图

在线框图阶段对内容的尺寸和位置进行调整,不仅有助于你更好地支持多种屏幕配置,还能帮助你设计出在横屏和竖屏模式下都能良好运行的 APP。

为多个屏幕进行规划可能意味着创建针对不同屏幕配置的多个线框图,或者可能意味着使用片段以不同的方式组合你的内容。

在上一章中,我们提到在较小设备上显示我们的搜索屏幕作为一个独立的活动,但在较大屏幕设备上,将其作为片段与主页并排显示。既然我们已经为第一种可能性制作了线框图,那么绘制搜索屏幕作为多窗格布局的一部分似乎也是正确的。

为了加快这个流程,我将重用我们之前创建的主页线框图以及原始的基本搜索屏幕线框图。

和往常一样,让我们从导航开始。除了左侧菜单中的“搜索食谱”项目外,我们主页线框图中的大多数导航元素仍然有效。由于在这种情况下搜索框将出现在主页旁边,我们可以从菜单中删除此链接。

同样,原始的搜索线框图包含一个带有左向箭头的操作栏,这现在不再必要;由于片段作为主页的一部分出现,用户除了退出应用外没有其他地方可以导航回,我们不希望帮助他们这样做!

导航问题解决后,让我们看看内容。原始主页和搜索线框图中的所有内容仍然相关,因此它们在我们的多窗格布局中都有位置。

让我们来看看最终的结果:

搜索屏幕作为片段

注意

感到不知所措了吗?

在尝试为多个设备设计的同时确定每个元素的最佳位置,可能会让你对着一张空白的纸发呆,想知道从哪里开始?如果你感到不知所措,那么将线框视为一系列草稿可能会有所帮助。

在创建你的第一个草稿时,你应该在不担心尝试支持多个设备的情况下绘制线框。相反,集中精力定义你的应用在其自然状态下的外观。如果你不担心在小型手机上空间不足,你希望那个标题有多宽?理想情况下,那个按钮应该离屏幕边缘有多远?你真的想并排显示两张图片吗?你确定你不会更喜欢三张?或者四张?五张呢?

拿起铅笔,无忧无虑地绘制线框。然后,在你为自己出色的初稿拍手称快之后,你就可以开始担心这个线框如何在不同设备上转换了。

数字线框

一旦你对你的纸质线框感到满意,你应该进一步发展它们。这可能意味着使用你信任的铅笔和纸张创建一个更详细、经过深思熟虑的草图,或者这可能意味着将这些纸质线框发展成数字线框。

如果你决定数字化你的线框,市面上有大量的线框软件,从专业的图像编辑程序,如 Adobe Photoshop、Illustrator 和 Fireworks,一直到专门为线框设计的工具,如 Pidoco、Axure、InDesign、Sketch、Omnigraffle 和 Balsamiq Mockups。

数字线框

上一张图片是我们食谱应用主屏幕的示例,使用 Balsamiq 数字线框工具创建。

如果你不确定要使用哪个工具,那么快速进行一次谷歌搜索会返回大量的潜在线框软件,这时你将面临一个全新的问题——选择太多!

如果你难以在几个潜在的线框工具之间做出选择,那么请自问以下问题:

  • 这个工具是否具备我需要的所有功能?在做出决定之前,你应该确保你选择的工具具备你快速轻松创建针对你特定项目线框所需的所有功能。如果你在投资专业的线框软件,这一点尤为重要。如果你下载了免费的线框软件,然后发现它并不完全适合你,那么至少你没有浪费你辛苦赚来的钱!为了避免被有说服力的网站文案所影响,记下你理想的线框工具应该包含的所有功能。然后你可以检查每个潜在的工具是否符合你的标准;如果不符,那么你可以继续考虑下一个。

  • 是否可定制?能够调整线框图程序以满足你的具体需求总是积极的,但如果你在开发不寻常或利基的用户界面时,这一点尤为重要。

  • 你能存储可重用元素吗?你通常会在多个屏幕上使用许多 UI 元素,例如操作栏、菜单和按钮。为了提升线框图过程,留意一下允许你轻松存储和重用视觉元素的工具。

  • 学习曲线是怎样的?专业的矢量插图工具可能听起来正是你项目所需的东西,但复杂的工具可能会让你感到困惑,尤其是如果你对设计不太熟悉。线框图的整个目的就是通过快速测试多个布局来节省你的时间。然而,如果你花几个小时埋头于程序的用户手册而不是实际创建线框图,那么整个练习就变得毫无意义。

  • 你是否已经熟悉具有线框图潜力的软件?即使这个程序不是最现代或功能最丰富的,如果你能在合理的时间内用它创建有效的线框图,那么使用这个老牌的软件,而不是花时间学习新软件,可能是有意义的。

如果你正在与众多线框图工具的多样性作斗争,那么你可能想要专注于免费的线框图软件,而不是一头扎进购买专业的图像编辑工具。免费工具的巨大好处是,你可以通过下载几个并快速试用,无需支付巨额账单,就能亲自看看它们是否适合你。

向你的线框图添加内容

到目前为止,我们一直强调不在线框图中添加内容;但到了某个时候,你需要测试你的屏幕形状是否适合你想要显示的内容类型。一旦你对一系列满意的数字线框图感到满意,就是时候测试你的内容是否适合这些线框图,或者你是否需要调整这些线框图。

内容,主要是副本,在任何平台上看起来都不太吸引人,但在移动设备上更是如此。智能手机或平板电脑的小屏幕可能会使未分化的文本块看起来更大。

如果你打算以优雅的方式展示你的应用副本,那么你需要对其设计进行一些思考,而线框图是完美的起点。

当你进入数字线框图领域时,你可以开始尝试设计元素,如不同的字体、文本大小、项目符号和编号列表,以及标题和副标题。最终结果?一个将你闪耀、引人入胜的副本与吸引人的展示和设计相结合的 UI——基本上,是用户真正想要阅读的文本。

如果你把内容和复制设计问题留到最终屏幕设计摆在你面前的时候,那么你可能会试图说服自己这并不是一个真正的大问题;或者你会寻找最快最简单的工作方案,即使这些方案并不适合最佳的屏幕设计。

当你在线框中添加内容时,牢记真实内容的外观至关重要。使用恰好适合你的线框大小和长度的文本很诱人。不幸的是,除非你非常幸运,否则这并不是真实内容的工作方式,因此在使用线框时使用逼真的文本很重要。

虽然你可以使用经典的Lorem ipsum拉丁语填充文本或一些占位符内容,但测试你的布局是否适合你想要显示的内容的最佳方式是使用最终将出现在屏幕上的相同复制内容。

在我们的食谱应用的情况下,这带来了一点问题,因为每个详细食谱屏幕上的文本都将不同。例如,每个食谱标题的长度都不同,那么我们如何确保所有这些标题都能适合可用空间?

解决办法是查看最终需要放入标题框中的所有标题的完整范围,然后计算出平均长度。你还应该尽力寻找尴尬的内容,因为这有助于你规划你的 UI 如何支持这些边缘情况。在这种情况下,这意味着找到最长和最短的食谱标题,然后想出如何有效地展示它们。

当详细的食谱屏幕需要显示简短标题时,标题框的大小会保持固定还是缩小?如果它缩小了,我们如何防止大量空白出现在新缩小的框周围?框会扩展以容纳具有异常冗长标题的食谱吗?文本会换行到多行吗?

你不需要为每一种可能性都绘制线框,但你应该尝试识别所有可能由复制和内容引起的重大问题。只有在这种情况下,你才能创建一个足够灵活的线框,以便轻松处理不寻常的复制和内容。

纸质原型是什么?

一旦你创建了一个相对满意的数字线框,通过纸质原型来测试这个设计是个好主意。

纸质原型是在你创建组成你应用的各个屏幕的草图时,将每个屏幕表示为单独的一张纸。然后你在一个用户(或者理想情况下,多个用户)身上测试这个纸质原型,并将用户的反馈纳入你的数字线框中。这种可用性测试方法可能看起来很简单,但实际上它是一种非常有价值的方法,可以审查和改进你的设计,使其尽可能完美。

创建纸质原型是设计过程中的一个重要步骤,主要有两个原因:

  • 它们非常适合识别你可能忽视的任何可用性问题:发现你自己的设计中的错误并不总是容易的。纸质原型为你提供了一个在不需要编写任何代码的情况下对你的目标受众进行应用测试的方法。而且正如我们之前讨论的,你越早发现设计中的缺陷,就越容易修复这些缺陷。

  • 它们帮助你提前测试你的应用导航:设计简单且无摩擦的导航是创建成功应用的关键部分,但在实际体验导航之前,很难正确评估导航。原型提供了一种测试你计划中的导航的方法,并亲眼看到用户导航你的应用是容易还是困难。如果你跳过纸质原型阶段,风险是直到你真正开始构建应用时,你才意识到你的导航有问题。而且由于导航应该在应用中保持一致,如果你发现你需要调整一个屏幕的导航,你可能会发现你需要改变所有屏幕的导航;而这不是你希望在最后一刻才发现的事情!纸质原型增加了你第一次就做对导航的机会。

可用性测试

纸质原型非常适合可用性测试。理想情况下,你希望对你的目标受众成员进行原型测试,但只要他们没有参与设计过程,同事、朋友和家人也可以作为很好的替代者。

为了进行最有效的可用性测试,你希望有人像普通用户一样,在下载了你的应用后,以盲测的方式对你的应用进行真实反应。如果你请了一位对你项目有内部知识的人帮忙,那么这并不能真正复制典型的用户体验。

在可用性测试中,测试对象与纸质原型互动,而你则操纵原型以反映用户的行为。例如,如果他们点击了“转到主页”按钮,你应该切换页面并向他们展示你的主页原型。好吧,这听起来可能有点愚蠢,但它也是在你投入更多时间进行设计和开发应用之前发现潜在可用性问题的最有效方法之一,所以为了这个目的,感觉有点愚蠢也是值得的。

当用户在使用你的纸质原型时,请他们提供反馈,并确保提示他们解释他们为何执行每个动作以及他们期望接下来会发生什么。你也应该鼓励他们说出任何让他们感到困惑的地方,或者如果他们对你的用户界面有任何不喜欢的地方。他们是否理解如何导航到下一个屏幕?他们是否立即理解在每个新屏幕上需要执行的任务,或者他们需要停下来思考?

理想情况下,您将获得足够的信息来对您的数字线框进行一些修订。如果您对线框进行了任何重大更改,那么请考虑创建一套新的纸质原型并在同一用户上进行测试。重复此过程,直到您的测试对象能够轻松地导航您的纸质原型,并且没有进一步的反馈或建议。

在这个阶段,您应该有一套您感到自信的数字线框。我们将在下一章中使用这些线框来创建更详细、更精细的数字原型。

快速原型设计

如果您的应用程序是团队合作的结果,您可能希望考虑将纸质原型设计作为快速原型设计方法的一部分。

在快速原型设计中,每个团队成员都会创建一个纸质原型并在单个用户上进行测试。然后团队聚集在一起分享他们的反馈和想法,此时每个人会创建第二个原型。同样,在团队再次聚集分享反馈之前,每个原型都会在单个用户上进行测试。您可以根据集体反馈更新您的数字线框。这是从多个用户那里收集反馈并整合所有这些宝贵信息到您的设计中的一种最快、最简单的方法。

摘要

当谈到开发 Android 应用程序时,规划的价值是无法高估的。

在本章中,我们探讨了如何将上一章中制定的高级计划转化为实际的屏幕设计。我们创建了快速纸质线框,然后以此为基础创建包含内容和副本的详细数字线框。最后,我们创建了纸质原型,并探讨了可用性测试如何帮助您完善屏幕设计,使它们尽可能完美。在下一章中,我们将探讨如何使用 Android Studio 和 Android SDK 创建更复杂和精细的数字原型。

如果这一章是您对线框和原型设计的第一次尝试,那么您可能仍然感到不知所措,但重要的是要记住,只要您在进行某种形式的线框和原型设计,那么您就已经比之前做了更多的规划。

唯一的警告是,在设计过程的早期阶段不要投入太多时间和精力去完美化您的屏幕设计。您的设计应该一开始就非常粗糙,随着您通过不同的规划阶段,逐渐变得更加精细和详细。

如果你仍然不确定,那么请再次查看本章中包含的食谱线框图,并问自己“我如何可以改进这些设计?”然后,绘制线框图!你甚至可能想要根据这些线框图创建原型。或者,启动你最喜欢的 Android 应用,创建所有主要屏幕的线框图和原型版本。这些都是提高你技能的有价值的方法,这样当你开始规划自己的真实 Android 项目时,你将是一名线框图和原型制作的专家。

现在我们已经为我们的应用程序得到了一个粗略的设计,是时候深入开发工作了。在下一章中,你将使用 Android Studio 根据你的线框图和纸版原型创建一个可工作的数字原型。

第七章。构建原型

在前几章中进行了大量的初始设计工作之后,现在是时候从设计阶段过渡到开发的早期阶段。第一个开发任务是创建一个数字原型。

在创建了多个线框图和纸制原型之后,你可能迫不及待地想要开始一些真正的开发工作,但数字原型是设计过程中的一个至关重要的步骤。

尽管你已经投入了大量的时间和精力在你的设计上,但在这个阶段,它仍然只是一个计划。数字原型是测试这个计划是否在现实世界中可行的地方。此外,数字原型本身也是一种纯粹的乐趣,因为它允许你在极短的时间内从无代码版本快速过渡到工作的应用版本。

到本章结束时,你将开发出一个工作的数字原型。你还将通过规划应用更详细的细节来最终确定你的设计,这意味着开始考虑图形设计。

注意

由于它是 Android 推荐的开发环境,本章重点介绍使用 Android Studio 创建数字原型。如果你还没有安装 Android Studio,你可以从 developer.android.com/sdk 获取它。

如果你更喜欢使用不同的集成开发环境(IDE),例如 Eclipse,那么创建数字原型的步骤将会相似,因此你仍然可以在你选择的 IDE 中继续操作。

在 Android Studio 中创建原型

便利的是,创建 Android 原型最好的工具之一你可能已经安装了——Android Studio。

这个 IDE 具有先进的布局编辑器,非常适合快速创建数字原型。这个布局编辑器会在你打开 Android Studio 中的任何 XML 布局资源文件时自动打开。

在 Android Studio 中创建原型

创建数字原型的最快方式是选择编辑器的 设计 选项卡(如前一张截图中的光标位置)。将打开一个新区域,显示你的应用布局的 画布 预览,以及一个包含许多现成 UI 元素(称为 小部件)的 调色板。你可以通过从 调色板 拖动小部件并将它们拖放到画布上来快速创建原型。

注意

在创建原型(以及一般而言的 Android 应用)时,你应该使用默认的小部件,除非你有非常充分的理由不这样做。这些现成的小部件不仅使开发者的生活更加轻松,而且大多数 Android 用户已经熟悉它们。因此,他们可以立即知道如何与你的应用 UI 的至少一些元素进行交互。

如果您需要更具体地更改应用布局,可以通过选择 文本 选项卡访问其底层的 XML,在代码级别进行修改,然后返回到 设计 选项卡以查看这些更改在画布上的渲染效果。使用这种方法,您将很快拥有一个可工作的原型,您可以在自己的 Android 设备或模拟器上对其进行测试。

在 Android Studio 中创建原型也是测试您的设计在不同屏幕配置下看起来和功能如何的绝佳方式。如果您恰好有多台 Android 设备,您可以将原型安装到所有设备上,但大多数 Android 开发者通过创建多个 Android 虚拟设备AVD)来在不同设备上测试他们的项目。

注意

我们将在下一章更详细地探讨部署和测试您的数字原型。

您还可以使用 Android Studio 内置的 预览 窗格快速预览您的原型在不同设备上的效果。要访问此窗格,请确保已选择 文本 选项卡,然后点击屏幕左侧的 预览 选项卡(如下面的截图所示,光标所在位置):

在 Android Studio 中创建原型

在此选项卡打开时,您将看到您的应用在单个 Android 设备上的预览。要检查您的布局在不同 Android 设备上的外观,请点击当前选中设备的名称(如下面的截图所示,光标所在位置),然后从下拉菜单中选择新设备。您甚至可以通过从下拉菜单中选择 预览所有屏幕尺寸 来一次性预览您的 UI 在多个设备上的效果:

在 Android Studio 中创建原型

创建您的第一个原型

您可以从原型化您应用中的任何屏幕开始,但我发现从用户启动应用时看到的第一个屏幕开始更有意义。在我们的食谱应用示例中,这是主页,因此这是我将要原型化的第一个屏幕。

回到我的线框图,我可以看到主页应该包括欢迎信息和来自应用中一些特色食谱的照片。我还想包括一个菜单,以便用户可以轻松导航到应用最重要的屏幕。具体来说,我希望这个菜单包括搜索屏幕、用户的食谱剪贴簿以及所有不同的食谱类别。

由于此菜单需要包含相当多的选项,我将使用导航抽屉。在这里,导航抽屉是一个有效的解决方案,因为它可以滚动,并且在不再需要时可以整齐地收起来。

注意

如果你不太熟悉导航抽屉的概念,那么你可能想查看 Android 的 Material Design 指南,其中有一个专门介绍导航抽屉的部分 www.google.com/design/spec/patterns/navigation-drawer.html

启动 Android Studio 并创建一个新的项目。选择对你特定项目最有意义的设置,但如果你正在跟随这个示例,那么请选择导航抽屉模板。正如你可能已经从名称中猜到的,这个模板内置了导航抽屉,这将使创建你的主屏幕数字原型变得更加容易。

点击完成以继续创建你的项目。默认情况下,该项目已经包含相当多的代码和资源。我的任务是修改所有这些自动生成的代码,以匹配我的主屏幕线框图。让我们从最直接的任务开始:创建屏幕的欢迎信息和添加我们的图片。

打开项目的content_main.xml文件,确保它使用RelativeLayout作为父容器。然后,选择设计选项卡,从画板拖动一个TextView和两个ImageView小部件,并将它们拖放到你的画布上。

接下来,用文本和图像填充这些小部件。创建一个包含你选择的欢迎文本的字符串资源。找到你想要使用的两张图片并将它们添加到你的项目drawable文件夹中(在我的项目中,我使用的是mushroomtoast.jpegsundayroast.jpeg)。更新TextView以显示字符串资源,并更新ImageViews以显示你选择的图片。

小贴士

数字原型设计:不要浪费时间!

到目前为止,你可能想投入大量时间定位你的ImageViewTextView对象,以便它们在最终的应用中看起来完全正确。然而,要小心不要花太多时间完善你的原型。

需要记住的关键点是,数字原型设计的目的是测试你项目设计的理论,而不是创建项目的早期版本。如果你花太多时间使你的数字原型完美,那么你不妨直接开始创建你的应用的第一版!

你的数字原型可能会突出你设计中的一些潜在问题,这意味着你需要返回并修改你的线框图,甚至完全重新制作它们。如果发生这种情况,那么至少你可以安慰自己,知道在真正开始构建你的应用之前发现设计中的问题更好。数字原型是早期检测工具箱中强大且节省时间的工具——但仅此而已,如果你不投入大量时间来完善你的原型。

不要陷入担心细节的陷阱;相反,尽量快速地创建你的原型。这通常意味着创建的屏幕只是最终屏幕外观的粗略表示,并且通常功能很少或没有。

这里是我的主屏幕原型的 XML 代码。由于这只是一个原型,我没有在对齐每个 UI 元素上投入太多时间:

<?xml version="1.0" encoding="utf-8"?> 
<RelativeLayout  

  android:layout_width="match_parent" 
  android:layout_height="match_parent" 
  android:paddingBottom="@dimen/activity_vertical_margin" 
  android:paddingLeft="@dimen/activity_horizontal_margin" 
  android:paddingRight="@dimen/activity_horizontal_margin" 
  android:paddingTop="@dimen/activity_vertical_margin" 
  app:layout_behavior="@string/appbar_scrolling_view_behavior" 
  tools:context="com.example.jessica.myapplication.MainActivity" 
  tools:showIn="@layout/app_bar_main"> 

  <ImageView 
      android:layout_width="175dp" 
      android:layout_height="225dp" 
      android:id="@+id/imageView2" 
      android:src="img/mushroomtoast" 
      android:layout_below="@+id/textView2" 
      android:layout_toEndOf="@+id/textView2" /> 

  <ImageView 
      android:layout_width="175dp" 
      android:layout_height="225dp" 
      android:id="@+id/imageView3" 
      android:src="img/sundayroast" 
      android:layout_below="@+id/imageView2" 
      android:layout_alignStart="@+id/imageView2" /> 

  <TextView 
      android:paddingTop="10dp" 
      android:layout_width="wrap_content" 
      android:layout_height="wrap_content" 
      android:textSize="40sp" 
      android:text="@string/welcome!" 
      android:id="@+id/textView2" 
      android:layout_alignParentTop="true" 
      android:layout_alignParentStart="true" /> 

</RelativeLayout> 

检查一下在你的模拟器或 Android 设备上的显示效果:

创建你的第一个原型

下一个任务更复杂:向导航抽屉添加我们自己的菜单项。

你可以在项目的res/menu/activity_main_drawer.xml文件中找到所有的导航抽屉代码。activity_main_drawer.xml中已经有相当多的代码了,但这段代码很好地说明了如何向导航抽屉添加项,所以我们不妨将其用作模板。

目前,activity_main_drawer.xml文件看起来是这样的:

<?xml version="1.0" encoding="utf-8"?> 
<menu > 

  <group  

//This is an optional, invisible container that groups related <items> together in the navigation drawer// 

android:checkableBehavior="single"> 

//This is another optional element that defines the checkable behavior for either individual menu items (via the android:checkable attribute in an <item> element) or for an entire group, via android:checkableBehavior in a <group> element. In this example, we're marking all <items> in this <group> as checkable. The "single" value means that the user can only select one item at a time, within this group. The other potential values are "none" (the user can't check any items in the group) or "all" (the user can select multiple items in this group at any one time)// 

    <item 

//Creates a new menu item// 

          android:id="@+id/nav_camera" 

//Assigns the menu item a unique resource ID so the app recognizes when the user selects this item in the menu// 

          android:icon="@drawable/ic_menu_camera" 

//Defines what drawable to use as this item's icon// 

          android: /> 

//The text you want to display in the navigation drawer// 

      <item 
     ............ 
     ............ 
     ............ 

  </group> 

//This is the end of this particular group//  

  <item android:> 
      <menu> 

//This is the start of a new group// 

     ............ 
     ............ 
     ............ 

      </menu> 
  </item> 

</menu> 

正如之前提到的,我想添加的菜单项是剪贴簿、搜索屏幕和不同的食谱类别。第一个任务是决定为这些菜单项使用哪些图标。

在可能的情况下,你应该使用系统图标,因为大多数用户已经熟悉它们,因此会知道它们的意思。你可以在design.google.com/icons/找到所有 Material Design 系统图标的完整列表。

从这个长长的图标列表中,我将使用标准的搜索图标,所以请下载这张图片并将其放入你的项目的drawable文件夹。为其他所有项目找到图标并不那么直接,但既然这只是一个原型,我将暂时使用一些占位符图标。我决定使用描述图标,因为它有点像笔记本(或剪贴簿),我还将使用刀叉本地餐饮图标为每个食谱类别。

一旦所有这些图像都安全地存放在drawable文件夹中,就是时候向导航抽屉代码中添加一些新的条目了:

<?xml version="1.0" encoding="utf-8"?> 
<menu > 

  <group android:checkableBehavior="single"> 
      <item 
          android:id="@+id/breakfast" 
          android:icon="@drawable/ic_local_dining" 
          android: /> 
      <item 
          android:id="@+id/lunch" 
          android:icon="@drawable/ic_local_dining" 
          android: /> 
      <item 
          android:id="@+id/dinners" 
          android:icon="@drawable/ic_local_dining" 
          android: /> 
      <item 
          android:id="@+id/healthy" 
          android:icon="@drawable/ic_local_dining" 
          android: /> 
      <item 
          android:id="@+id/lightmeals" 
          android:icon="@drawable/ic_local_dining" 
          android: /> 
      <item 
          android:id="@+id/vegan" 
          android:icon="@drawable/ic_local_dining" 
          android: /> 
      <item 
          android:id="@+id/budget" 
          android:icon="@drawable/ic_local_dining" 
          android: /> 

  </group> 

  <item 
      android:id="@+id/scrapbook" 
      android:icon="@drawable/ic_scrapbook" 
      android: /> 
  <item 
      android:id="@+id/search" 
      android:icon="@drawable/ic_search" 
      android: /> 
     ............ 
     ............ 
     ............ 

</menu> 

就这样!在你的 Android 设备或模拟器上运行完成的原型,但这次,请确保通过在屏幕上拖动来打开导航抽屉。你将看到这个抽屉中的一些新添加项:

创建你的第一个原型

创建你的第二个原型

线框图可以给你一个更清晰的 UI 视图,但有时即使是线框图也无法有效地说明某个屏幕的每个版本将如何显示。我们的应用程序的搜索结果屏幕就是这样,因为这个屏幕将根据用户搜索的内容而变化。那么,我们如何创建一个能够有效地代表我们的搜索结果屏幕的单个原型呢?

答案是创建一个灵活的原型布局,并给它一些假数据。这些假数据应该准确地代表这个屏幕最终需要显示的完整范围的真实内容。您应该尽力找到您应用内容中最尴尬的例子,这样您才能真正测试您的设计。在我们的菜谱应用示例中,这意味着使用标题异常长或短的菜谱。

要创建一个复杂搜索结果屏幕的原型,我将创建ListView,然后我将创建样本菜谱标题和图像的数组,我将它们提供给ListView。然后ListView将在我布局中显示这些数组,就像它们是真实的搜索结果一样。

一旦您创建了第一个原型,您可以选择为这个项目添加更多屏幕,或者将每个后续的原型作为一个单独的项目来创建。我个人觉得后者是最直接的选择,所以我会将我的搜索结果原型作为一个新的项目来创建——这次使用空白模板。

如果您也决定将每个屏幕作为一个单独的项目来原型设计,那么请确保每个项目都有一个不同的包名,这样您就可以在同一台 Android 设备上安装和测试它们。

回顾我的线框图,我可以看到搜索结果屏幕需要包括一个菜谱标题列表和相应的图像,以及一个标题。让我们从最直接的任务开始:创建标题。打开您的项目strings.xml文件并创建一个字符串,它将提供搜索屏幕的标题文本:

<string name="sResults">Search Results...</string>) 

打开activity_main.xml文件,并确保其父容器是一个垂直的LinearLayout。创建一个TextView并将其设置为显示sResults字符串。

接下来是处理线框图中的这部分,它使得这个屏幕更难进行原型设计:搜索结果。如前所述,我将创建一个ListView并给它一些假数据,所以第一步是将ListView添加到我们的布局资源文件中:

<?xml version="1.0" encoding="utf-8"?> 
<LinearLayout  
  android:layout_width="match_parent" 
  android:layout_height="match_parent" 
  android:orientation="vertical" > 

  <TextView 
      android:id="@+id/textview" 
      android:text="@string/sResults" 
      android:textSize="30dp" 
      android:layout_width="match_parent" 
      android:layout_height="wrap_content" 
      android:paddingTop="10dp" 
      android:paddingLeft="10dp"/> 

  <ListView 
      android:id="@+id/listview" 
      android:layout_width="match_parent" 
      android:layout_height="match_parent" 
      /> 

</LinearLayout> 

小贴士

ListView、适配器和列表项

ListView是一个视图组,它以垂直、可滚动的列表形式显示项目。用户可以通过点击列表中的任何一项来选择,这使得ListView非常适合显示我们的搜索结果,因为我们最终希望用户能够通过点击标题来打开菜谱。

要查看ListView的实际应用示例,请拿起您的 Android 设备并查看内置的联系人应用。它包含了一个可滚动的联系人列表;您可以在列表中点击任何一项以查看该联系人的更多信息。听起来熟悉吗?

一旦您识别出一个ListView,您就会开始注意到它们无处不在。翻阅一下您设备上安装的一些应用,您可能会惊讶地发现ListView出现的频率有多高。

ListView列表项组成。每个列表项在ListView中显示为行,这些项有自己的布局,你在单独的 XML 文件中定义。你可以为列表项创建一个简单的布局,或者你也可以创建更复杂的列表项,这些列表项包含多段文本和多个图像,这些图像在RelativeLayout中排列。

这个拼图的最后一块是一个适配器,它充当ListView及其底层数据之间的桥梁。适配器从指定的源(如数组)中提取内容,并将这些内容适配到视图中。然后,适配器将每个视图放置在ListView中作为一个单独的行。

在本章中,我将使用SimpleAdapter,这是一种将静态数据映射到 XML 文件中定义的视图的适配器类型。这是一个相当直接的适配器,但它有一个缺点:SimpleAdapter需要一个MapsArrayList来定义ListView中的每一行。

如果你想了解更多关于ListView和不同类型的适配器的信息,最好的地方是官方的 Android 文档:developer.android.com/guide/topics/ui/layout/listview.html

让我们定义将在我们的ListView中出现的单个项目/行布局。创建一个新的布局资源文件,命名为simple_list_layout.xml,然后创建TextViewImageView,它们最终将包含食谱标题和相应的图片:

<?xml version="1.0" encoding="utf-8"?> 
<LinearLayout  
  android:layout_width="match_parent" 
  android:layout_height="match_parent" 
  android:orientation="horizontal"  > 

  <ImageView 
      android:id="@+id/images" 
      android:layout_width="250dp" 
      android:layout_height="150dp" 
      android:paddingTop="10dp" 
      android:paddingRight="5dp" 
      android:paddingBottom="10dp"  /> 

      <TextView 
          android:id="@+id/recipe" 
          android:layout_width="wrap_content" 
          android:layout_height="wrap_content" 
          android:textSize="20dp" 
          android:paddingRight="10dp"/> 

  </LinearLayout> 

接下来,添加你假搜索结果的所有图片。我将以下内容添加到我的项目的drawable文件夹中:

  • blueberrypancake.jpg

  • brownies.jpg

  • calamari.jpg

  • chilli.jpg

  • fryup.jpg

  • kashmiricurry.jpeg

  • pie.jpeg

  • redberrypancake.jpeg

  • scallops.jpeg

  • surfandturf.jpg

  • sweetandsourprawns.jpeg

  • tuna.jpeg

我将把这些图片以及相应的食谱标题添加到单独的数组中。然后,我将通过适配器将这些数组传递给ListView

注意

这比第一个原型复杂得多,所以为了加快速度,我将尽可能快地编写这段代码,在这个过程中牺牲了一些效率和优化。

打开你的项目的MainActivity.java文件,并添加以下内容:

package com.example.jessica.myapplication; 

import java.util.ArrayList; 
import java.util.HashMap; 
import java.util.List; 

import android.app.Activity; 
import android.widget.ListView; 
import android.os.Bundle; 
import android.widget.SimpleAdapter; 

public class MainActivity extends Activity { 

    String[] recipes = new String[] { 

//Create a recipes array// 

          "Easy veggie chilli", 
          "Deep fried calamari with garlic and lemon mayo", 
          "Best-ever chocolate brownies", 
          "Everyday fish pie with cheesy sauce", 
          "Seared tuna with stir-fried veggies", 
          "American style blueberry pancakes with strawberry and banana", 
          "Full English fry up", 
          "Kashmiri curry", 
          "Red berry pancakes with cream", 
          "Sticky sweet and sour prawns", 
          "Surf and turf for two" 
  }; 

//Add all the preceding titles to the recipes array//  

  int[] images = new int[]{ 

//Create an images array//  

          R.drawable.chilli, 
          R.drawable.calamari, 
          R.drawable.brownies, 
          R.drawable.pie, 
          R.drawable.tuna, 
          R.drawable.blueberrypancake, 
          R.drawable.fryup, 
          R.drawable.kashmiricurry, 
          R.drawable.redberrypancake, 
          R.drawable.sweetandsourprawns, 
          R.drawable.surfandturf 
  }; 

//Add all the preceding image files to the images array//  

  @Override 
  public void onCreate(Bundle savedInstanceState) { 
      super.onCreate(savedInstanceState); 
      setContentView(R.layout.activity_main); 

      List<HashMap<String,String>> aList = new
      ArrayList<HashMap<String,String>>(); 

//Create an array list, called aList//  

      for(int i=0;i<10;i++){ 
          HashMap<String, String> myMap = new
          HashMap<String,String>(); 
          myMap.put("recipe", recipes[i]); 
          myMap.put("images", Integer.toString(images[i]) ); 
          aList.add(myMap); 
      } 

//Add recipes and images to aList//  

      String[] from = { "images","recipe", }; 

      int[] to = { R.id.images,R.id.recipe}; 

//Use the ImageView and TextView from simple_list_layout.xm// 

      SimpleAdapter adapter = new
      SimpleAdapter(getBaseContext(), aList,
      R.layout.simple_list_layout, from, to); 

//Create a new SimpleAdapter and feed it three parameters. The first parameter is the context reference (getBaseContext); the second is the collection of data we want to display (aList), and the third is the layout we want to use for each row (simple_layout_list.xml)// 

      ListView listView = ( ListView )
      findViewById(R.id.listview); 

//Get the ListView object// 

       listView.setAdapter(adapter); 

//Assign the adapter to the ListView object// 

  } 
} 

将你的 Android 设备连接到计算机或启动模拟器,查看结果:

创建你的第二个原型

花些时间与你的原型互动,并在不同的屏幕配置下测试它,无论是通过 Android Studio 的预览功能还是通过创建多个 AVD。

现在你已经有了两个工作原型;问问自己是否有任何方法可以改进这些原型。特别是,要留意任何将你的 UI 拆分成可以组合成适应不同屏幕配置的多窗格布局的片段的机会。

如果你的数字原型激发出任何新的想法,那么拿一张纸并尝试绘制这些想法的线框图。如果这些新的纸笔线框图有潜力,那么理想情况下你应该将它们通过我们在上一章中提到的同样的严格的数字线框图、纸原型和可用性测试阶段。至少,你应该创建你新设计的数字原型,并花时间尽可能在多种不同的屏幕配置上测试这些原型。

这可能感觉像是退步,但这对确保你的设计真的是尽可能最好的至关重要;如果它改善了你的应用,那么这是值得的时间。如果你在怀疑你本可以改进基本设计的情况下发布了你应用的第一版,你不会过得愉快——你的用户也不会。

此外,那个老套话是正确的——你不会得到第二次机会来留下第一印象。你的普通用户将会对一款看似从天而降的惊人应用感到更加兴奋,而不是他们很久以前尝试过但不太感兴趣的某个应用的 2.0 版本。

即使你探索了你的想法然后放弃了它们,转而支持你的原始设计,至少你将确信你真的发布了你应用可能最好的版本。基本上,如果你的数字原型激发出了任何新的想法,那么现在是探索这些想法的时候了。

注意

如果你创建了新的线框图和原型,总是好的得到第二意见,所以你可能想要考虑进行另一轮用户测试——特别是如果你发现自己对你的应用的不同设计方向犹豫不决。

一旦你有一套你 100%满意的数字原型,就是时候完善这些设计了。

完善你的设计

在这个阶段,你应该已经牢固掌握了你应用设计的根本,包括每个屏幕上应该出现哪些 UI 元素,它们将在屏幕上的位置,以及你想要使用的导航模式。然而,我们还没有太多地思考我们应用的外观和感觉的细节。你打算为标题使用哪种字体?按钮应该是什么颜色?会有背景音乐或音效吗?以及你的应用将如何使用如高度和阴影这样的 Material Design 属性?

在完善你的设计时,以下是一些你需要考虑的事项。

视觉效果

这是你的 UI 元素外观的细节之处。尽管你应该尽可能使用 Android 的标准小部件,但仍然有很多空间来对标准元素,如按钮和菜单,进行自己的调整,特别是在你使用的颜色调色板以及如高度和阴影这样的 Material Design 元素方面。

你的视觉内容的比例也很重要;你的屏幕应该有很多图像和动画,还是应该更注重文本?像往常一样,确保你设计的是能够吸引你特定目标受众的东西。一个包含大量图像和明亮的鲜艳色调的应用可能适合一个针对年轻受众的有趣应用,但对于一个帮助忙碌的专业人士平衡财务的应用来说,可能就不太合适了。

背景音乐和音效

声音是一种设置氛围、唤起情感反应并立即让用户感受到他们可以从你的应用中期待什么的强大方式。

然而,你不必觉得一定要在你的应用中包含声音;沉默并没有什么不好,而且在某些情况下,一个完全静音的应用可能更受欢迎,尤其是如果人们可能会在噪音可能令人烦恼或不合适的情况下使用你的应用。

如果你包含某种形式的声音,始终考虑声音对于用户能够成功与你的应用交互有多重要。一般来说,无论用户音量调得很大、很小,或者他们处于一个无法清楚地听到你的应用的地方,如繁忙的酒吧或餐厅,你的应用都应该可用。

唯一的例外是那些基本依赖于声音的应用,例如音乐流媒体应用、为用户提供驾驶方向的应用或一些游戏应用。在这些情况下,你可以确信没有人会期望在没有提高设备音量的情况下使用你的应用。

您的文本

文本是您与用户直接沟通的最直接方法。在制作文本时,您需要考虑其内容及其视觉外观。

你的文本的外观对用户阅读和理解文本的难易程度有巨大影响,但外观也可以向用户传达关于文本的微妙信息。例如,你可以使用textSize来传达每段文本的重要性以及它属于哪个类别,例如它是否是标题、字幕或代码块。

要创建真正与用户产生共鸣的文本样式,你可以使用以下属性:

  • 文本大小:你应该在设计的早期阶段决定你应用中所有主要文本类型的尺寸,而不是逐屏决定。通过将自己限制在预定义尺寸的选择中并一致地使用它们,你的用户将很快学会每个textSize的含义。然后他们可以使用这些信息来解读他们在你的应用中遇到的每个新屏幕,因为他们会立即知道哪个文本是副标题,正文文本,等等。例如,你使用android:textSize属性指定字体大小,例如android:textSize="10sp."。在提前设置textSize的最常见方法是通过主题和样式,我们将在本章后面讨论。

  • 文本样式:Android 支持粗体、斜体和下划线效果,但请确保你使用得体,不要过度使用。如果你经常使用这些效果,你会添加很多视觉杂乱,这可能会使你的文本难以阅读,而且你最终会削弱效果的影响力。你可以使用 android:textStyle 属性应用粗体、斜体、下划线和 bold|italic 效果,例如,android:textStyle="bold."。下划线文本稍微复杂一些,因为你需要创建一个字符串资源,并将下划线应用于该资源内的文本,例如,<string name="main_title"><u>This text is underlined</u>This text isn't</string>。然后,从你的项目布局资源文件中引用这个资源(android:text="@string/main_title"),文本就会在你的用户界面中显示为下划线。

  • 大写:和粗体、斜体和下划线效果一样,你应该谨慎使用大写,以免降低其影响力。你也应该避免使用全大写,因为全大写的文本通常会被理解为喊叫,而且对用户来说阅读起来也更困难。

你接下来需要关心的是你精心设计的文本实际上在说什么。

将文本视为你应用的“声音”。这个声音应该与你的应用整体设计相一致。如果你的设计色彩丰富、节奏欢快,配有愉快的音乐,那么你的文本也应该同样友好。如果你的设计更加前卫,那么你可能希望给你的文本赋予更多的态度。

千万不要过分热情!大多数情况下,极端地使用文本并不会使你的文本更有意义,相反,你应该选择一种简洁和中性的语气。记住,你的文本的语气和内容应该反映你应用的整体设计。所以,如果你的用户界面简洁,专注于完成任务,那么你很可能希望你的文本也同样直截了当。

如果不确定,就用中性的语气来确保安全。

你的应用个性

我把这一点放在最后,因为它是一个有点模糊的概念,它结合了我们之前讨论的方方面面。

你的应用个性是一个结合了众多不同设计元素的综合体,从视觉元素到背景音乐和文本的语气。

如果你的应用想要给你的用户留下清晰而鲜明的印象,那么所有这些不同的方面都需要保持一致。如果这些设计元素不一致,那么你可能会让你的用户感到不安,觉得你的应用有点儿不对劲

想象一下,你创建了一个使用中性、漂亮的粉彩色调的应用,里面充满了可爱的图形,并且每行愉快的、乐观的文本都以至少三个感叹号结束——但是背景音乐却是垃圾金属乐!

仔细审视你刚刚做出的所有设计决策。它们是否都汇聚在一起形成一个统一的整体?或者是否有一个(或多个)元素像俗语所说的那样“突出”?

小贴士

规则是为了被打破的!

虽然大多数时候你都会希望所有设计方面保持一致,但偶尔你可能会决定给用户一个意外,比如一个故意与你的应用其他部分冲突的元素。这种技术可以创造出一些非常强大的效果;例如,你可能会将明亮的颜色和热情的文字与令人不安的音乐结合起来,创造出一种令人毛骨悚然和不安的经历,或者你可能想将可爱的图形与干巴巴和讽刺的文字结合起来,让用户发笑。

如果你决定打破规则,请确保你清楚地知道你想要达到的目标以及你打算如何实现它,因为这很容易出错!

如果你难以决定你的应用的整体个性应该是什么,那么考虑一下什么最能吸引你的目标受众。

总是如此,如果你在寻找灵感,就去谷歌应用商店搜索一些与你的项目类似或针对相同受众的应用。

由于我们希望我们的食谱应用能够吸引学生,我将访问应用商店并搜索包含单词student的应用。立刻,我看到了一大堆时间表和日程安排应用,专门针对大学生的论坛,以及家庭作业规划器。在这种情况下,我会下载一些评分最高的应用,然后花些时间浏览它们,以了解它们的个性。我可能还会下载一些五星级的食谱应用,并给予它们同样的待遇,然后再将所有发现结合起来,用以影响我自己的食谱应用的视觉效果和感觉。

创建主题和样式

现在你已经对 UI 的细节做出了一些决策,是时候考虑如何实现它们了。这就是主题和样式发挥作用的地方,因为它们是跨视图、活动甚至整个应用实现设计决策最快、最简单的方式。

一旦你做出了这些设计决策,创建样式甚至是一个主题来帮助你轻松且一致地实现设计是个好主意。

样式和主题本质上是一样的:一组属性。这些属性可以是文本的颜色、ImageView的大小,甚至是"wrap_content"属性。区别不在于你如何创建这组属性,而在于你如何将它们应用到你的项目中:

  • 样式是一组控制视图外观的属性;例如,你可能会将一个样式应用到TextView上,以指定其中所有文本的大小和颜色。

  • 主题是一组属性,你可以将其应用到活动或整个应用中。当你将主题应用到活动时,活动内的每个视图都将使用适用于它的任何属性。如果你将主题应用到应用中,那么应用中的每个视图都将使用此主题的所有适用属性。你通常会扩展另一个主题而不是从头创建一个。

样式和主题确实需要一些准备工作,所以为什么你要费心创建主题和样式,而不是直接将属性应用到视图中呢?有几个原因:

  • 它很高效:如果你打算在应用中多次使用同一组属性,那么提前定义这些属性会使应用它们变得容易得多——实际上就像输入style="@style/captionFont"一样简单。

  • 它是一致的:将一组属性定义为样式或主题有助于在整个应用中创建一致的视觉和感觉。而且,正如我们之前讨论的,用户喜欢一致性。

  • 它很灵活:设计变更几乎是生活的常态,因此你应该在整个开发过程中预期会不断调整你的应用视觉。主题和样式提供了一个中心位置,你可以在这里一次性做出更改,然后它们会立即出现在你的应用中。决定你的标题应该比 10dp 大?简单,只需增加你的style="@style/heading"中的android:textSize属性。

注意

记得我们之前在谈论textSize属性以及如何仅使用少量文本大小可以帮助用户解码你的 UI 吗?这条规则也适用于样式。如果你使用有限数量的样式,你的用户会很快了解这些样式的含义,然后利用这些信息来帮助他们解码新的屏幕。为了最大限度地利用样式(以及,在一定程度上,主题),请注意那个老套话:少即是多。

定义样式

要创建样式(或一系列样式),你需要在你项目的res/values文件夹中创建一个styles.xml文件,如果它还没有这个文件的话。

你可以使用以下格式创建样式:

<?xml version="1.0" encoding="utf-8"?> 
<resources> 

//<Resources> must always be the root node of your styles.xml file // 

   <style name="FooterFont" parent="@android:style/FooterText"> 

//Create a new style and assign it a name. In this example, I'm using FooterFont// 

       <item name="android:layout_width">match_parent</item> 

//Add each property to your style using the <item> element//  

       <item name="android:layout_height">wrap_content</item> 

//The value of each <item> can be a string, a color, a reference to another resource, or another valid value. You'll find a few examples of possible <item> elements below// 

      <item name="android:layout_width">wrap_content</item> 
       <item name="android:textColor">#ffff0000</item> 
      <item name="android:textSize">12sp</item> 
       <item name="android:typeface">sans_serif</item> 

   </style> 

//End of the FooterFont style// 

<style name="CodeFont" parent="@android:style/TextAppearance.Medium"> 

//Start of a new CodeFont style// 

     ............ 
     ............ 
     ............ 

//This is where you'd define the CodeFont attributes// 

   </style> 

//End of the CodeFont style// 

</resources> 

好吧,这看起来可能像是一项大量工作,但请记住,你不会创建一个只使用一次的样式。通常,你会在项目前期定义一个样式,然后在项目中的多个地方使用它。此外,一旦定义了样式,你就可以使用继承来创建此样式的变体(我们将在本章后面更详细地探讨)。

小贴士

名字里有什么?

在为每个样式命名时,要考虑一下你叫什么,因为样式的名称可以传达有关其目的和其他样式之间关系的有价值信息。

为了获得最佳效果,始终根据其目的而不是外观来命名你的样式,因为随着你精炼 UI,外观可能会改变。

例如,CaptionStyle是一个好的名称,但ItalicsLightCaption就不太合适,因为你的 UI 可能会发展到轻量级斜体文本不再适合你的 UI。在这种情况下,你有两个选择。你可以继续使用这个不一致的样式名称,这可能会造成混淆,尤其是如果你与其他人合作进行这个项目的话。

或者,你需要打开styles.xml文件,给你的样式赋予一个新的名称,但这样你还需要手动更改项目中所有对这个样式的引用。正如你所看到的,这两种方法都不是理想的解决方案!

一旦你创建了样式,将其应用到视图上就很简单了;只需打开一个 XML 布局资源文件,并使用@style属性,后跟你想应用的样式的名称:

<TextView 
   style="@style/CodeFont" 
   android:text="@string/helloworld" /> 

并非所有视图都接受相同的样式属性;例如,ImageView不接受android:textAlignment。官方 Android 文档是检查特定视图支持哪些属性的最佳地方,特别是视图对应的类参考,在那里你可以找到一个支持 XML 属性的表格。例如,如果你正在创建一个文本样式,那么你可能想查看TextView类参考(developer.android.com/reference/android/widget/TextView.html)。此外,请记住,一些视图扩展了其他视图,所以如果你正在创建应用于EditText的样式,那么查看TextView类参考也是值得的,因为EditText扩展了TextView类。

有时候,你可能会无意中将不兼容的属性应用到视图上,尤其是在你将样式应用到整个活动或应用主题时。那么会发生什么呢?答案是不多。视图只会接受它支持的属性,并忽略其余的。

注意

当你将一种样式应用到视图上时,这种样式只会应用到该视图。如果你将样式应用到ViewGroup上,子视图元素不会继承样式的属性。如果你想一次性将样式应用到多个视图上,那么你应该将样式作为一个主题来应用。

一些样式属性不被任何视图支持,你只能将它们作为主题应用到活动或整个应用中。例如,隐藏应用标题的属性在应用到单个视图时不会有任何影响。这些属性很容易识别,因为它们都以window开头,例如windowNotTitlewindowTitleSizewindowNoDisplay。你可以在官方 Android 文档的R.attr参考中找到完整的列表,该参考作为官方 Android 文档的一部分在developer.android.com/reference/android/R.attr.html提供。

继承

你可以通过将它们用作父样式并继承它们的属性来快速轻松地创建现有样式的变体。

使用 <parent> 属性来指定你想要继承的样式。然后你可以添加属性,通过用新值覆盖它们来更改现有属性。你可以从你自己创建的样式继承,或者从 Android 平台内置的样式继承,如下所示:

<style name="MyAppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> 

//Creates a new style/theme called MyAppTheme that inherits from the Theme.AppCompat.Light.DarkActionBar platform theme. MyAppTheme will inherit all the characteristics of its parent//  

  <item name="colorPrimary">@color/colorPrimary</item> 

  <item name="colorPrimaryDark">@color/colorPrimaryDark</item> 
  <item name="colorAccent">@color/colorAccent</item> 

//Define whatever properties you want to add or change. Here, I'm overriding the parent theme's colorPrimary, colorPrimaryDark and colorAccent values with values I've defined in my project's colors.xml file//  

</style> 

如果你想要继承你定义的样式,你不需要使用 <parent> 属性。然而,通常认为将父样式的名称作为你的新样式的名称前缀,并用句点分隔是最佳实践。

假设我想创建一个新的样式,它继承了我之前创建的自定义 FooterText 样式,但将 textsize 从 12sp 增加到 20sp。我会用以下代码来做这件事:

   <style name="FooterText.Large"> 

//Notice there's no parent attribute in the <style> tag. Instead, I'm using the name of the original style (FooterText), and affixing the name of the new style (Large)//  

           <item name="android:textSize">20sp</item> 
   </style> 

你将以与任何其他样式完全相同的方式引用这个继承的样式;在这个例子中是 style="@style/FooterText.Large"

你可以继续这样继承,次数不限;只需将这些样式名称用句点连接起来。没有任何阻止你创建 FooterText.Large.BlueFooterText.Large.ItalicFooterText.Large.Blue.Bold 等等的。

然而,为了更容易记住所有样式名称并帮助区分样式,最好不要过于沉迷!毕竟,FooterText.Large.Blue.Bold.Underlined.Monospace 并不是最容易记住的。

注意

通过将样式名称链接起来继承属性仅适用于你自己创建的样式。如果你想从 Android 的内置样式继承,你需要使用父属性。

与主题一起工作

到目前为止,你可能想知道为什么我们花了这么多时间关注样式,而没有真正讨论主题。这是因为你定义主题的方式与定义样式的方式完全相同。你可以将上面定义的任何样式作为主题应用。

要应用主题,打开你的项目 Manifest.xml 文件,并将 android:theme 属性添加到以下任何一个:

  • 标签:如果你想在整个应用中应用主题,找到 <application> 标签,并添加 android:theme 属性以及你的样式名称,例如,<application android:theme="@style/FooterFont">

  • 标签:要将你的主题应用到特定的活动,向相关的 <activity> 标签添加 android:theme

当你应用一个主题时,活动或应用中的每个视图都将使用它所能支持的所有的主题属性。如果你将FooterFont应用到活动上,该活动中的所有TextView(以及扩展TextView的视图)都将使用这些属性集合。如果你在整个应用中应用FooterFont,所有TextView和扩展TextView的视图都将假定这些属性,但任何不支持FooterFont属性的视图将简单地忽略它们。如果一个视图支持一些FooterFont属性但不支持其他属性,它将应用支持的属性并忽略其余的。

为了节省你的时间和精力,Android 平台包括许多预定义的主题,你可以使用,包括一系列的 Material 主题。你可以在官方 Android 文档的R.style部分找到所有可用的样式列表(developer.android.com/reference/android/R.style.html)。要使用平台样式和主题,将样式名称中的所有下划线替换为点,例如Theme_Dialog变为"@android:style/Theme.Dialog"

如果你想要自定义内置主题,你应该将主题用作你自己的自定义主题的<parent>标签,然后添加你自己的属性或覆盖现有属性,如下所示:

<style name="MyTheme" parent="android:Theme.Light"> 

//Create a new style called MyTheme, that inherits from the android:Theme.Light platform theme// 

   <item
 name="android:windowBackground">@color/custom_theme_color</item> 

//This is where you add your own attributes, or override existing attributes//  

   <item name="android:colorBackground">@color/custom_theme_color</item> 

</style> 

最后一步是更新你的项目的Manifest以使用你的新主题。所以,如果你想在一个特定的活动中使用MyTheme,你会编写以下内容:

<activity android:theme="@style/CustomTheme"> 

要在整个应用中使用MyTheme,你会编写以下内容:

<application android:theme="@style/CustomTheme"> 

准备好错误吧!

在一个完美的世界里,用户只会在你应用的最佳条件下与之互动。如果你的应用从互联网获取内容,人们只有在拥有闪电般的网络连接时才会打开你的应用。你的应用永远不会遇到诸如讨厌的来电或短信消息这样的中断,而且用户当然永远不会输入错误的用户名或密码。

可惜,现实世界并不是这样。你的用户会犯错误,耗尽存储空间,失去互联网连接,在你 RPG 应用的最终 Boss 战中途接到电话。你的应用必须能够处理所有这些不理想的情况和错误。

为错误做准备可能看起来很奇怪,但不幸的是,即使是最好的我们也会出错。不仅你的应用应该能够在出错时处理它们,而且它应该能够优雅地处理它们。

在本章的最后部分,我们将探讨如何设计你的应用,使其能够处理现实世界抛给它的任何东西。

用户输入错误

用户输入错误是指用户输入错误信息或留空字段。用户错误最常见的地方是登录屏幕或支付屏幕等表单。

假设你已经清楚地传达了用户需要输入的信息,从技术上讲,这些错误不是你的责任,但你仍然应该设计你的应用程序能够处理这类错误。

当用户错误发生时,你的应用程序应该清楚地传达发生了什么,并给用户足够的信息以便他们能够解决问题。仅仅显示一个写着“错误”的弹窗是远远不够的。

理想情况下,你的错误消息应包括帮助用户修复错误的操作。在我们的弹窗示例中,一旦用户关闭了弹窗,我们的应用程序可以自动选择用户输入错误信息的EditText字段,以便他们开始输入。

你应该只提供你可以实际支持的操作,所以如果错误是由于用户存储空间不足造成的,那么提供一个“重试”按钮是无法解决问题的。如果你无法帮助用户解决错误,那么向他们解释错误是什么、是什么原因造成的以及他们如何修复它,要比提供一个可能让他们感到更加沮丧的空洞手势要好得多。

当你的应用程序遇到用户错误时,它应该尽可能保留用户输入的信息。因此,如果一个用户填写了表单但输入了错误的密码,你的应用程序应该保留他们的用户名、地址以及他们正确填写的任何其他字段,同时突出显示他们输入了错误的密码。

然而,在处理用户输入错误的同时,不要忽视良好设计的重要性。如果你的应用程序将要失败,那么它至少应该以一种风格化的方式失败!一个设计良好的错误消息应该感觉像是你应用程序的自然延伸。在我们的弹窗示例中,我们可以编辑弹窗,使其颜色更好地与我们的应用程序整体配色方案相匹配,或者我们可以更改字体或包含反映我们应用程序个性的自定义音效。

尽管我以弹窗消息为例,但你应该始终寻找使你的错误消息尽可能不显眼的方法,而弹窗并不特别微妙。更不显眼的方法包括下划线、突出显示、自动选择用户输入错误信息的字段、在屏幕上添加一些解释已发生错误的文本后重新加载屏幕,或者将用户带回到他们输入错误信息的屏幕。

虽然用户输入是导致错误最常见的原因之一,但还有其他错误和不太理想的情况,你的应用程序需要能够处理。以下是一些例子:

  • 连接问题:如果用户离线并尝试访问需要互联网或网络访问的功能,你应该显示一个不显眼的提示信息,说明他们需要互联网连接才能完成此操作。

  • 不兼容状态:当用户尝试执行冲突的操作时,例如在离线状态下尝试通过在线消息应用发送消息时,会发生这些错误。当这些错误发生时,您的应用应显示一条解释错误的消息,并最好为用户提供一种简单的方法来更改他们的当前状态。

  • 缺少权限:如果用户选择了一个需要特定权限的操作,您的应用应通知用户,并给他们提供授予该权限的选择。如果用户拒绝此权限请求,您应禁用所有相关功能,这样用户就不会继续尝试访问应用目前无法支持的功能。您还应为用户提供一种简单的方法来授予之前拒绝的权限,以防他们改变主意。如果某些权限对您的应用至关重要,您应提前请求它们。我们将在第十章“最佳实践和应用程序安全”中更详细地讨论权限,最佳实践和应用程序安全

  • 空状态:当您的应用无法显示常规内容时会发生这种情况;例如,一个没有任何搜索结果的ListView。您应该设计您的应用能够处理缺失的内容,尽管处理这种情况的最佳方法将取决于上下文和缺失的内容类型。在我们的搜索结果示例中,您可以创建一个“无结果找到”屏幕,或者应用可以自动扩展搜索查询并显示最佳匹配的内容。例如,如果用户搜索burito,您的应用可以显示所有包含单词burrito的食谱,而不是显示无内容。

    • 如果你决定采取最佳匹配的方法,那么你应该表明此内容并非完全匹配。在我们的搜索结果示例中,我们可能会显示一条类似的消息,例如“我们找不到任何关于 burito 的结果,您是指 burrito 吗?”
  • 内容不可用:这与空状态微妙不同,因为它涉及到应该出现在您的应用中的内容,但由于某种原因不可用。例如,也许您的 UI 包括需要从互联网下载的图片或视频,但用户目前处于离线状态。如果您的应用包含此类内容,您应该在内容不可用时创建占位符,这些占位符将出现在您的布局中。如果您不这样做,那么当缺失的内容变得可用并被插入到布局中时,您的布局可能会突然跳动,这可能是您在移动设备上浏览加载缓慢的网页时遇到过的。创建占位符还可以确保即使某些内容缺失,您的布局也能保持其预期的结构。

摘要

在本章中,你学习了如何快速创建基于你的线框的数字原型。虽然你之前创建了纸质原型,但数字原型对于测试你的屏幕设计在真实 Android 设备上以及在不同屏幕配置下的外观和功能尤其有用。

从本章中你应吸取的最重要的一点是,线框是规划你的屏幕在理论上将如何看起来和工作的方式,但数字原型是你将这种理论付诸实践的方式。有时,你可能会发现理论在实际中并不适用,你可能需要对你的线框进行一些修改,甚至完全重新设计,但这没关系——发现你设计中的问题是你创建最佳应用版本过程中的一个重要部分。

现在你已经完成了你的设计,在下一章中,我们将更深入地探讨如何确保这个新完成的设计尽可能正确地转换到尽可能多的不同 Android 设备上。

第八章. 扩大受众范围 – 支持多种设备

Android 是一个对硬件限制很少的移动操作系统。制造商可以自由地创建配备高端硬件的 Android 设备,如 DSLR 级别的相机、巨大的内部存储和闪电般的 CPU;或者他们可以创建更经济实惠的设备,采用无装饰的硬件设计。Android 屏幕也可以有各种尺寸、形状和屏幕密度。

这些设备唯一共同点就是它们都运行 Android 操作系统——即使在这一点上,Android 智能手机和平板电脑也不一致。当前的 Android 智能手机和平板电脑市场由许多不同版本的 Android 操作系统组成,从遗留版本一直到最新的 Android 发布。即使两台设备运行的是完全相同的 Android 版本,也无法保证它们完全相同,因为制造商有修改 Android 操作系统的坏习惯,以推出他们自己的原始设备制造商OEM)版本。运行 Android 7.0 的三星智能手机可能并不一定与运行 Android 7.0 的索尼智能手机相同。

这种灵活性对制造商来说是个好消息,他们可以通过推出新的和创新型的硬件、软件和屏幕配置,使他们的设备在竞争中脱颖而出。对消费者来说也是个好消息,他们可以四处寻找,找到最适合他们的 Android 设备。

但这对开发者来说好吗?有点儿

所有这些关于硬件、软件和屏幕配置的变体意味着有很多机会进行创新并推出真正原创的应用。然而,这也带来了巨大的挑战,因为你需要创建一个能够在所有可能遇到的不同硬件、软件和屏幕配置上提供一致体验的应用。

不幸的是,没有快速解决的办法。本质上,创建灵活用户界面的关键是提供一系列替代资源,这些资源针对所有可能遇到的不同硬件、软件、屏幕配置、语言和区域设置进行了优化,这可以说是 Android 应用开发中最耗时的一部分。

在本章中,我将涵盖你需要牢记的所有主要方面,如果你打算创建一个尽可能兼容多种不同 Android 设备的灵活应用。

支持不同的 Android 版本

虽然大多数 Android 平台的新版本都引入了令人兴奋的新功能,你可能会迫不及待地在应用中使用它们,但如果你想要触及尽可能广泛的受众,那么你还需要尽可能支持尽可能多的旧版 Android 平台。

这是一个微妙的平衡行为。支持旧版 Android 平台需要时间和精力,而且你走得越远,就越需要努力才能让你的应用与旧版 Android 良好地协同工作。

如果你继续努力支持越来越旧的 Android 版本,在某个时候,你不可避免地会发现自己在遇到无法支持你最初想要创建的应用类型的 Android 版本时,不得不妥协你的应用的 UI、功能以及整体用户体验。在我们的食谱应用示例中,我们依赖于设备足够强大,能够在用户进行搜索时加载多个高分辨率图片。如果用户运行的是较旧的 Android 操作系统版本,处理这些图片可能会导致搜索结果加载得更慢。尽管问题出在用户的设备上,但典型的 Android 用户更有可能责怪你的应用,而不是他们过时的智能手机或平板电脑。

我们可以缩小图片或甚至完全删除它们,但你愿意在没有看到成品照片的情况下尝试一个食谱吗?在这个时候,你应该退一步问问自己,所有这些时间和努力,以及妥协,是否真的值得

为了确定支持旧版 Android 何时变得比其价值更大,你需要查看当前的 Android 市场——特别是运行每个版本 Android 操作系统的设备数量。一旦你有了这些信息,你就可以做出明智的决定,关于何时继续支持旧版 Android 已不再有意义。

获取这些信息的一个来源是谷歌的仪表板(developer.android.com/about/dashboards/index.html),它提供了运行每个版本 Android 的设备相对数量的百分比。但请注意,这些信息是以非常特定的方式收集的。这基本上是过去 7 天内访问谷歌 Play 商店的所有设备的快照。这些数据来自谷歌 Play 应用,并不一定代表整个 Android 市场的当前状态。还值得注意的是,谷歌 Play 应用仅与 Android 2.2 及以上版本兼容,因此运行低于 2.2 版本的 Android 的设备不包括在这组数据中;尽管根据谷歌在 2013 年 8 月的数据,运行低于 Android 2.2 版本的设备仅占大约 1%,所以我们这里谈论的是一个非常小的百分比。

花些时间探索仪表板数据,并决定你将支持哪些版本的 Android,以及哪些版本你将不支持。

指定最小和目标 API 级别

一旦你决定了要支持哪些版本的 Android,你需要在你的项目中包含这些信息。如何添加这些信息将取决于你使用的 IDE,因此你需要打开以下文件之一:

  • 清单文件(Eclipse)

  • 模块级别的 build.gradle 文件(Android Studio)

我们将在下一节讨论这个文件的组成部分。

minSdkVersion

这个属性标识了你的应用兼容的最低 API 级别,例如,minSdkVersion 16。Google Play 将使用你的应用的 minSdkVersion 属性来确定用户是否可以在设备上安装它。

当你在考虑你的应用的 minSdkVersion 值时,确保你咨询仪表板统计信息,因为这提供了你潜在受众的快照。最终,你需要决定支持这个受众的每一部分额外的时间和工作是否值得。

targetSdkVersion

这个属性标识了你在测试你的应用时使用的最高 API 级别。

targetSdkVersion 的值对于向前兼容性尤其重要,因为系统不会应用在新 Android 版本中引入的任何行为更改,直到你更新你的应用的 targetSdkVersion 值。为了确保你的应用能够从最新的 Android 功能中受益,你应该将你的应用的 targetSdkVersion 值设置为 Android 的最新版本。每当 Google 发布新的 Android 版本时,将你的应用更新为目标新的 SDK 应始终是最高优先级,但你应该在彻底测试你的应用与最新的 SDK 版本兼容性之后才这样做。永远不要在没有测试的情况下盲目更新你的 targetSdkVersion 值。

理想情况下,你的 targetSdkVersioncompileSdkVersion 值应该始终与 Android SDK 的最新版本相对应。

compileSdkVersion

这个属性告诉 Gradle 应该使用哪个版本的 Android SDK 来编译你的应用。

你的 compileSdkVersion 值不包括在你的发布 APK 中;它仅用于编译时。更改你的 compileSdkVersion 值不会改变运行时行为,因此建议你始终使用最新的 SDK 进行编译。

运行时检查版本

有时候,你会有一个明确的截止点,对于你的应用停止支持更早版本的 Android 来说是合理的,但这条线可能并不总是那么明确。

假设你的应用包含一个在 Android 5.0 及更早版本上不受支持的辅助功能,但除了这个功能外,你的应用与更早版本的 Android 兼容。由于这个 Marshmallow 及以上版本的功能不是必需的,阻止运行 Android 5.0 或更早版本的设备用户安装你的应用是没有意义的。在这种情况下,你可以通过确保任何依赖于更高 API 级别的代码仅在相关 API 可用时执行来禁用此功能。基本上,运行 Android 5.0 或更低版本的用户的设备将无法使用此功能,但这些用户仍然可以安装并使用你的应用。

你可以通过使用Build常量类来指定相关代码何时运行来实现这一点;例如,以下代码验证你的应用是否运行在 Lollipop 或更高版本:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) 

Android 为每个平台版本提供唯一的代码,你应该与Build常量类(在这个例子中,代码是LOLLIPOP)一起使用。你可以在官方 Android 文档中找到这些代码的完整列表(developer.android.com/reference/android/os/Build.VERSION_CODES.html)。

支持不同屏幕

Android 设备形状和尺寸各异。作为开发者,你的任务是创建一个 UI,使其在小屏幕的预算友好型智能手机上看起来和在大屏幕的顶级 Android 平板电脑上一样好,以及介于两者之间的所有设备。

所以让我们来分解一下。Android 以两种方式对屏幕进行分类:

  • 屏幕尺寸:传统上,Android 支持四种通用屏幕尺寸:smallnormallargexlarge。然而,Android 3.2(API 级别 13)引入了一些新的配置限定符,允许你更具体地指定屏幕尺寸。

  • 屏幕密度:设备的屏幕密度是其分辨率和显示尺寸的组合,以每英寸点数(dpi)来衡量。设备的 dpi 越高,每个单独的像素越小,这意味着每英寸的清晰度和细节越多。Android 支持六种通用密度:低(ldpi)、中(mdpi)、高(hdpi)、超高(xhdpi)、超超高(xxhdpi)和超超超高(xxxhdpi)。

你可以预期你的应用将被安装在屏幕尺寸和密度各不相同的设备上。你应该有更高的目标;仅仅让你的应用兼容这些不同的屏幕配置是不够的,你应该让用户感觉到你的应用是专门为他们的特定屏幕设计的,无论屏幕的大小和密度如何。

在本节中,我将向您展示如何创建一个能够处理广泛不同尺寸和密度的应用。您将不断遇到的一个基本主题是,您的应用布局和可绘制资源必须以适合当前屏幕的尺寸进行渲染。Android 系统足够智能,可以自动处理大部分渲染工作,并将布局和资源缩放到适合当前尺寸和密度,但您不应依赖 Android 系统为您完成所有艰苦的工作。

仅靠 Android 的自动渲染无法提供最佳的用户体验。您需要提供多个版本的资源,这些资源针对不同的屏幕尺寸和密度进行了优化。这些资源可以是字符串、布局、图形或任何其他您的应用所需的静态资源。

要将这些资源添加到您的项目中,您需要创建项目目录的替代版本,并使用正确的配置限定符进行标记;例如,如果您有一个针对横向布局优化的布局,您需要创建一个 res/layout-land 目录,然后将您的横向布局文件放置在这个目录中。然后,当用户运行您的应用时,Android 系统将自动加载与当前屏幕配置最匹配的资源,无论是默认布局还是您的横向优化布局 res/layout-land

配置限定符

Android 支持一系列配置限定符,您可以将这些限定符附加到项目的资源目录中。这些配置限定符是控制系统显示资源版本的关键。

配置限定符指定了资源是为哪些特性设计的,例如为特定屏幕尺寸或屏幕密度设计的图像。您可以在官方 Android 文档中找到有效配置限定符的完整列表,具体为 “提供资源”页面中的表 2 (developer.android.com/guide/topics/resources/providing-resources.html#AlternativeResources)。

您需要使用的配置限定符将根据您的应用和目标设备类型而有所不同,但作为最低要求,您通常使用尺寸、密度和屏幕方向限定符来提供针对不同屏幕配置优化的可绘制资源和布局。

要使用配置限定符,在您的项目 res/ 目录中创建一个新的目录,并使用以下格式命名:

<resources_name>-<config_qualifier> 

因此,如果您正在创建一个用于存放针对横向模式手持设备优化的布局的目录,您将使用 land 限定符并创建一个 res/layout-land 目录,然后将您的布局优化布局放置在这个目录中。

注意

永远不要将任何资源直接放置在项目res/目录中,因为这会导致编译器错误。您也不能嵌套替代资源,因此您不能创建一个res/drawable/drawable-xxhdpi/目录。

您可以同时使用多个限定符,通过破折号分隔每个限定符;例如,一个res/drawable-en-hdpi目录将包含为设置为英语语言(en)且屏幕密度属于高密度范围的设备设计的可绘制资源。如果您确实使用了多个限定符,它们在目录名称中出现的顺序至关重要。它们必须与在提供资源页面(developer.android.com/guide/topics/resources/providing-resources.html#AlternativeResources)中出现的顺序相同。如果您以错误的顺序使用限定符,Android 系统将无法识别该目录,并随后忽略它包含的所有资源。

您无需担心目录名称的大小写,因为资源编译器在处理之前会将目录名称转换为小写。如果您的项目包含由多个配置限定符组成的冗长名称的目录,那么您可能想利用这种自动转换,并使用大写字母来使目录名称更容易阅读。

对您放置在目录中的资源进行命名也很重要。当您创建同一资源的多个版本时,它们必须与默认资源具有完全相同的名称。任何变化,Android 系统都不会将其识别为同一资源的替代版本。

注意

如果您的项目包含系统不应根据当前屏幕配置进行缩放的资源,请将这些资源放置在具有nodpi配置限定符的目录中,例如,res/drawable-nodpi

Android 如何选择最佳资源

当您的应用具有同一资源的多个版本时,Android 系统在运行时决定显示哪个版本时遵循一套严格的规则。

当 Android 系统根据屏幕大小或密度查找资源时,它首先搜索一个无需缩放即可显示的精确匹配项。如果找不到合适的大小或密度特定的版本,Android 将切换到计划B并搜索为屏幕尺寸小于当前屏幕设计的版本。

如果可用的资源比当前屏幕大,系统将使用资源的默认版本。Android 系统假设您的项目默认资源是为基准屏幕大小和密度设计的,这是一个normal大小和中等密度。因此,系统将默认资源向上缩放以适应高密度或更大的屏幕,向下缩放以适应低密度屏幕。

如果系统找不到合适的密度特定资源,甚至找不到所需资源的默认版本,那么你的应用将会崩溃——这就是为什么提供每个资源的默认版本是至关重要的。

即使你确信你已经提供了你项目可能需要的所有替代资源,你的应用也可能最终运行在一个具有你未曾预料到的硬件、软件、屏幕尺寸、屏幕密度或语言设置的设备上,因此你没有为这些特定资源提供具体资源。在这种情况下,系统可能会回退到你的项目默认资源,如果你的应用不包括这些默认资源,那么你的应用将会崩溃。

注意

默认资源是指存储在没有任何配置限定符的目录中的所有资源,例如res/drawable

创建别名资源

有时候,你会有一个适合多个配置限定符的资源;例如,你可能有一个想要添加到你的项目的res/drawable-hdpires/drawable-xhdpi目录中的 drawable。

注意

你不能创建使用相同类型多个配置限定符的目录,因此不可能创建res/drawable-hdpi-xhdpi目录。

你可以复制/粘贴资源,使其出现在两个目录中;但这并不高效,而且重复资源会增加你项目的大小,这对你的最终用户来说是个坏消息。最好的解决方案是使用别名

假设你有一个scene.png drawable,你想要在hdpixhdpi屏幕上使用;这是一个使用别名的完美机会。在这种情况下,你需要将默认版本放在你的项目的res/drawable文件夹中,就像平常一样。然后,将你想要用于hdpixhdpi屏幕的图像版本保存在res/drawable文件夹中,但给它一个与默认资源不同的名字,例如scenery_alias.png

到目前为止,你有两个 drawable:

  • res/drawable/scenery.png

  • res/drawable/scenery_alias.png

接下来,在两个密度特定目录中创建一个 XML 文件。在这些 XML 文件中,添加一些指向你的项目res/drawable/scenery_alias.png资源的代码:

<?xml version="1.0" encoding="utf-8"?> 
<bitmap    android:src="img/scenery_alias" /> 

当 Android 系统尝试从res/drawable-hdpires/drawable-xhdpi加载场景资源时,它会识别别名并显示res/drawable/scenery_alias.png。这样,你可以用小的 XML 文件替换内存占用大且效率低下的重复资源。

你还可以使用别名功能在多个目录中重用相同的布局资源文件,如下所示:

  1. 创建一个默认布局(main.xml),并将其放置在你的项目的res/layout目录中。

  2. 创建你想要在多个目录中使用的布局。给这个布局一个与默认布局不同的名字(我将使用main_alias.xml),并将其放置在你的项目的res/layout目录中。

  3. 在你想要使用 layout_alias.xml 文件的所有目录中创建 XML 文件。

  4. 添加一些引用 layout_alias.xml 的 XML 代码:

            <?xml version="1.0" encoding="utf-8"?> 
            <merge> 
               <include layout="@layout/main_alias"/> 
            </merge> 
    
    

虽然不太常用,但你也可以为字符串和其他简单值创建别名,如下所示:

<?xml version="1.0" encoding="utf-8"?> 
<resources> 
   <color name="yellow"> #ffff00 
</color> 
   <color name="highlightColor">@color/yellow</color> 
</resources> 

在这个例子中,highlightColor 现在是 yellow 的别名。你还可以为字符串创建别名:

<?xml version="1.0" encoding="utf-8"?> 
<resources> 
   <string name="title">Student Recipe</string> 
   <string name="appTitle">@string/title</string> 
</resources> 

在这个例子中,appTitle 现在是 title 的别名。

屏幕密度

在设计你的 UI 时,你应该力求实现 密度独立性。这就是 Android 系统在不同密度的屏幕上保持你的 UI 元素物理大小不变的地方。

密度独立性对于提供良好的用户体验至关重要。高密度屏幕每英寸像素更多,这意味着相同数量的像素可以适应更小的区域。低密度屏幕像素较少,因此相同数量的像素可以适应更大的区域。如果你使用绝对单位进行测量,例如指定 UI 元素为像素,那么你的 UI 元素在低密度屏幕上会显得更大,在高密度屏幕上会显得更小。

注意

人们普遍认为,具有相同屏幕分辨率的设备自动具有相同的屏幕密度。即使两个设备具有相同的屏幕分辨率,这些屏幕可能尺寸不同。这意味着屏幕在显示内容时占据的空间不同,这相当于不同的 dpi 数量。

如果你的 UI 元素在不同设备上改变大小,那么这可能会导致布局问题以及可用性问题,这将导致用户体验普遍较差。

在大多数情况下,你可以通过指定你的布局尺寸为密度无关像素,并通过用更灵活的元素(如 "wrap_content""match_parent")替换静态、硬编码的大小来确保密度独立性。

当涉及到可绘制资源时,Android 会根据当前屏幕的密度自动缩放每个可绘制资源,因此你的可绘制资源将以适合当前设备的适当物理大小渲染。然而,这种自动缩放可能会导致模糊或像素化的图像。为了确保你的可绘制资源看起来尽可能好,你需要为每个可绘制资源创建不同的版本,这些版本针对不同的屏幕密度进行了优化。

问题在于 Android 市场包含了比你希望支持的屏幕密度更多的种类;即使你能做到,提供如此多的备用可绘制资源会导致你的项目大小失控,以至于它可能连普通的 Android 智能手机或平板电脑都放不下。

正是因为这个原因,Android 将屏幕密度分为以下几种通用的 密度桶

  • ldpi(低密度):120dpi

  • mdpi(中等密度):160dpi

  • hdpi(高密度):240dpi

  • xhdpi(超高密度):320dpi

  • xxhdpi(超高清密度):480dpi

  • xxxhdpi(超超高清密度):640dpi

可变布局的第一步是为这些密度桶中的每一个创建一个目录,例如,res/drawable-ldpires/drawable-mdpi,等等。然后,只需为这些不同的密度桶创建每个资源的版本,Android 系统将处理其余部分。

为了为每个密度桶创建优化的可绘制资源,你需要将 3:4:6:8:12:16 的缩放比例应用于六个通用密度。为了获得最佳效果,首先创建一个最大支持密度的图像版本,然后按比例缩小每个后续密度桶的图形。

注意

大多数情况下,创建最大支持密度的图形意味着为xxhdpi密度桶创建一个图像,而不是xxxhdpi桶。这是因为xxxhdpi目录是为启动器图标保留的。

将 dpi 转换为像素和反之亦然

由于不同屏幕的像素密度不同,相同数量的像素在不同设备上会转换为不同的物理尺寸。像素不是密度无关的单位,所以 40 像素在每台设备上的大小并不相同。

因此,你不应该使用绝对像素来定义距离或大小。偶尔,你可能需要将 dpi 值转换为像素值,反之亦然。你可以使用以下公式来完成:dpi = (像素宽度 * 160) / 屏幕密度

注意

Android 使用mdpi(160dpi)作为其基准密度,其中每个像素正好等于一个密度无关像素。这就是公式中的 160 值来源。

让我们用一些数字试一试:

(180 px x 160) / 120 = 240 dpi 

你可能还想使用在线转换器(jennift.com/dpical.html)来玩转 dpi 值。

如果你想要将 dpi 值转换为像素,那么使用以下公式:

dp * (dpi / 160) = px 

例如,看看以下内容:

120 x (240 / 160) = 180 

为不同屏幕尺寸提供不同的布局

Android 支持广泛的屏幕尺寸,并且它会自动调整你的 UI 以适应当前屏幕。正如我已经提到的,你不应该依赖 Android 系统为你完成所有艰苦的工作,因为这种自动缩放通常无法充分利用大屏幕上可用的空间,尤其是在平板尺寸的设备上。

如果你使用模拟器和多个Android 虚拟设备AVDs)彻底测试你的应用,你可能会遇到你的应用在某些屏幕上难以显示或正确运行的情况。例如,你可能会发现,当设备的屏幕密度低于某个 dpi 值时,Android 的自动缩放会使你的 UI 看起来拥挤;或者,在另一端,你可能会发现你的 UI 在大屏幕的平板尺寸屏幕上有大片空白区域。

如果你的 UI 在特定屏幕尺寸上存在问题,你应该创建针对这些屏幕优化的布局。

提供备用布局的过程与提供任何备用资源的过程相同:创建具有适当配置限定符的目录。然后,创建针对特定屏幕尺寸优化的布局资源文件,并确保这些布局与相应的默认布局具有相同的名称。Android 系统将根据当前的屏幕配置选择适当的布局。

然而,屏幕尺寸配置限定符并不像密度配置限定符那样简单直接,因为 Android 3.2 引入了一些新的配置限定符,允许你以 dpi 单位定义每个布局所需的具体宽度和/或高度。这些新的配置限定符为你提供了对资源的更多控制,但它们可能稍微难以理解。

注意

在版本 3.2 之前,Android 支持屏幕尺寸分组:smallnormallargexlarge。为了适应更多种类的屏幕尺寸,Android 团队用新的配置限定符替换了这些分组。本章重点介绍新的配置限定符,但如果你想了解更多关于现在已弃用的屏幕尺寸分组的信息,你可以在官方 Android 文档中找到更多信息,请参阅(developer.android.com/guide/practices/screens_support.html)。

这些强大的新限定符将在下一节中讨论。

最小宽度 – swdp

如其名所示,你使用 smallestWidth 限定符来定义在 Android 系统可以使用特定布局之前必须可用的最小宽度(以 dpi 为单位)。例如,如果你的布局需要至少 700dpi 的最小宽度,则 smallestWidth 配置限定符将是 sw700dp。在这种情况下,你将创建一个 res/layout-sw700dp 目录并将你的布局放在里面。系统只有在当前设备至少有 700dpi 的可用宽度时才会使用此布局。

这个限定符特别有用,因为宽度在设计布局时通常是一个重要因素。许多应用是垂直滚动的,但水平滚动的 UI 很少见。大多数应用对它们在水平方向上需要的最小空间有严格的要求,而这个配置限定符为你提供了一种以 dpi 值指定这个最小值的方法。

设备的宽度是一个固定特性,当用户在纵向和横向模式之间切换时不会改变。用户对屏幕宽度和高度的感觉可能会改变,但系统对设备 smallestWidth 的感知永远不会改变,即使用户将设备从纵向模式切换到横向模式,反之亦然。

可用屏幕宽度 – wdp

有时,你的应用可能需要响应当前可用的宽度或高度,这意味着需要考虑设备的当前方向。例如,想象一下你的 UI 有一个选项在多面板布局中并排显示两个片段。如果用户正在以横向模式查看你的应用,显示多面板布局是有意义的,但一旦用户将设备切换到纵向模式,可能就不再有足够的宽度来并排显示这些片段。

这就是 sw<N>dp 配置限定符发挥作用的地方。你可以使用这个限定符来设置资源所需的最小宽度,例如,res/layout-w700dp。然而,与 smallestWidth 限定符不同,w<N>dp 代表当前可提供给你的 UI 的宽度,考虑到当前的屏幕方向。这允许你的应用响应当前可用的宽度,而不仅仅是设备的固定 smallestWidth 值。

可用屏幕高度 – hdp

如前所述,你的典型 Android 应用对所需的最小高度相当灵活。然而,如果你确实需要指定布局或资源所需的最小屏幕高度,你可以使用 h<number>dp 限定符,例如,res/layout-h720dp

系统分配给屏幕高度的值会在屏幕方向改变时发生变化,因此类似于 w<N>dp,你可以使用这个限定符来检测你的应用当前是否正在以纵向或横向模式查看。

虽然使用这些新的配置限定符可能看起来比使用传统的屏幕尺寸组更复杂,但它们确实给了你更多控制权,以控制你的 UI 如何在不同的屏幕上转换,并且允许你指定应用从针对 Android 智能手机优化的布局切换到针对平板电脑优化的布局的确切点。

注意

并非所有版本的 Android 都支持所有限定符,例如 sw<N>dp 是在 API 级别 13 中引入的。然而,当你在你项目中使用限定符时,系统会自动且隐式地添加平台版本限定符,因此较老的 Android 版本至少可以识别不受支持的限定符,并随后忽略它们。

针对不同屏幕方向的设计

你可能还想创建针对横向和纵向方向优化的布局版本。你可以像提供不同屏幕尺寸和密度的布局一样,提供针对替代方向的优化布局:你创建一个带有正确方向限定符的额外目录,并将特定方向的布局放置在该目录中。

尽管 Android 支持两种方向——纵向和横向——但你只需要创建一个额外的目录,因为你将使用项目默认的 res/layout 目录来处理这两种方向之一。

选择你希望用作应用默认方向的朝向,然后为另一个方向创建一个目录,因此你的项目可能包含一个 res/layout 目录,其中包含你的默认/竖屏布局,以及一个 res/layout-land 目录,其中包含你的项目横屏布局。

或者,如果你希望将横屏方向作为项目的默认方向,创建一个 res/layout-port 目录,并使用 res/layout 作为项目横屏布局:

为不同的屏幕方向设计

注意

当你准备测试你的应用时,确保你在各种屏幕配置下以横屏和竖屏模式测试它。

响应方向变化

有时,你的应用需要注册配置更改,然后根据结果修改其行为。

最常见的场景之一是响应屏幕是横屏还是竖屏模式。例如,想象你的应用由两个片段组成:一个片段显示列表,另一个片段显示当前选中项的信息。在较小的屏幕上,应用以单窗格布局单独显示每个片段,而在较大的屏幕上以多窗格布局并排显示。当用户在多窗格布局中选择一个项目时,信息将在同一活动中显示。然而,当用户在单窗格布局中选择一个项目时,你的应用需要显示此信息在新活动中。在这种情况下,你的应用需要知道用户当前查看的是哪种布局(单窗格或多窗格),以便相应地做出反应。

一种方法是识别一个仅在多窗格布局中可见的视图,然后查询此视图当前是否可见:

public class MyActivity extends FragmentActivity {  
   boolean mIsDualPane; 

   @Override 
   public void onCreate(Bundle savedInstanceState) { 
       super.onCreate(savedInstanceState); 
       setContentView(R.layout.main_layout); 

//Check whether detailsView is currently visible// 

       View detailsView = findViewById(R.id.article); 
       mIsDualPane = detailsView != null && 
                      detailsView.getVisibility() ==
                      View.VISIBLE; 

//If this view is visible, and we're in multi-pane mode....// 

if (mDualPane) { 

... 
... 

//This is where you'd define your app's multi-pane behavior// 

} else { 

//If this view isn't visible, then we're in single-pane mode//  

... 
... 

//This is where you'd define your app's single-pane behavior// 

你可以使用 getConfiguration() 方法检索设备的当前配置:

Configuration config = getResources().getConfiguration(); 

要检索设备的当前屏幕方向并对结果采取行动,请运行以下代码:

if (getResources().getConfiguration().orientation 

//If the screen is currently in landscape mode...// 

               == Configuration.ORIENTATION_LANDSCAPE) { 

... 
... 

//This is where you'd define your app's landscape behavior// 

} else  

//If not, then the device is in portrait orientation// 

... 
... 

//This is where you'd define your app's portrait behavior// 

在多个屏幕上进行测试

在发布你的应用之前,你应该在所有支持的屏幕尺寸和屏幕密度上,以横屏和竖屏模式彻底测试它。除非你恰好能访问到一堆不同的 Android 智能手机和平板电脑,否则最实用的方法是使用 Android SDK 中的模拟器。

当你启动AVD 管理器并选择创建虚拟设备...时,你可以从基于现实设备的大量现成Android 虚拟设备AVDs)中选择,或者通过选择新硬件配置文件来创建自己的设备。在创建新的 AVD 时,你可以选择你的 AVD 是否支持横屏和/或竖屏模式,但通常你应该在创建的每个 AVD 上测试你的应用在横屏和竖屏方向上的表现。

你还可以输入你选择的屏幕尺寸和分辨率。当你输入这些设置时,你将在窗口的右侧面板中看到该特定设备的密度

在多个屏幕上进行测试

要测试你的应用屏幕支持,你需要创建代表所有最常见 Android 屏幕配置的多个 AVD。这里没有捷径,一般来说,你花在测试应用上的时间越多,用户体验就会越好,无论最终它运行在哪种设备上。

注意

在本书中,我将使用 Android SDK 自带仿真器,但如果你不是这个默认仿真器的粉丝,也有其他替代方案。一个流行的替代方案是 Genymotion(www.genymotion.com)。

展示你的屏幕支持

当你最终准备你的应用 Google Play 列表时,你应该使用展示应用最佳效果的截图。理想情况下,你应该在运行最新版本 Android 的大屏幕高密度屏幕上拍摄所有截图,这样潜在用户可以看到你 UI 最令人印象深刻版本。

如果你需要为你的网站、博客、社交媒体账户或其他地方创建推广图片,那么你应该通过将截图包裹在设备艺术中,来为它们提供上下文。最简单的方法是使用 Android 的拖放设备艺术生成器(developer.android.com/distribute/tools/promote/device-art.html)。

吸引国际受众

你的 Android 应用有潜力触达全球受众,但只有如果你投入时间和精力进行应用本地化。

在本地化项目时,你最大的任务是将其文本翻译成目标语言,但你还需要翻译包含文本的任何可绘制资源、任何包含文本或对话的视频以及包含对话的音频。你还需要确保任何数字、货币、时间或日期都正确地格式化以适应目标受众,因为格式在不同语言和国家之间可能有所不同。

你可以像提供其他资源一样提供这些替代资源:创建新的目录并使用适当的配置限定符。

当涉及到本地化时,你需要使用locale配置限定符,它由以下内容组成:

  • 语言代码:这些是 ISO 639-1 定义的两个小写字母 ISO 代码,如 ISO 639-1(en.wikipedia.org/wiki/ISO_639-1)所述。

  • 国家或地区代码(可选):由 ISO 3166-1 定义的两个字母大写 ISO 代码(en.wikipedia.org/wiki/ISO_3166-1_alpha-3),并在其前面加上小写的r。你可以使用国家/地区代码与语言代码结合,以提供针对特定语言和位于特定国家或地区的设备的资源。例如,你可以通过将语言代码(fr)与地区代码(CA)以及小写的r结合,为位于加拿大的法语使用者提供资源,因此你的目录将是res/values/fr-rCA。你不能单独使用国家或地区代码;它必须始终在语言代码之前。

注意

虽然将地区视为与国家同义很诱人,但这并不总是如此。虽然你可能创建一个使用法语和国家的目录,但你也可以创建一个将法语语言代码与加拿大国家代码结合的目录。

确定目标语言和地区

本地化应用的第一步是确定你想要支持的语言、地区和国家。

穿上你的商业头脑,寻找你应用可能存在潜在市场的地区。特别是,你应该寻找以下语言、地区或国家:

  • 有大量或越来越多的 Android 用户

  • 国际语言,如英语,并不被广泛使用

  • 市场上需要一款属于你这一类型或主题的应用程序

所有上述因素意味着为该地区本地化你的应用可能特别有利可图。

决定你首先想要针对哪些国家或地区,然后确定你的应用需要支持哪些语言(以便吸引这一地区的人们)。

一旦你有了所有你将要支持的地区列表,从 ISO 639-1(en.wikipedia.org/wiki/ISO_639-1)中获取它们的语言代码,以及任何必要的地区或国家代码(en.wikipedia.org/wiki/ISO_3166-1_alpha-3)。

提供替代文本

由于翻译应用文本通常是最大的本地化任务,我们将首先解决这个问题。

打开你项目的res文件夹,创建你应用需要的所有替代values目录以支持你的目标地区。例如,如果你想让你的应用支持西班牙语,你将创建一个名为res/values-es的目录。如果你想要为墨西哥的西班牙语使用者提供西班牙语文本,你将创建一个res/values-es-Rmex目录。

在这些目录中的每个目录内创建strings.xml文件:

提供替代文本

将所有翻译后的字符串资源放入相应的strings.xml文件中(我们稍后会看看如何获取你的文本翻译的一些选项)。

继续我们的西班牙主题,我们的res/values-es/strings.xml文件可能看起来像这样:

<?xml version="1.0" encoding="utf-8"?> 
<resources> 
     <string name="hello_world">Hola Mundo</string> 
</resources> 

在运行时,Android 系统记录设备的区域设置,检查你的项目是否有任何合适的特定区域资源,然后加载相应区域特定目录的资源或回退到你的项目默认资源。

小贴士

将文本与你的应用程序代码分开

将应用程序的本地化部分与应用程序代码分开是一个好习惯,并且永远不要将任何文本硬编码到你的应用程序中,因为这会使文本本地化变得非常困难。相反,将所有文本声明为字符串资源,因为从项目中提取字符串、发送给翻译人员并将它们重新集成到应用程序中而不修改编译代码很容易。

你可能还需要本地化哪些其他资源?

本地化过程的大部分工作是将你的项目文本翻译成其他语言,特别是字符串资源,但你可能还想提供其他资源的特定区域版本。例如,你可能想提供以下内容的翻译版本:

  • 包含文本的绘图资源

  • 包含文本或对话的视频

  • 包含对话的音频

你可能还想提供不适用于你目标区域的资源的替代版本。例如,如果你提供的图片不能代表你目标区域,你可能想提供替代场景,比如某个城市或风景。阳光明媚的海滩和清澈的海水并不是每个国家的典型风景(不幸的是)。

你还应该意识到,某些图像在某些文化中可能被认为是不恰当或冒犯性的。

你应该以与提供备用字符串资源相同的方式提供任何资源的特定区域版本;创建一个新的目录并使用相同的语言和国家/地区代码组合。例如,如果你想提供多个版本的typical_highshcool.jpg可绘制资源,你将创建多个可绘制文件夹,例如res/drawable-ja-rJPNres/drawable-en-rUSAres/drawable-sv

为什么默认资源很重要

确保你的应用程序在任何语言、国家或地区设置上都能正确运行的关键是提供一套完整的默认资源。

你需要提供的最重要的默认资源之一是res/values/strings.xml。这里你将定义你整个项目中使用的每个字符串资源,在你的应用默认语言中。

注意

默认语言是大多数目标受众所说的语言。这不一定是你受众的第一语言。

如果你没有为每个字符串资源提供默认版本,那么当你的应用最终运行在未提供特定字符串资源的地区设置设备上时,它将会崩溃。即使用户不理解你的应用默认语言,这仍然比你的应用崩溃或拒绝打开要好。

你还需要为你在应用中使用的每个其他地区特定资源提供默认版本,因为如果你的项目缺少一个默认资源,那么它可能无法在不受支持的地区设置设备上运行。

默认字符串资源在你想在设备地区设置中保持某些文本一致性时也非常重要。一个常见的例子是你的应用标题,你可能希望在不同版本的你的应用中保持一致。

要在地区之间保持文本的一致性,你只需要在你的项目的默认strings.xml文件中定义一次字符串,然后从所有地区特定的strings.xml文件中省略它。然后,当系统尝试从地区特定的字符串文件中加载你的应用标题时,它会意识到这个字符串缺失,并回退到默认版本。最终结果是,相同的字符串资源将在你的应用的每个版本中使用。

小贴士

我应该翻译我的应用标题吗?

对于这个棘手的问题,并没有一个直接的答案。拥有多个标题会让应用更难维护,并且可能会使通常简单的决策变得更加复杂;如果你的应用有多个名称,那么你的应用在 Twitter 上的处理方式应该是什么?你需要为你的应用创建多个版本的标志和图标吗?你需要创建多个支持电子邮件地址吗?

你还必须更加努力地推广一个有多个不同名称的产品,尤其是在像搜索引擎优化(SEO)这样的事情上。

尽管有这些缺点,但仍有几个很好的理由让你可能想要在多个名称下发布你的应用。最常见的原因是,你的应用标题可能对不精通你应用默认语言的人来说没有太多意义。如果我们决定将我们的食谱应用命名为“最佳学生食谱”,英语用户会立刻知道期待什么,但对于说不同语言的用户来说,“最佳学生食谱”可能完全没有意义。一个无意义的标题对在 Google Play 商店偶然发现你的应用的潜在用户来说并不特别吸引人,因此你可能想要考虑在多个名称下发布你的应用。

这里没有真正的对或错。唯一重要的是,你根据你正在开发的应用和目标地区做出最佳决定。

无论你做出什么决定,都要确保坚持下去!在你发布应用之后更改应用名称只会让用户感到困惑。

哪些配置限定符最重要?

到目前为止,我们已经涵盖了相当多的不同配置限定符:地区限定符、屏幕方向、屏幕密度和屏幕尺寸限定符。

如果你在这类项目中混合使用这些资源,有很大可能性会有多个版本的资源适合当前设备。想象一下,如果你的应用安装在一个具有高密度屏幕和西班牙语设置的手机上,并且当前处于横屏模式。系统需要显示一个可绘制资源,但当前设备适合多个版本的该资源:

  • res/drawable

  • res/drawable-es

  • res/drawable-land

  • res/drawable-hdpi

系统将使用哪个可绘制资源?当 Android 有多个选择时,它会遵循一套严格的规则。在所有你可以使用的限定符中,地区限定符几乎总是得到优先考虑。在这个例子中,设备配置为使用西班牙语,因此 Android 会从res/drawable-es目录加载图形资源,即使相关的设备也具有高密度屏幕并且当前处于横屏模式。

注意

只有移动国家代码MCC)和移动网络代码MNC)的限定符比地区限定符具有优先级。如果你在项目目录中使用这些代码,它们将始终优先于你的地区限定符。

翻译你的应用

翻译你的应用资源是本地化过程中的一个关键部分,除非你精通你目标的语言,否则在某个时候你将需要寻求翻译者的帮助,无论是付费的专业人士还是友好的志愿者。

尽管你可能想走一条快速简便的路线,使用机器翻译,但像 Google Translate 这样的服务永远无法与人类翻译者相比,依赖机器翻译几乎总是会导致你的国际用户有糟糕的体验。

如果你决定雇佣专业翻译者,你应该尽早联系翻译者,这样你可以了解周转时间并将这些信息纳入你的开发计划。这也给你时间四处寻找并找到最适合你预算的最佳翻译者。

你可以在网上找到翻译者,或者你可能想探索通过开发者控制台提供的 Google Play 应用翻译服务。

要访问这些服务,请执行以下步骤:

  1. 登录开发者控制台(play.google.com/apps/publish)。

  2. 如果你还没有这样做,请通过选择添加新应用来上传你的 APK。

  3. 一旦你上传了你的应用,它就会出现在你的所有应用仪表板上。选择你想要购买翻译服务的应用。

  4. 确保在左侧菜单中选择了APK

  5. APK 翻译服务下,选择开始

  6. 选择你原始文本中使用的源语言。

  7. 翻译 API下,添加包含您想要翻译的字符串的 XML 源文件。

  8. 选择您的目标语言。

  9. 选择一个翻译供应商。

请注意,尽管您是通过开发者控制台进行购买,但这是一种您与所选供应商之间的直接商业协议,因此您需要直接与翻译者合作。通过开发者控制台购买翻译后,您将收到供应商的电子邮件,并负责从那里管理项目。

充分利用您的翻译

您翻译的质量取决于翻译者的技能,但也取决于您输入的质量。如果您为所选的翻译者提供高质量的输入,那么您将得到高质量的输出。

在本节中,我将向您展示如何提高您获得高质量、准确翻译的机会。

将字符串置于上下文中

在声明字符串资源时,您应该尽可能提供有关每个字符串的信息,因为这将帮助翻译者更好地理解您的字符串资源。

至少,您应该为每个字符串提供一个描述性的名称,但理想情况下,您还应该提供注释来解释这个字符串的用途,以及字符串将在您的应用程序中何时何地出现,以及任何限制,例如 UI 的这一部分可以显示的最大字符数。例如,看看以下内容:

<string name="search_submit_button">Search</string> 

//Text that appears on a button that can fit 9 characters. Must be one word. The user taps this button to submit a search query// 

如果您的应用程序包含任何专业语言或技术术语,那么您应该彻底解释每个术语的含义。如果这些内容太多,无法放入注释中,或者您发现自己正在解释多个字符串,那么您可能需要创建一个单独的词汇表文档,解释strings.xml文件中使用的所有专业术语的含义和使用方法。然后,您可以随字符串一起发送这份附加文档给翻译者。

一致地使用术语

通过在整个应用程序中一致地使用术语,您可以大大提高成功且准确的翻译的机会。例如,如果您应用程序的主要导航方式是通过一系列按钮,允许用户进入下一屏幕,那么您应该一致地标记这些按钮,而不是使用含义相同但术语混合的术语,如前进下一页确定提交。这将使翻译者的工作变得更加容易。

注意

一致地使用术语也将改善您应用程序的整体用户体验。

将字符串排序

为了帮助翻译者理解你的项目字符串资源,你应该花几分钟时间对你的strings.xml文件进行一些管理。删除你可能早期添加但从未在项目中实际使用的任何未使用字符串,并删除随着时间的推移从你的项目中消失的字符串。你还应该删除任何重复的字符串,并留意任何拼写错误或打字错误。最后,确保你的字符串格式正确且一致。这种维护工作可能看起来很简单,但它可以使翻译者的工作变得容易得多,尤其是如果他们本身不是 Android 开发者的话。

不创建不必要的字符串

更多的字符串意味着更多的工作!即使你支持多种语言,你可能也不需要为你的应用支持的所有语言中的每个字符串创建区域特定的版本。

可能有一些文本你希望在所有区域设置中保持一致,例如你的应用标题,这意味着没有必要将此字符串添加到每个特定区域的strings.xml文件中。记住,如果你没有在特定区域目录中包含特定的字符串,你的应用将使用默认字符串。

你的应用可能也支持与同一父语言变体相同的语言。例如,假设你的应用默认语言是英国英语,但它也支持美国英语。很可能你定义在项目res/values/strings.xml文件中的大多数资源都适合你的美国英语受众。你仍然应该创建一个res/values-en-rUSA/strings.xml文件,但此文件应仅包含美国英语拼写与英国英语拼写不同的字符串。

总是标记不可翻译的文本

你的项目可能包含一些你不想翻译的字符串资源,例如你的应用标题、URL、促销代码、社交媒体链接和电子邮件地址。在将项目交给翻译者之前,你应该清楚地标记所有你不想翻译的文本。

<xliff:g>标签包裹在你不希望翻译的文本周围,并使用id属性解释为什么这段文本不应该被翻译:

<string name="name"> This text will be translated<xliff:g id="This is my explanation">this text won't be translated</xliff:g> this text will be translated.</string> 

假设你想要显示一条包含你的应用标题的欢迎消息。你希望实际的欢迎文本被翻译,但你希望应用的标题在所有区域设置中保持一致。在这种情况下,你会使用以下 XML:

<string name="welcomeMessage"> Welcome to the<xliff:g id="appTitle">Student Recipes</xliff:g>app.</string>  

最后,为了防止你的项目抛出错误,你需要更新strings.xml文件中的资源标签:

<resources > 

小贴士

标记动态值为不可翻译

有时候,你可能会想在单个字符串中结合一些预定的和动态文本。例如,你的应用可能会要求学生输入他们的名字,接收他们的输入,将其与一些预定的文本结合,然后显示欢迎使用学生食谱应用,妮可!的消息。

那么,这与翻译有什么关系呢?当你创建包含动态文本的字符串时,你可以使用占位符来表示动态文本,如下所示:<string name="name">欢迎来到应用 %1$s</string>

你应该让翻译者清楚这个占位符不是一个错误或打字错误,因此他们不应该尝试翻译它。当你与一个不熟悉 Android 开发的翻译者合作时,这一点尤为重要。

为了明确占位符不应被翻译,你可以使用常规的<xliff:g>标签和id属性。然而,为了清晰起见,你也应该提供一个示例,说明这种占位文本最终可能显示的内容。在我们的欢迎信息示例中,它看起来如下所示:<string name="welcomeMessage">欢迎来到食谱应用<xliff:g id="userName" example="Nicole">%1$s</xliff:g>!</string>

其他需要考虑的事项

虽然语言可能是最明显的因素,但如果你想在国际市场上取得成功,还有几个不那么明显的事情需要考虑。

从右到左支持

不要自动假设你的受众会从左到右阅读(LTR)。在一些国家,从右到左阅读(RTL)是常态。根据你针对的本地化,你可能需要考虑实现 RTL 和 LTR 的支持。

好消息是,在 Android 4.2 及以上版本中,当用户将系统语言切换为从右到左的脚本时,系统会自动镜像你的应用 UI。

要利用这种自动镜像功能,打开你的项目Manifest文件并添加以下行:

android:supportsRtl="true" 

然后,将你应用的所有left/right布局属性更改为start/end等效属性:

  • 如果你针对的是 Android 4.2 及以上版本,请使用startend而不是leftright,例如,android:paddingRight变为android:paddingEnd

  • 如果你针对的是低于 4.2 版本的 Android,你应该除了leftright之外还使用startend;例如,你应该同时使用android:paddingRightandroid:paddingEnd

格式化值

请记住,并非每个本地化都以相同的方式格式化值,例如日期和数字。你不应该基于对用户本地化的假设来硬编码格式,因为这可能会在用户切换到另一个本地化时引起问题。

相反,始终使用系统提供的格式和工具,如下所示:

本地化您的应用 – 最佳实践

本地化您的应用是一种吸引可能数百万更多用户的有效方式。为什么只限制自己在一个市场呢?实施以下最佳实践将增加您的应用与全球受众建立联系的机会。

设计一组灵活的布局

根据您选择支持的语言,一些备用字符串资源在翻译过程中可能会显著扩展或收缩,以至于它们不再适合您的布局。

为了最小化在您的应用中发生这种情况的可能性,您应该创建灵活的布局,以便容纳所有尺寸的备用字符串资源。一个有用的技巧是为您的应用默认文本分配比所需更多的空间,这样当涉及到适应尺寸的轻微变化时,您的 UI 已经有一些调整空间。

您应该设计包含文本的 UI 元素,使其能够水平和垂直扩展,以适应文本高度和宽度的微小变化,例如,按钮可以根据其按钮标签的大小增长和缩小。然而,您还应该考虑这些扩展和收缩的 UI 元素可能对您的其余 UI 产生的影响。一个扩展的按钮是否会使得您的 UI 看起来拥挤或倾斜?或者更糟,它是否会将相邻的按钮推离屏幕,使它们变得无法触及?

特别注意用户可以与之交互的 UI 元素,例如按钮;如果这些元素改变大小,可能会迅速影响用户体验。两个在扩展时相互靠近的可触摸对象可能会导致用户不断意外地按下错误的按钮。

如果您在设计布局时考虑到灵活性,那么通常您将能够在所有区域设置中使用相同的布局。然而,如果您在设计一个足够灵活的布局以容纳所有备用字符串时遇到困难,您可能需要对自己的文本进行一些修改。

简洁、直截了当的文本通常意味着在翻译过程中变化较少,所以如果你在处理文本缩放或扩展变化很大的问题时,应该从问题的根源开始——你的应用程序的默认文本。寻找任何简化此文本的机会,任何可以从你的用户界面中移除的不必要文本,以及任何可以用普遍理解的图片或符号替换的单词,例如用勾选标记替换确定文本,或者用交叉符号替换取消。**

注意

简化你的应用程序文本并移除任何不是绝对必要的单词将提供更好的用户体验,无论用户是查看你应用程序的本地化版本还是以默认语言访问你的应用程序。

根据你需要显示的文本数量以及你的应用程序支持的区域设置,可能无法创建一个可以容纳所有目标语言的单一布局。布局在所有这些之后只能有一定的弹性!

作为最后的手段,你可以为导致你的应用程序出现许多问题的语言(或语言)创建替代布局,这引出了下一个要点。

仅在需要时创建替代语言

你可以通过创建一个新的布局目录并使用相关的配置限定符来创建针对特定语言的布局。例如,如果你想创建一个针对你项目main.xml布局版本,该版本针对显示你棘手的德语翻译进行了优化,你将创建一个res/layout-de/main.xml目录,然后在这个目录中放置你的德语布局资源文件。

然而,替代布局确实会使你的应用程序更难维护,所以只有作为最后的手段使用它们。

在不同区域设置中测试你的应用程序

一旦你创建了所有你的区域特定布局和资源,你需要确保你的应用程序真正为国际观众做好了准备。这意味着测试你应用程序的本地化版本,使用与你的不同目标区域设置相对应的本地化设置,以及至少一个设置为你的应用程序明确支持的语言和区域设置的 AVD。

在测试你的应用程序在不同区域设置时,务必检查以下内容:

  • 你的项目正在显示当前安装的设备上正确的字符串资源、布局以及任何其他特定于区域设置的资源。将strings.xml文件放在错误的目录中,遗漏配置限定符,甚至只是一个简单的拼写错误,都可能让你的应用程序在应该显示库尔德语文本(res/values-ku/strings.xml)时显示韩语文本(res/values-ko/strings.xml)。

  • 你的区域特定资源在当前布局中显示正确,并且不会引起任何可用性问题。

你还应该意识到某些屏幕配置、硬件和软件可能在某些地区更常见。确保你研究所有目标地区的最流行的设备,看看你是否需要将特定硬件、软件或屏幕配置的支持纳入你应用的本地化版本中。

测试不同地区

你可以通过打开设备的设置应用,选择语言,然后从菜单中选择一种新语言来在物理设备上测试你的应用的语言设置。这对于了解你的应用的本地化版本在不同语言设置的设备上如何运行非常有用。然而,你也应该测试你的应用在不同国家和区域设置下的功能,这在物理 Android 设备上不容易改变,这就是 AVD 的作用所在。

虽然你不能创建具有特定语言、地区或区域设置的 AVD,但你可以在模拟器中运行 AVD 后更改这些设置。

首先,创建一个具有你想要测试应用屏幕配置、硬件和软件设置的 AVD,并在该 AVD 中启动你的项目。

打开你的 Mac 终端或 Windows 命令提示符,并更改目录cd),使其指向Android 调试桥接器adb)所在的目录。例如,我使用的命令是:

cd /Users/jessica/Downloads/adt-bundle-mac/sdk/platform-tools 

一旦你的 AVD 启动并运行,你可以通过发出 adb 命令来更改其地区。为此,你需要你目标语言的 ISO 代码(ISO 639-1),以及你使用的任何国家或地区代码(ISO 3166-1)。

在你的adb窗口中,输入以下命令:

adb shell

几分钟后,你将看到一个#提示符。在这个时候,你可以输入以下命令:

setprop persist.sys.locale [ISO language code, with optional country/region code] ;stop;sleep 5;start 

例如,如果你想在一个设置为西班牙语(es)的设备上测试你的应用,你会运行以下命令:

setprop persist.sys.locale es;stop;sleep 5;start

如果你想要检查一个设置为在墨西哥地区使用西班牙语的设备上的应用,你会运行以下命令:

setprop persist.sys.locale es-Rmex;stop;sleep 5;start

到这一点,模拟器将重新启动并带有新的地区设置。重新启动你的应用,这就完成了——你现在可以测试你的应用与新的地区设置了。

寻找常见的本地化问题

因此,一旦你设置了测试环境,你应该寻找哪些具体的问题?

首先,你应该检查你应用中的每一个屏幕,确保没有文本被裁剪、行包裹不佳、看起来奇怪的单词或行中断,或者不正确的字母顺序。你也应该注意任何文本应该从右到左(RTL)显示的地区,并检查在你的布局中这确实发生了。

你还应该留意任何无意中未翻译的文本;无意中未翻译,可能是因为在某些情况下,你可能希望在整个应用程序中一致地使用相同的文本,例如应用程序的标题和你的联系信息。如果你发现任何无意中未翻译的文本,那么你应该深入查看你的应用程序代码,以确定到底出了什么问题。

此外,还要留意任何翻译错误的文本。这种错误可能很难发现,因为你可能不是你应用程序支持的所有语言的熟练使用者。在这种情况下,使用在线翻译器,如谷歌翻译,是可接受的,因为它通常是快速且简单的方法,可以再次确认你的应用程序的西班牙语文本确实是西班牙语。

你还应该寻找任何文本或其他区域特定资源与当前布局不匹配的情况。这可能是以下情况:

  • 小于默认版本的资源,例如在翻译过程中显著缩小的文本。缩小的资源可能会在布局中留下巨大的空白,或者使其他 UI 元素失去平衡,创建一个虽然不是完全损坏,但看起来相当奇怪的布局。

  • 在某些版本的 UI 中存在而在其他版本中缺失的资源。同样,这可能会在布局中造成尴尬的空白和美学问题。如果你有意省略某些资源,你可能想要寻找一些东西来填补这个资源留下的空白,或者作为最后的手段,提供一个专门为该区域设计的替代布局。

  • 大于默认版本的资源,这可能导致界面看起来杂乱拥挤,或者出现诸如可触摸区域过于靠近的问题。

  • 不适合当前屏幕配置的资源。如前所述,不同的区域可能也意味着不同的硬件、软件和屏幕配置。如果你针对多个国家和区域,请确保你研究这些特定人口统计中最受欢迎的 Android 设备,然后在这些不同区域中测试你的应用程序,使用反映这些不同区域中最常用硬件、软件和屏幕配置的 AVD。

测试默认资源

在测试了你的应用程序想要支持的所有语言、国家和区域之后,你应该在一个不明确支持区域设置的 AVD 上安装你的应用程序。这听起来可能有些奇怪,但这是检查你是否包含了应用程序所需的所有资源默认版本的最有效方法。

正如我在本章中多次提到的,包括默认资源意味着如果您的应用遇到困难,它有东西可以优雅地回退,而不是崩溃。提供所有项目字符串、可绘制元素和其他资源的默认版本确保了即使它最终运行在一个不支持的区域设置的设备上,您的应用也能运行。

要测试您的应用,请打开您的终端或命令提示符窗口,并告诉您的模拟器切换到一个您的应用没有明确支持的区域设置。如果您已经提供了必要的默认资源,您的应用应该会加载这些资源,包括项目res/values/strings.xml文件的内容。如果您的应用崩溃,那么这表明您的项目至少缺少一个默认资源。

在关键国家计划测试版发布

一旦您在不同区域测试完您的应用,并且您确信无论用户的语言、国家或地区设置如何,它都能正确运行,那么您可能想考虑向您的潜在目标受众开放反馈,特别是所有支持的语言的母语者。这通常以测试版的形式出现。

注意

安装您应用测试版版本的用户无法在您的 Google Play 页面上留下评论,所以请不要担心这些早期版本的应用会负面(且不公平地!)影响您的 Google Play 评分。

获得真实世界的反馈总是很有价值,但当你计划进行国际发布时,这种反馈变得更加宝贵。母语者能够轻松地指出你文本中的任何语法或拼写错误,甚至是一些听起来有点不自然的地方。他们还可以迅速指出关于你的应用内容的不适当、令人困惑或难以与他们产生共鸣的任何问题。

这都是非常有价值的反馈,可以帮助您完善应用的本地化版本,并创建出感觉像是专门为您的目标受众群体中的这一部分人量身定制的应用,而不是一个匆忙拼凑的翻译,目的是为了增加一些额外的下载量。

如果您想启动一个测试版测试程序,那么 Google Play 可以提供帮助。在您登录到开发者控制台并上传 APK 后,您可以设置用于测试版测试的用户组。

您可能想从小规模的封闭测试版开始,通过输入他们的 Gmail 地址来指定一组测试者。如果您手头有一份测试者名单,您可以通过以下步骤启动一个封闭测试版程序:

  1. 登录到开发者控制台,并从左侧菜单选择APK

  2. 选择上传您的第一个 APK 到测试版按钮,并按照屏幕上的说明上传您的 APK。

  3. 一旦您的 APK 上传完成,选择测试版测试

  4. 如果你看到选择测试方法选项,打开相关的下拉菜单,选择设置封闭测试版列表,然后选择创建列表

  5. 输入所有你想参与测试的人的电子邮件地址。点击保存

  6. 复制Beta opt-in URL。然后你可以将此链接与你的测试者分享。当潜在测试者点击此链接时,他们会看到关于成为测试者所涉及内容的说明。然后他们可以决定是否参与测试。

  7. 在复制Beta opt-in URL后,你需要输入一个电子邮件地址或 URL,你将使用它来收集测试者的反馈。在反馈渠道字段中输入此信息,然后点击保存

小贴士

收集测试者反馈

由于测试者不能在你的应用 Google Play 页面上留下评论,你需要为他们提供一种发送反馈给你的替代方式。每次你通过开发者控制台启动测试计划时,完成反馈渠道字段至关重要,这是你的测试者与你沟通的主要方式。

这个反馈渠道可以是地址或 URL 的形式,但你可能还想考虑为你的测试者设置一个 Google+页面或 Google Group。无论你首选的收集反馈方式是什么,重要的是要认识到,任何测试你的应用的人都在为你做善事,所以你应该尽可能让他们容易地发送反馈给你。

如果提供反馈感觉像是一项艰巨的工作,那么你的测试者将不会费心——他们会将想法保留在自己心中,你的项目也因此受到影响。

经过一轮封闭测试后,你可能想要进行公开测试,在那里你指定测试者的最大数量,但不需要指定他们的电子邮件地址。

要启动公开测试计划,请确保你已登录开发者控制台,然后从左侧菜单中选择APK。选择测试,打开下拉菜单,选择设置公开测试,然后指定将能够测试你的应用的最大用户数量。

复制Beta opt-in URL并与你的测试者分享。再次确认,你已在反馈渠道中输入了电子邮件地址或 URL,然后点击保存

准备起飞!

如果你想让你的应用在国际市场上引起轰动,支持多种语言和地区只是开始。你还需要制定某种形式的国际营销活动。

这可能听起来像需要巨额预算和专门的营销团队的事情,但你可以根据你的预算和可用于推广应用的空闲时间来调整你活动的范围。即使你的应用是一个人的项目,也有无数种预算友好(在某些情况下,免费)的方式可以将你的应用推广给国际受众,尤其是在你在线推广应用时。

一场营销活动可能简单到撰写一份宣布您应用发布的新闻稿,然后安排将此文本翻译成您应用支持的各种语言。然后,您可以通过社交网络、免费新闻和新闻稿网站、您自己的博客或任何其他可以免费发布内容的平台来传播您的新闻稿。

注意

不要沉迷于将您的营销材料发布到各个地方,仅仅是为了发布。垃圾邮件只会从长远来看损害您应用的声誉。

您还应该翻译您创建的任何其他促销材料,例如横幅、虚拟徽章、广告或表情包。

即使您的促销图形不包含任何文本,您也应该寻找机会调整这些图形,使它们更有可能与您的每个目标地区产生共鸣。例如,您是否可以包含任何会使特定地区的用户对您的图形更感兴趣的地理或文化参考?在我们的食谱应用示例中,这可能意味着确定可能更吸引我们目标受众一部分的食谱,然后在应用的促销横幅和广告中更突出地展示这些食谱。

如果您创建任何促销视频,您还应该为您的应用支持的每种语言创建这些视频的本地化版本。这可能意味着为每个视频录制全新的音频或提供字幕。

记住,吸引国际观众的关键是营造一种假象,即您的应用是为每个用户的特定地区设计的。

本地化您的 Google Play 商店列表

您的应用在 Google Play 上的列表是国际用户对您应用的第一个印象。为了确保这个第一印象是积极的,您需要本地化您的商店列表,这意味着为您的应用支持的每个地区翻译其文本。

您可以通过开发者控制台创建您应用 Google Play 列表的多个本地化版本。然后,当用户打开您应用的列表时,Google Play 将自动确定该用户的位置,并显示最适合他们语言、国家或地区设置的列表版本。如果 Google Play 找不到适合特定地区版本的您的应用列表,它将显示您应用的默认 Google Play 页面。

要创建本地化的 Google Play 列表,请登录您的开发者控制台账户,并从左侧菜单中选择应用应用。选择您要工作的应用,然后选择商店列表

要创建一个新的本地化版本,请点击管理翻译按钮。在此阶段,您可以通过点击购买翻译并遵循屏幕上的说明来购买翻译:

本地化您的 Google Play 商店列表

如果您已经有一些想要添加到您本地化商店列表中的翻译文本,请选择添加自己的翻译。选择您想要创建本地化列表的所有语言(不用担心,您稍后总是可以添加更多),然后点击添加

在这一点上,开发者控制台将带您回到商店列表表单,但现在,如果您点击语言按钮,您将看到所有您刚刚添加的语言的下拉列表。点击任何这些语言将带您到一个单独的表单,您可以在其中创建一个全新的商店列表,该列表将在有人使用这些区域设置在 Google Play 查看您的应用时显示。

如果您在任何时候需要删除您的本地化页面之一,只需点击管理翻译按钮,然后选择删除翻译,接着选择您想要删除的语言。如果您想更改默认的 Google Play 页面版本,请再次点击管理翻译按钮,并选择更改默认语言

创建本地化商店列表不仅仅是翻译您应用的描述;您还应该提供替代图形。即使用户正在查看您 Google Play 页面的本地化版本,他们也可能仍然不确定您的应用是否真的支持他们的语言。不要留下任何疑问的空间;为支持的所有语言截图您的 UI,然后将这些截图上传到您应用 Google Play 页面对应的版本。

注意

当您构建您应用的 Google Play 页面时,开发者控制台将提供有关如何让您的应用触及最广泛受众的提示。这些提示包含一些有用的信息,所以请确保您通过从左侧菜单选择优化提示来查看它们。

在您的项目发布后

当您成功发布您的应用后,艰苦的工作并没有结束。一旦您点击发布按钮并将您的应用发送到广阔的野生世界,您就可以做很多事情来帮助该应用触及最广泛的受众。

在成功发布后——支持国际用户

一旦您吸引了国际受众,您需要保持这个受众——这意味着提供多种语言的支持。

您可以提供多少支持将取决于您是否将您的应用作为团队的一部分创建,或者您是否是一个在业余时间创建 Android 应用的独立开发者。但提供某种级别的支持对于您能否保留您最初如此努力获得的用户至关重要。

至少,您应该监控您应用的 Google Play 评论。Google Play 商店会方便地为您翻译所有评论,并且您应该尝试回复通过 Google Play 收到的任何问题或建议,无论它们是用什么语言写的。

在可能的情况下,你应该用评论者的语言回复 Google Play 评论,即使这意味着需要求助于在线翻译工具。但请注意,翻译工具往往会生成一些不太准确的结果,所以让用户知道你正在使用翻译工具是个好主意,这样他们就可以忽略任何笨拙的措辞或语法错误。

小贴士

创建 Google Play 徽章

一旦你的应用上线,你的目标就是尽可能地将尽可能多的人引导到其 Google Play 页面。实现这一目标的一种方式是创建一个 Google Play 徽章。每当有人点击这个徽章时,它就会直接带他们到你的应用在 Google Play 上的列表。

你可以在任何你想的地方发布这个徽章——在你的网站、博客、社交媒体账户上,或者你可以将其整合到你的电子邮件签名中。但请记住,不要过度宣传;没有人喜欢垃圾邮件发送者,而且长期来看,垃圾邮件发送你的应用 Play 徽章并不会为你赢得忠实的追随者。

你可以使用 Google Play 徽章生成器创建徽章(play.google.com/intl/en_us/badges)。

监控你应用的国际表现

当你支持多个地区时,你的应用不可避免地会在世界上的某个地区比其他地区更受欢迎。这可能有各种各样的原因,从你可以控制的事情(比如你翻译的质量),到你不能控制的事情(比如在某个目标地区市场上已经占据主导地位的竞争对手应用)。

对你支持的所有地区中应用受欢迎程度的清晰了解对于确定你需要投入更多时间和精力的领域以及可能需要减少努力或甚至完全停止支持的领域至关重要。

如果你的应用在某个地区非常受欢迎,你可能会决定投入更多精力来满足这部分受众的需求,比如在这个地区进行更多市场研究,为这些用户创建本地化内容,甚至创建特定语言的用户群体,例如 Google+社区、LinkedIn 群组或 Facebook 页面。

那么,你如何确定用户喜爱的地区呢?开发者控制台提供了关于你的应用在所有支持地区累积的下载量、安装量和卸载量的统计数据。要访问这些信息,请登录到你的开发者控制台账户,选择你想要调查的应用,然后从左侧菜单中选择统计信息

但是,让我们现实一点,这些统计数据并不总是好消息,有时它们可能会揭示你的应用在某些地区难以吸引用户。如果是这种情况,那么你应该试图找出可能阻碍你的应用成功的原因。

这可能是一些你可以解决的问题,比如不够完美的翻译,或者你的应用对硬件、软件或屏幕配置的要求并不代表这个地区常用的 Android 设备。然而,问题可能是一些不太容易解决的问题,比如对你应用类型或主题缺乏兴趣,或者已经占据市场主导地位的有竞争力的应用。

根据你应用性能不佳背后的原因,你可能决定尝试解决这个问题,或者你可能认为放弃对这个特定地区的支持,将时间和精力投入到你的应用已经找到热情受众的领域更有意义。

只需记住,将你的应用国际化只是第一阶段。如果你的应用想要取得全球成功,那么你必须持续努力支持来自世界各地的用户,并持续监控你的应用在所有不同地区的表现。

摘要

在本章中,我们探讨了如何触及尽可能广泛的受众,从支持不同的 Android 操作系统版本,到优化你的应用以适应不同的硬件、软件、屏幕尺寸、屏幕密度和方向。我们甚至涵盖了将我们的应用翻译成不同语言的内容。我们还简要介绍了如何通过广告和营销活动推广你的应用。

现在你已经知道了如何吸引受众,在下一章中,我们将探讨如何通过优化我们应用的 UI 来让这个受众感到惊艳。

第九章。优化您的 UI

您的应用程序的用户界面是您与用户最直接的联系,因此您需要确保它完美

到目前为止,我们一直专注于创建一个设计精美、功能丰富的用户界面——但这只是战斗的一半。如果您的应用要获得那些 5 星 Google Play 评论,您需要创建一个快速渲染、响应迅速,并且总体上提供卓越用户体验的 UI。

性能对于创建成功的用户界面以及一般意义上的成功应用至关重要。如果您的应用运行缓慢、容易崩溃、消耗大量数据和内存,或者耗尽用户的电池,那么无论您的 UI 看起来多么好,没有人会想使用它!

在本章中,我将向您展示如何找出可能影响您应用的所有最常见性能问题。由于您会想修复您遇到的任何问题,因此在这个过程中,我还会涉及到这些问题最初发生的原因,以及您可以采取的解决这些问题的步骤。

在完成本章后,您将知道如何创建一个流畅且响应迅速的 UI,人们会喜爱使用。

代码计时

不要等到您的应用开始抛出错误后再去寻找性能相关的问题。您的应用可能会缓慢泄漏内存、分配过多的对象,或者在一个复杂的视图层次结构下苦苦挣扎。这些都不一定会抛出错误,但它们肯定会对您应用的性能产生负面影响。

如果您要创建一个高性能的应用,您需要去寻找问题。

代码计时是一种强大的方式,可以确切地了解您的应用中正在发生什么,包括任何运行速度或持续时间比其他部分更慢或更长的代码段。我们将要查看的第一个 Android SDK 工具让您可以做到这一点。

TraceView是一个图形查看器,可以分析您设备上运行的任何 Android 应用。

TraceView,就像本章中我们将要介绍的大多数工具一样,只能测量运行中的应用程序。因此,第一步是安装您想要在 Android 设备上测试的应用程序,然后将该设备连接到您的计算机。或者,您可以使用模拟器和合适的 AVD。确保您的应用正在运行,并且其 UI 是可见的。

小贴士

前后对比——衡量您应用的性能

当您发现您的应用存在问题,显然您会想修复这个问题。但您在做出一些更改后,如何知道问题真正得到了解决?

如果您在更改前后使用 TraceView 来衡量您应用的性能,您将拥有所有必要的数据来查看您的优化是否对您应用的性能产生了任何显著的影响。所以即使您没有识别出需要优化的代码,TraceView 的输出也是您在优化应用时应该保留并参考的宝贵数据。

要启动 TraceView,请从 Android Studio 工具栏中选择 工具,然后选择 AndroidAndroid 设备监控器Android 设备监控器将在新窗口中打开。

备注

Android 设备监控器 是一个独立的工具,它是 Android SDK 的一部分。在本章中,我将使用包含在 Android 设备监控器 中的几个工具。尽管在本章中我将通过 Android Studio 用户界面访问 Android 设备监控器,但您也可以单独启动 Android 设备监控器。如果您想绕过 Android Studio,或者如果您正在使用 Eclipse,您可以通过在您的 Android SDK 下载中找到 monitor 文件并双击它来启动 Android 设备监控器。然后,Android 设备监控器 将在新窗口中打开。

Android 设备监控器 窗口中,选择 DDMS 选项卡。屏幕左侧您将找到一个 设备 选项卡,其中列出了所有当前检测到的设备和模拟器。选择包含您的应用程序的设备或 AVD,您将看到在此设备上运行的所有进程的列表。选择您想要分析的进程。

如果您在列表中看不到您的应用程序,请检查它是否正在运行并且其 UI 是否可见。

通过点击开始方法分析图标(如下截图中的光标位置)来开始方法分析:

计时您的代码

在这一点上,您将面临两种分析选项:

  • 基于跟踪的分析:跟踪每个方法的进入和退出,无论大小如何。这种分析类型具有巨大的开销,因此您只有在绝对不知道要分析什么时才应该跟踪一切。然后,您可以使用这些数据来缩小后续基于样本分析的范围。

  • 基于样本的分析:以您指定的频率收集调用栈。这种分析类型的开销与采样频率成比例,因此开销通常更容易管理。

进行选择后,TraceView 将开始记录。花些时间与您的应用程序交互,确保与您想要分析的所有部分和方法进行交互。然后,点击停止方法分析图标,DDMS 将从您的设备中提取跟踪文件并将其加载到查看器中。

备注

根据您记录的内容量,这些数据可能需要几秒钟才能启动,因此您可能需要耐心等待。

您最终会得到一个看起来像这样的跟踪文件:计时您的代码

跟踪文件由以下内容组成:

  • 时间线面板:

    此面板显示每个线程和方法开始和结束的位置,因此您可以跟踪代码随时间执行的情况。

    时间线面板以不同的颜色显示每个线程的执行情况,每行一个。你会在每个方法开始(左侧条形图)和停止(右侧条形图)的地方看到峰值。这两点之间的线条表示方法执行所需的时间。

    如果你发现任何相同颜色的长线条,这表明这种方法正在消耗处理时间,你应该收集更多关于这个方法的信息。点击任何你想要了解更多信息的方法,其统计数据将出现在配置面板中。

  • 配置面板:

    此面板提供了关于当前所选方法内部发生情况的大量信息,包括该方法被调用的次数、递归调用的次数以及该方法使用的总 CPU 时间百分比(包括和排除所有子方法执行时间)。这些数据有助于你识别任何需要优化的方法。

识别过度绘制

当 Android 绘制屏幕时,它从最高级别的容器开始,然后在这个父视图中绘制所有子视图和孙视图。这意味着单个像素可能在一个循环中被绘制多次,这个过程被称为过度绘制

为那些最终会被后续视图覆盖的像素着色是浪费处理能力,而且你绘制屏幕的次数越多,你添加的过度绘制就越多。

对于内存带宽有限且可能难以处理 GPU 密集型绘图任务的移动设备,如 Android,过度绘制是一个特别的问题。通过识别和纠正不必要的或过度的过度绘制,你可以提高应用的渲染速度。

注意

大量的过度绘制也可能表明你的用户界面存在更严重的问题,因此检查应用中的过度绘制量也可以帮助你找到需要进一步调查的区域。

尝试消除所有过度绘制的实例是不现实的,因为一些过度绘制区域是正常且不可避免的。如果你的应用背景是蓝色,那么你放置在背景之上的每一个元素都会导致一些过度绘制,但这不可避免——你能想象发布一个只有单一蓝色屏幕,什么都没有的应用吗?

你只需要担心过度绘制过多的情况,比如多个全屏层,或者对用户最终在屏幕上看到的最终图像没有任何贡献的过度绘制,比如完全隐藏在其他视图后面的内容。

运行 4.2 及更高版本的 Android 设备具有一个内置功能,允许你查看安装在 Android 设备上的任何应用(如果你对此类事物感兴趣,甚至可以查看整个 Android 系统中存在的过度绘制量)。

要查看应用中发生的过度绘制量,请将你要测试的应用安装在运行 4.2 或更高版本的物理 Android 设备上。然后打开你的设备的设置,然后是开发者选项。点击调试 GPU 过度绘制,并从出现的弹出窗口中选择显示过度绘制区域

识别过度绘制

注意

调试 GPU 过度绘制弹出窗口包含一个显示色盲异常区域选项,它具有与标准调试 GPU 过度绘制相同的功能,但具有颜色校正,以补偿色盲(对绿光敏感度降低)的人。

选择此选项后,Android 系统根据每个像素被绘制和重绘的次数,以不同的颜色为屏幕的每个区域着色。

识别过度绘制

这些颜色是指导你识别过度绘制特定问题的区域:

  • 无颜色 = 无过度绘制:这些像素只被绘制了一次。

  • 蓝色:1 倍过度绘制,这意味着这些像素被绘制了两次。换句话说,屏幕被绘制了一次,然后再次在上面绘制。尽管你可以承受的过度绘制量因设备而异,但大多数设备应该能够处理单层过度绘制。大面积的蓝色是可以接受的,但如果整个窗口都是蓝色,那么你可能需要进一步调查,看看是否可以消除一些这种过度绘制。

  • 绿色:2 倍过度绘制。中等大小的绿色区域是可以接受的,但如果屏幕超过一半是绿色,你应该调查是否可以优化一些绿色。

  • 浅红色:3 倍过度绘制。一些浅红色的小区域可能是不可避免的,但任何中等或大面积的红色都应引起关注。

  • 深红色:4 倍过度绘制,这意味着这个像素至少被绘制了五次——甚至可能更多!你应该始终调查任何深红色区域。

当你在调查过度绘制区域时,你的第一步应该是检查布局对应的 XML 文件,看看是否有明显的重叠区域。特别是要注意:

  • 任何对用户不可见的可绘制元素。

  • 任何重叠绘制的背景,尤其是全屏背景。如果你的 UI 在应用开始绘制内容之前就有几层背景,那么你不可避免地会遇到一些严重的过度绘制。

  • 任何在另一个白色背景上绘制白色背景的区域。

  • ViewGroups嵌套在ViewGroups内部。考虑是否可以用RelativeLayout替换这些多个ViewGroups

另一个用于识别不必要的视图和嵌套布局的有用工具是 Android SDK 中包含的层次结构视图工具,通过Android 设备监控器访问。

与本章中我将使用的许多诊断工具一样,层次结构视图只能与在 AVD 或物理 Android 设备上运行的应用程序通信。然而,与其它诊断工具不同,层次结构视图只能连接到运行 Android 操作系统开发者版本的手机。如果您没有开发者手机,可以通过将ViewServer类(github.com/romainguy/ViewServer)添加到您的项目中来绕过这个限制。

如果您使用的是物理 Android 设备,您还需要确保已启用调试。打开您的设备设置,然后选择开发者选项,并将Android 调试滑块拖到开启位置。

打开 Android 设备监控器(通过选择工具 | Android | Android 设备监控器)并点击层次结构视图按钮(如下截图中的光标位置):

识别过度绘制

窗口选项卡中选择您的设备,您将看到在所选设备上运行的所有 Activity 对象的列表,按组件名称列出。

要填充各种层次结构视图面板,请点击蓝色将视图层次结构加载到树视图图标。根据您的应用视图层次结构的复杂程度,树可能需要一些时间来加载。

一旦您的项目的层次结构视图加载完成,您可能想花些时间在各个窗口中探索这个层次结构(我将在下一节中更详细地讨论这些内容),但快速且简单识别大面积过度绘制的一种方法是将 Activity 的层次结构导出为 Photoshop 文档。

当您从层次结构视图输出创建 PSD 文档时,每个View都显示为单独的层。这意味着您可以使用 PSD 文档来剥离您应用的每一层,并确切地看到每一层对 UI 的贡献。检查每一层可以帮助您识别过度绘制的原因,或者如果您已经怀疑您的应用中某些区域存在过度绘制,那么您可以通过隐藏不同的层来测试您的理论,看看这如何影响用户在屏幕上看到的最终渲染图像。

要将您的层次结构导出为 Photoshop 文档,请点击捕获窗口层为 Photoshop 文档图标(如下截图中的光标位置):

识别过度绘制

此按钮生成一个 PSD 文件,您可以在 Adobe Photoshop 或任何支持 PSD 文件(如免费开源的 Gimp 程序www.gimp.org)中检查。

识别过度绘制

在 GIMP 中检查应用层

花些时间探索构成您 UI 的不同层。

这个 PSD 文档特别有助于识别过度绘制的主要原因之一:多个白色背景。多个白色背景可能难以发现,所以一个技巧是将你的 PSD 文件中的白色背景替换为不同的图像。然后,你可以检查当你通过 UI 的不同层移动时,这些图像的哪些部分是可见的。

简化你的层次结构视图

另一个常见的性能问题来源是应用程序的层次结构视图

当 Android 系统渲染每个视图时,它会经过三个阶段:测量、布局和绘制。系统完成每个阶段所需的时间受你层次结构中视图数量的影响,以及这些视图的排列方式。

将你的视图安排在更深、更复杂的层次结构中将对你的应用程序的渲染速度产生明显影响。你应该寻找任何简化视图层次结构和移除嵌套布局的机会。

除了突出显示过度绘制区域外,层次结构视图还帮助你可视化应用程序的视图层次结构,并提供有关每个视图渲染所需时间的非常有用的性能信息。

层次结构视图工具由三个不同的窗口组成。

树视图

此窗口提供了当前所选 Activity 视图层次结构的鸟瞰图:

树视图

树视图中的每个节点代表一个单独的View。当你选择一个节点时,关于该View的附加信息会出现在节点上方的小窗口中:

  • 视图类:对象的类

  • 视图对象地址:指向View对象的指针

  • 视图对象 ID:对象的android:id属性值

你还将看到这个View在 Android 设备上的预览。通过确切地了解每个View对最终 UI 的贡献,你可以决定这个View是否添加了任何有价值的内容。如果没有,那么你应该从你的应用程序中移除它。

树概览

此窗口包含 Activity 整个视图层次结构的地图表示:

树概览

这种对层次结构结构的宏观视图特别有助于了解你的视图层次结构实际上有多复杂,同时也有助于你识别嵌套布局和其他简化布局的机会。

布局视图

此窗口显示你的 Activity UI 的骨架。

当你在树视图树概览窗口中选择一个节点时,布局视图会突出显示这个View所绘制的区域。再次强调,这有助于你剔除冗余的Views

布局视图

虽然层次结构视图对于发现嵌套布局和冗余视图非常有用,但仅通过查看这三个窗口,你仍然可能无法发现一些视图层次结构的问题。

为了帮助您识别可能隐藏在表面之下的任何问题,您可以使用层次视图工具来测量每个视图在渲染过程的每个阶段(测量、布局和绘制)中移动所需的时间。有了这些信息,您将确切知道哪些视图需要优化。

默认情况下,层次视图的树概览不显示渲染时间。要将此信息添加到树概览中,您需要选择您想要分析的部分树的根节点。然后点击绿色、红色和紫色维恩图图标(当您悬停在图标上时,您将看到获取以所选节点为根的树的布局时间工具提示)。

几分钟后,每个视图层次结构部分的节点上都会出现三个彩色点。这些点表示此视图相对于其他已分析视图的渲染速度。

从左到右,这些点表示完成以下操作所需的时间:

  • 测量视图

  • 布局视图

  • 绘制视图

每个点的颜色表示视图在渲染过程的每个阶段(测量、布局和绘制)中移动所需的时间:

  • 绿色:此视图比至少一半的其他已分析节点更快。在测量位置出现的绿色点表示此视图的布局时间比至少 50%的其他已分析节点更快。

  • 黄色:此视图位于所有已分析节点中最慢的 50%。

  • 红色:此视图是所有已分析节点中最慢的。

您可以使用这些信息来识别哪些视图在测量、布局和绘制方面最慢,这样您不仅知道哪些视图需要优化,而且也知道您应该关注渲染过程的哪个部分。

当您点击一个已分析的节点时,您还会看到该视图的测量、布局和绘制时间,以毫秒为单位显示。

布局视图

只需记住,这些性能指标是相互比较的,所以您的视图层次结构总是包含一些红色和黄色节点。

在您开始寻找使用黄色和红色点优化视图的方法之前,问问自己这些视图是否有渲染更慢的合理原因,例如拥有更多子视图的视图总是会比拥有较少子视图的节点落后。

查找内存泄漏

虽然 Android 可能是一个内存管理的环境,但您仍然需要仔细检查您的应用是如何处理内存的。

垃圾回收GC)只能移除它识别为不可达的对象。如果您的应用分配了 Android 系统不识别为不可达的对象,那么这些对象将永远不会被垃圾回收。它们将悬挂在那里,污染您的堆,并占用宝贵的空间。

随着您的应用继续泄漏系统无法垃圾回收的对象,可用的空间将越来越少。Android 系统将通过运行更长和更频繁的 GC 事件来尝试补偿这种内存减少。

虽然您典型的 GC 事件不会对您的应用性能产生明显影响,但随着更多更长的 GC 事件在短时间内发生,您的用户可能会注意到性能下降,甚至可能遇到OutOfMemoryError

内存泄漏可能难以检测,但 Android SDK 附带了一些工具,您可以使用这些工具扫描您的应用,寻找那些有时微妙的内存管理问题迹象。

内存监控器

内存监控器会跟踪您应用随时间变化的内存使用情况。这是另一个只能与运行中的应用程序通信的工具,因此在继续之前,请确保在物理设备或模拟器上安装了您的应用。

您可以通过从主 Android Studio 屏幕中选择屏幕底部的Android 监控器选项卡,然后选择内存选项卡来访问内存监控器。

确保您想要测试的应用程序在屏幕上是可见的。一旦内存监控器检测到您的运行中的应用程序,它就会开始记录内存使用情况,以深蓝色显示您的应用正在使用的内存,以浅蓝色显示未分配的内存。

内存监控器

小贴士

故障排除

如果内存监控器显示“没有可调试的应用程序”的消息,请打开 Android Studio 的工具菜单,选择Android,并确保启用 adb 集成被选中。这个功能可能会有些不可靠,所以如果一开始不起作用,请尝试几次启用 adb 集成开启和关闭。如果您使用的是物理 Android 设备,断开设备并重新连接到不同的 USB 端口也可能有所帮助。

在监控内存的同时,花些时间与您的应用互动。最终,您的应用内存使用量会增加,直到没有未分配的内存。在这种情况下,系统将通过触发 GC 事件来释放一些内存,导致已分配的内存减少。

大多数垃圾回收(GC)事件都是完全正常的,但如果您发现 GC 事件变得越来越长且越来越频繁,那么这表明您的应用可能正在发生内存泄漏。

如果您在一段时间内跟踪一个疑似内存泄漏,您最终可能会看到 Android 系统试图通过授予更高的内存上限来满足您应用对内存的无尽渴望。如果在内存监控器中看到这种情况发生,那么这是您的应用中存在严重内存泄漏的迹象,您应该更详细地调查它。

堆选项卡

如果您在内存监控器中看到异常的内存使用情况,您可以使用 Android 设备监控器的堆选项卡来收集有关您的应用如何使用内存的更多信息。

正如其名所示,此选项卡提供了有关您的应用堆内存使用情况的数据,包括您的应用正在分配的对象类型、分配的对象数量以及这些对象占用的空间量。

安卓智能手机和平板电脑的堆空间是有限的,只能容纳一定数量的对象。随着堆的增长,安卓系统将通过触发 GC 事件来尝试释放内存——这是我们已知对性能不利的。

要访问堆标签,启动Android 设备监控器,选择DDMS标签,然后从设备面板中选择您的设备或 AVD,接着选择您想要检查的进程。点击更新堆按钮(如下面的截图所示):

堆标签

选择标签,并花些时间与您的应用程序进行交互。

堆输出仅在垃圾回收(GC)事件发生后才会显示,因此您可能需要耐心等待一个有机的 GC 事件,或者可以通过点击触发 GC按钮强制执行 GC 事件。一旦发生 GC,堆标签将显示关于您的应用程序堆使用的详细信息。

堆标签

在执行不同操作之前和之后与您的应用程序进行一些交互,触发 GC 事件,这样您就可以比较这些操作对堆的影响。通过这种方式,您可以隔离导致内存泄漏的操作,以及您的应用程序可能遇到的任何其他内存相关的问题。

当您在应用程序的堆中追踪问题时,生成一个 HPROF 文件可能会有所帮助,这是一个包含您应用程序堆中所有对象的快照,以及有关相关类和实例的详细信息。

生成 HPROF 文件后,您可以在 Android Studio 或单独的剖析工具中查看它,例如 Eclipse 内存分析器(www.eclipse.org/mat)。

要检索 HPROF 文件,点击转储 HPROF图标(位于更新堆图标旁边)。为您的文件命名并保存。

要在 Android Studio 中分析堆转储,将您的 HPROF 文件作为一个新的 Android 项目打开。Android Studio 将自动在 Android 内存 HPROF 查看器中打开该文件,以便您进行更详细的分析。

堆标签

对象分配 – 理解内存碎片

您还应该检查的另一个常见内存问题是内存碎片。

当您的应用程序在短时间内分配大量临时对象时,就会发生内存碎片,这可能会迅速消耗设备的可用内存,触发那些性能消耗的 GC 事件。

您可以使用 Android SDK 的分配跟踪器来检查内存碎片,该跟踪器列出了您在执行不同操作时应用程序分配到内存的所有对象。如果您发现任何可疑的分配,您可以使用分配跟踪器来检查负责分配这些对象的类和线程。

在 Android 设备监控器中,选择DDMS标签,然后打开分配跟踪器标签。从设备标签中选择您的设备或 AVD,接着选择您想要检查的进程。

Allocation Tracker 选项卡中,点击 Start Tracking 按钮,并花一些时间与你的应用交互。要查看自开始跟踪以来已分配的所有对象的列表,请点击 Get Allocations 按钮。Android Studio 将打开一个选项卡,显示在此采样期间发生的所有分配。

点击任何已分配的对象,以查看该对象更多信息:

对象分配 – 理解内存波动

当你准备好停止收集数据时,点击 Stop Tracking 按钮。

Allocation Tracking 选项卡中的每一行代表一个特定的分配,并为该分配提供以下信息:

  • 分配顺序

  • 分配大小

  • 分配的类

  • 线程 ID。这是进行分配的线程

  • 分配位置。这是你的代码中负责此分配的函数

调试你的项目

在发布应用之前,彻底测试你的应用以查找错误至关重要。Android Studio 提供了一系列工具,你可以使用这些工具在模拟器或物理 Android 设备上调试应用,尽管这些工具只能测试你的可调试版本的应用,这意味着你需要以调试模式运行你的应用。

要以调试模式运行你的应用,打开你的项目模块级别的 build.gradle 文件,并添加以下内容:

apply plugin: 'com.android.application' 

......... 

......... 
  } 
   buildTypes {  
          debug {  
                  debuggable true 
            }  
    }  
} 

在编辑你的 Gradle 构建文件后,确保同步你的项目。接下来,点击 Debug 图标或从 Android Studio 工具栏中选择 Run,然后选择 Debug

选择你想要安装和测试可调试版本应用的 AVD 或物理 Android 设备。一旦你的应用加载完毕,Android Studio 的 Debug 视角应该会自动打开。

注意

如果调试视角没有自动打开,请从 Android Studio 工具栏中选择 View,然后选择 Tool windowDebug

调试视角包括:

  • 调试器:显示线程和变量

  • 控制台:显示设备状态

要调试已运行的应用,点击 Attach debugger to Android process。在 Choose Process 窗口中,选择你想要附加调试器的设备和应用,然后点击 OK

一旦你的应用调试版本运行起来,你就可以查看与应用程序相关的日志消息。要查看这些消息,请选择位于 Android Studio 屏幕底部的 Android Monitor 选项卡,然后选择 logcat 选项卡:

调试你的项目

Logcat 有时可能会出现信息过载的情况,因此 Android Studio 提供了多种过滤 logcat 输出的方式。一种方法是使用 Log Level 下拉菜单。默认情况下,此菜单设置为 Verbose,显示所有日志消息,但你还可以选择其他几个更具体的选项:

  • 调试:显示在开发期间有用的日志消息,以及列表中较低的消息级别

  • 信息:显示常规使用中的预期日志消息,以及列表中更低的消息级别

  • 警告:显示尚未成为错误的问题,以及列表中更低的消息级别

  • 错误:显示导致错误的问题,以及列表中更低的消息级别

  • 断言:显示永远不会发生的问题

如果这些过滤器中没有任何一个满足你的调试需求,你可以创建自定义过滤器。打开仅显示所选应用程序下拉菜单(位于日志输出面板的右侧),然后选择编辑过滤器配置

这将打开一个创建新 LogCat 过滤器窗口,你可以通过提供以下信息来创建一个新的过滤器:

  • 过滤器名称:如果你正在创建一个新的过滤器,你应该给这个过滤器一个独特的名称。如果你正在修改现有的过滤器,请从左侧窗格中选择它,其名称将出现在此字段中。

  • 日志标签:每个日志消息都与一个标签相关联,该标签指示消息源自哪个系统组件。如果你只想看到来自特定系统组件的消息,你可以在此处输入该组件的标签。

  • 日志消息:如果你只想看到包含某些元素或字符字符串的消息,请在日志消息字段中指定它们。

  • 包名:如果你想让你的过滤器仅显示与特定包相关的消息,请在此处输入此包名。

  • 进程 ID:如果你只想看到引用特定进程的消息,请在此处输入该进程 ID。

  • 日志级别:要基于日志级别进行筛选,请打开此下拉菜单并选择除默认详细选项之外的内容。

与断点一起工作

难以确定错误来源?你可以使用断点在特定的代码行暂停你的应用程序的执行。通过创建多个断点并在每次停止时仔细检查你的应用程序,你可以逐步隔离出导致错误的代码部分。

要设置断点:

  1. 打开你想要创建断点的文件。

  2. 定位到你想要设置断点的行,并点击该行。

  3. 点击左侧侧边栏中出现的黄色部分。会出现一个红色点,表示你已经成功创建了一个断点。

与断点一起工作

创建断点后,点击重运行应用程序图标(位于左侧工具栏上的绿色播放图标)。

每当 Android Studio 达到断点时,它将暂停你的应用程序的执行,并在你的代码中突出显示触发的断点。然后你可以打开调试窗口(通过在底部工具栏中选择调试选项卡),并使用调试器和控制台收集有关你的应用程序在代码执行此特定点的更多信息。你可能还想查看日志输出。重复此过程,直到你隔离出导致问题的代码。

配置您的断点

如果您心中有一个非常具体的断点类型,您可以对断点设置进行一些修改。首先,点击查看断点图标(如下截图中的光标位置):

配置您的断点

断点窗口出现,并列出当前项目中创建的所有断点。要查看您可以对断点进行哪些更改,请从左侧列表中选择该断点。

配置您的断点

此窗口为您提供了配置所选断点的许多不同选项:

  • 挂起:选择此复选框以为此断点启用挂起策略,然后从所有(当断点被触发时,所有线程都会挂起)或线程(当断点被触发时,只有断点所在的线程会挂起)中选择。

  • 条件:选择此复选框,然后在相应的文本框中指定达到此断点的条件。该条件必须是一个具有true/false值的 Java 布尔表达式。每次达到断点时都会评估此条件,如果结果为 true,则执行指定的操作。

  • 将日志消息记录到控制台:选择此复选框以在达到此断点时在控制台中显示日志消息。

  • 记录评估表达式:选择此复选框以在达到此断点时评估一个表达式,并在 Android Studio 的控制台中显示结果。

  • 一旦触发即删除:当启用时,此断点将触发一次然后被删除。

  • 禁用直到所选断点被触发:此断点依赖于另一个断点,并且只有在指定的断点被触发后才会启用。

  • 实例过滤器:为了将断点命中限制为特定对象的实例,选择此复选框,然后提供您想要使用的实例的 ID 值。

  • 类过滤器:选择此复选框以使此断点对不同的类有不同的行为。然后,在相应的文本框中指定将触发断点的类。要定义不应触发断点的类,将这些类添加到文本框中,但要在前面加上减号符号。

  • 通过计数:通过选择此复选框并指定断点应跳过的次数来定义断点应达到但忽略的次数。在指定的次数通过之后,断点将正常触发。

使用 Lint 检查您的代码

检查您的代码的结构质量是否存在问题非常重要,因为这些可能会引起错误并对您的应用程序性能产生负面影响。方便的是,Android SDK 附带了一个名为 Lint 的静态代码扫描工具,它非常适合识别和纠正您代码中的结构问题。

在 Android Studio 中,配置的 Lint 表达式会在每次构建应用时运行,并将输出打印到 Android Studio 的 事件日志 中,您可以通过选择 事件日志 选项卡来访问它。

然而,您也可以在任何时候通过右键单击特定模块的文件或文件夹,然后选择 分析 | 检查代码,接着选择您想要检查的区域(整个项目模块自定义范围)来运行 Lint。做出选择后,Android Studio 会自动打开一个新的 检查 选项卡,您可以在其中查看 Lint 输出。

为了帮助您聚焦于关键问题,Lint 为它报告的每个问题都提供了描述和严重程度级别。

使用 Lint 检查您的代码

在发布应用之前,请确保您已纠正 Lint 检测到的 所有 错误。

如果您需要修改默认的 Lint 设置,请从工具栏中选择 Android Studio,然后选择 首选项。在出现的窗口中,双击 编辑器 并选择 检查

这将打开 检查配置 页面,其中列出了所有支持的 Lint 配置文件和检查。

使用 Lint 检查您的代码

在这里,您可以探索不同的检查,并进行诸如更改它们的严重程度级别和范围等编辑。

您还可以通过将 lintOptions 属性添加到项目的模块级 build.gradle 文件中,为特定的构建变体或所有构建变体运行 Lint 检查。例如,如果您想将 Lint 的 abortOnError 选项设置为 false,您需要将以下内容添加到您的 build.gradle 文件中:


apply plugin: 'com.android.application' 

android { 

........... 
............. 
.............. 

  lintOptions { 

//Lint shouldn't exit the process when it discovers errors// 

         abortOnError false 
    } 

您可以在 Google 的 GitHub 上找到所有 Lint 配置选项的完整列表(google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.LintOptions.html#com.android.build.gradle.internal.dsl.LintOptions)。

使用 ProGuard 优化您的代码

ProGuard 工具通过删除未使用的代码和将类、字段和方法重命名为语义上模糊的名称来缩小和优化您的代码。最终结果是更小的 APK 文件,更难进行逆向工程,这对于您的应用可以访问敏感信息尤其重要。

ProGuard 在您以发布模式构建应用时会自动运行。要构建应用的发布版本,您需要在项目的模块级 build.gradle 文件中启用 minifyEnabled 属性,并确保 buildTypes 设置为 release。例如:

android {   ...  buildTypes { 

 //The buildTypes element controls whether your app is built in debug or release mode// 
        release { 

//In this example, we're building a release version of our app so we can run ProGuard. If your build.gradle file contains the debug attribute, then make sure you remove it so it doesn't prevent ProGuard from running//  

           minifyEnabled true 

//To enable ProGuard, add the minifyEnabled property and set it to true// 

            proguardFiles getDefaultProguardFile('proguard-android.txt'), 
           'proguard-rules.pro'
        }
    }
  }

getDefaultProguardFile属性获取在下载的 Android SDK 中指定的默认 ProGuard 设置,该设置位于Android/sdk/tools/proguard/proguard-android.txt文件中。或者,你可以使用proguard-android-optimize.txt文件,它包含相同的规则,但启用了优化。

如果你想向默认 ProGuard 设置中添加一些项目特定的选项,请打开你的项目的Gradle Scripts/proguard-rules.pro文件并添加你的新规则。

有关可以添加到此文件的不同 ProGuard 设置的更多信息,请参阅 ProGuard 手册(stuff.mit.edu/afs/sipb/project/android/sdk/android-sdk-linux/tools/proguard/docs/index.html#manual/retrace/examples.html)。

仔细检查每个像素

你可能还想探索另一个工具,即像素完美

像素完美窗口显示连接的 Android 设备或模拟器上当前可见的屏幕的放大版本,并允许你检查构成你的 UI 的各个像素。你还可以用它来在你的 UI 上叠加图像,这对于检查你的 UI 与你的数字线框图相比如何非常有用。

像素完美集成到Android 设备监控器中。要启动像素完美,从Android 设备监控器工具栏中选择窗口,然后选择打开视角像素完美,最后选择确定

仔细检查每个像素

像素完美窗口包含以下区域。

像素完美面板

此窗口显示连接的 Android 设备或 AVD 上当前可见的 UI 的放大版本。

默认情况下,像素完美不会自动更新以反映屏幕上的变化,因此你需要不断点击此面板的刷新截图图标。或者,如果你想使像素完美自动更新,请选择自动刷新截图图标。

像素完美最有用的功能之一是能够将jpgjpegpnggifbmp图像作为叠加层加载。如果你想要比较当前屏幕与你的原始设计,这特别有用,因为你可以加载一个数字线框图(或你手头上的任何其他数字设计文档)作为叠加层。

要将图像作为叠加层加载,导航到你要工作的屏幕,并确保它在像素完美窗口中显示。选择加载图像以叠加截图图标,然后选择你想要用作叠加层的图像。

像素完美树

这是一个当前可见的所有View对象的分层列表。只需注意,系统对象也会出现在此列表中。

像素完美放大镜面板

此面板包含放大的屏幕图像,上面覆盖了一个网格,其中每个方格代表一个像素。要查看特定像素的更多信息,请选择它,放大镜面板将显示以下内容:

  • 像素色块:填充与所选像素相同颜色的矩形

  • HTML 颜色代码:像素对应的十六进制 RGB 代码

  • RGB 颜色值:像素的 R、G 和 B 颜色值

  • X 和 Y 坐标:像素的坐标,以 px 值表示

进程和线程

您的应用如何处理线程和进程对您的应用性能有重大影响。默认情况下,当用户启动一个应用时,Android 系统为该应用创建一个执行线程。所有组件都在这个单线程上运行,这个线程被称为主线程UI 线程

除非您另有指定,否则您在应用中执行的大多数操作都在主线程上以前台运行。大多数情况下,这种单线程模型运行良好,但如果您的应用需要执行特别密集的工作或长时间运行的操作,那么主线程可能会变得阻塞。这可能导致您的应用冻结、显示系统错误,甚至可能崩溃。

如果您想要提供良好的用户体验,那么您绝对不要使用密集或长时间运行的操作来阻塞 UI 线程。如果您确实需要运行要求高的进程,那么您应该创建额外的线程。

这涉及到在项目的Manifest文件中指定某个组件属于哪个进程。每种类型的组件(活动、服务、接收器或提供者)的Manifest条目都包含一个android:process属性,该属性指定了此组件应运行的进程。如果您希望系统为此组件创建一个新的进程,请在android:process值前加冒号,例如android:process=":myprocess"

如果您需要处理更复杂的交互,您可能希望考虑使用Handler来处理从主线程传递的消息,或者您可以使用AsyncTask类来简化需要与您的 UI 交互的工作线程任务的执行。

提示

使用 AsyncTask

AsyncTask为您提供了一个在用户界面中执行同步工作而不阻塞主线程的简单方法。使用AsyncTask,您可以分离任务为主线程上应执行的工作和应在单独的工作线程上执行的操作。这样,AsyncTask是一种在后台线程中执行一些工作然后将结果发布回主线程的方法。

要使用AsyncTask,您需要继承AsyncTask并实现doInBackground()回调方法,该方法在工作线程上自动执行并执行后台操作。doInBackground()返回的值随后发送到onPostExecute(),从这里您可以通过在主线程上调用execute()来运行任务。

理想情况下,你应该只为短操作使用AsyncTasks。如果你需要保持线程运行超过几秒钟,建议使用ExecutorThreadPoolExecutorFutureTask等 API,这些 API 作为java.util.concurrent包的一部分提供。

终止进程

在决定你的应用程序应该如何处理进程时,值得记住的是,尽管 Android 系统会尽可能长时间地维护所有进程,但如果内存开始变低,它将终止进程。

在决定保留哪些进程和终止哪些进程时,系统通过将这些进程放入一个重要性层次结构中来决定每个进程对用户的重要性。重要性最低的进程首先被终止,而重要性层次结构顶部的进程很少被终止。

注意

如果另一个更重要进程依赖于它,Android 系统将提高进程的排名。支持另一个进程的进程永远不能排名低于它所支持的最重要进程。

从最重要的到最不重要的,Android 的重要性层次结构的不同级别在以下章节中详细说明。

前台进程

这是一个对用户当前执行的操作至关重要的进程。如果进程托管以下任一内容,Android 系统将分配此排名:

  • 用户正在与之交互的活动Activity

  • 正在前台运行的服务Service

  • 绑定到用户当前交互活动的服务Service

  • 执行其生命周期回调(onCreateonStartonDestroy)的服务Service

  • 正在执行其onReceive()方法广播接收器BroadcastReceiver

如果这些条件中的任何一个成立,该进程被视为前台进程。Android 系统只有在万不得已的情况下才会终止前台进程。

可见进程

这是一个没有前台组件但可能仍然影响用户在屏幕上看到的进程。如果进程托管一个绑定到可见活动Activity的服务,或者一个不在前台但仍然对用户可见的活动(例如,在onPause()方法已被调用的活动),Android 系统将分配此排名。

如果没有足够的内存来支持所有前台进程,Android 系统将只终止可见进程。

服务进程

这是一个运行服务Service的进程,但不属于上述两个更高类别。

虽然服务进程与用户看到的任何内容没有直接关系,但它们通常执行用户关心的操作。除非这样做是保持所有前台和可见进程运行的唯一方法,否则系统将避免终止服务进程。

后台进程

后台进程持有当前对用户不可见的 Activity。由于后台进程不会直接影响用户体验,如果系统需要为前台、可见或服务进程回收内存,它可以在任何时候终止后台进程。

空进程

这是一个不包含任何活动组件的过程。系统可能会为了缓存目的保留空进程,但请记住,如果系统需要释放内存,它们将是第一个被终止的。

使用>和重用布局

Android 平台提供了一系列简单、可重用的 UI 组件,称为小部件,但有时可能需要跨多个屏幕重用更大或更复杂的 UI 组件,例如包含进度条和取消按钮的面板,或者由用户名和头像组成的用户资料。

如果您的项目包含您想要多次使用的元素,您可以通过将这些元素实现为可重用布局来节省时间和精力。然后,您可以使用 Android 的><merge/>标签将这些可重用元素导入到您想要的任何布局文件中。

为了将常用元素提取到可重用的布局中,创建一个新的 XML 布局资源文件,然后定义您想要重用的 UI 元素。请注意文件的根视图,因为每次您将此组件嵌入其他布局时,它也将被包含在内。

要将您的可重用组件导入到布局资源文件中,只需添加include标签并引用您想要导入的布局文件。例如,如果您创建了一个名为contactslist的可重用布局,并希望将其组件导入到另一个布局中,您将使用以下方法:

   <include layout="@layout/contactslist"/> 

然而,请注意,使用include标签可能会将冗余的ViewGroups引入到您的视图层次结构中。

假设您有一个名为main_layout.xml的布局,它使用一个垂直的LinearLayout作为其根视图。您有一个可重用的contactslist_layout.xml组件,您希望将其包含在main_layout文件中,但contactslist_layout也使用一个垂直的LinearLayout作为其根视图。

如果您在主布局中包含contactslist_layout.xml,您最终会在一个垂直的LinearLayout内部得到一个垂直的LinearLayout。这个重复的垂直布局对 UI 没有任何贡献,但它确实使您的视图层次结构更加复杂,并可能减慢您的应用程序。那么我们如何去除这个重复的LinearLayout呢?答案是使用merge标签。

merge元素有助于消除在包含可重用布局时可能进入您的视图层次结构的冗余ViewGroups

当你将 merge 元素用作可复用布局的根视图时,LayoutInflator 会跳过 merge 标签,并将可复用视图插入到 main_layout 中,就像它们一直是该布局的一部分一样。因此,你的视图层次结构更简单——这对你的应用性能来说是个好消息!

只在需要时加载视图

根据你的应用,你可能会发现自己有一个包含大量复杂视图的用户界面,这些视图你很少使用,例如弹出窗口和进度指示器。

解决这个问题的可能方法之一是通过 ViewStub 添加一些这些复杂的 Views,它是 include 标签的一种变体。ViewStub 是一个轻量级视图,它不会直接包含在你的布局中,因此它在视图层次结构中保持成本低廉。

当你通过 ViewStub 添加一个 View 时,ViewStub 只在需要时加载 View。这允许你创建由许多小视图组成的复杂布局,而且你的 UI 仍然可以快速、平滑地渲染,因为你没有立即用大量的复杂 Views 填充你的用户界面。

要使用 ViewStub,你需要使用 android:id 属性指定要膨胀的布局,例如:

<ViewStub 
   android:id="@+id/popup" 
   android:inflatedId="@+id/popup_import" 
   android:layout="@layout/basic_popup" 
   android:layout_height="wrap_content"  
   android:layout_width="match_parent" /> 

当需要加载 ViewStub 的布局时,你只需将 ViewStub 设置为可见。为此,可以通过调用 setVisibility(View.VISIBLE) 来更改 stub 的可见性:

((ViewStub) findViewById(R.id.popup)).setVisibility(View.VISIBLE); 

或者调用 inflate() 方法:

View importPopup = ((ViewStub) findViewById(R.id.popup)).inflate(); 

膨胀的布局替换了 ViewStub,此时 ViewStub 元素就不再是你的视图层次结构的一部分了。

注意

ViewStub 目前不支持在要膨胀的布局中使用 merge 标签。

摘要

在本章中,我介绍了一些在开发 Android 应用时需要了解的常见性能问题,包括过度绘制、内存泄漏和复杂的视图层次结构。

我们深入探讨了你可以用来检查一些最常见性能问题是否影响你的 Android 项目的众多工具。我们还探讨了如何收集有关任何诊断出的问题的更多信息,这样你就能更好地解决问题。

剩下最后一章了!在最后一章中,我将涵盖所有那些没有很好地融入前几章的最佳实践和指南。鉴于安全问题是当前移动用户和开发者的一大关注点,我还会向你展示如何锁定你的 UI(以及你的应用总体上)以确保你的应用不会让用户暴露在安全漏洞之下。

第十章。最佳实践和确保您的应用安全

Android 操作系统是在预期黑客尝试执行常见攻击的情况下设计的,例如试图欺骗用户交出个人信息或安装恶意软件的社会工程学攻击。

Android 内置的安全功能可以显著降低安全漏洞成功的可能性,并限制任何成功攻击的影响。

这些内置的安全控制默认为您的应用、您的用户和您的用户提供了一定程度的安全保护。尽管如此,遵循安全最佳实践对于进一步减少您的应用使用户容易受到漏洞、数据泄露和其他安全相关问题的攻击的风险至关重要。

在本章中,我们将探讨如何充分利用 Android 内置的安全功能。在本章的末尾,我还会讨论一些在之前的章节中没有详细介绍的最佳实践,包括如何设计更有效的通知,以及确保您的应用对所有用户都易于访问。

保护用户数据安全

对于典型的 Android 用户来说,最常见的安全问题是他们选择的安装的应用程序是否可以访问他们设备上存储的敏感数据。

如果您的应用可以访问用户的数据,那么您有责任确保这些数据保持安全。保护用户数据的一种最快、最简单的方法是仔细考虑您的应用是否真的需要访问这些数据。如果您首先就最小化应用可以访问的数据,那么您就最小化了应用意外泄露个人信息的风险。您还减少了攻击者试图利用您的应用程序以获取其有权访问的敏感数据的诱惑。您应该始终寻找在不要求应用直接访问敏感数据的情况下实现相同效果的方法。

在可能的情况下,不要在用户的设备上存储用户名或密码。相反,您的应用应使用用户提供的用户名和密码进行初始身份验证,然后切换到短期有效的、特定于服务的授权令牌。

为了帮助保护您的用户免受钓鱼攻击,您还应该尽量减少您的应用请求用户凭据的次数。这样,钓鱼攻击更有可能被用户视为可疑,因为您的应用请求敏感信息的行为与其常规行为不符。

注意

如果您的应用确实需要访问密码和用户名,请记住,您可能依法需要提供隐私政策,说明您的应用如何使用和存储这些数据。

连接到网络

网络事务固有的安全风险,尤其是移动用户更有可能连接到未加密的无线网络,如公共 Wi-Fi 热点。

每当您的应用程序连接到网络时,实施以下最佳实践至关重要,以帮助保护您的用户安全:

  • 在可能的情况下使用HTTPS而不是HTTP。此外,永远不要自动信任从不受信任的协议(如HTTP)下载的任何数据。

  • 使用SSLSocket类实现经过身份验证、加密的套接字级别通信。

  • 当您处理敏感的 IPC 时,请使用 Android IPC 机制来验证连接到您的 IPC 的应用程序的标识,例如BinderIntentBroadcastReceiver或带有服务的Messenger。这比使用 localhost 网络端口更安全。

  • 不要使用未经身份验证的短信数据执行敏感命令。短信默认既未加密也未进行强认证,因此容易在网络上被拦截。

  • 当从网络服务器向您的应用程序发送数据消息时,您应该尽可能使用Google Cloud Messaging API和 IP 网络。

安卓 N 还引入了网络安全配置文件的概念,您可以使用它为您的应用程序创建自定义的网络安全设置,而无需修改实际的应用程序代码。您可以使用这个新的网络安全配置文件来:

  • 指定哪些证书颁发机构被信任用于您的应用程序的安全连接

  • 限制应用程序的安全连接到特定的、命名的证书

  • 在您的应用程序中调试安全连接,而不会对已安装的基础设施造成额外风险

  • 保护应用程序免受意外使用明文流量,这尤其是一个安全风险,因为它以人类可读的格式传输可能敏感的数据。

要创建一个网络安全配置文件,创建一个新的 XML 值资源文件,路径如下:

res/xml/network_security_config.xml:

然后在您的项目清单中引用此文件:

<?xml version="1.0" encoding="utf-8"?> 

... 

<app ...> 

   <meta-data android:name="android.security.net.config" 
              android:resource="@xml/network_security_config" /> 

... 

</app> 

简单网络配置文件的结构如下:

<?xml version="1.0" encoding="utf-8"?> 
<network-security-config> 
   <base-config> 

//This is the default configuration used by all connections that are not covered by a specific domain-config// 

       <trust-anchors> 

<certificates src="img/trusted_cas"/> 

        //This is a reference to an XML file where you'd name any trusted Certificate Authorities//  

       </trust-anchors> 

   </base-config> 

   <domain-config> 

       <domain>mydomain.com</domain> 

... 
... 

 //This is where you'd create a configuration to use for connections to a specific destination, in this example that's www.mydomain.com//   

        </domain-config> 
</network-security-config>

请求权限

安卓设备功能强大——从拍照和录制视频,到提供导航、发布到社交媒体和发送短信。这意味着您的典型安卓设备可以访问大量的敏感信息。

好消息是,安卓做了很多工作来帮助保护这些信息的安全。该平台基于一个权限分离的系统,其中应用程序相互独立运行,并且与系统独立,在一个受限的沙箱中。这限制了每个应用程序可以访问的数据和功能。默认情况下,没有任何安卓应用程序有权限执行可能对操作系统、用户或其他应用程序产生不利影响的操作,这有助于防止恶意应用程序损坏数据或访问敏感信息。这些权限还限制了每个应用程序对基本沙箱之外提供的设备功能的访问,保护用户免受应用程序未经授权使用硬件(如访问设备的摄像头)或外部通信通道(如连接到互联网)的影响。

然而,你的应用可能需要访问用户的信息或设备功能,这是有合法理由的,例如需要访问联系人列表的短信应用或需要访问设备摄像头的视频录制应用。如果你的应用确实需要访问受保护的信息或功能,那么你需要提示用户进行访问。用户随后可以选择接受或拒绝这个权限请求。

最初,Android 应用在用户甚至可以从 Google Play 下载应用之前就请求所有可能需要的权限。如果用户不想授予应用一个或多个请求的权限,他们唯一的选项就是完全放弃安装,并寻找替代应用。

Android 6.0 完全重构了这一权限模型,用新的 运行时权限 取代了它。

从 Android 6.0 开始,应用在运行时逐个请求权限,当应用需要访问受保护的服务、数据或设备功能时,就会进行请求。

例如,假设你开发了一个支持传统文本笔记的应用,同时也为用户提供记录语音备忘录的选项。在 Android 6.0 之前,用户必须在安装时授予此应用访问麦克风的权限。然而,在新权限模型下,用户可以启动你的笔记应用并写下他们想要的任何文本备忘录,可能根本不需要应用请求访问麦克风。你的应用只有在用户第一次尝试记录语音备忘录时才会请求麦克风访问权限,因为这项权限是完成当前任务所必需的。

在 Android 6.0 及更高版本中,用户可以通过打开设备的 设置 并选择 应用 来手动更改应用的权限。此时,他们将看到设备上安装的所有应用的列表。他们可以从列表中选择任何应用,然后点击 权限 来查看该特定应用可以访问的所有权限类别。

请求权限

用户可以通过将权限的滑块拖到 关闭 位置来撤销任何权限。

请求权限

这种新的权限模型对用户和开发者都有许多好处:

  • 用户不再需要在安装应用之前阅读权限列表,这使得安装过程更加流畅。

  • 如果你发布的更新需要新的权限,之前用户必须接受这些权限才能安装更新。在 Android 6.0 及更高版本中,应用会自动更新,并且将在需要时(以及如果需要)提出任何与更新相关的新权限请求。

  • 用户对每个应用可以访问的信息和功能有更多的控制权。新的权限模型给用户提供了拒绝单个权限请求的选项,这在之前的 Android 版本中是不可能的。例如,一个下载了图片编辑应用的用户可能会乐意授予该应用访问其图库的权限,但可能会更犹豫是否要授予该应用访问其设备相机的权限。在 Android 6.0 及更高版本之前,如果用户对某个特定的权限请求感到不舒服,他们可以自由地拒绝该应用访问其相机。

  • 由于请求现在发生在用户尝试首次访问相关功能时,用户更有可能理解您的应用请求每个权限的原因。当用户理解了您的应用请求访问敏感信息或设备功能的原因时,他们更有可能批准这些请求。

新的权限模型 – 向后兼容性

在新的权限模型下,用户可以在任何时间撤销之前授予任何应用的任何权限,即使该应用针对 API 级别 22 或更低。这意味着您需要测试,如果用户拒绝了一个或多个权限请求,您的应用是否仍然可以正常工作——无论您的应用的 targetSdkVersion 值是多少。

初始时,如果您的应用运行在 Android 5.1(API 级别 22)或更低版本的设备上,或者您的应用的 targetSdkVersion 是 22 或更低,系统默认使用旧的权限模型,并在安装时提前请求所有权限。如果用户拒绝这些权限请求,则系统不会安装该应用。同样,如果您在应用更新的版本中添加了新的权限,系统将要求用户在安装更新之前授予这些新权限。

然而,用户仍然可以通过选择设备的设置,然后选择应用,并从列表中选择您的应用来手动撤销之前授予的权限。

如果您的应用针对 Android 平台的早期版本,并且您尚未考虑到新的权限模型,如果用户手动撤销了您应用的一个或多个权限,您的应用可能不再正常工作(或根本无法工作)。系统将警告用户,撤销针对早期版本 Android 的应用权限可能会导致相关应用停止工作,但用户仍然可以选择继续撤销这些权限,如果他们真的想这么做。

即使您的应用仅针对 Android 平台 6.0 之前的版本,您也不应仅仅假设可以忽略新的权限模型。您仍然应该旨在创建一个提供良好用户体验的应用,即使用户选择撤销其之前授予的一些(或全部)权限。

权限组

Android 的权限分为两类:

  • 正常权限:

    这些权限使您的应用能够访问其沙盒之外的数据或资源,但几乎不会对用户的隐私、设备的操作或其他应用构成直接风险。

    如果您在Manifest中声明您的应用需要正常权限,系统将自动授予该权限。

  • 危险权限:

    这些权限使您的应用能够访问可能对用户隐私构成风险的数据或资源,或者可能影响用户存储的数据、其他应用或设备的正常操作。例如,能够读取用户的联系人列表被视为危险权限。

    如果用户的设备运行 Android 6.0(API 级别 23)或更高版本,并且您的应用的targetSdkVersion为 23 或更高,那么危险权限仅在用户尝试执行需要此权限的操作时才会触发权限请求。例如,如果您的应用需要READ_CONTACTS权限,系统可能会在用户第一次尝试创建短信消息时请求该权限。

    如果您的应用运行在 Android 5.1 或更低版本的设备上,或者其targetSdkVersion为 22 或更低,Android 系统将在安装时要求用户授予所有危险权限。

除了正常危险评级之外,Android 还将相关的权限分组。当您的应用请求访问一个权限时,用户将看到一个对话框,请求访问整个权限组。

如果您的应用请求一个危险权限,并且用户已经授予了该应用来自同一组的另一个危险权限,系统将自动授予该权限,无需用户提供任何额外输入。

这种方法帮助移动用户更明智地决定他们的设备哪些部分以及每个应用应该有权访问哪些信息,而不会因为技术信息过多或权限请求过多而使他们感到不知所措。

权限分为九组:

  • 日历

  • 摄像头

  • 联系人

  • 位置

  • 麦克风

  • 电话

  • 人体传感器

  • 短信

  • 存储

声明权限

在您开发应用的过程中,每次您的应用需要访问它自己不创建的资源或信息,或者每次它尝试执行可能影响用户隐私、其他应用行为或设备整体的行为时,都要做笔记。大多数情况下,这些操作将需要您的应用请求权限。

要声明一个权限,打开您的项目Manifest,并将<uses-permission>元素作为顶级<manifest>元素的子元素添加:

<manifest     

 package="com.me.app.myapp" > 

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> 
       android:label="@string/permlab_fineLocation" 

//Provide a label for each permission your app requests, using android:label. This label is displayed to the user when they're viewing a list of permissions. Try to keep this label short - a few words will usually be sufficient//   

       android:description="@string/permdesc_fineLocation" 

//Describe what granting this permission request will allow your app to do, using the android:description attribute. Ideally, your descriptions will be two sentences long - the first should describe the permission that's being requested, and the second should warn the user about the dangers of granting this particular permission//   

       android:protectionLevel="dangerous" /> 

//Indicates the potential risk associated with this permission. The possible values are normal, dangerous, signature, and SignatureOrSystem//   
   ... 
</manifest> 

下面是这个权限的标签和描述字符串资源的可能样子:

 <string name="permlab_fineLocation">Precise location (GPS and network-based)</string> 

   <string name="permdesc_fineLocation"> 
This app will be able to retrieve your precise location using the GPS or network location sources. Apps may consume additional battery power while using location services</string> 

验证权限

您的应用并不总是需要请求权限来访问受保护的信息或功能——如果您的应用当前可以访问麦克风,那么它就不需要请求此权限,例如。然而,由于运行 Android 6.0 及更高版本的用户的权限可以在任何时候被撤销,因此您的应用需要在每次需要根据相关权限采取行动时检查它是否当前有权访问受保护的信息或功能。即使用户之前已经授予了此权限,在 Android 6.0 及更高版本中,也无法保证用户在某个时间点没有手动撤销该权限。

要确定您的应用当前是否有权访问信息或功能,您需要调用ContextCompat.checkSelfPermission()方法。例如,以下代码片段展示了如何检查您的应用是否有权访问互联网:

int permissionCheck = ContextCompat.checkSelfPermission(this,                Manifest.permission.INTERNET); 

注意

ContextCompat.checkSelfPermission()作为support-v4 library第 23 次修订的一部分提供,以实现向后兼容(developer.android.com/tools/support-library/features.html?utm_campaign=runtime-permissions-827&utm_source=dac&utm_medium=blog#v4)。

如果您的应用当前有权访问请求的权限,ContextCompat.checkSelfPermission将返回PackageManager.PERMISSION_GRANTED,您的应用可以继续执行操作。如果用户没有授予您的应用此权限,或者他们授予了权限但在某个时间点撤销了它,此方法将返回PackageManager.PERMISSION_DENIED,您需要通过调用requestPermission方法之一来请求该权限:

if (ContextCompat.checkSelfPermission(myActivity, 
               Manifest.permission.INTERNET) 

//Does this app have permission to access the Internet?//  

       != PackageManager.PERMISSION_GRANTED) { 
      if (ActivityCompat.shouldShowRequestPermissionRationale(myActivity, 
           Manifest.permission.INTERNET)) { 

//If shouldShowRequestPermissionRationale returns true, the app doesn't currently have this permission, and you'll need to request access//  
           .......... 
      .......... 

   } else { 

         ActivityCompat.requestPermissions(myActivity, 

//Request the permission. Note, when your app calls requestPermissions(),the system displays a standard dialogue that you cannot customize. If you want to provide some additional information, such as an explanation about why your app needs this permission, you must do so before calling requestPermissions()// 

               new String[]{Manifest.permission.INTERNET}, 
               MY_PERMISSIONS_REQUEST_INTERNET); 

   } 
} 

处理权限请求响应

当您的应用发起权限请求时,系统会向用户显示一个对话框。当用户做出回应时,系统会调用您的应用的onRequestPermissionsResult()方法,并将用户的响应传递给它。为了确定用户是否授予或拒绝权限请求,您的应用需要重写该方法:

@Override 
public void onRequestPermissionsResult(int requestCode, 
       String permissions[], int[] grantResults) { 
//The Activity's onRequestPermissionsResult method is called and passes the user's response//  
   switch (requestCode) { 
       case MY_PERMISSIONS_REQUEST_INTERNET: { 
                    if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { 
........ 
......... 
// If the user has granted the permission, this is where you'd perform your app's Internet-related tasks//  

           } else { 
.......   
....... 
// The user has denied the permission request. This is where you'd disable the functionality that depends on Internet access. Since denying a permission may prevent parts of your app from working correctly, you may also want to provide some feedback explaining what impact denying this permission will have on your app's functionality//   

           } 
           return; 
       } 

   } 
} 

权限和<uses-feature>

您的应用可能依赖于用户设备上存在某些硬件或软件功能,例如,您典型的相机应用几乎肯定需要带有相机硬件的设备。

为了防止用户在硬件或软件不支持的情况下安装您的应用,您需要在项目的Manifest文件中添加<uses-feature>声明:

<uses-feature android:name="android.hardware.camera" /> 

然后,您指定您的应用是否需要此功能才能运行(true)或者是否更喜欢有此功能,但在必要时可以不使用它(false):

<uses-feature android:name="android.hardware.camera" 
             android:required="true"/> 

虽然 Android 系统不会检查您的Manifest中的<uses-feature>元素,但 Google Play 确实使用您的应用<uses-feature>元素来决定您的应用是否与用户的设备兼容。Google Play 不会允许安装任何它认为与用户设备硬件或软件不兼容的应用。

注意

如果您在Manifest中添加了<uses-feature>元素,但没有包含android:required属性,Google Play 将假设您的应用需要此功能(android:required="true")。

理想情况下,您应该声明您项目中的所有硬件和软件要求,但为了以防万一您忘记提及一个或多个<uses-feature>元素,已经设置了一些安全措施。

Google Play 会检查您的应用中是否有任何隐式的硬件相关权限。如果发现,它将相应的硬件或软件功能添加到您的应用元数据中,并在决定每个潜在用户是否可以安装您的应用时考虑这些因素。

这确实有助于防止用户下载不兼容的应用,但也可能有些麻烦。想象一下,您的应用请求android.permission.CAMERA,但您没有在Manifest中包含<uses-feature android:name="android.hardware.camera">。在这种情况下,Google Play 将假设您的应用需要摄像头硬件才能运行,因此不会允许在没有任何摄像头的设备上安装您的应用。如果您的应用实际上不需要摄像头,这种误解可能会阻止具有完全兼容设备的用户安装您的应用。

为了防止这种误解,您需要打开您项目的Manifest并指定摄像头是首选的,但不是必需的:

<uses-feature android:name="android.hardware.camera" android:required="false" /> 

要查看所有暗示功能要求的权限的完整列表,请查看官方 Android 文档(developer.android.com/guide/topics/manifest/uses-feature-element.html#permissions)。

注意

声明<uses-feature>不会自动授予您的应用访问相关功能或信息的权限。您仍然需要像往常一样请求您的应用所需的每个权限。

应用权限的最佳实践

尽管这个新的运行时权限模型提供了许多功能,但它确实给开发者带来了一些新的挑战。首先,由于您的应用可以在任何时间请求权限,因此现在在准确的时间发出权限请求已成为用户体验的重要组成部分。

其次,由于用户现在可以拒绝单个权限,您需要确保无论用户是否选择拒绝您的应用的一个或多个权限请求,您的应用都能继续提供良好的体验。

注意

这并不意味着如果用户持续拒绝其请求,您的应用就必须运行。有可能拒绝某个权限会使您的应用完全无法执行其核心功能。然而,即使如此,您仍然可以通过让用户知道为什么您的应用突然停止工作来提供良好的用户体验。例如,每当用户尝试启动您的应用时,您可能显示一个对话框,列出应用为了运行所需的所有缺失权限,并给用户一个简单的方式来授予这些权限。

在本节中,我们将探讨所有关于权限的最佳实践,以便您能够更明智地决定您的应用请求哪些权限,以及它在何时何地发出这些请求。

尽可能少地请求权限

这是权限请求的金科玉律。

权限旨在保护用户和设备,因此您的应用应该请求它运行所需的权限。研究(repository.cmu.edu/hcii/268)表明,应用请求的权限数量和类型直接影响到用户行为。当面对具有相似功能的两款应用时,用户更有可能选择需要较少权限的那款。

如果您想触及尽可能广泛的受众,您需要限制您的应用所需的权限数量。在新的权限模型下,这一点尤为重要,因为请求是在用户与您的应用交互时提出的。想象一下,您在去一个重要会议的路上迷路了——您打开昨晚下载的地图应用,却发现每次尝试与应用交互时都会遇到权限请求。这可不是您在匆忙时想要的情况!很容易让忙碌的多任务处理型移动用户被过多的信息淹没。

如果您不确定,请记住,通过更新向应用添加权限比删除它们要容易得多——因此,采取少即是多的方法更安全。

小贴士

自动权限调整

Android 平台的新版本可能会带来新的限制,这可能导致您的应用需要请求之前不需要的权限。为了确保您的应用能够继续为运行最新 Android 版本的用户提供服务,系统可能会介入并自动将这些新的权限请求添加到您的项目的 Manifest 中。

当决定是否自动向您的应用添加权限时,Android 会查看您的应用的 targetSdkVersion。如果该值低于添加新权限的版本,系统可能会自动添加这些权限。这可能导致您的应用请求它并不一定需要的权限——这绝对不是好事!

为了避免这种情况,请将测试你的项目与 Android 平台的新版本对照作为优先事项,并在你确信你的应用与最新版本兼容后,立即更新你的targetSdkVersion

提前请求关键权限

如果你的应用的核心功能依赖于访问某些权限,你应该提前请求这些关键权限。这是因为用户更有可能花时间阅读并因此授予在第一次启动你的应用时出现的权限对话框,而不是在应用中执行重要任务中途被中断时授予权限请求。

对于非关键权限,你应该等到用户尝试访问相关功能时,再在上下文中请求该权限。

在必要时提供额外信息

理想情况下,你的权限请求应该是自我解释的,尤其是在运行 Android 6.0 或更高版本的设备上,因为应用在上下文中请求权限。例如,如果用户点击你的应用的为您的个人资料图片拍照按钮,那么当你的应用请求访问他们的相机时,他们不太可能感到困惑。

然而,如果你怀疑为什么你的应用请求某个权限可能并不立即明显,那么你将希望向用户提供更多一些的信息。当用户理解为什么一个应用请求访问信息或功能时,他们通常会感到更加舒适。如果你提供这些信息,那么你的用户可能会怀疑你故意让他们处于黑暗之中。

只要注意不要解释一切。你不需要为应用请求的每个权限都提供解释。如果你用过多的信息让用户感到困扰,他们可能会因为不断的打扰而感到沮丧,最终可能只是快速浏览你的解释(不好)或者完全跳过它们(更糟)。

如果你发现自己每次应用请求权限时都要提供解释,这可能表明你的应用在处理敏感数据或使用设备功能方面存在更深层次的问题。也许你的应用请求了过多的权限?或者它请求了一些与核心功能无关的杂项权限?

记住,权限请求对话框并不是与用户沟通的唯一方式。你可能需要考虑其他方式来向用户提供他们需要的信息,以更好地理解应用权限请求的上下文。例如,你可以:

  • 将此信息添加到你的应用 Google Play 描述中。

  • 制定一份隐私政策,概述你的应用需要访问的信息以及如何使用这些信息。

  • 创建支持文档,以便用户在需要更多信息时可以查阅,例如在线用户手册或应用内的帮助部分。

  • 更新你的用户界面,使权限请求更加直观。如果用户点击一个默认头像,你的应用突然请求访问他们的摄像头,他们可能会感到困惑,但如果默认照片上有“点击这里拍摄个人照片”的标签,那么你的用户就不太可能感到困惑。

  • 为你的用户提供专门的客服渠道,例如论坛、社交媒体页面或一个电子邮箱地址,他们可以通过这些渠道向你提出任何疑问。

注意库所需的权限

当你在项目中包含一个库时,你的项目会继承该库请求的任何权限。在你使用任何库之前,始终检查该库需要的权限以及它使用这些权限的目的。

在不检查权限的情况下将多个库添加到你的项目中,可能会迅速让你陷入与用户的纠纷,因为从用户的角度来看,是你的应用在发起这些请求,而不是外部库。这些库甚至可能请求与你的应用看似完全不相关的权限,这可能会让用户更加怀疑。

如果你需要在项目中包含一个库,但又担心它需要太多的权限,那么你总是可以寻找提供类似功能但权限请求较少的替代库。

透明地访问摄像头和麦克风

有时你的应用可能需要访问特别敏感的功能,例如设备的摄像头或麦克风。大多数时候,你的应用不需要持续访问这些功能,但一旦用户已经授权你的应用访问,他们如何知道它不是在不断地从他们的麦克风和摄像头中提取信息?

没有人喜欢觉得自己被监视,所以你应该明确告知用户你的应用何时会访问设备的摄像头或麦克风。例如,你可以显示一个带有倒计时的弹出窗口,表明你的应用将在 3、2、1 后打开设备的摄像头,或者你可以在应用正在监听用户时,在屏幕角落显示一个闪烁的麦克风图标。

考虑替代方案

即使你的应用确实需要访问受保护的信息或功能,你可能能够设计你的应用,使其不需要自己发起这些请求。

应用只需要请求它直接执行的操作的权限 - 不是当它请求其他应用代表它执行任务或提供信息时。例如,你不需要请求访问设备的摄像头,你可以使用MediaStore.ACTION_IMAGE_CAPTURE来启动用户已经在设备上安装的相机应用。你还可以使用系统意图从其他应用请求信息,例如从用户的联系人应用请求联系人信息,而不是发出READ_CONTACTS权限。

这种方法的缺点主要是每次您的应用需要访问受保护的信息或功能时都会向用户显示一个对话框,因此请考虑这些请求的频率或时间是否可能激怒您的用户。

通知

在 Android Lollipop 中,通知经历了大规模的改进,不仅获得了材料设计的外观,还引入了帮助 Android 更智能地排序通知的新方法。

在 Android 5.0 及更高版本中,您可以使用 setPriority() 方法注释您的通知。低优先级通知可能会从用户那里隐藏,而高优先级通知更有可能打断用户当前正在做的事情。

不同的优先级级别是:

  • Notification.PRIORITY_MAX: 用于关键和紧急通知,提醒用户注意时间敏感的情况,或者在他们继续当前任务之前可能需要解决的问题。

  • Notification.PRIORITY_HIGH: 通常用于重要的通讯,如聊天消息。高优先级通知会出现在包含操作按钮的浮动 heads-up 窗口中。操作按钮允许用户在无需离开当前屏幕的情况下对或忽略浮动的通知。

  • Notification.PRIORITY_LOW: 用于不那么紧急的通知。

  • Notification.PRIORITY_MIN: 用于上下文或背景信息。最低优先级的通知通常不会显示给用户,除非在特殊情况下,例如详细的通知日志。

  • Notification.PRIORITY_DEFAULT: 您可以使用此优先级标志为所有不属于更具体优先级类别的通知。

许多 Android 设备都配备了通知 LED,您可以使用它来通知用户应用内部正在发生的事件,即使屏幕处于关闭状态。将您应用的通知优先级设置为 MAXHIGHDEFAULT 应该会导致 LED 发光。

在 Android 5.0 及更高版本中,通知也会出现在设备的锁屏上,这意味着您需要考虑您的通知是否包含用户可能不希望在全世界的注视下出现在锁屏上的信息。

您可以使用 setVisibility() 来告诉 Android 应在锁屏上显示多少信息,并分配以下值之一:

  • VISIBILITY_SECRET: 通知的任何部分都不会出现在锁屏上。如果您应用的通知包含个人信息或可能令人尴尬的信息,则建议使用此选项。

  • VISIBILITY_PRIVATE: 通知在锁屏上显示一些基本信息,但大部分信息被隐藏。

  • VISIBILITY_PUBLIC: 通知完整地出现在锁屏上。

通知最佳实践

设计良好的通知,它们将为用户提供真正的价值——此外,它们还将作为吸引用户回到你应用的便捷方式,通过向他们展示关于应用程序内发生的事件的有趣和及时更新。

要充分利用 Android 的通知系统,你应该牢记以下最佳实践。

提供正确的内容

至少,你的通知应该包含:

  • 一个标题(setContentTitle)和次要文本(setContentText

  • 一个时间戳;请注意,这表示事件发生的时间,而不是通知发布的时间

  • 通知类型(可选)

为了确保用户可以在系统栏中识别出你的应用挂起的推送通知,你应该包含一个独特的应用图标(setSmallIcon)。

你的通知图标应该简单,避免任何过多的细节,但同时也必须引人注目,并且与其他用户可能遇到的推送通知图标明显不同。你应该使用 Material Light 操作栏图标样式,并避免不透明的背景——基本上,你的通知图标应该是一个白色透明背景的图片。

如果你感到缺乏灵感,试着启动你的 Android 设备,看看其他应用程序使用的推送通知图标类型。

谨慎使用通知

每次你发布一个通知时,你都在打断用户当时正在做的事情,因此你谨慎地使用通知非常重要。

你绝对不应该使用通知来提醒用户关于不需要他们输入或不是时间敏感的背景操作。你也不应该使用通知来提醒用户关于屏幕上已经发生的事件。相反,你应该通过你应用的 UI 在上下文中通知用户这些事件。例如,如果用户在你的游戏应用中解锁了新关卡,并且该应用当前在屏幕上,你应该通过屏幕上的消息提醒他们,而不是发送推送通知。

尽管通知可以用来提醒用户你的应用程序,但你绝对不应该使用不必要的通知来诱使用户打开你的应用。从长远来看,不重要或不想要的推送通知只会让用户认为你的应用是寻求注意,甚至可能导致他们卸载你的应用,并在 Google Play 上给你留下负面评价。

给用户选择权

理想情况下,你应该给用户选择更改你应用的通知设置,例如在声音警报和振动警报之间切换,甚至允许用户完全禁用通知。

对通知进行分类

在对通知进行排名和筛选时,Android 系统可能会考虑应用类别,因此你应该为每个应用的通知分配一个合适的类别。为此,使用setCategory()选项并从以下支持的类别中选择:

  • CATEGORY_ALARM:闹钟或定时器

  • CATEGORY_CALL: incoming voice or video call

  • CATEGORY_EMAIL:异步批量消息

  • CATEGORY_ERROR:后台操作中的错误

  • CATEGORY_EVENT:日历事件

  • CATEGORY_MESSAGE:传入的直接消息,例如短信

  • CATEGORY_PROGRESS:后台运行操作的进度

  • CATEGORY_PROMO:促销或广告

  • CATEGORY_RECOMMENDATION:一个具体、及时的推荐

  • CATEGORY_SERVICE:表示在后台运行的服务

  • CATEGORY_SOCIAL:社交网络或分享更新

  • CATEGORY_STATUS:关于设备或上下文状态的持续信息

  • CATEGORY_SYSTEM:此类别保留用于系统或设备状态更新

  • CATEGORY_TRANSPORT:媒体传输控制以进行播放

利用操作

你也可以向你的通知添加操作按钮。操作按钮允许用户从通知 UI 执行常见任务,而无需打开原始应用。

你可以使用以下语法向通知添加按钮:

NotificationCompat.Builder nBuilder = new NotificationCompat.Builder(context) 
       .setContentTitle("This is a notification title")  

//Sets the notification's title// 

      .setContentText("This is the notification's body text.")  

//Sets the second line of the notification's text// 

            .addAction(R.drawable.accept, "Download", pIntent) 

//Adds a 'Download' action to this notification, complete with icon// 

      .addAction(R.drawable.cancel, "Cancel", pIntent); 

//Adds a 'Cancel' button, complete with icon - this time the R.drawable.cancel icon//  

小贴士

每个操作都应该有自己的图标和名称。

虽然是可选的,但通常给每个应用的通知至少添加一个操作是个好主意。只是不要过分——每个通知限制最多三个操作。

使用展开布局

对于运行 Android 4.1 及更高版本的用户,你可以为每个应用的通知提供两种不同的视觉风格:

  • 正常视图:这是默认的紧凑布局。

  • 大视图风格:当用户通过捏合或拖动打开通知时出现的单独布局。

你可以选择三种不同的大视图风格。

大文本风格

此布局提供了在展开通知的详细区域显示的附加文本,代替通知的常规setContentText

.setStyle(new NotificationCompat.BigTextStyle() 
               .bigText("This text replaces the context text in the big view")) 

大图风格

此布局包括一个大型图像附件:

.setStyle(new Notification.BigPictureStyle() 
        .bigPicture(aBigImage)) 

收件箱风格

此布局包括最多五个项目的列表:

.setStyle(new Notification.InboxStyle() 
        .addLine(string1) 
        .addLine(string2) 
        .addLine(string3) 
        .setSummaryText("+2 more")) 

直接回复通知

Google 在即将发布的 Android N 版本中为通知添加了一些新功能,包括一个允许用户从通知 UI 直接回复的内联回复操作按钮。

直接回复通知对于消息应用来说尤其是个大新闻,因为它们使用户能够在甚至不需要启动消息应用的情况下进行回复。你可能已经在 Google Hangouts 中遇到了直接回复通知。

要创建一个支持直接回复的通知操作,你需要创建一个RemoteInput.Builder实例,并将其添加到你的通知操作中。

以下代码将一个RemoteInput添加到Notification.Action中,并创建了一个快速回复键。当用户触发操作时,通知会提示用户输入他们的回复:

private static final String KEY_QUICK_REPLY = "key_quick_reply"; 
String replyLabel = getResources().getString(R.string.reply_label); 
RemoteInput remoteInput = new RemoteInput.Builder(KEY_QUICK_REPLY) 
       .setLabel(replyLabel) 
       .build(); 

要从通知界面检索用户的输入,你需要调用getResultsFromIntent(Intent)并将通知操作的 intent 作为输入参数传递:

   Bundle remoteInput =
 RemoteInput.getResultsFromIntent(intent); 

//This method returns a Bundle that contains the text response// 

   if (remoteInput != null) { 
           return remoteInput.getCharSequence(KEY_QUICK_REPLY); 

//Query the bundle using the result key, which is provided to the RemoteInput.Builder constructor// 

组合通知

你早上第一件事就是连接到互联网,然后你的 Gmail 应用立刻用4 条新消息通知你,但不会给你关于这些个别电子邮件的任何更多信息,这不是特别有帮助!

当您收到一个由多个项目组成的通知时,您唯一能真正做的事情就是打开应用并查看构成这个分组通知的个别事件。

然而,即将发布的 Android N 版本承诺通过引入捆绑通知来解决这个问题。这种新的通知样式允许您将来自同一应用的多条通知组合成一个单一、捆绑的通知。捆绑通知由一个父通知组成,显示该组的摘要信息,以及个别通知项。

如果用户想了解更多关于这些个别项目的信息,他们可以通过用两根手指向下滑动来展开捆绑通知卡片,将其展开成单独的、更小的通知。当用户展开捆绑通知时,系统会显示每个子通知的更多信息。然后用户可以单独对每个这些迷你通知采取行动,例如他们可能会选择删除关于垃圾邮件的前三条通知,但打开第四封电子邮件。这与您可能在 Android Wear 中遇到的 Notification Stacks 功能类似。

注意

当您的应用有可能生成多个通知,并且每个子通知都是可操作的时候,捆绑通知特别有用。

要分组通知,为每个想要添加到同一通知堆栈的通知调用setGroup(),然后为这些通知分配相同的键:

final static String GROUP_KEY_MESSAGES = "group_key_messages"; 
Notification notif = new NotificationCompat.Builder(mContext) 
        .setContentTitle("New SMS from " + sender1) 
        .setContentText(subject1) 
        .setSmallIcon(R.drawable.new_message) 
        .setGroup(GROUP_KEY_MESSAGES) 
        .build(); 

当您创建属于此堆栈的另一个通知时,只需为其分配相同的组键:

Notification notif2 = new NotificationCompat.Builder(mContext) 
   .setContentTitle("New SMS from " + sender1) 
    .setContentText(subject2) 
     .setGroup(GROUP_KEY_MESSAGES) 
  .build(); 

应用小部件

小部件为用户提供应用最重要的数据的小型、便捷样本,通常从他们舒适的首页开始。

要创建一个基本的应用小部件,您需要完成以下章节中描述的过程。

在您的项目 Manifest 中声明 AppWidgetProvider 类

AppWidgetProvider类定义了允许您根据广播事件以编程方式与您的应用小部件交互的方法。

这里是一个基本的AppWidgetProvider实现的例子:

<receiver android:name="MyAppWidgetProvider" > 

//Specifies the AppWidgetProvider that's used by this widget// 

   <intent-filter> 

       <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> 

//The <action> element specifies that this AppWidgetProvider accepts the ACTION_APPWIDGET_UPDATE. This is the only broadcast event you need to declare explicitly, as the AppWidgetManager class forwards all other app widget broadcasts to the AppWidgetProvider automatically// 

   </intent-filter> 

   <meta-data android:name="android.appwidget.provider" 

//Specifies the AppWidgetProviderInfo resource, which in this example is   
android.appwidget.provider// 

              android:resource="@xml/my_appwidget_info" /> 

//The <meta-data> element also requires an android:resource attribute, which specifies the location of the AppWidgetProviderInfo resource// 

</receiver> 

创建 AppWidgetProviderInfo 文件

下一步是定义您小部件的基本特性,例如其最小宽度和高度,以及更新的频率。如果您的项目还没有包含一个res/xml目录,请创建一个,然后创建一个新的 XML 布局文件。在这个例子中,我们使用my_appwidget_info.xml

<appwidget-provider  

   android:minWidth="50dp" 

   android:minHeight="50dp" 

//Specifies the minimum height and width the widget consumes by default// 

   android:updatePeriodMillis="80000000" 

//This is how often the app widget framework requests an update from the AppWidgetProvider. By default, if the device is asleep when the widget requests an update, the device will wake up in order to perform this operation. For the sake of the user's battery, don't set this value too low//  

   android:initialLayout="@layout/my_appwidget" 

//The layout resource where you'll define the widget's layout// 

   android:resizeMode="horizontal" 

//Specifies how the widget can be resized. The possible values are horizontal, vertical, and none. You can also combine these values, for example android:resizeMode="horizontal|vertical"// 

   android:widgetCategory="home_screen"> 

//Declares whether your widget can be displayed on the home screen (home_screen), the lockscreen (keyguard) or both. Only devices running lower than Android 5.0 support lockscreen widgets//  

</appwidget-provider> 

创建小部件的布局

您以创建常规布局资源文件相同的方式在 XML 中定义您的小部件布局。唯一的重大区别是,应用小部件布局基于RemoteViews,它不支持每个布局和视图。

因此,应用程序小部件仅支持以下布局类:

  • FrameLayout

  • GridLayout

  • LinearLayout

  • RelativeLayout

它们也仅支持以下小部件类:

  • AdapterViewFlipper

  • AnalogClock

  • Button

  • Chronometer

  • GridView

  • ImageButton

  • ImageView

  • ListView

  • ProgressBar

  • StackView

  • TextView

  • ViewFlipper

注意

RemoteViews也支持ViewStubs

应用程序小部件最佳实践

设计良好的应用程序小部件,显示有用的信息,可以作为您应用程序出色的持续提醒,诱使用户启动您的应用程序——至少,这会使他们不太可能卸载您的应用程序。

为了确保您的小部件以最佳方式展示您的应用程序,请确保您遵守以下最佳实践。

包括早期版本的 Android 的边距

您的小部件不应延伸到屏幕边缘或与其他小部件相撞,因为这会使用户的首页看起来杂乱无章。解决方案是在您的小部件的每个边缘添加边距,Android 4.0 及以上版本在用户将小部件放置在首页时自动执行此操作。要利用此行为,您只需将应用程序的targetSdkVersion设置为14或更高版本。

由于 Android 4.0 及以上版本会自动为您的应用程序应用边距,因此当您的应用程序安装在运行 Android 4.0 及以上版本的设备上时,您不应为小部件添加任何额外的边距。然而,当您的应用程序安装在运行 Android 早期版本的设备上时,您仍然需要添加边距。

解决方案是创建两个dimens.xml文件,分别指定不同版本的 Android 的不同边距:

  • res/values-v14/dimens.xml:此文件定义了运行 Android 4.0 及以上版本的设备的 0dp 边距:
        <dimen name="widget_margins">0dp</dimen>
  • res/values/dimens.xml:此文件定义了运行 Android 4.0 以下版本的设备的边距:
        <dimen name="widget_margins">10dp</dimen> 

然后,您可以在小部件的布局资源文件中引用这些dimens.xml值,Android 系统将在运行时选择适当的widget_margins值:

android:padding="@dimen/widget_margins" 

提供灵活的图形和布局

Android 首页分为一个网格。用户可以将应用程序小部件放置在空白单元格中,然后水平或垂直拉伸以占用不同数量的单元格。

为了确保您的小部件布局足够灵活,能够适应不同的网格大小,您应该使用可拉伸的九宫格图像定义您的小部件背景,然后使用灵活的布局,如LinearLayoutRelativeLayoutFrameLayout

不要更新得太频繁

如果当应用程序小部件框架请求从AppWidgetProvider更新时设备处于睡眠状态,设备将唤醒以执行此操作。请求过多的更新是耗尽用户电池的可靠方法,可能会导致他们卸载您的应用程序,或者至少将您的应用程序小部件从他们的首页上移除。

考虑你的小部件真正需要多久接收一次新信息是很重要的,例如,你通常需要比提醒用户关于即将到来的电子邮件的小部件更频繁地更新天气或新闻小部件。

如果你正在构建一个需要频繁更新的小部件,那么基于一个不会唤醒设备的警报来执行这些更新通常是一个好主意。如果这个警报在设备处于睡眠状态时响起,那么更新将不会在设备下一次唤醒时执行,因此对设备电池的影响较小。

要创建此类警报,请使用AlarmManager设置一个带有你的AppWidgetProvider接收的Intent的警报,然后将警报类型设置为ELAPSED_REALTIMERTC,例如:

alarmManager.setRepeating(AlarmManager.RTC... 

最后,将小部件的updatePeriodMillis设置为零,这样它就不会覆盖你的警报并唤醒设备。

可访问性最佳实践

只有当每个人都能导航、理解并成功使用你的应用时,你的应用才是真正可访问的,包括可能存在视觉、身体或年龄相关限制的人。

Android 有几个内置的可访问性功能,可以帮助你优化你的应用以适应有视觉或身体障碍的用户,所以好消息是,在大多数情况下,创建一个可访问的应用不需要对代码进行任何重大更改。

在本节中,我将向你展示如何使用这些内置平台功能确保每个人都能享受使用你的应用。

注意

如果你的应用是团队工作的一部分,那么确保你的团队中的每个人都考虑到可访问性是很重要的。如果你与设计师或测试团队一起工作,那么确保他们也熟悉以下可访问性指南是一个好主意。

向你的 UI 控件添加描述性文本

如果你已经很好地设计了你的 UI,那么你不需要为每个屏幕元素添加显式的标签,例如,一个电话图标在联系人应用中的按钮具有相当明显的用途。然而,有视觉障碍的用户可能无法捕捉到这些视觉线索,因此你需要为他们提供一些额外的信息。

你应该为每个没有可见文本的 UI 组件提供内容描述。同时考虑这些描述是否足以提供足够的上下文,让用户完全理解相关的视觉元素——在没有视觉上下文的情况下,删除呼叫所选联系人的内容描述可能并不特别有帮助。

描述属性中的文本不会显示在屏幕上,但如果用户启用了基于语音的可访问性服务,如 TalkBack,当用户导航到该项目时,此描述会被读出来。

你可以使用android:contentDescription XML 布局属性来添加描述:

<ImageButton 
   android:id="@+id/newSMS" 
   android:src="img/newSMS" 
   android:contentDescription="@string/newSMS"/> 

假设@string/newSMS的值为Create a new SMS message。当用户在有辅助服务启用的状态下悬停在图标上时,此描述将被读出,用户将了解这个 UI 元素的功能。

注意

对于EditText,您的首要任务是帮助用户理解他们需要输入到这个空白字段中的内容,因此您应该提供一个android:hint属性而不是内容描述。一旦用户在EditText中输入了一些文本,辅助服务将朗读此文本,而不是android:hint的值。

在某些情况下,您可能希望将项目的内容描述基于动态元素,例如滑块的状态或列表中当前选中的文本。如果是这种情况,那么您需要使用setContentDescription()方法在运行时编辑内容描述。

对于ImageButtonImageViewCheckbox组件,提供描述尤为重要,但您应该在您怀疑不同能力的用户可能从一些额外信息中受益的地方添加内容描述。只是不要过分,不要添加不必要的描述,因为这只会增加用户在尝试解析您的 UI 时遇到的噪音,使他们在辅助服务中提取有用信息变得更加困难。

在可能的情况下,使用 Android 的标准控件,因为它们默认内置了ContentDescriptions,因此可以自动与 TalkBack 等辅助服务一起工作。

设计焦点导航

焦点导航是指用户使用方向控制器来导航构成应用 UI 的各个元素,类似于电视上的四向遥控器导航。视力受限或手动灵活性有限的用户通常使用这种导航模式而不是触摸屏导航。

方向控制器可以是基于软件的或基于硬件的,例如轨迹球、D-pad 或外部键盘。用户还可以选择启用在运行 Android 4.1 及以上版本的设备中可用的手势导航模式。

为了确保用户能够使用方向控制器成功导航您的应用,您需要验证所有 UI 输入控件是否可以在不使用触摸屏的情况下访问和激活。您还应该验证点击方向控制器的中心或OK按钮与触摸已获得焦点的控件具有相同的效果。

为了支持焦点导航,您应该确保所有应用导航元素都是可聚焦的。您可以通过添加android:focusable="true"属性到 UI 元素中来实现这一点,或者在每个 UI 控件上使用View.setFocusable()方法在运行时进行此修改。

Android 框架提供的 UI 控件默认是可聚焦的,系统通过改变控件的外观来视觉上指示焦点。

当用户使用方向控制向任何方向导航时,焦点会从一个 UI 元素传递到另一个 UI 元素,这是由焦点顺序决定的。系统根据在给定方向上找到最近邻的算法自动确定焦点顺序。

然而,有时结果可能并不完全符合您的预期,或者它们可能无法为用户提供最佳体验。如果是这种情况,Android 提供了四个可选的 XML 属性,您可以使用这些属性来覆盖此自动焦点顺序,并确切指定当用户向该方向导航时哪个视图将接收焦点:

  • android:nextFocusUp:定义当用户向上导航时将接收焦点的下一个视图

  • android:nextFocusDown:定义当用户向下导航时将接收焦点的下一个视图

  • android:nextFocusLeft:定义当用户向左导航时将接收焦点的下一个视图

  • android:nextFocusRight:定义当用户向右导航时将接收焦点的下一个视图

以下示例 XML 展示了两个可聚焦的 UI 元素,其中已显式设置了 android:nextFocusDownandroid:nextFocusUp 属性。Button 元素位于 TextView 的右侧,然而,多亏了 nextFocus 属性的魔力,当焦点在 TextView 上时,用户可以通过按向下箭头键来访问 Button 元素:

   <TextView android:id="@+id/text" 
      android:text="Hello, world" 
        android:focusable="true" 
       android:nextFocusDown="@+id/Button" 
       ... /> 
   <Button android:id="@+id/button" 
       android:focusable="true" 

       android:nextFocusUp="@id/text" 
       ... /> 

测试您导航的最简单方法是在模拟器中运行您的应用,并使用模拟器的箭头键和 OK 按钮仅进行导航。检查导航是否在所有方向上按预期工作,包括在反向导航时。

注意

您还可以在运行时修改 UI 组件的焦点顺序,使用例如 setNextFocusDownId()setNextFocusRightId() 等方法。

自定义视图控件

如果您构建自定义界面控件,请确保您为这些自定义视图实现了可访问性接口,并提供了内容描述。

注意

如果您希望您的自定义控件与所有版本的 Android(回溯到 1.6)兼容,您需要使用 Support Library 来实现最新的可访问性功能。

在创建自定义视图时,您需要确保这些视图在用户选择项目或更改焦点时成功创建 AccessibilityEvents,因为可访问性事件是提供如文本到语音等可访问性功能的重要部分。

要生成 AccessibilityEvents,请调用 sendAccessibilityEvent(int) 并使用表示已发生事件类型的参数。您可以在 AccessibilityEvent 参考文档中找到 Android 当前支持的事件类型的完整列表(developer.android.com/reference/android/view/accessibility/AccessibilityEvent.html)。

提供音频提示的替代方案

为了帮助听力受损的用户,您应该避免在您的应用中包含任何仅音频的反馈。您应该始终使用辅助反馈机制,例如字幕、文本记录、屏幕通知或另一种视觉替代方案来伴随您应用中的音频反馈。

测试各种字体大小

安卓用户可以手动更改其设备上显示的字体大小,从设备的设置 | 可访问性屏幕中进行。为了确保这些大小更改也会影响您应用内的文本,请使用缩放像素(sp)定义您应用中的文本和相关容器。

此外,请记住,当用户启用大字体时,您应用中的文本可能比您最初分配的空间要大,因此您需要检查在用户启用大文本时,您的文本和 UI 看起来良好且功能正常。特别是,请确保您的 UI 元素不会重叠,并且所有可触摸元素在各个文本大小下都保持可触及。

使用推荐的触摸目标大小

确保您应用的所有触摸目标至少为 48 x 48dp,并确保屏幕元素之间的空间始终至少为 8dp。这有助于确保您的 UI 对有手动灵活性挑战的人以及正在发展运动技能的儿童来说更容易导航。

提供对超时辅助功能的替代方案

一些应用具有在一段时间后消失的图标或控件,例如,视频播放控件在用户观看视频几秒钟后通常会淡出。

这对使用如 TalkBack 等可访问性功能的人构成了问题,因为 TalkBack 会在读取控件描述之前等待用户将焦点放在控件上。如果您的 UI 有快速淡出的控件,这些控件实际上可能在用户有机会聚焦之前就消失了——这意味着它们永远不会被读出,因此用户不会意识到它们的存在。

因此,您不应依赖于超时控件来完成高优先级任务或重要功能。如果您的 UI 确实具有超时控件,您可能希望在可访问性服务启用时禁用此功能,这样这些超时控件就不会消失。

为了确保有视觉障碍的用户可以更容易地阅读您的文本,建议您在应用背景和文本之间使用 4.5:1 的对比度比。一般来说,文本越小,您需要使用的对比度就越大。

此外,请记住,您的某些用户可能患有色盲,因此您不应仅使用颜色信号作为传达重要信息的唯一手段。除了颜色之外,您还可以使用图案、形状、大小、纹理或文本等元素。

测试您应用的可访问性功能

测试是创建可访问应用的关键部分,因为它可以揭示您可能没有注意到的用户交互问题。

测试您的应用的可访问性功能通常涉及:

  • 使用启用了声音反馈的应用:

    可听可访问性服务提供音频提示,将屏幕内容读给用户听,当他们移动到您的 UI 上时。测试您为视障用户提供体验的最有效方式是在您的 Android 设备上启用可听可访问性服务,然后仅使用声音与您的应用进行交互。

    对于 Android 用户,反馈通常通过 TalkBack 可访问性服务提供。TalkBack 预安装在许多 Android 设备上(打开您的设备的设置并选择辅助功能,然后选择TalkBack),但您也可以从 Google Play 免费下载 TalkBack。

    一旦您启用了可听可访问性服务,花些时间仅使用语音反馈在您的应用中导航。寻找任何可以改善可能在没有视觉辅助的情况下与您的应用交互的用户体验的机会。

    您还应该检查您的应用是否提供了足够的信息,以便用户可以使用 TalkBack 等服务理解并采取行动,而不会因信息过多而感到负担。这可能会是一个棘手的平衡行为——过多或过少的信息都会使用户难以理解您的 UI。

  • 仅使用方向控制导航您的应用,而不是触摸屏

作为可访问性测试的一部分,您应该验证您的应用仅使用方向控制即可轻松导航,这意味着不使用触摸屏,并确保用户可以在您的应用 UI 元素之间以有意义的方式移动焦点。

如果可用,您可以使用带有 D-pad 或轨迹球的物理设备,但如果您的设备没有这些硬件功能,您可以使用基于软件的方向控制器,或者使用 Android 模拟器和其模拟键盘控制。您甚至可能想使用 TalkBack 手势(support.google.com/accessibility/android/answer/6151827),这允许用户使用非常具体的手势来导航应用(以及他们的设备)。

摘要

在本章的最后,我们介绍了一些在前面章节中没有详细探讨的最佳实践,包括应用安全和可访问性最佳实践。您还学习了如何创建更有用的通知,并开始探索 Android N 中即将推出的新通知选项。

如果你想要了解更多关于开发有效的 Android 应用的信息,你可以在 Android 文档中找到大量额外的信息(developer.android.com/training/index.html),在 Android 博客上(android-developers.blogspot.co.uk/),或者通过查看一些 Google 的代码示例(developer.android.com/samples/index.html)。

posted @ 2025-10-25 10:43  绝不原创的飞龙  阅读(5)  评论(0)    收藏  举报