安卓可穿戴设备高级教程(二)
六、Android 表盘设计简介:考虑因素和概念
现在,您已经掌握了一些关于 Android 5 及其新功能、材质设计、Android Studio IntelliJ IDEA 和 AVD 仿真器的“基础知识”,并且已经设置、更新和配置了一个开源 Pro Android 可穿戴设备应用开发工作站,现在是时候进入正题,了解 Android 5 API 中直接应用于并影响可穿戴设备应用开发的各个部分了。
你已经在第四章中学习了如何创建一个全新的 ProAndroidWearable 应用(我想给你一个好的开始),所以在这一章你将开始学习最流行的 Android Wear SDK 应用。这被称为表盘 API ,由谷歌发布,使开发者能够创建定制的表盘,将适用于所有智能手表型号。
因为人们对如何在 Wear SDK 中创建表盘非常感兴趣,尽管它很复杂,但它为创建其他一些更高级类型的可穿戴应用提供了基础,所以我将在本书中详细介绍表盘。我将在第十七章中讲述更多高级 Android 可穿戴开发主题。我们先来看一下表盘设计考虑因素;之后,在接下来的几章中,你将学习一些高级的图形设计概念。
表盘设计:注意事项和指南
谷歌的 Android Wear SDK 最近在 Android Studio 1.x 中引入了一个 Watch Faces API ,允许开发者创建定制的表盘。这使得开发人员可以使用定制的设计来创建智能手表“皮肤”或“面孔”,这些设计基于 Java 代码、XML 用户界面和新媒体素材(如 SVG(形状)、PNG(图像)和 UI 小部件)的组合。
这些 Watch Faces 应用可以简单地以一种新的独特方式告诉用户现在是什么时间,它们还可以向手表佩戴者显示上下文相关的信息,如通知、天气、健康信息、收到的短信、电话呼叫者姓名以及用户希望实时访问的类似信息,以改善他们的日常生活。Watch Faces API 允许 Android 5 开发者创建一个将所有这些数据集成到一个无缝用户体验中的设计。
手表面向用户界面:艺术与功能的无缝融合
您的 Watch Faces UI 设计将从您的 activity_main 开始。 xml 文件,您在第四章中创建了该文件,然后在 square_activity_main 中从前进到更定制的设计。 xml 和round _ activity _ main . XML文件如您所知,这些文件保存了您不同的 watch faces UI 设计类型,我将很快介绍这些类型。
正如你可能想象的那样,表盘设计将图形、算法和数据完美融合,创造出一种视觉用户体验,以一种美丽的方式向用户告知各种类型的信息,而无需任何额外的观看工作。
作为 Watch Faces API 开发人员,您的目标是创建优雅、清晰、有条理且有吸引力的用户界面布局,能够适应不同的智能手表显示类型、屏幕形状和边框大小。
在本章和本书的其余部分,您将学习如何设计 Watch Faces UI,以及如何为用户提供颜色和外观选项。这将使您的用户能够使用适合他们生活方式的穿戴式智能手表设备来创建他们自己的个性化手表面孔用户体验。
您还需要考虑 Android OS 用户界面元素将如何与 Watch Faces 应用设计进行交互,包括电池电量、蓝牙或 4G LTE 信号指示灯等系统图标。在本章的后面,我将讨论在你的表盘设计的 UI 设计布局中定位它们的各种选项。
手表面临电源使用:互动和环境模式
Android Watch Faces API 要求开发者为他们的 Watch Faces 应用提供两种不同的用电模式。这些被称为交互(彩色)模式和环境(灰度)模式。你的表盘 UI 设计需要考虑这些不同的功耗模式。我将讨论如何优化这两种模式的设计。
通常,如果表盘设计在环境模式下看起来很专业,那么在交互(色彩)模式下会看起来更原始。相反的情况并不总是如此,因为在交互模式下形成对比的某些颜色在灰度模式下可能会显示出相同的灰度,因此,Watch Faces API 图形设计将比您想象的要复杂得多。
观看面孔互动模式:全彩色,30 FPS 动画
你的 Watch Faces 智能手表皮肤 UI 设计中最高级别的模式是交互模式,它允许你的手表 Faces 设计使用全彩和动画。在交互模式下,当用户移动手腕看手表表面时,他们的屏幕会切换到交互模式。
您的手表表面(或皮肤)图形设计可以在交互模式下使用全彩色像素和高帧率动画。这并不是说你应该使用高帧率动画,如果你可以避免这样做的话,因为它使用电源的速度比静态交互模式的表盘设计要快得多。
观看面孔环境模式:每分钟更新的灰度
Watch Faces 智能手表皮肤 UI 设计中的下一个最高级别模式将是环境模式。这种模式将通过使用更少的颜色(在选定的几个型号上)或使用灰度或黑白来代表您的手表表面 UI 设计,帮助智能手表设备节省电力。您的 UI 设计可以让您的用户清楚地看到,他们的智能手表屏幕处于环境模式,只使用灰度颜色作为表盘设计组件。
重要的是不要在环境模式中使用大量的纯白(完全打开#FFFFFF 白色像素颜色值)。这是因为与黑色背景相比,它会耗尽电池寿命,并会分散用户的注意力。
在环境模式下,智能手表显示屏每分钟只会更新一次。出于这个原因,手表表面 UI 设计应该只在环境模式下更新小时和分钟。只有在互动模式下,并且只有在表盘需要时,才应该显示秒。
当智能手表设备切换到环境模式时,Android 操作系统会提醒你的表盘应用,正如你将在本书的这一部分中看到的那样。您可以创建一个专门用于此模式的表盘环境模式设计。因此,如果你真的想设计 Watch Faces 应用,你需要在像素级别设计你的图形,以适应所有这些不同的模式,包括那些超低功耗模式,我将在本章的下一节介绍。
手表表面节能:低位和烧伤保护
今天所有最新的 Android Wear 智能手表设备都利用了各种不同的显示屏硬件技术。其中包括 LCD (液晶显示器) LED (发光二极管)有机发光二极管(有机发光二极管) AMOLED (有源矩阵有机发光二极管),高通 Mirasol 前光技术。
这些显示器像素阵列制造方法中的每一种都有其自身的优点和节能考虑。为你的表盘设计环境模式显示屏的一个重要考虑因素是它如何影响电池寿命甚至屏幕老化,这可能会影响一些屏幕技术,如 AMOLED 和有机发光二极管。
如果您想在 Java 编码和 XML 设计中获得细节,可以配置 watch face 应用,根据每个设备上可用的屏幕类型显示不同的环境设计图形。我将在本书的这一部分讨论所有这些屏幕类型常数和更复杂的手表表面 API 考虑因素,以便您可以为所有屏幕类型的手表表面设计最佳的整体设计。
低位超节能:考虑因素和技术
当处于环境模式时,诸如有机发光二极管或透反射 LED 的一些显示屏技术中的像素将是“开”或白色,或者“关”或黑色。在这些情况下的环境模式,即在这些屏幕类型上,如华硕 ZenWatch,通常被称为“低位”环境模式。
当为低位环境模式设计时,仅使用黑白颜色,避免使用灰度值,因为它们在低位显示器上不起作用。确保在使用低位环境模式的设备上测试您的 UI 设计。
这意味着您需要在绘制样式中禁用抗锯齿,我将在本书的后面讨论这一点,本书涵盖了图形设计概念,如抗锯齿、alpha 通道、混合模式以及实现这些概念所需的工作流程。
老化保护和预防:考虑因素和技术
AMOLEDs 和 OLEDs 等显示屏会产生像素老化,就像旧的阴极射线管(CRT)屏幕长时间显示同一图像一样,在设计时,您应该最大限度地减少白色像素着色,这既是为了提高能效,也是为了最大限度地减少显示屏老化效应。
当这些类型的显示屏在环境模式下运行时,操作系统会将显示屏的内容周期性地移动几个像素,这将有助于避免这种像素老化现象。
对于低比特环境模式设计,最小化屏幕老化的关键是保持 90%的像素为黑色。用低位环境模式中的轮廓形状替换常规环境模式设计中的实心灰度形状,以提供老化保护。
另一个好主意是替换用像素(矩阵)图案填充的灰度图像区域。对于模拟(圆形)表盘设计,挖空一个中心(指针相遇的地方)区域,这样当手表处于低位环境模式时,可以避免手表中心的像素老化。
手表表面 UI 设计形状:方形与圆形
如果你要成为一名专业的 Watch Faces API 开发人员,那么你需要学习如何优化你的 Java 代码、XML 标记、UI 设计和图形设计,以适应方形和圆形设备。这可以通过使用适合两种形状的单一设计或者通过提供两种不同的设计来实现。
如果你提供两种不同的设计,那么你可以使用 Android 操作系统的功能来检测脸型。在本书的这一部分,我将讨论如何在 Java 代码和 XML 标记中做到这一点。
正如你所料,一些表盘设计概念在这种或那种格式下可能会更好。然而,通过一点巧妙的设计,您可以创建一个混合设计,允许用户使用手表表面,而不管他们的智能手表使用的显示屏格式。
让我们回顾一下一些 Watch Faces API 设计指南,它们将帮助您的 Watch Faces 应用跨越方形和圆形设备。
手表表面概念设计:创造一个灵活的设计概念
表盘设计的视觉功能应适用于圆形和方形两种形式。你的表盘的视觉功能应该足够灵活,无需任何调整就能以任何一种形式显示。
然而,一些手表表面设计概念最终将需要不同的执行,即不同的 Java 代码、不同的 XML 标记、不同的图形设计和不同的动画,跨越方形和圆形屏幕。
手表外观风格设计:使用一套通用的设计风格
类似于您将 CSS3 用于 HTML5 应用和网站,或者将样式、主题和材质设计用于 Android 应用,使用一组风格化的颜色、线条粗细、阴影、渐变、图形设计元素、动画和其他设计元素来绘制方形和圆形 Watch Faces 应用版本之间的视觉联系。
通过使用相似的调色板或一致的视觉元素,您的方形和圆形表盘的整体 UI 设计的外观将看起来像是为每个表盘形状定制的,同时仍然保留表盘应用设计是同一视觉系统的一部分的感觉。
表盘设计类型:圆形模拟与方形数字
你的一些表盘设计概念可能会采用传统模拟时钟的格式,因为这是几个世纪以来最受欢迎的计时外观,它采用径向(圆形)方式,带有时针、分针和秒针的中心支点。
使用这种传统的表盘设计方案,开发人员将有机会吸收圆角区域,这些区域在将这种设计转换为方形表盘格式时将不可避免地出现。
这将给你的表盘设计过程带来创造性的火花,因为你试图发现创新和有吸引力的方式来扩展和探索这个额外的表盘屏幕“房地产”这可能包括装饰元素、操作系统指示器、动画元素、功能用户界面元素、日期相关指示器或日历日期信息。
手表界面集成:吸收操作系统功能
Android Watch Faces API 要求您在手表 Faces 的设计中吸收所有必要的操作系统功能,以便您的设计适应基本的 Android Wear UI 元素,如状态、状态和通知。
这些由操作系统渲染(控制)的 UI 元素为你的 Watch Face 用户提供关于他们可穿戴硬件的状态信息(电源、信号等)。)并显示来自运行 Wear 外设应用的用户手机或平板电脑上的服务的各种类型的通知。
出于这个原因,有一个在逻辑位置显示关键操作系统 UI 元素并在表盘设计中明确定义的表盘设计工作流程是很重要的。例如,确保操作系统提供的 UI 元素和消息不会被任何 Watch Faces 图形设计或 UI 设计元素所掩盖。
Android 通知:CardView UI 布局消息
Android“Cards”是 Android 5 中的一个新通知系统,它在可穿戴外设和其主机移动设备之间架起了信息桥梁。这是大多数可穿戴应用通知终端用户各种事情的方式。用户可能会在他们的可穿戴设备上收到关于电子邮件或文本消息的通知。作为一名表盘开发者,你需要在你的设计中支持大卡片和小卡片。您的 watch faces 应用可以指定您对卡片大小的偏好,但用户可以覆盖此设置。用户也可以通过向下滑动来暂时隐藏卡片。
一张 Peek 卡片 是通知流中最上面的卡片,将会出现在智能手表屏幕的底部。一个可变 Peek 卡有一个高度属性,由通知内的文本量决定。一个小 Peek 卡会给表盘设计留下更多空间。圆形模拟指针的表盘通常使用小 Peek 卡。如果可变 Peek 卡的最大高度上方的时间清晰可见,如果您愿意,您可以选择使用可变 Peek 卡。可变 Peek 卡将显示更多通知信息,但是,下半部分带有信息的手表表面最好使用小 Peek 卡。
需要注意的是,当 Peek 卡的边界(尺寸)发生变化时,您的 Android 5 操作系统会通知您的表盘设计(Java 代码)。因此,如果 Peek Card Bounds 对象发生变化,您的 Java 代码将交互地重新排列表盘设计中的用户界面和图形设计元素,从而使交互重新排列成为必要。
Android 硬件状态指示器:硬件模式状态
Android 状态指示器用于告诉用户可穿戴设备的状态,例如它还剩多少电量,它是否正在充电,或者它是否处于飞行模式。在设计表盘时,你需要考虑这些指示器图标如何融入表盘的视觉设计。
智能手机或平板电脑上的 Android 状态指示器出现在状态栏中,这并不奇怪;然而,在表盘设计中,它们可以放置在可穿戴设备显示屏周围的几个固定位置。
Android CardView 类用于在材质设计下创建新的“卡片”范例。如果您需要支持更大的 Peek 卡,状态指示器图标的指示器位置应该靠近表盘设计的顶部或中心。如果你把硬件状态图标放在表盘设计的底部,操作系统将被迫使用小的 Peek 卡。
如果表盘周边包含重要的视觉元素,例如装饰元素、刻度或数字,则将指示器置于表盘设计的中央。
安卓热门词汇布局:OK 谷歌短语
将 Android 操作系统功能集成到 Watch Faces UI 设计中要记住的另一个重要因素是,要为 Android“热门词汇”留出空间。Android 的热门词汇是短语 OK Google ,它在启动时显示,告诉用户他们可以通过使用语音识别技术与手表进行交互,包括预定义的语音命令。
当你的用户打开他们的可穿戴设备时,这个热门词汇会在智能手表显示屏上出现几秒钟。在用户说 OK Google 五次后,热门词汇不再出现,因此,Android 操作系统 UI 元素的放置没有状态图标或卡片那么重要。
你应该避免使用 Android hotword 位置来掩盖手表外观设计的重要 UI 元素。还有针对热门词和状态指示器的“背景保护”UI 元素设置,可以帮助增加对比度(可读性)。应该打开这些 UI 元素选项,除非您的设计是定制的,使 UI 元素以最大对比度出现在它的顶部(例如,使用没有图案的深色)。
Android 外设连接:Wear Companion 应用
Android Wear companion 应用是“主机”智能手机或平板电脑与“外围”Android Wear 智能手表硬件之间的桥梁。此应用将允许您的用户访问应用中的所有表盘设计,并允许他们从包含的设计中进行选择,以及更改他们的设置,如颜色、数字、风格、动画、功能等。
观察面孔清单:你不需要提供一个启动图标
所有可用的 Watch Faces 应用都可以从 Android Wear 配套应用或您捆绑的第三方应用中访问。因此,您的应用启动器图标适用于捆绑的外设(智能手机或平板电脑)应用,而不是 Watch Faces 应用。
因此,任何 Android Wear Watch Faces 应用都不需要在 AndroidManifest.xml 文件中声明独立的应用启动器图标。
观察面孔控制面板:您的设置对话框面板
如果您的设计有需要使用设置对话框设置的有用选项,您的手表面孔设计也可以有一个手表面孔设置面板。设置对话框(或面板)可以使用表盘本身或使用安装在用户智能手机或平板电脑上的配套应用的(更大)显示屏来访问。
您应该在表盘上设计表盘设置,以限制开或关(称为二进制)选择。你也可以使用 ListView objects (类)来实现可滚动的设置列表。
智能手机或平板电脑上 Wear companion 应用的设置可能包括更复杂的配置项目,除了您在 watch face UI 设计上提供的基本设置之外。
在大多数情况下,你可以使用标准的 UI 布局容器类(UI 组件),比如 Android CardView 类,来设计一个设置对话框或者设置面板,这一点你将在本书后面学到。
当你成为一名更高级的 Android Watch Faces API 开发人员时,一旦你建立了设计表盘的可靠工作流程,你可能还想探索其他更复杂、更有创意的设置选项设计。
观察面孔功能:功能数据集成
现在我已经介绍了表面设计规则和表单,接下来我将介绍表面函数。您的表盘设计可以向用户显示“上下文相关”的数据,如外面的天气、月相或代表夜间、日出、正午或日落的颜色以及类似的文本数据表示,这将把原始数据转化为表盘设计图形。
表盘设计通常会通过改变表盘设计的颜色、样式或图形设计来可视化不同类型的外部数据。这是使用 Java 代码和 XML 标记完成的。让我们来看看为表盘设计增加功能的诸多考虑因素。
数据可视化:您希望用户看到的数据
在涉及数据集成的表盘设计中,第一步是图形设计,它将赋予数据本身以生命或视觉效果。
决定你的表盘将如何定义你的观众对你想作为表盘设计的一部分显示的数据的看法。你对数据的视觉概念化应该容易被用户识别或理解。此外,您的手表设计试图可视化的数据需要得到真实用户日常需求的支持,以及将这些数据应用于现实世界的需求。
你需要想一想,当最终用户看到表盘设计后,你想让他们知道什么。他们会理解你的设计试图用你选择的颜色、风格变化或图形设计来传达什么吗?
一旦你设计了你的数据可视化并对其进行了测试,以确保用户能够识别你的设计是如何可视化他们感兴趣的数据的,接下来你需要做的就是确定表盘将如何获得你要可视化的数据。
数据整合:表盘设计与数据的融合
如果您要可视化数据,而不仅仅是一天中的时间,请设计 watch faces 应用,使其包含与时间数据密切相关的其他有用类型的数据,这些数据是 watch faces 的核心。
时间数据中包含的逻辑数据类型当然包括日期,可能还有计时器(秒表)、闹钟、日历约会、时区功能、天气预报、月相,甚至可能还有位置或健身数据。
您还需要找到一种方法,将数据可视化与您的表盘设计无缝集成,当观众快速浏览表盘应用时,创造性地激发他们使用颜色、文本、样式或自定义图形设计来消费您正在可视化的数据。
您的设计在可视化和整合外部数据方面的出色表现将直接关系到表盘应用的受欢迎程度。因此,你需要避免在基于时间的表盘上覆盖额外的数据,而不是使用灵感设计,以巧妙的方式将其无缝集成到整体设计中。
在设计数据集成时,您需要考虑如何通过您用于表盘的设计来表达这种类型的数据。例如,如果您正在设计一个与天气相关的表盘,请使用反映当天当前温度范围的颜色渐变,因此 80 到 100 度的范围可以用橙色到红色来表示,20 到 40 度的范围可以用白色到浅蓝色来表示。
数据同化:使用简单、统一的设计目标
一旦决定了如何可视化数据,并达成了手表外观概念决策,就该使用 Java 代码、XML 标记和数字图像(例如 GIMP)来实现您想要的 UI 设计了。
最受欢迎的表盘设计最终将是简单而优雅的设计。能够通过简单的一瞥传达大量信息的表盘将在服装市场大受欢迎。能够提供表达不同类型数据的统一设计的表盘将被认为是谷歌 Play 商店中最“天才”的表盘。
为了精心制作一个单一的表盘数据可视化“信息”,你必须在设计中对你想要可视化的最重要的数据进行排序。例如,不要试图在表盘上显示一个全面的天气预报,你可以使用一个图形设计来显示你外出时天空的样子(晴天、多云、星星、雨、雪等等)。
如果你显示的是基于文本的信息,在任何给定的时间尽量减少屏幕上的字符数。例如,如果您正在添加日历功能,而不是试图显示整个月的日历事件,您的设计应该只显示一个或最多两个即将到来的事件。
利用“逆向归约”的过程,我将在下面讨论,你将能够在设计中精心制作一个单一的数据表达。
观察面部发展:从基础开始,然后逐步增加
确保你的腕表表盘设计工作流程从仔细考虑你的腕表表盘将为你的最终用户提供什么开始。这将使您深入了解最终用户的需求和期望,从而构建出一个成功的 watch face 应用,这也是本书讨论的主题。
让其他智能手表爱好者来运行一个概念,看看关于设计概念的共识是否是好的和受欢迎的,这总是一个好主意。你还应该在开发时彻底测试你的表盘设计,甚至实施一个“beta 测试计划”,并让其他智能手表用户确认你对表盘设计的任何设计假设。
在餐巾纸上画出表盘设计的草图,然后让一两个智能手表最终用户告诉你他们对设计的看法,以及他们会用它来做什么,这可能是一个很好的主意。
不要假设你会在第一次尝试时就开发出一个史诗般的表盘设计,因为这不太可能发生。您需要结合不同的设计场景,尝试不同类型数据的表盘设计和数据组合。在开始编码之前,你还应该确保用实际的手表屏幕来测试你的表盘设计。
手表表面图形设计:多媒体概念
让我们来看看 Android 5 中不同类型的图形概念和支持,您可能会在表盘设计中使用它们。你将在 Watch Faces 设计中使用的主要素材是矢量插图,在 Android 中称为形状和渐变,以及位图图像,它们使用 JPEG、WebP 和 PNG 等图形文件格式,以及动画,它们支持矢量 动画 和位图动画,并将这两种类型的多媒体带入第四维(随时间移动)。
让我们先了解一下最具数据压缩性的图形技术,矢量插图,它仅使用代码完成,然后介绍 Android 中支持的位图和图形格式以及核心数字图像概念,最后研究如何在 Android 中实现动画,使用 XML 标记结合位图图像资源和矢量代码。在我介绍完所有这些内容之后,您就可以开始研究 Java 编码、XML 标记和创建手表外观所需的图形设计工作流程了!
矢量观察面孔:使用 SVG、形状和渐变
可用于表盘设计的数据优化最多的新媒体素材类型是数字插图,俗称矢量图形 。如果你熟悉 Adobe Illustrator 或者 InkScape,那么你已经知道矢量图形涉及线条、曲线、笔画、填充、渐变。
矢量图形的主要开源文件格式称为SVG??,代表可缩放矢量图形 。所有流行的开源平台都支持 SVG 数据,包括 Android,以及 HTML5 和 JavaFX (Java 8)。
作为一名专业的 Android 开发人员,您知道 Android 中的多媒体素材是使用 Drawable 对象来表示的。矢量形状将使用 ShapeDrawable 类来创建矢量形状的轮廓,并可以使用 GradientDrawable 类的渐变填充形状。您的 Watch Faces 应用可以仅使用 Java 代码,或结合 XML 定义文件使用 Java 代码,在智能手表屏幕上创建这些可缩放的矢量图形元素。
值得注意的是,整个 Watch Faces 应用只能使用 SVG 创建,这意味着它们将是 100%的 Java 代码和 XML 标记。因此,这些应用 APK 文件的文件大小会特别小,因为 APK 文件中没有新的媒体素材(数字图像文件)存储。
某些表盘设计元素特别适合可伸缩的矢量元素。例如,线条可用于时针、分针和秒针,文字可用于数字和罗马数字,圆形元素可用于表圈或计时元素。
通常会有一些矢量图形元素、位图和动画的组合用于手表外观设计,所以接下来让我们看看 Android 的 BitmapDrawable 对象,以及其他支持的数字图像格式,甚至一些重要的数字图像概念,以便您在本书的剩余部分更好地理解我所谈论的内容。
位图观察面:位图格式和图像概念
因为你将使用数字图像来设计你的表盘,并且数字图像也是你在 Android 中的动画绘制对象的基础,我将花一些时间来为那些不是专业数字图像编辑的人提供理解本书概念所需的基础知识。Android 5 支持许多流行的开源数字图像文件格式,其中一些格式,如 GIF,已经存在了几十年。接下来让我们看看这些。
Android 数字图像格式支持:PNG、JPEG、WebP 和 GIF
Android 5 支持的数字图像格式从你几十年前的 Compuserve 图形信息格式( GIF ) 和古老的联合图像专家组( JPEG )格式,到最近的便携式网络图形( PNG ) 和网络照片( WebP )格式。我将按照起源的顺序介绍这些内容,从旧的(不太理想的)GIF 到最新的 WebP 格式。
Android 5 操作系统仍然支持 Compuserve GIF,但是不建议日常使用。GIF 是一种无损数字图像文件格式,因为它不会丢弃任何图像数据来达到更好的压缩效果。GIF 压缩算法被称为编解码器 (编码器-解码器),不像其他格式那样精细(意思是:强大)。它只允许索引颜色,我将在本章稍后介绍。也就是说,如果已经创建了所有图像资源,并且它们使用 GIF 格式,您仍然可以毫无问题地使用它们,除了手表表面的视觉质量下降之外。
Android 支持的下一个最古老的数字图像文件格式是 JPEG,它使用真彩色深度而不是索引颜色深度。JPEG 是一种有损的数字图像文件格式。该术语来源于这样一个事实,即它“丢弃”或丢失原始图像数据,以便能够实现更小的文件大小。JPEG 格式可以将影像压缩到或超过一个数量级(或者十倍,如果你想知道的话)。
需要注意的是,原始图像数据(称为“raw”或未压缩图像数据)在通过 JPEG 编解码器编码进行压缩后是不可恢复的。因此,在通过 JPEG 数字图像压缩算法运行图像之前,您应该确保保存您的原始(未压缩)图像。
如果你在压缩后放大 JPEG 图像,你会看到变色的区域,而这些区域在原始图像中是不存在的。JPEG 图像数据中的这些退化区域在数字图像行业中被称为压缩伪像。使用有损图像压缩时会出现压缩伪像。
这是 JPEG 文件格式不是 Android 中强烈推荐使用的数字图像格式的主要原因。Android 5 应用开发中最推荐使用的图像格式是 PNG 文件格式。PNG 在数字图像行业读作“ping”。PNG 既有索引颜色版本,称为 PNG8 (或 PNG5,如果你只需要使用 32 种颜色),你将在本章的后面发现,也有真彩色版本,称为 PNG24 (无 alpha 通道)或 PNG32 (有 alpha 通道)。我将在本章的后面讨论数字图像 alpha 通道的概念,因为它非常重要。
我使用的 PNG8 和 PNG24 编号扩展表示颜色支持的位深度,因此带有 alpha 通道的 truecolor PNG 在技术上可以称为 PNG32。同样,使用 16 色的 PNG 应该说是 PNG4,使用 64 色的 PNG 应该称为 PNG6,使用 128 色的 PNG 应该称为 PNG7 等等。推荐 Android 5 使用 PNG 格式的原因是它使用无损压缩。这将提供高图像质量以及良好的数字图像数据压缩效率。
最新的图像格式是在 Google 收购 ON2(WebP 图像格式)时添加到 Android 5 中的。Android 2.3.7 支持这种格式,支持图像读取或回放,Android 4.0 或更高版本支持图像写入或数字图像文件保存。
如果你想知道,Android 中的图像写入支持将与你的 Android 相机一起使用,因此你的用户可以通过远程网络服务器将图像保存或写入他们的 SD 卡或云。WebP 是 WebM 视频编码器文件格式的静态图像版本。WebM 在业内也被称为 ON2 VP8 视频编解码器,它被谷歌收购,然后宣布(也称为“发布”)开源可用性。
手表表面数字图像的基础:像素
数字影像由 2D 阵列或网格组成。这些包含通常称为像素?? 的数据元素。这个行业术语是单词图片(有人称这些为“pix”)和元素(如果你缩短单词元素,就得到时髦的单词“els”)。
您的数字图像素材中的像素数量用一个叫做分辨率 的术语来表示。这是图像的宽度(用 X 轴的 W 或 X 表示)和高度(用 Y 轴的 H 或 Y 表示)的像素数。图像素材的分辨率通常使用两个(X 和 Y)数字表示,中间有一个“X”,或者使用单词“by”,例如 800x480 或作为 800 乘 480 像素。
要找到 2D 图像中的像素总数,只需将宽度像素乘以高度像素。例如,HDTV 分辨率为 1920 x 1080 的图像包含 2,073,600 个像素,即超过 200 万个像素。这也被称为两百万像素。图像中的像素越多,可以说其分辨率越高,从而提供更高的视觉质量。
就像数码相机一样,从 300 万像素的智能手机相机到 7500 万像素的数码单反相机,数码图像网格或阵列中的像素越多,使用图像可以达到的质量水平就越高。这就是 4K UHDTV 屏幕越来越受欢迎的原因,这种屏幕的分辨率为 4096×2160。Android 通过 4K UHDTV 支持智能手表分辨率。
面向数字图像的手表形状:图像纵横比
更复杂的方面(无意双关!)的数字图像分辨率将是图像纵横比 ,这一概念也适用于 Android 5 设备硬件显示器。纵横比是宽度与高度的比率**,或者 W:H ,或者如果你喜欢用 X 轴和 Y 轴来思考,它将是 X:Y 。纵横比将定义图像或显示屏的形状,即图像或显示屏可能是方形或矩形(通常称为宽屏)。手表表面有一个正方形的长宽比。**
1:1 纵横比的显示器(或数字图像)是完美的正方形,2:2 或 3:3 纵横比的图像也是如此。重要的是要注意,是这两个数字之间的比率**定义了图像或屏幕的形状,而不是数字本身。这就是为什么这被称为纵横比,尽管它通常被简称为图像“纵横比”。
图像宽高比通常表示为宽高比冒号两边可以达到的最小的一组或一对数字。如果你在高中时注意过,当你学习最低(或最少)公分母时,长宽比数学将非常容易。
我通过继续将每边除以 2 来执行数学入学考试。以一个相当常见的 1280 乘 1024 SXGA 分辨率为例,1280:1024 的一半就是 640:512;一半的话,就是 320:256;一半是 160:128;其中一半也是 80:64。一半是 40:32;一半是 20:16;一半是 10:8,一半是 5:4,所以 SXGA 屏幕使用的是 5:4 的纵横比。
有趣的是,所有上述纵横比都是相同的纵横比,因此都是有效的!因此,如果你想采取真正简单的方法,用冒号替换图像分辨率中的“x ”,你就有了图像的长宽比,尽管像我在这里所做的那样,将其提取到最低的格式,这要有用得多,并且是行业标准的做法。
最初的个人电脑屏幕使用更方形的 4:3 宽高比,早期的 2:3 宽高比 CRT 电视机也几乎是方形的。冒号两边的这些数字大小越接近,图像或屏幕纵横比就越方。永远记住,相同的数字代表一个正方形的长宽比,除非其中一个数字是 1。2:1 宽屏幕显示,3:1 宽屏幕显示将是彻头彻尾的全景,如果它真的出现的话!
当前的显示器市场趋势当然是向宽屏和超高分辨率显示器发展。Android 5 手表表面可能会改变这种趋势,回到方形长宽比。方形屏幕正被用于各种新的消费设备,其中之一是 Android 5 智能手表。
给你的数码图像上色:RGB 色彩理论
现在,您已经了解了数字图像像素,它们在 2D 矩形阵列中的排列方式,以及定义矩形形状的长宽比,下一个逻辑方面(同样没有双关语)是如何为每个像素分配颜色值。像素颜色由三种颜色定义:红色、绿色和蓝色(或 RGB)。这些在每个像素中以不同的量存在。Android 显示屏利用加色颜色,这是每个 RGB 颜色平面 的光波长可以加在一起的地方。加色用于创建数千万种不同的颜色值。这用于流行的 LED、LCD 和有机发光二极管显示器,后者用于智能手表、智能手机、iTV 电视机或平板电脑。加色与减色相反,减色用于打印机。
你可以用来混合的 RGB“色调”或光强的数量,决定了你能够复制的颜色总量。在今天的数字设备中,我们可以为每种 RGB 颜色产生 256 级光强度。为每个图像像素生成颜色,因此对于每个 RGB 数据值,图像中的每个像素将具有 256 级颜色强度。这些 RGB“板”或“平面”中的每一个对于每种 RGB 颜色将使用一个字节的数据。
手表表面数字图像的颜色量:颜色深度
用于表示数字图像素材中颜色数据的位数被称为该图像的色深 。重要的是要注意,在数字图像中,少于八位可以用来表示图像中的颜色量。这只适用于当你使用“索引”颜色模型时,我将在这一节讨论。
数字图像行业中有几种常见的颜色深度,我将在这里概述最常见的颜色深度,以及在 Android 操作系统中使用它们的数字图像文件格式。最低色深存在于八位索引彩色数字图像格式中。索引彩色图像将使用每像素 256 个总颜色值,并将使用 GIF 或 PNG8 图像格式来包含这些索引彩色数字图像数据。
索引彩色图像没有(RGB)色彩平面,因此它通常比真彩色 RGB 图像小三倍。相反,它使用多达 256 个最大颜色值的“调色板”来表示数字图像中的所有颜色。这个调色板是使用压缩算法(编解码器)来“挑选”的,该算法会在数字图像中找到最常用的颜色。
一个 24 位颜色或真彩色深度图像具有每个 RGB 色板(也称为颜色平面)的完整八位颜色数据值。这些真彩色图像每像素能够显示 1600 万种潜在颜色。这被计算为 256 × 256 × 256,等于 16,777,216 种颜色。
使用 24 位色深将为您提供最高的数字图像质量水平,这就是为什么 Android 更喜欢使用 PNG24 或 JPEG 图像文件格式。因为 PNG24 是无损的,这意味着它在压缩过程中不会损失任何质量,所以它提供了最高质量的压缩和最低的原始数据损失,以及最高质量的颜色深度。
因此,就 Android 而言,PNG24 是首选的数字图像格式。这是因为使用 PNG 可以在所有 Android 5 应用中产生最高质量的视觉效果。
值得注意的是,目前存在更高色深(每个 RGB 通道 16 位数据)的图像,这是 i3D 游戏行业流行的。这种颜色深度被称为 HDRI(高动态范围图像)。
在表盘上显示颜色:使用十六进制记数法
现在你已经知道了什么是色深,颜色是由任何给定图像中三种不同颜色通道的组合来表示的,让我们看看,作为程序员,我们将如何在 Android 应用中表示这三种 RGB 颜色值,以便我们能够创建可见色谱中的任何颜色。
需要注意的是,在 Android 5 操作系统中,颜色不仅用于称为 BitmapDrawable 对象的数字图像素材,还用于可扩展的矢量图形,如颜色填充和渐变,这一点您在前面已经了解过。颜色数据值也用于设置 UI 颜色,例如用户界面屏幕中使用的背景颜色值,或者用于用颜色填充字体轮廓的 textColor 值。
在 Android 5 中,不同级别的 RGB 颜色强度使用十六进制符号表示为数据值。十六进制记数法是基于几十年前用来表示 16 位数据值的最初的 Base16 计算机记数法。十进制记数法将从 0 计数到 9,而十六进制记数法将从 0 计数到 F,其中 F 表示十进制值 15。从 0 到 15 的计数给出了 16 个数据值。
为了告诉 Android 你给它的是十六进制值,你可以在 Base16 值的前面加上磅符号 ,也称为散列标签,就像这样: #FFFFFF 。这个十六进制记数法数据值代表一种颜色白色,因为如果你将红色、绿色和蓝色光波长混合在一起,你得到的光颜色将是白色,有时被认为根本没有颜色!
因为这种 24 位十六进制表示中的每个槽代表一个 16 进制值,所以要获得每种 RGB 颜色所需的 256 个值将占用其中的两个槽,因为 16 × 16 等于 256。因此,对于一个 24 位的映像,在您的 hash 标签后需要 6 个槽,对于一个 32 位的映像,在您的 hash 标签后需要 8 个槽。在本章的下一节,我将介绍什么是 32 位图像,以及它们的用途。
十六进制数据槽以下列格式表示 RGB 值: #RRGGBB 。因此,对于白色,该十六进制颜色数据值表示中的所有红色、绿色和蓝色通道处于全开的最大亮度或 FF,这将是 16 × 16,并且对于每个 RGB 颜色通道是完整的 256 个数据值。正如你所看到的,我使用了不同的行业术语(颜色通道、颜色平面、颜色板),你会发现这些术语在图形设计行业中被广泛使用。如果你愿意,所有这些数字图像术语可以互换使用。
如果你把所有的颜色加起来,你会得到白光。以不同的量,它们会产生颜色!颜色黄色由打开的红色和绿色通道以及关闭的蓝色通道表示,因此黄色的十六进制符号表示将是 #ffFF00 ,其中红色和绿色通道槽将完全打开,使用 FF 表示颜色强度(级别)值 256,蓝色通道槽完全关闭,使用 00 表示零值。
正如我在本节前面提到的,还有一个 32 位图像色深,其数据值使用一个 ARGB 颜色通道模型来表示。在这个模型中,A 代表 alpha ,是 alpha 通道 的简称。在本章的下一节,我将更详细地介绍图像 alpha 和 alpha 通道的概念,我还将介绍更高级(相关)的概念像素混合。
您的 ARGB 颜色通道模型数据值的十六进制符号数据槽将保存以下格式的数据:# aarggbb。因此,为了表示完全不透明的白色,十六进制颜色数据值表示中的所有 alpha、红色、绿色和蓝色通道都应该处于最大亮度(和最大不透明度)。使用 FF 值将 alpha 通道设置为完全不透明,因此完整的十六进制值将是 #FFFFFFFF 。
另一方面, 100%透明的阿尔法通道由设置为零的阿尔法槽表示。因此,一个完全透明的图像像素可以配置为#00FFFFFF,或#0000000,甚至#00F7D9C4,如果你喜欢。
这里需要注意的是,如果一个图像 alpha 通道被设置为透明的,那么由最后六个十六进制数据槽值表示的每个像素颜色数据值甚至都无关紧要!这是因为对于特定像素的 ARGB (alpha 通道加颜色)数据值,100%透明度数据值将覆盖使用“像素需要与完全透明的设置合成”的任何颜色值。
在表盘中表现透明度:使用 Alpha 通道
这一节将介绍数字图像如何在被称为图像合成 的过程中合成在一起。这是由被称为数字图像排版师的专业图形艺术家完成的。
数字图像合成是将不止一层数字图像(一张照片)混合在一起的过程。这样做是为了获得更复杂的图像。显示屏上的合成图像看起来就像是一个单独的图像。事实上,图像合成实际上是一个堆栈中的集合,由多个无缝合成的数字图像层组成。为了能够实现无缝的图像合成,每个层中使用的图像需要使用与图像中的每个像素相关联的 alpha 通道(透明度级别)数据值。
您可以使用图像中每个像素的 alpha 值来精确控制该像素与具有相同图像坐标或位置的其他像素的混合,但是在其他层上,在该特定图像层的上方或下方。正是因为这种层堆叠范式,我将这种合成称为 3D,因为这些层沿着 z 轴堆叠,可以说具有特定的 Z 顺序。不要将此与 3D 建模软件(如 Blender3D)混淆,因为数字图像合成(层)堆叠的最终结果仍然是 2D 数字图像素材。
像所有通道一样,alpha 通道也支持 256 级透明度。它们使用 ARGB 数据值的十六进制表示形式中的前两个数据槽来表示,该十六进制表示形式有八个数据槽(32 位),而不是用于表示 24 位图像的六个数据槽。24 位图像可以视为 32 位图像,带有不透明的 alpha 通道数据。除非需要透明度值,否则不要使用 32 位图像格式!
为了与图像合成相关,24 位图像不使用 alpha 通道,也不会用于图像合成,除非它是合成层堆栈中的底板(或背板)。另一方面,32 位图像将被用作其他需要在某些像素位置显示(通过透明度值)的内容之上的合成层。这些位于 24 位背板顶部的 32 位合成图像层使用像素透明度来创建最终的合成数字图像。所以你可能想知道如何有一个阿尔法通道,并使用数字图像合成因素到手表表面图形设计。
一个主要的优势是能够将看起来像单个图像的东西分割成多个组件层。这样做的原因是为了能够将 Java 代码逻辑应用到各个层元素,以便控制您无法单独控制的表盘设计的组件部分。
手表面孔中的算法图像合成:混合模式
图像合成还有一个更强大的方面,叫做混合模式 。如果您熟悉 Photoshop 或 GIMP,您会知道数字图像合成中的每个图层都将被设置为使用不同的混合模式。混合模式是算法,指定一个图层的像素如何与之前的图层(在该图层下面)混合(数学上)。
这些像素混合算法考虑了你的透明度,它们可以用来实现几乎任何你想要实现的合成效果。混合模式可以在 Android 5 中使用 PorterDuff 类实现。这个 PorterDuff 类为表盘设计者提供了与 Photoshop(或 GIMP)提供给数字图像工匠相同的混合模式。
与 Android 的主要区别在于混合模式可以使用定制的 Java 7 编程逻辑进行交互控制。对于我们这些 Watch Faces 开发者来说,这是令人兴奋的部分。一些强大的 Android PorterDuff 类混合模式包括异或、屏幕、叠加、变暗、变亮、相乘,或者相加。Apress 的Pro Android Graphics(2013)标题涵盖了如何在一个完整的图像合成管道中实现 PorterDuff 混合模式,如果你有兴趣深入 Android 5 的这一领域的更多细节。
屏蔽手表表面数字图像:利用阿尔法通道
alpha 通道最常见的用途之一是“遮盖”数字图像的某个区域。这创建了可以在图像合成层堆栈中使用的层。这对于观察表面设计显然很重要,因为指针、数字或装饰性组件等组件会用到它。
蒙版 是一个提取主题的过程,本质上是从你的源图像中剪切出主题,这样就可以把它放置(粘贴)到自己的透明层上。在本书的过程中,我将解释使用 GIMP 执行这个屏蔽过程的工作过程。
遮罩过程会在自己的图层上生成图像的一部分。被遮罩的主体将与源图像的其余部分隔离,但由于图层的透明度,它看起来好像仍在最终的图像合成中。一旦被遮罩的图像元素有了自己的 alpha 通道,您就可以对该元素进行旋转、着色、缩放或移动等操作,而不会影响图像合成的其余部分。
表盘设计的含义是显而易见的,这就是为什么我在这里介绍这一基础材质,也是为什么你将在本书后面使用蒙版工作流程,这样你就可以获得一些蒙版经验。
遮罩工作流程允许您将图像元素(主题材质)放入其他图像中使用,如手表脸或用于特殊效果应用。数字图像软件(Photoshop 和 GIMP)有许多工具和功能,专门用于蒙版,以及后来的图像合成。如果不创建蒙版,你就无法真正有效地进行图像合成,因此这是图形设计师和 Pro Android 可穿戴设备(和 Watch Faces)开发人员需要掌握的重要领域。
蒙版过程中最重要的考虑是在蒙版对象周围获得一个平滑但清晰的边缘,这样当你将它“放入”一个新的背景图像时,它看起来好像一开始就属于那里。
屏蔽的关键是正确的选择工作流程。使用数字图像软件选择工具(GIMP 2.8 中有半打这样的工具)以适当的方式和最佳的工作流程是“拉”出“干净”图像蒙版的关键(这是一个额外的很酷的行业术语,让你四处折腾,让你看起来既艺术又懂技术)。
如果要遮罩的对象周围有颜色一致的区域,这会使遮罩过程更容易。您可以在蓝屏或绿屏上拍摄对象,然后您可以使用“魔棒工具”和阈值设置来选择除对象之外的所有内容,然后反转选择集,以获得包含对象的选择集。
其他 GIMP 选择工具包含复杂的算法,可以查看图像中像素之间的颜色变化。这些在边缘检测中非常有用,可以用于其他类型的选择工作过程。
GIMP 剪刀边缘检测选择工具将允许您沿着您想要遮罩的对象的边缘拖动光标,而边缘检测选择工具的算法自动(基于其算法)放置选择边缘的精确、像素完美的位置。
平滑手表表面边缘:反走样的概念
抗锯齿是一种通常使用算法实现的成像技术。它的作用是找到图像中两种相邻颜色相遇的地方,并混合锯齿状边缘周围的像素。消除锯齿将沿着两个彩色区域之间的边缘添加混合颜色,以在视觉上平滑沿着(以前的)锯齿状边缘的混合颜色。这使得当图像缩小时,当像素不是单独可见时,锯齿边缘看起来更平滑。抗锯齿的作用是让你的眼睛看到更平滑的边缘,以消除通常所说的“锯齿”消除锯齿提供了令人印象深刻的效果,对需要看起来更平滑的边缘上的像素使用非常少(七或八个)的中间平均颜色值。
我说的中间色或平均色指的是一些颜色或色谱,它介于沿一条边相交的两种颜色之间。我创建了一个抗锯齿的可视化示例来展示效果。正如你在图 6-1 中看到的,我在黄色背景上创建了一个看似平滑的红色圆圈。我放大到那个圈的边缘,抓拍了一张截图。我把它放在缩小的圆圈旁边,以显示在圆圈边缘相互邻接的红色和黄色值之间的抗锯齿(橙色)值。请注意,有七个或八个平均颜色值。
图 6-1 。黄色背景上的红色圆圈(左)和显示抗锯齿的放大视图(右)
获得很好的抗锯齿效果的最好方法是使用正确的图像遮罩工作过程,对您可能使用的任何给定的选择工具使用正确的设置。实现你自己的抗锯齿效果的另一个技巧是在包含有锯齿状边缘的对象的透明层上使用具有非常低的模糊值(0.15 到 0.35)的高斯模糊工具。这将提供你在图 6-1 中看到的同样的反走样,不仅如此,它也将“模糊”你的阿尔法通道(蒙版)本身的透明度值。这将允许您使用任何背景图像消除 32 位图像对象的锯齿,您可能会尝试对其进行无缝合成。我将在本书的第十二章的中向你展示这些使用 GIMP 2.8 的很酷的数字图像合成技术,所以准备好学习如何成为一名数字图像合成 Android 可穿戴表盘设计师和开发者吧!接下来我们来看图像优化!
优化你的手表表面:数字图像压缩因子
有几个技术因素影响数字图像压缩,这是一个使用编解码器的过程,算法会查看您的图像数据,并找到一种方法将其保存为使用较少数据的文件。编解码器的编码器本质上是在图像中找到“数据模式”,并将它们转换成编解码器的解码器部分可以用来重建原始图像的数据形式。
有一些方法可以用来获得更高质量的图像压缩结果,这应该导致更小的文件大小以及更高的图像质量。具有小文件大小和高质量水平的图像可以说实现了高度优化的数据足迹。
这是优化数字影像的一个主要目标,即尽可能减少数据占用空间,同时获得高质量的视觉最终效果。让我们首先讨论对数据占用空间影响最大的图像属性,并研究这些方面如何对给定数字图像的数据占用空间优化有所贡献。有趣的是,这与我在本章后半部分所讲述的数字图像概念的顺序相似!
对您生成的图像文件大小(您的数据足迹)最重要的影响因素是像素数或数字图像的分辨率。这是合乎逻辑的,因为需要存储每个像素,以及这些像素的 RGB 颜色通道的颜色值。因此,在保持图像细节的同时,图像分辨率越小,图像文件就越小,因为数据越少。
原始(未压缩)图像大小可以使用以下公式计算:宽度×高度×颜色通道。因此,对于 24 位 RBG 图像,有三个(RGB)颜色通道,对于 32 位图像,有四个(ARGB)颜色通道。因此,一个未压缩的真彩色(24 位)VGA 图像将有 640 × 480 × 3,等于 921,600 字节的原始未压缩数据。如果将 921,600 除以 1,024(千字节中的字节数),将得到原始 VGA 图像中的千字节数(900 KB 的偶数)。
正如您所看到的,颜色深度因此是图像中数据足迹的第二个最重要的因素,因为图像中的像素数乘以一个(8 位)或两个(16 位)或三个(24 位)或四个(32 位)颜色数据通道。这可能是索引彩色影像仍然被广泛使用的主要原因,通常是通过 PNG8 图像格式。像 PNG8 这样的无损压缩算法不会丢失任何图像数据(质量),并且 PNG8 通常比 PNG32 少使用四分之一的数据,比 PNG24 少使用三分之一的数据,因此仅使用 PNG8 就可以减少 200%到 300%的数据占用空间。
可以增加图像数据占用空间的最后一个概念是 alpha 通道,因为添加 alpha 会为正在压缩的图像添加另一个八位颜色通道(透明度)。如果您需要 alpha 通道来定义透明度,为了支持您的图像的未来合成需求,没有其他选择,只能包括这些 alpha 通道数据。只要确保您没有使用 32 位图像格式来包含具有空(未使用的)alpha 通道的 24 位图像。
有趣的是,大多数用于遮罩图像中对象的 alpha 通道都可以很好地压缩。这是因为 alpha 通道包含具有很少灰度值的白色(不透明)或黑色(透明)填充区域。仅有的灰度值存在于黑色和白色之间边缘的像素中。这些反走样的面具。alpha 通道中的这些灰度值是抗锯齿值,如您所知,它们用于为图像合成提供视觉上平滑的边缘过渡。
这是因为在 alpha 通道图像遮罩中,八位透明度渐变是用定义 alpha 通道透明度级别的白色到黑色光谱(渐变)定义的。蒙版中每个对象边缘的灰度值实际上是将对象的颜色与目标背景图像中的颜色进行平均(混合)。这实质上提供了使用任何背景图像的实时反走样。
在表盘中使用索引彩色图像:抖动像素
如果用于创建图像的颜色变化不大,索引色图像可以模拟真彩色图像。索引彩色图像使用八位数据来定义图像颜色,使用 256 种最佳选择的颜色调色板,而不是三个 RGB 颜色通道。根据图像中使用的颜色数量,仅使用 256 种颜色来表示图像可能会导致一种称为条带的效果,即相邻颜色之间的转换不平滑。索引彩色图像编解码器有一个选项来纠正这种情况,称为“抖动”抖动 是沿着图像中两个相邻颜色区域之间的边缘创建点图案的过程。这会让你的眼睛误以为使用了第三种颜色。只有当 256 种颜色中的每一种都与其他 256 种颜色中的每一种相邻时(否则会更少),抖动才会为我们提供 65,536 种颜色(256 乘以 256)的感知数量。
您可以看到创建额外颜色的潜力,并且您会惊讶于索引颜色图像在某些情况下可以实现的结果,即对于某些图像。我拍摄了一张真彩色图像,比如图 6-2 中的所示,并保存为索引色图像,向你展示抖动效果。看看这张奥迪 3D 图像中驾驶员侧后挡泥板上的抖动效果,因为它包含一个颜色渐变,当我将其保存为索引颜色时,它将显示抖动效果。
图 6-2 。真彩色源图像使用 16,777,216 种针对 8 位 PNG8 优化的颜色
我设置编解码器对 PNG8 图像进行编码,如图图 6-3 ,使用五位颜色(32 色),这样你就可以清晰的可视化抖动效果。正如您所看到的,通过抖动算法,许多点图案被添加到相邻的颜色之间,从而产生了附加颜色的感觉。
图 6-3 。显示压缩设置为 32 色(五位色)的索引彩色图像中的抖动效果
有趣的是,在压缩 8 位索引彩色图像时,您可以选择使用少于 256 种颜色。这样做通常是为了减少数据占用空间。例如,使用 32 色可以获得良好效果的图像实际上是五位图像(PNG5),尽管该格式通常称为 PNG8。请注意,您还将设置使用抖动的百分比。我通常选择 0%或 100%设置,但是你可以在这两个极端值之间的任何地方微调抖动效果。你也可以选择你的抖动算法类型。我使用扩散抖动,因为它可能会沿着不规则形状的渐变产生更平滑的渐变效果,就像你在奥迪挡泥板上的图 6-3 中看到的那样。
正如您可能想象的那样,抖动会将数据模式添加到图像中,这对编解码器的算法压缩来说更具挑战性。因此,抖动会增加几个百分点的数据占用空间。请务必比较应用和不应用抖动的文件大小,以确保抖动提供改进的视觉效果。
现在我已经介绍了静态数字图像的概念和技术,在我完成本章之前,a 将提供一些关于 Android 5 如何使用 Animation 和 AnimationDrawable 类(对象)的信息,以允许您使用动画将您学习的数字图像带到下一个级别。
动画手表表面:动画和动画可绘制
Android 5 OS 既有位图动画,又称帧动画,又有矢量动画,俗称程序动画。矢量动画在 Android 行话中被称为补间动画。Android 中的动画由两组不同的类处理。 AnimationDrawable 类处理帧动画,使用 /res/drawable 项目文件夹保存动画资源,而 Animation 类处理矢量动画,使用 /res/anim 项目文件夹保存程序动画定义。
手表表面的帧动画:AnimationDrawable 类
Android AnimationDrawable 类是实现通常称为“动画书”动画的方法,允许您快速连续播放一系列位图帧,以创建运动的幻觉。AnimationDrawable 类使开发人员能够使用强大的第三方工具,如 Blender 或 Lightworks,在 Android 5 的之外创建动画素材**。如果您想在 Android 中创建动画,您应该使用仅使用 Java 代码和 XML 的过程动画类。Apress 的 Pro Android UI (2014)标题非常详细地涵盖了这两个动画主题。**
使用 AnimationDrawable 类相当简单,因为您所要做的就是使用 GIF、JPEG、WebP 或 PNG 图像资源定义您的位图帧。这是通过使用 XML 格式来定义每个帧资源的文件名以及它在屏幕上显示的持续时间来完成的。然后,使用 Java 7 代码将这个 XML 文件“膨胀”成一个 AnimationDrawable 对象,然后可以使用 Java 7 方法来控制帧动画的新媒体资源的播放,并从这一点开始在 Watch Faces 应用中播放。与向量动画相比,帧动画资源使用更多的内存和更少的处理器,因为它很容易“翻转”帧,但这些帧必须保存在内存中才能做到这一点。向量动画使用非常少的内存来保存动画“移动”的代码,但处理器需要计算和“渲染”移动,从而创建动画数字插图。
表盘的补间动画:动画类
Android Animation 类是用代码(过程)而不是像素实现动画的一种不同方式。这种类型的动画允许定义什么叫做变换 。其中包括平移(移动)旋转(方向变化)缩放(大小变化)。
矢量动画允许开发者使用 AnimationSet 类定义复杂的变换“集合”。这些包括逻辑分组变换,包括移动(平移)、定向(旋转)和调整大小(缩放)。
Android 操作系统使用用户设备上的处理器将这些渲染到屏幕上,创造出一种运动的错觉。一个动画类让开发者能够在 Android 5 内部创建动画素材,只使用 XML 和 Java 代码,不需要外部的新媒体素材。有趣的是,程序动画不仅可以用于制作矢量形状、渐变和文本的动画,还可以变换位图资源,包括在帧制作动画时。我将在这一章的下一节,也就是我喜欢称之为“混合动画”的那一节讲述这个问题
使用 Animation 类不像使用 AnimationDrawable 那么容易,因为您必须使用 XML 标记或 Java 代码定义相当复杂的转换结构。这通常通过 XML 定义文件来完成,该文件用于定义分组旋转、缩放和移动变换的层次结构。这些利用了 Android 动画类的力量,其中包括一个动画、动画集(用于分组),当然还有旋转动画、缩放动画、平移动画和alpha 动画类。正如你可能已经从本章的前一节中猜到的,这个 AlphaAnimation 类也允许你程序化地制作不透明度的动画,这将允许你淡入和淡出你的动画组件!这五个专门的转换类都是一个动画超类的直接子类**,所以这六个类将无缝地协同工作。**
使用 Java 7 代码,您的过程动画 XML 定义文件也将被“膨胀”到一个动画对象中,之后,Java 7 方法可用于从您的 Watch Faces 应用中的该点开始控制矢量动画的新媒体资源的回放。记住,Android 5 用的是 Java 7。
与帧动画相比,矢量动画资源使用更多的 CPU 处理资源和更少的系统内存资源。这是因为用户的硬件设备处理器正在使用数学、数据和代码实时渲染矢量动画。这需要进行大量的处理,但是只有很少的内存来保存随时间推移而被处理的向量和代码变量。
手表表面的混合动画:组合的力量
有趣的是,在我结束这一章之前,可以把你的 AnimationDrawable frame 动画 XML 定义和你的基于动画类的矢量动画 XML 定义结合起来。
这是通过将矢量动画应用于包含运行帧动画的 UI 元素来实现的。如果你设置正确,你将能够使用所有的 Android 动画类来实现更加复杂和奇妙的动画效果。
摘要
在这一章中,你学习了手表表面设计的注意事项和指导方针,你将需要在本书的其余部分创建手表表面。
您了解了手表面临的重要节能考虑,以及一些智能手表制造商(如华硕)使用的互动模式和环境模式,以及低位模式。你看了手表外观设计形状,以及如何在手表外观设计中吸收 Android 5 操作系统的功能,如硬件状态图标、热门词汇和通知卡消息。您还了解了创建专业应用所需的高级手表面孔设置对话框和数据集成注意事项。
接下来,我花了一些时间来确保您了解多媒体概念,我将使用这些概念来帮助您创建 Pro Android 可穿戴设备应用。这样,我在这一章中介绍了你需要的所有基础知识。
在下一章中,您将开始学习如何将 Java 代码和 XML 标记放在适当的位置,以形成 Watch Faces 设计和 Watch Faces 应用的基础。**
七、为穿戴设计表盘:创建表盘代码基础
现在,您已经掌握了有关 Android 表盘设计、数字图像和 Android 动画概念的基础知识,您可以开始编写表盘应用了,使用的是您在第章第 4 中开始的引导 Java 代码和 XML 标记。
因为在早期的代码中没有新的 Android 项目工作流程来创建 Watch Face Bootstrap 项目基础架构,所以本章将向您展示如何变形一个标准的 Wear 项目 Bootstrap 基础架构,将其转换为 Watch Faces 项目。在这个过程中,您将了解如何在 Android 中设置 Watch Face,它需要什么权限,Watch Faces 和 AndroidManifest XML 文件与标准应用有何不同,等等。
让我们首先看看 Gradle Build 配置文件及其引用的存储库和依赖项,然后向您的 Wear 和移动应用 AndroidManifest.xml 文件添加权限条目。
一旦这些就绪,您将学习如何创建一个新的 Java 类。这将创建表盘服务和引擎,它们将成为你的表盘设计和表盘处理代码的基础。之后,您将创建一个新的 /res/xml 资源目录,并创建一个 watch_face.xml 文件。创建动态壁纸范例需要这个文件,动态壁纸范例用于使 Watch Faces API 可操作。接下来,您将修改一些 Watch Face 预览可绘制素材,并将您的服务类和十几个相关参数添加到您的 AndroidManifest XML 文件中。我们开始吧!
分级脚本:设置分级构建依赖关系
如果您还没有这样做,请使用您在第二章中创建的快速启动图标启动 Android Studio 开发环境。这将启动 IntelliJ IDEA 并且显示你在第四章中创建的 ProAndroidWearable 项目。点击 IDE 左侧面板中 Gradle Scripts 文件夹旁边的箭头将其打开,如图图 7-1 所示。
图 7-1 。打开你的 ProAndroidWearable 项目中的 Gradle Scripts 文件夹,打开项目 build.gradle
右键单击主build . gradle(Project:ProAndroidWearable)文件,如图 7-1 左侧用蓝色突出显示的,选择跳转到源代码菜单选项,或者如果你愿意,简单地使用 F4 功能键快捷键。这将在 IDEA 的编辑区打开顶层(主)项目 Gradle 配置文件,如图 7-1 右三分之二所示。
这个顶级 build.gradle 文件将在 dependencies 部分使用一个(绿色)类路径引用,通过使用com . Android . tools . Build:Gradle:1 . 0 . 0,引用 Android 存储库服务器上的 Android Gradle 构建工具。
如果 Android Gradle 构建工具已经更新,那么版本编号可能会有所不同。这都是由 Android 新项目系列对话框正确设置的;在图 7-1 中要注意的重要事情是一个重要的信息:*注意:不要把你的应用依赖放在这里;它们属于单独的模块 build.gradle 文件。*因为一个 Android Wear 项目有一个 Wear 和一个 Mobile 组件,正如你在左边的 IntelliJ 项目导航器窗格中看到的,每个应用组件都有自己独特的 Gradle 构建脚本文件,我将在下面讨论。
将 Gradle build 依赖项放在与每个 Wear 和移动应用组件匹配的文件中很重要,这样 Gradle Build 才能正常工作。
接下来,右键单击移动build . gradle(Module:mobile)文件,图 7-2 左侧以蓝色突出显示的,选择跳转到源代码菜单选项,或者如果您愿意,也可以直接使用 F4 功能键快捷键。
图 7-2 。打开第二个 build.gradle 模块:ProAndroidWearable 项目中的移动 gradle 构建脚本
这是您的移动应用组件的 build.gradle 文件,它将包含您的应用 ID ,您的包名和类名的串联,即com . pro . Android . wearables . proandroidwearable,以及在 API 级别 18 (Android 4.3)和 API 级别 21 (Android 5)的目标 SDK 版本设置的最低 SDK 版本规范,如
在图 7-2 的底部,您将看到 Gradle 依赖关系部分,引用 wearApp 项目(':wear') 项目类型和 Android 支持库的编译依赖关系,您在第四章中安装了该库,使用com . Android . Support:app compat-V7:21 . 0 . 3表示,以及 Google Play 服务支持库,使用
需要注意的是,您将会看到一些项目,比如那些仍在使用 Eclipse ADT IDE 的项目,它们在 Android 清单文件中设置了 Google Play 服务。这可以使用下面的元数据标签 : 来完成
<application>
<meta-data android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
</application>
因为您在 build.gradle 文件中设置了这个编译依赖项,所以您不需要在 Android Manifest XML 文件中包含这个标记,您将在本章中转换这个文件以便与 WatchFaces API 一起使用。稍后我会解释 <元数据> 标签是做什么的!
最后,右键单击 wearbuild . gradle(Module:wear)文件,如在图 7-3 左侧以蓝色突出显示的,并选择一个跳转到源菜单选项,或者如果你喜欢,简单地使用 F4 功能键快捷键。
图 7-3 。打开第三个 build.gradle 模块:在您的 ProAndroidWearable 项目中使用 gradle 构建脚本
这是智能手表(Wear SDK)应用组件的 build.gradle 文件,正如您所见,它还将包含您的应用 ID(??)以及 SDK 最低版本(??)规范。注意在这个 Gradle Build 规范文件中,这是为 Wear 设置的,在 API 级别 21 (Android 5),与 API 级别 21 (Android 5)的目标 SDK 版本相同,如图 7-3 上半部分的 build.gradle 文件的 android 部分所示。
在图 7-3 的底部,你会看到梯度依赖部分。这个 Wear Gradle 构建规范将引用 Android Wear 支持库,而不是引用 Android 支持库、,而是引用 com . Google . Android . Support:wearable:1 . 1 . 0 存储库路径以及文件和版本字符串串联。有趣的是,Android 支持库位于 com . Android . Support;Wear 也在 com.google.android.support!
请注意,您将需要再次引用 Google Play 服务支持库,使用您在移动 Gradle 构建中使用的相同的编译语句。
Android 权限:Watch Face 使用-权限
单击左侧面板中 Gradle Scripts 文件夹旁边的向下箭头,关闭该文件夹,然后单击 wear 旁边的箭头打开该文件夹。接下来点击manifest文件夹旁边的箭头,同样打开该文件夹,显示 AndroidManifest.xml 文件,如图图 7-4 所示。右键单击**/wear/manifest/androidmanifest . XML文件,如图 7-4 左侧蓝色高亮所示,选择跳转到源**菜单选项,或者如果您愿意,可以简单地使用 F4 功能键快捷键。如你所见,这将打开 AndroidManifest.xml 文件,该文件是由你在第四章中使用的一系列新的 Android Studio 项目对话框创建的。
图 7-4 。添加两个< uses-permission >标签,用于提供 _ 背景和唤醒 _ 锁定穿戴清单
在指定智能手表硬件的< uses-feature >标签之后(或者之前,如果你愿意的话)添加两个 < uses-permission > 标签。uses-permission 属性是使用这个 XML 标记设置的,它定义了您的应用将从 Android 操作系统请求使用哪些权限。
注意,这些子标签需要“嵌套”在您的 <清单> 父标签中。这是因为< uses-permission >标签将访问 Androidmanifest . permission类中包含的常量值。如果你是好奇型的,想在一个地方看到 Android 操作系统允许的所有权限,请访问以下 URL:
[`developer.android.com/reference/android/Manifest.permission.html`](http://developer.android.com/reference/android/Manifest.permission.html)
您将使用的权限之一没有列出,我将在下面解释原因!
uses-permission 属性使用 android:name 变量来设置预定义的操作系统常量,这些常量用于指定在 Android OS 中使用某些硬件(或软件)功能所需的权限。
如果您使用的常量不是 Manifest.permission 类的一部分,那么该常量需要以存储库路径开头,因此您会注意到在图 7-4 顶部突出显示的 PROVIDE_BACKGROUND 常量使用了一个com . Google . Android . permission . PROVIDE _ BACKGROUND常量引用,而 WAKE_LOCK 权限常量引用使用了一个 android.permission.WAKE_LOCK,这是一个较短的引用路径,直接访问现在可以在 Manifest.permission 类中看到的常量
如果您访问我之前包含的 Manifest.permissions URL,您会看到 WAKE_LOCK 常量用于启用允许使用 Android PowerManager 唤醒锁功能的权限,该功能可以防止硬件设备处理器休眠,并防止您的(智能手表)屏幕变暗。您需要添加到清单中的 XML 标记应该如下所示:
<uses-permission android:name="com.google.android.permission.PROVIDE_BACKGROUND" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
接下来,点击移动旁边的箭头打开该文件夹。接下来点击你的Manifest文件夹旁边的箭头打开那个文件夹,显示另一个 Android Manifest 文件,如图图 7-5 所示。右键单击这个**/mobile/manifest/androidmanifest . XML文件,如在图 7-5 左侧以蓝色突出显示的,并且选择一个跳转到源**菜单选项,或者如果你愿意,简单地使用你的 F4 功能键快捷键。将这两个相同的标记也添加到这个清单文件中,因为您的可穿戴权限需要是移动(手机或平板电脑)权限集的子集(或与之相等)。
图 7-5 。添加< uses-permission >标签用于移动清单的提供背景和唤醒锁
现在您已经准备好为您的 Watch Faces 应用创建核心类,您将把它命名为 ProWatchFaceService.java,并将使用 Android CanvasWatchFaceService 类作为它的超类。越来越令人兴奋了!
画布表面服务:一个表面引擎
本节将介绍驱动手表脸服务基础的类, CanvasWatchFaceService 和 CanvasWatchFaceService。引擎,以及它们在 Java 和 Android 类层次结构中的位置。您还将了解它们是如何在您当前的 ProAndroidWearable 项目代码库中实现的,您将把它转变为一个专业的 Watch Face 项目,这样,如果您想使用 Android 5 WatchFaces API 进行开发,您就知道如何做到这一点。
CanvasWatchFaceService 类:概述
AndroidCanvasWatchFaceService类是一个公共抽象类 ,这意味着你必须对它进行子类化才能使用它。你将在本章的下一节中做这件事,在我提供了这个类本身及其来源的鸟瞰图之后,这将告诉你关于它正在做什么以及它从哪里获得这样做的能力的相当多的信息。
Java 类层次结构相当复杂,如下所示:
java.lang.Object
> android.content.Context
> android.content.ContextWrapper
> android.app.Service
> android.service.wallpaper.WallpaperService
> android.support.wearable.watchface.WatchFaceService
> android.support.wearable.watchface.CanvasWatchFaceService
当然,Java 中的每个类和对象都是基于 java.lang.Object 的,而在 Android 中,服务类是基于上下文类的,因为服务将需要关于您的服务(在本例中是 WatchFaceService )试图完成什么的上下文信息。注意 WatchFaceService 是从 WallpaperService 继承而来的子类,它告诉你 Watch Face API 是基于 Android Wallpaper API 的,这就是为什么在本章中你必须实现 Wallpaper 对象和 BIND_WALLPAPER 功能。
CanvasWatchFaceService 类是一个基类,用于创建使用画布对象在显示屏上绘制手表表面的手表表面应用。这个类提供了一个无效屏幕刷新机制,类似于 invalidate( ) 方法调用。方法调用是 Android 的基础,可以在视图类中找到,它允许视图对象被刷新。
创建 ProWatchFaceService 子类 : extends 关键字
现在让我们使用这个抽象的 CanvasWatchFaceService 类,并在这个 ProAndroidWearable 项目中创建一个全新的 Java 类。点击向右箭头图标打开你的 /wear/java 文件夹。接下来右键点击com . pro . Android . wearables . proandroidwearable(包)文件夹,然后选择 New Java Class 菜单序列,如图 7-6 中蓝色部分所示。为了节省空间,我还将创建新类对话框放在了右边的截图中。从种类下拉菜单选择器中选择类选项,输入 ProWatchFaceService 的名称,最后点击确定按钮,创建新的 ProWatchFaceService WatchFace 服务和引擎子类。
图 7-6 。右键单击您的包文件夹并使用新的 Java 类来创建 ProWatchFaceService 类
一旦你创建了新的 Java 类,它将在一个编辑标签中为你打开,如图 7-7 的右三分之二所示。bootstrap 类为您提供了以下“空”公共类 Java 代码:
package com.pro.android.wearables.proandroidwearable;
public class ProWatchFaceService {...}
您需要做的第一件事是添加 Java extends 关键字,您将在 ProWatchFaceService 类名之后插入该关键字,引用 CanvasWatchFaceService 超类,并为您的 ProWatchFaceService 类提供 Android CanvasWatchFaceService 超类的所有功能、算法、方法、变量和特性。
如果你输入 Java extends 关键字和这个超类名的前几个字母,IntelliJ IDEA 会为你下拉一个助手选择器对话框,如图图 7-7 所示,你可以找到并双击你的 CanvasWatchFaceService(Android . support . wearable . watch face package)选项,让 IntelliJ 为你完成代码的编写。现在您有了空的 ProWatchFaceService 子类,并准备在其中编写引擎。
图 7-7 。使用 extends 关键字从 CanvasWatchFaceService 创建 ProWatchFaceService 类的子类
在编写 WatchFaceService 引擎类之前,让我们快速看一下 CanvasWatchFaceService。Engine 了解它能做什么。
CanvasWatchFaceService。发动机类别:发动机
CanvasWatchFaceService。引擎是一个公共类,它提供了调用 onDraw( ) 方法的绘制引擎,以实际完成在智能手表屏幕上绘制(或在某些情况下制作动画)手表表面的繁重工作。Java 类层次仍然来自 WallpaperService。然而,Engine 远没有 CanvasWatchFaceService 复杂,看起来如下:
java.lang.Object
> android.service.wallpaper.WallpaperService.Engine
> android.support.wearable.watchface.WatchFaceService.Engine
> android.support.wearable.watchface.CanvasWatchFaceService.Engine
这个类提供了一个使用 onDraw()在画布上绘制的手表表面的实际实现。您将需要实现**。onCreateEngine( )** 方法,以便让您的类返回可用的引擎实现。这个类的构造函数方法将与 onCreateEngine()方法 结合,让 Android OS 返回新的引擎对象。
这是使用下面的 Java 构造函数方法结构来完成的,您将在本章的下一节中实现它:
public CanvasWatchFaceService.Engine onCreateEngine() { return new Engine(); }
该类有许多强大的方法,您将在高级 Watch Face Java 代码开发中使用这些方法,一旦您在本章的后面建立了 Watch Face 代码基础,您将在本书的其余部分中添加这些方法。
最重要的方法之一是 invalidate( ) 方法,,它使引擎重新绘制表盘屏幕。该方法将调度对 onDraw(Canvas,Rect) 方法的调用,请求引擎绘制动画或时间更新的下一帧。
有几个处理画布表面的 onSurface( ) 方法调用,包括您的**。onSurfaceChanged(Surface holder holder,int format,int width,int height)** 方法,在这里你可以定义当一个表面表面发生变化时会发生什么,以及 **。onSurfaceCreated(Surface holder holder)方法,在该方法中,您可以定义每当创建手表表面时发生什么。还有就是。onsurfacererawedned(Surface holder holder)**方法,在这里您可以定义每当需要重画手表表面时发生什么。
还有就是**。** postInvalidate( ) 方法,该方法请求 Android 操作系统发布一条消息,安排调用。onDraw(Canvas,Rect)方法,请求它绘制下一帧。现在让我们创建引擎类!
创建私有引擎类:使用 onCreateEngine()
在 ProWatchFaceService 类(在花括号内)中,您需要使用 Java @Override 关键字实现一个 CanvasWatchFaceService()构造函数方法。键入@Override 然后按回车键输入新的一行代码,然后键入 Java public 关键字并开始键入构造函数方法名 CanvasWatchFaceService()。
当 IntelliJ 弹出方法插入帮助器对话框时,如图图 7-8 所示,选择**CanvasWatchFaceService(Android . support . wearable . watch face)**选项,让 IntelliJ IDEA 为你编写 Java 代码语句。键入句点和单词 Engine,以访问完整的 CanvasWatchFaceService.engine 类路径。
此时,您所要做的就是在 public 关键字和类名之后添加上一节中讨论的 onCreateEngine()方法调用。在花括号内,添加**return new Engine();**语句,创建您的表盘引擎的构造就创建好了。这将使用以下 Java 代码结构来完成:
public CanvasWatchFaceService.Engine onCreateEngine( ) {
return new Engine( );
}
图 7-8 。Add @Override,键入 Java 关键字 public 和字母 C,然后选择 CanvasWatchFaceService
一旦你把这个公共的 CanvasWatchFaceService。Engine onCreateEngine()就位,如图图 7-9 所示无误,你可以为你的引擎私有内部类编写一个结构,它将保存 onDraw()方法结构。
图 7-9 。添加 onCreateEngine()方法调用并返回 new Engine();结构内部的语句
使用 Java private 关键字在名为 Engine 的 ProWatchFaceService 类中创建一个私有内部类,并使用 Java extends 关键字对 CanvasWatchFaceService 进行子类化。引擎类。你得到的结构应该看起来像下面的 Java 代码:
private class Engine extends CanvasWatchFaceService.Engine { // An Empty Class Structure }
正如你在图 7-10 中看到的,代码是没有错误的,你已经准备好在这个私有引擎类中编写你的公共 void onDraw( ) 方法了。
图 7-10 。编写一个名为 Engine 的空私有类来扩展 CanvasWatchFaceService。引擎等级
在 Engine 类中(在花括号中),您需要实现一个公共的 void onDraw()方法,再次使用 Java Override 关键字。键入@Override,然后按 Return 键输入新的一行代码。然后键入 Java public 关键字和一个 void Java 返回类型,带有名为 watchface 的 Canvas 对象参数的 onDraw( ) 方法名,名为 rect 的 Rect 对象参数,以及你的两个花括号,如图 7-11 所示。
图 7-11 。用 Canvas watchface 和 rect 对象参数编写空的公共 void onDraw 方法
在图 7-11 的底部可以看到,在表盘下面有一个绿色波浪下划线高亮。如果你把鼠标放在它上面,你会看到 IntelliJ 所关心的“参数‘watch face’从未被使用”。您现在可以忽略这个绿色(轻度警告级别)代码高亮显示,因为稍后您将调用这个 watchface Canvas 对象的. drawColor()方法。
还要注意 IntelliJ 添加到 Canvas 和 Rect 对象(类)中的红色。这意味着在代码中使用这些语句之前,您需要编写一个 import 语句。将 Canvas 和 Rect 对象传递到。CanvasWatchFaceService 中的 onDraw()方法。引擎超类使用 Java super 关键字,你将看到如何让 IntelliJ 为你编码这些导入语句。耐心是一种美德!
在 onDraw()方法的主体中,也就是在左花括号和右花括号中,键入 Java super 关键字和一个句点字符以打开 IntelliJ helper 对话框,显示您的超类中可以使用的方法。图 7-12 显示了 onDraw(Canvas canvas,Rect bounds)选择;一旦你双击这个,IntelliJ 会为你写一个 Java 代码语句,生成一个红色波浪线(严重错误级别),我接下来会讨论这个(这也是我为什么拿这个具体的工作过程)。
图 7-12 。在 onDraw 方法内部,使用 Java super 关键字调用 onDraw(Canvas,Rect)方法
正如你在图 7-13 中看到的,如果你将鼠标悬停在红色波浪状错误高亮上,IntelliJ 会告诉你你的问题是什么。在这种情况下,显示 android.graphics.Canvas 和 android.graphics.Rect 的包和类,并显示错误“引擎中的 onDraw(Canvas,Rect)无法应用”。
这里使用了 import 语句的包和类名部分,这一事实应该会在您的思维过程中触发一个“import 语句”,所以您唯一需要知道的是如何让 IntelliJ 对这些进行编码。
图 7-13 。将鼠标悬停在突出显示的红色波浪错误上,查看 Canvas 和 Rect 类的问题
正如你在图 7-14 中看到的,如果你把鼠标放在画布上或者你的公共 void onDraw(Canvas watchface,Rect rect)方法声明中的 Rect 上,一个有点神秘的,缩写的?android.graphics.Rect?Alt+Enter”消息。我会把这个隐晦的信息翻译成“问题:为你导入你的 android.graphics.Rect 包和类?如果是,按 Alt 键,同时按回车键,我来编码!”你会发现,如果你将鼠标悬停在这些红色的类名上,并按下建议的 Alt-Enter 击键序列,IntelliJ 将为你编写两个导入语句,如你在图 7-15 中看到的。
图 7-14 。将鼠标悬停在代码中的红色画布和 Rect 类名上,并使用 Alt+Enter 导入这些类
现在是时候在 onDraw()方法的主体中使用 watchface Canvas 对象了。您将调用一个**。**draw color(int color)方法关闭此对象将画布的颜色设置为黑色。当然,这优化了表盘设计的功耗,因为黑色像素的功耗为零!
输入 watchface 对象名称,然后输入一个句点键,然后输入几个 drawColor 字符,会弹出一个帮助对话框,包含以字母 C 开头的绘制方法,如截图右下方的图 7-15 所示。双击 drawColor(int color) 选项,调用 watchface Canvas 对象的方法。现在,您所要处理的就是您的 Color 类参数,您将把它传递到方法调用中,并且您将完成基本引擎结构的编码,这将绘制黑色(空)的手表表面!
图 7-15 。键入 watchface Canvas 对象,并使用句点和 drawC 调出方法助手对话框
您可能已经注意到,我的方法是在试图获得更复杂的代码之前,先获得一个空的 Java 代码结构并使其工作。这是因为 Java 是复杂的语言,Android 5 是复杂的操作系统;因此,我通常从最底层的功能编码开始。
我的开发方法是从空的开始逐步构建,但是没有错误的代码构造具有所有需要的导入语句和编码语法(关键字、括号、花括号等)。)恰当地就位。
正如你在图 7-16 中看到的,我已经输入了一个颜色类名(对象)并按下句号键调出颜色常量助手对话框,这样我就可以找到我想用于表盘背景色的颜色。
出于省电的原因,我选择了 Color 的颜色类常量。黑色(android.graphics) ,如图 7-16 中蓝色选中所示。
图 7-16 。在 watchface.drawColor()方法中,键入颜色类并选择黑色
双击颜色后。黑色常量在帮助器对话框中,你会在 Java 代码编辑器窗格的左边看到一个灯泡,如图 7-17 左侧所示。您可以将鼠标悬停在图标上,或者单击图标旁边的下拉箭头,打开 IntelliJ 为您提供的关于您刚刚生成的这行代码的解决方案。
这条消息说“为 android.graphics.Color.BLACK 添加静态导入”,所以看起来 IntelliJ IDEA(或 Android Studio)希望您为 Color 而不是 Color 类添加导入语句。黑常数本身!
创建 Java 静态导入语句是为了提供一种类型安全的机制,用于在 Java 代码中包含常量,而不需要引用最初定义为常量的整个(在本例中为 Color)类。
图 7-17 。单击灯泡下拉箭头,并添加建议的静态导入
恭喜你!现在,您已经为您的 ProWatchFaceService 及其附带的引擎类建立了 Java 代码基础,包括您的核心 onDraw()方法,该方法目前在智能手表显示屏上绘制一个空白的黑色空手表表面。
这个核心。onDraw()方法最终会在必要时更新您的手表表面。诚然,这目前是一个空的 Java 结构,尽管它确实为您提供了一个空白的黑屏(画布)来创建您的 Watch Faces 设计,因此,您已经将它放在适当的位置,并且您正在一步步地学习这个 Watch Faces API 是如何实现的,以及它在幕后是如何工作的,这仍然是非常重要的。
你的代码现在没有错误了,这可以在图 7-18 中看到。您可以看到 color 类的三个导入语句,一个静态导入语句和一个不需要的(在 IntelliJ 中使用灰色显示)导入语句。这表明引用了 Color 类,但不需要完整的 import 语句。IntelliJ 有一个代码优化导入特性,一旦应用开发完成,你可以用它来删除所有不必要的导入语句。
图 7-18 。完成了私有类引擎和五个导入语句,一个静态
既然已经有了提供核心外观处理功能的 Java 代码,那么让我们完成所有 XML 基础标记。还有很多设置工作要做!
Watch Faces API 设置的 xml 部分将包括一个全新的 /res/xml 目录,XML 壁纸对象定义,您将完成移动应用和 wear 应用 AndroidManifest.xml 文件的变形,从默认的 Wear 应用引导到 Watch Faces API 应用兼容性。完成此操作后,您将能够创建面部图像预览。
表盘 XML 资源:创建和编辑 XML 文件
尽管您已经添加了与 Watch Faces API 相关的权限,并使用 Java 7 代码创建了 Watch Faces 呈现引擎,但是仍然需要使用 XML 标记来解决一些问题。
这些包括创建 XML 资源文件夹、壁纸对象定义,以及将<服务>声明添加到 AndroidManifest.xml 文件中。您还将添加(或删除)一些其他 XML 标记和参数条目,以便在两个(移动和穿戴)Android 清单 XML 定义文件中进行一些关键的调整。这将向您展示如何将 bootstrap Wear 应用“变形”为 Watch Faces API 应用,这也是本书本章的全部内容!让我们开始处理所有这些 XML 标记,这样您就可以完成这一基础章节,然后开始创建手表外观设计!
表盘壁纸:用 XML 创建壁纸对象
接下来你需要创建的是 /res/xml/watch_faces.xml 文件,它定义了你的壁纸对象并包含了手表 faces 的设计。正如您在图 7-19 中看到的,项目文件夹中还不存在一个 /res/xml 文件夹,因此您需要右键单击 /wear/res 文件夹,并使用NewAndroid resource directory菜单序列来创建保存 watch_faces.xml 文件所需的目录,您接下来将创建该文件来保存 XML****对象父标签和 xmlns
图 7-19 。右键点击/res 文件夹,选择新建安卓资源目录
调用该菜单序列后,将会看到一个新建资源目录对话框,如图图 7-20 所示。选择您的资源类型为 xml ,并将目录名设置为 xml 。重要的是要注意,如果您首先选择一个资源类型,IntelliJ 将为您命名目录。其他一切保持不变,然后点击 OK 按钮创建 XML 文件夹,如您所见,在图 7-21 中已经成功创建。
图 7-20 。选择一个 xml 资源类型,将把这个目录命名为/res/xml
右键单击 new /res/xml 文件夹,选择新建 XML 资源文件,如图图 7-21 所示,新建 watch_faces.xml 壁纸文件。
图 7-21 。右键单击/res/xml 目录,并选择新建 XML 资源文件
调用菜单序列后,你会看到一个新建资源文件对话框,如图图 7-22 所示。选择你的源设置为主,然后设置文件名为 watch_face.xml 。保持其他一切设置不变,然后单击 OK 按钮创建 watch_faces.xml 定义文件,正如您在图 7-23 中看到的,该文件已成功创建,并已在 IntelliJ IDEA 内的编辑窗格中打开进行编辑。
图 7-22 。将文件命名为 watch_face.xml,并将源设置为 main(保留默认值)
使用下面的 xml 标记进入 < xml > 版本容器标签和 <壁纸> 父标签,可以在图 7-23 的右半部分看到:
<?xml version="1.0" encoding="utf-8"?>
<wallpaper xmlns:android="http://schemas.android.com/apk/res/android" />
这将在 Android 中创建壁纸对象,用于您的手表表面。
图 7-23 。右键单击 watch_face.xml,使用跳转到源代码在选项卡中查看它
现在,您已经将能够在 wear Android Manifest XML 文件中创建<服务>声明所需的一切就绪,这将声明您的 ProWatchFaceService(和引擎)以供使用,并使用父<服务>标签内的<元数据>子标签引用所有其他表盘图像预览和壁纸对象。
声明 WatchFace 服务:XML 标签
在 IntelliJ 中点击您的 wear 应用的 AndroidManifest.xml 选项卡,这样您就可以声明您的服务以在 Watch Face 应用中使用。如图 7-24 中 IntelliJ 的标题栏所示,它显示了当前正在编辑的文件的路径。使用此功能可确保您编辑的是正确的(wear) AndroidManifest,而不是移动 AndroidManifest。我已经将 < uses-feature > 和****标签及其参数放在一行代码中,以便为您接下来将创建的<服务>父标签及其子标签结构腾出空间。
<服务> 标签本身有半打参数来配置它的使用,从 BIND_WALLPAPER 权限开始,它允许你的服务对象“绑定”绑定是指与你在上一节创建的壁纸对象建立“实时刷新”或实时更新的连接。您还需要给<服务>标签一个服务类名,即**。ProWatchFaceService** ,还有一个 Pro 手表脸的标签。最后,您需要为allowed embedded选项设置一个 true (on)标志,并为必需的 taskAffinity 参数(属性)添加一个空字符串值。这可以使用以下 XML 标记来完成:
<service
android:permission="android.permission.BIND_WALLPAPER"
android:name=".ProWatchFaceService"
android:label="Pro Watch Face"
android:allowEmbedded="true"
android:taskAffinity="" >
<meta-data
android:name="android.service.wallpaper"
android:resource="@xml/watch_face" />
<meta-data
android:name="com.google.android.wearable.watchface.preview"
android:resource="@drawable/preview_pro_square" />
<meta-data
android:name="com.google.android.wearable.watchface.preview_circular"
android:resource="@drawable/preview_pro_circular" />
<intent-filter>
<action android:name="android.service.wallpaper.WallpaperService" />
<category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />
</intent-filter>
</service>
图 7-24 。添加定义服务的<服务>父标签和<元数据>子标签
在父标签中还有三个子标签以及一个子标签。我将首先介绍意图过滤器,因为只有一个,而且非常重要。意图过滤器子标签有两个自己的(嵌套的)子标签。一个是用于一个动作,它是一个壁纸服务类,是 WatchFaceService 类的超类。这是 ProWatchFaceService 类的 CanvasWatchFaceService 超类的超类。另一个子标签用于动作的类别,不出所料,它是一个 WATCH_FACE 常量。
我在这里详细介绍 CanvasWatchFaceService 类(及其嵌套的引擎类)的原因是,当需要查看意图过滤器设置时,您将理解 WallpaperService 是可绑定服务类的最高级类型,WATCH_FACE 类别实际上是为这个 WallpaperService 超类指出了一个 WatchFaceService 子类类型。
接下来,您需要添加 <元数据> 子标签,为这个 Watch Faces 服务声明定义某些东西。其中最重要的将定义 Wallpaper 对象,它是在上一节中使用 watch_face.xml 文件创建的,并在名为 android.service.wallpaper 的元数据标记中引用,该标记使用**Android:resource = " @ xml/watch _ face "**参数引用语法引用/res/xml 中的 XML 资源文件。
另外两个子标签将为表盘圆形和方形版本预览提供可绘制素材。接下来让我们来创建它们。
square watch face 预览的子标签将使用名称值com . Google . Android . wearable . watch face . preview,并引用您将在下一节创建的 XML 资源文件,该文件将使用**Android:resource = " @ drawable/preview _ pro _ square "**参数引用语法。注意在 name 参数中,默认的表盘屏幕形状类型是方形,而圆形将被编码为 preview_circular 。
注意在图 7-24 中 IntelliJ 是绿色波浪下划线突出单词 watchface,就像它对 Java 对象名做的一样。原来 IntelliJ 认为这是一个拼写错误,所以,希望在某个时候 IntelliJ 会更新它的字典,并在 watchface 和 WatchFaces API 中添加正确的拼写!
round watchface 预览的子标签将具有名称值com . Google . Android . wearable . watch face . preview _ circular和 XML 引用,该 XML 引用指向您将在下一节中使用**Android:resource = " @ Drawable/preview _ pro _ round "**参数引用语法创建的可绘制资源文件。
接下来,您需要创建这些 watchface.preview 素材并将其添加到正确的/res/drawable 文件夹中,因此,在我用一个完全加载(但为空)并配置好的 Watch Face API 应用结束本章之前,让我们来看一些数字图像素材放置工作,以向您展示这是如何完成的。
观看脸部图像预览:使用可绘制资源
下一步,你需要做的是删除下半部分图 7-24 中显示的手表表面预览图像的标签中的红色错误指示器文本高亮,将两个(方形和圆形)手表表面预览 PNG 图像复制到项目的 wear 部分的正确/res/drawable 文件夹中。我将使用第二部分后面一章中的预览图,包括图形设计素材创建和集成到表盘设计、代码、UI 设计、XML 标记和应用中。
预览图像的图像分辨率需要为 320 x 320 像素。这意味着这些图像是 HDPI(高密度像素图像)分辨率,因此需要进入**/wear/src/main/RES/drawable-hdpi**文件夹,以便 Android 5 找到它们。该文件夹可以在图 7-25 的左侧看到,也可以在图 7-26 的左侧看到。
在图书的资源库中找到 Pro Android 可穿戴设备档案中的 preview_pro_square 和 preview_pro_circular PNG 图片,复制到/**AndroidStudioProjects/ProAndroidWearable/wear/src/main/RES/drawable-hdpi/**文件夹中,如图图 7-25 所示。这些预览图像将与 watch faces 配套应用一起使用,该应用运行在手机或平板电脑上,向用户逐个像素地显示表盘的样子(表盘使用 320 像素)。
图 7-25 。将 preview_pro_circular 和 preview_pro_square 文件复制到 drawable-hdpi
一旦数字图像素材被正确复制到您的/wear/src/main/RES/drawable-hdpi/文件夹中,图 7-24 中出现的红色错误代码高亮将会消失。这可以在图 7-26 的右侧看到,现在 AndroidManifest.xml 标记也没有错误了。在这一章中,你已经取得了很大的进步!
图 7-26 。一旦将 PNG 资源复制到/res/drawable 文件夹中,错误就会消失
摘要
在本章中,您学习了如何通过使用 IntelliJ IDEA 将您的 Pro Android 可穿戴 bootstrap 应用变形为表盘应用,从而为您的表盘应用创建基础。
首先,您了解了项目中的 Gradle 脚本需要如何进行分层,以及如何设置它们以获得成功的 watch faces 应用层次结构。您了解了依赖性和存储库,以及如何使用 Gradle 设置 Google Play 服务。
接下来,您添加了创建 Watch Faces 应用所需的 Android 权限,并了解到您需要处理两个 Android 清单 XML 文件:一个用于可穿戴外围设备,另一个用于手机或平板电脑“主”移动设备。
之后,您学习了 Android CanvasWatchFaceService 类及其 Java 类层次结构,描述了它是如何使用上下文启动服务的对象,该服务创建用于创建 Watch Faces 引擎的壁纸,该引擎使用画布绘制到智能手表显示屏。有趣的是,一个 Java 层次结构可以向我们展示 Google 是如何在 Android 5 操作系统中实现他们的 WatchFaces API 的,不是吗?
然后你了解了 Android 的 CanvasWatchFaceService。引擎类及其层次结构,以及如何将其实现为 ProWatchFaceService 类的私有类。您学习了如何实现?onDraw()方法,并创建了一个空的 Watch Faces 服务引擎,该引擎将用于本书涵盖 Watch Faces API 的第二部分的剩余部分。
接下来,我讨论了手表外观 XML 定义,您创建了一个/res/xml 文件夹,然后创建了一个 watch_face.xml 文件,该文件定义了一个用于手表外观设计的墙纸对象。
然后,我讨论了 Wear Android 清单,您精心制作了服务入口,它定义了新的 ProWatchFaceService.java 类以及其他关键内容,如墙纸对象、意图过滤器和预览图像。
最后,您将手表表面预览图像放到一个可绘制的 HDPI 文件夹中,这样所有的 Java 代码和 XML 标记都变成了绿色,因此没有错误!
在下一章中,您将开始向公共 ProWatchFaceService 类和私有 Engine 类添加 Watch Face 特性。
八、表盘计时引擎:使用时区、时间和广播接收器
现在您已经有了 CanvasWatchFaceService 子类和私有的 Engine 类,并且已经将 Gradle 脚本、XML 素材、Wallpaper 对象和预览素材安装到了它们应该安装的位置,您已经准备好开始学习更高级的 Java 工具类和 Android 类以及实现 WatchFaces API 的方法。
随着你在本书中逐步完成表盘的制作过程,主题会变得更加深入。在本章中,您将深入了解绘图表面,设置您的表盘样式、时区管理、time 对象和时区广播类,为了创建功能性表盘设计应用,需要实现这些内容。
您还将详细了解一下 WatchFaceStyle 类及其 WatchFaceStyle。构建器嵌套类。这些类将允许你建立和配置你在第六章中学到的必要参数。
你还会看到 Android 与时间相关的类和方法,比如时间和时区,以及 Android SurfaceHolder 类,最后是 Androidbroadcast receiver类和方法。这一章充满了关于 Java 和 Android 类的信息,所以,让我们开始吧!
你的 watch face Surface:Android Surface holder
你的表盘设计将通过使用一个 Androidsurface holderobject 来“托管”。这个对象是兼容的,可以与安卓画布对象和你的协同工作。onDraw( ) 方法,这两个方法都已经放在代码中,随着本章的深入,它们将扩展您对的使用。
在您用代码实现 SurfaceHolder 接口之前,我想给您一个概述。然后我将回顾一下 WatchFaceStyle 类,最后介绍关键的 WatchFaceService 和 WatchFaceService。引擎类,它包含了您将在接下来的几章中实现的大多数重要的 WatchFaces API 方法。
Android SurfaceHolder 界面:手表表面
Android WatchFaces API 使用一个 SurfaceHolder 对象作为最底层的对象来保存你的手表 Faces 设计。SurfaceHolder 是一个公共抽象 Java 接口,所以它定义了你需要实现的方法或常量,以便创建手表表面应用。它是android . View . surface holder包的成员,因为它是一种 Android 视图。
SurfaceHolder 对象旨在方便持有(承载)显示屏绘图表面。此界面允许您控制显示绘图表面的格式或大小,以及编辑其表面上的像素。
有一些方法可以用来监测表面的任何变化。通常使用 SurfaceView 类来访问这个接口,然而,WatchFaces API 通过来访问它。onCreate( ) 方法。我将在 WatchFaceService 中介绍这个方法。本章的引擎部分简要讨论了 SurfaceHolder 和 WatchFaceStyle 类。
Android SurfaceHolder 接口包含三个嵌套类:两个是可以用来确定何时发生了对 WatchFace 表面的改变的接口,一个是用于坏表面类型异常错误处理的类。
表面固定器。回调接口可以在您的 Java 代码中实现,它允许您的 Watch Face 应用接收有关 SurfaceHolder 对象更改的信息。还有第二辆 SurfaceHolder。回调 2 实现 SurfaceHolder 的接口。回调接口,提供可以接收的附加回调。
表面固定器。BadSurfaceTypeException 类提供了一个从。lockCanvas()方法,该方法是从其类型已设置为SURFACE _ TYPE _ PUSH _ BUFFERS的 SurfaceHolder 对象调用的。
这个常量通常不会影响 Watch Faces 的设计和应用开发,我在这里包含它只是为了完整地涵盖这个类在 Android 操作系统中的所有实现。
该接口还包含大约十几个公共方法,其中几个可以用于 Watch Faces 设计。这些方法是抽象的,因此您必须用自己独特的代码来实现它们。它们在表 8-1 中列出。你可以在这里熟悉他们,这样你就知道他们能做什么。
表 8-1 。SurfaceHolder 接口方法以及数据类型、方法调用结构和用途
|
方法类型
|
方法结构
|
方法目的
| | --- | --- | --- | | 抽象虚空 | addCallback(SurfaceHolder。回拨回拨) | 添加回调接口 | | 抽象曲面 | getSurface() | 访问曲面对象 | | 抽象直肠 | getSurfaceFrame() | 获取当前尺寸 | | 抽象布尔 | 正在创建( ) | 正在创建表面? | | 抽象画布 | lockCanvas() | 编辑表面像素 | | 抽象画布 | lockCanvas(Rect dirtyRect) | 肮脏的直肠锁画布 | | 抽象虚空 | removeCallback(SurfaceHolder。回调 cback) | 移除回拨 | | 抽象虚空 | setFixedSize(int width,int height) | 使表面固定宽×高 | | 抽象虚空 | setFormat(内部格式) | 设置像素格式 | | 抽象虚空 | setKeepScreenOn(布尔 ScreenOn) | 保持屏幕显示选项 | | 抽象虚空 | setSizeFromLayout() | 允许调整表面大小 | | 抽象虚空 | unlockCanvasAndPost(画布) | 完成编辑表面 |
现在您已经对 SurfaceHolder 接口有了一个大致的了解,让我们看看如何使用 SurfaceHolder 为您将用于 Watch Faces API 设计的 Canvas 对象提供一个表面(Holder)。
一个 SurfaceHolder 对象:onCreate(surface holder surface)
如果您还没有这样做,请使用您在第二章中创建的快速启动图标启动 Android Studio 开发环境。这将启动 IntelliJ IDEA,并显示您在第四章中创建的 ProAndroidWearable 项目,其中您在第七章中添加了手表面孔。
关闭除ProWatchFaceService.java选项卡之外的所有编辑选项卡,如图图 8-1 所示,因为在本章中您将会在该选项卡上工作。您将从 WatchFaceService 添加方法。引擎类,你会在这一章的后面学到,第一个是**。onCreate( )** 方法,如图图 8-1 所示。
图 8-1 。向私有引擎类添加一个公共 void onCreate()方法,并在参数列表中键入 Sur
键入@ Override public void onCreate(){ } methodinfra structure,并在参数区域内键入 Sur 以触发 IntelliJ 调出一个助手对话框,其中包含该方法调用可以使用的所有对象(类)类型。找到**SurfaceHolder(Android . view package)**选项,双击它将 surface holder 对象插入到方法调用中,并将其命名为 surface ,因为这就是它的作用(holding)。
创建 SurfaceHolder 对象(名为 surface)的公共 void onCreate()方法结构将采用以下 Java 结构,如图 8-2 所示:
@Override
public void onCreate(SurfaceHolder surface) { Your Java method body will go in here }
图 8-2 。键入 Java super 关键字,并选择 onCreate (SurfaceHolder)选项将其添加到
正如你在图 8-2 中看到的,如果你简单地在方法体中键入 Java 关键字 super 和句点键,IntelliJ IDEA 会提供一个方法助手对话框。您可以使用此对话框仔细阅读可用于的所有方法。onCreate()方法。
双击onCreate(surface holder)void选项并插入。onCreate()方法调用您的超类,使用您在公共 void onCreate(surface holder surface)方法构造中创建的表面对象。这会将名为 surface 的 SurfaceHolder 对象传递给超类。onCreate()方法,用于在更高级别进行处理。这就是你如何创建一个 SurfaceHolder 并在你的 WatchFace 中实现它。
你会在图 8-3 中注意到,如果你开始在 super.onCreate()方法调用参数区域中键入你的 SurfaceHolder 对象名(surface ), IntelliJ 会找到这个对象名,以及所有其他可用的兼容对象类型,并把它们放在一个大的帮助器对话框中供你选择。
图 8-3 。在中键入一个 s。onCreate()方法参数区,并从对话框中选择表面对象
现在你已经用这个把你的表面支架放好了。onCreate()方法调用,让我们使用 WatchFace Style 类构建一个 WatchFace 样式。
设置手表表面样式:Watch Face Style。建造者
WatchFaces API 包含您的 WatchFaceStyle 和 WatchFaceStyle。构建器类,用于构建和配置 Watch Faces 设计,满足您在第六章中了解到的许多 Android 设计需求。
这些考虑因素定义了你的 WatchFace 将如何吸收 Android 操作系统的考虑因素,例如状态栏指示器的位置,peek 卡支持,Google hotword 指示器的位置,等等。
Android WatchFaceStyle 类:设计手表的外观
Android WatchFaceStyle 类是扩展Object 类并实现 Java Parcelable 接口的公共类。这个类是Android . support . wearable包的一部分。这个 WatchFaceStyle 类的层次结构表明(由于层次结构中缺少任何其他类),它是由 Google 的 Android 开发团队“临时编码”的。创建该类是为了提供观察面孔样式,其层次结构如下所示:
java.lang.Object
> android.support.wearable.watchface.WatchFaceStyle
需要注意的是,这个 android.support.wearable 包中的可穿戴支持库类可能会发生变化。WatchFaceStyle 类提供了允许描述和配置手表外观的常量和方法。该类中概述的参数定义了 WatchFaceService 将如何在您的自定义手表表面设计上绘制 Android 的操作系统用户界面(UI)元素。
这个类的一个实例将被传递到一个方法中,您将在下一节中使用这个方法,称为setWatchFaceStyle(WatchFaceStyle),用于您在ProWatchFaceService.java类中的私有引擎类中的 onCreate 方法。
为了构造您的 WatchFaceStyle 对象实例,您将使用 Java new 关键字来创建一个新的 WatchFaceStyle。生成器对象。这是在 setWatchFaceStyle()方法中使用来自您的类的上下文对象来完成的。
因此,WatchFaceStyle 类包含一个单独的嵌套类,名为 WatchFaceStyle。建造者。我将在本章的下一节详细介绍这一点。当你构建一个 WatchFace 样式时,你也将在后面的章节中使用这个嵌套类。
WatchFaceStyle 类包含 11 个重要的手表外观常量,您将在手表外观设计和应用开发中使用这些常量。这些显示在表 8-2 中,以及它们的正确名称和功能描述。
表 8-2 。WatchFaceStyle 类常量名称及其预期功能
|
WatchFaceStyle 常量名称
|
WatchFaceStyle 常量函数
| | --- | --- | | 环境 _ 窥视 _ 模式 _ 隐藏 | 当手表处于环境模式时隐藏窥视卡 | | 环境 _ 峰值 _ 模式 _ 可见 | 当手表处于环境模式时,显示 peek 卡 | | 背景 _ 可见性 _ 中断 | 简要显示了中断时的 peek 卡背景 | | 背景 _ 可见性 _ 持久性 | 显示始终用于中断的 peek 卡背景 | | 峰值模式短 | 使用单行文本显示查看卡 | | 峰值模式变量 | 使用多行文本显示 peek 卡 | | PEEK_OPACITY_MODE_OPAQUE | 使用不透明/实心背景显示 peek 卡 | | 窥视 _ 不透明 _ 模式 _ 半透明 | 以半透明背景显示 peek 卡 | | 保护 _ 热门词汇 _ 指示器 | 在 Hotword 中使用半透明黑色背景 | | 保护状态栏 | 使用半透明黑色背景状态栏 | | 保护整个屏幕 | 屏幕使用半透明的黑色背景 |
此 WatchFaceStyle 类包含允许您访问有关 WatchFaceStyle 的信息的公共方法。这些大多是**。get( )** 方法,这些方法与。稍后您将使用嵌套的 WatchFaceStyle 访问 set()方法。生成器类。接下来我会报道这堂课。
有一个**。describeContents( )** 描述 WatchFaceStyle 内容的方法,以及一个**。equals(Object otherObject)** 方法,用于比较 WatchFaceStyle 对象。有一个**。getComponent( )** 方法,该方法为指定样式的表盘组件获取一个值,并且您的标准继承了**。toString( )** ,。hashCode( ) 和**。writeToParcel( )** 方法。
getter 方法包括一个**。getambientpekmode()方法,显示手表处于环境模式时主 peek 卡将如何显示,以及一个。getBackgroundVisibility( )** 方法显示如何为主 peek 卡设置背景显示。。getcardppeekmode()方法显示主 peek 卡将进入您的表盘显示屏多远,以及。gethotworindicatorgravity()方法显示您在表盘屏幕上放置 OK Google hotword 的位置。。getPeekOpacityMode( ) 显示一个 Peek 卡不透明度设置,和一个**。getShowSystemUiTime( )** 显示您的 WatchFaceStyle 是否配置为在表盘上显示系统时间。
还有一个**。getStatusBarGravity( )** 方法调用,允许您轮询并找出屏幕上状态栏图标的位置,以及一个**。getViewProtectionMode( )** 允许您对表盘屏幕上的元素添加透明黑色背景色的设置进行投票,以便它们在表盘上可读。
最后,还有一个方法叫做**。getshowunreadcountimindicator()轮询 WatchFaceStyle 对象,以确定 WatchFaceStyle 对象是否包含一个指示符,如果设置为 true 值,则该指示符显示还有多少张未读的 peek 卡**要由用户从未读的 peek 卡输入流中读取。
接下来,我们来看看 WatchFaceStyle。生成器嵌套类及其许多外观样式方法。在创建和构建 WatchFaceStyle 对象时,您将在下一节后面的章节中使用这些方法中的大部分。
Android WatchFaceStyle。构建器类:构建样式
Android WatchFaceStyle。构建器公共静态类也有一个扩展对象,表明它也是为构建你的 WatchFaceStyle 对象而临时编写的。该类提供了用于创建 WatchFaceStyle 对象的“生成器方法”。Java 类层次结构如下所示:
java.lang.Object
> android.support.wearable.watchface.WatchFaceStyle.Builder
这是另一个可穿戴支持库类,可以在 android.support.wearable 包中找到,与 WatchFaceStyle 类一样,它也将在未来随着智能手表制造商改变其产品功能的方式而发生变化。例如,随着消费电子产品组件小型化的进展,以及这些小型化组件的价格随着时间的推移不断下降,更多的智能手表将不可避免地从外设转向完整的 Android 设备(如 Neptune Pine)。
WatchFaceStyle 的公共构造函数方法 。构建器对象使用一个服务,特别是您的 WatchFaceService 子类,在本例中是一个 ProWatchFaceService 类,以及它的上下文对象(this),作为它的参数,使用以下格式:
WatchFaceStyle.Builder(Service service) // This is the Generic Constructor Method Format
WatchFaceStyle.Builder(ProWatchFaceService.this) // This is our specific Constructor Method Format
一个 Java 这个关键字包含了 ProWatchFaceService 类的上下文,这个上下文对象包含了关于这个类的所有相关信息。
这个 WatchFaceStyle。构建器类包含十个公共方法调用。我将向你展示如何在你的私有引擎类的中实现这些。onCreate( ) 方法。在本章的下一节中,当你把你的 WatchFaceStyle 对象添加到ProWatchFaceService.java类中时,你会做到这一点。
WatchFaceStyle 类中的核心方法是您的**。build( )** 方法,它实际上构建了 WatchFaceStyle 对象,使用了一种只读数据(对象)格式。这是因为 WatchFaceObject 应该在启动时使用。onCreate()方法,然后在 watch face 应用执行期间简单地读取。
。setambientpekmode(int ambientpekmode)方法允许您指定 peek 卡的可见性设置。这将决定当您的表盘处于环境模式时,是否会显示 Peek 卡。该方法使用表 8-2 中列出的前两个常数来确定表盘处于环境模式时是否显示或隐藏 Peek 卡。在这里,您将设置在环境模式下显示 peek 卡。
**。setBackgroundVisibility(int background visibility)**方法允许您指定如何显示 peek 卡的背景。该方法使用表 8-2 中列出的后两个常量来确定是否持续显示 Peek 卡背景。
**。setcardppeekmode(int peekMode)**方法允许你指定当一个表盘显示时,一张 peek 卡将覆盖表盘多远。该方法使用表 8-2 中列出的第三两个常数来确定有多少 Peek 牌将显示在你的表盘屏幕上。
**。setPeekOpacityMode(int peekOpacityMode)**方法允许你指定一个 Peek 卡片背景的不透明度为纯色或半透明背景。该方法使用表 8-2 中列出的第四个两个常数来确定 Peek 卡背景是纯色(不透明)还是半透明。
**。setview protection(int view protection)**方法允许你在 watch faces 屏幕上给 UI 元素添加深色半透明背景效果。该方法使用表 8-2 中列出的最后三个常量来确定状态栏、热门词或两者(整个屏幕)是否将被“保护”
**。sethotworindicatorgravity(int hotworindicatorgravity)**方法允许您设置一个位置常数(或多个常数)以将热门词定位在表盘上。参数使用一个或多个标准重力常数值。
**。setStatusBarGravity(int Status bargravity)**方法允许您设置表盘屏幕上状态栏图标的位置。该参数还使用一个或多个标准 Android 重力常数值。
。setShowSystemUiTime(boolean showSystemUiTime)方法调用允许你指定操作系统是否会在你的表盘设计上绘制时间。该方法的参数是一个简单的真或假值。
。setshowunreadcountimindicator(boolean show)方法允许您设置是否在状态栏图标旁边显示一个指示器,该指示器显示有多少张未读卡片等待读取。该方法的参数是一个简单的真或假值。您将在本章开发的手表 face 应用中使用一个真实值。
建立你的手表表面:使用。setWatchFaceStyle()
接下来让我们构建 WatchFaceStyle 对象,这样您就可以了解如何创建这个必需的 watch face 对象,它定义了您的 watch face 将如何吸收 Android OS 功能。您将把这个对象放在第super.onCreate(surface);行代码之后,这将为手表表面创建 SurfaceHolder 对象。
添加一行代码,键入单词集,启动 IntelliJ 方法助手对话框,如图 8-4 中的所示。找到您的**setWatchFaceStyle(WatchFaceStyle)WatchFaceStyle)**方法,双击它,将其添加到您正在创建的 Java 代码中,以实现 WatchFaceStyle 对象。
图 8-4 。在 super.onCreate()后添加一行代码,键入 set,并选择 setWatchFaceStyle 方法选项
在 setWatchFaceStyle()方法参数区域内,您将为该 WatchFaceStyle 嵌套您的构造函数方法。使用 Java new 关键字的生成器对象。这创建了一个更密集(也更复杂)的 Java 构造,但它也更紧凑,允许您使用不到十几行 Java 编程逻辑来构造和配置 WatchFaceStyle 对象。这甚至可能是一行(非常长的)Java 代码;然而,我将使用 IntelliJ IDEA 中的十几行来格式化它,以大大提高可读性!
setWatchFaceStyle()方法调用的主要 Java 构造和新的 WatchFaceStyle。嵌套在其中的 Builder()构造函数方法看起来像下面的 Java 代码,它也显示在图 8-5 中:
setWatchFaceStyle( new WatchFaceStyle.Builder(ProWatchFaceService.this) );
图 8-5 。添加一行,键入一个句点以弹出方法帮助器对话框,并选择 sethotworindicatorgravity
将光标放在 WatchFaceStyle 之后,按 Return 键添加一行代码。Builder()方法的右括号,在 setWatchFaceStyle 语句的结束括号和分号之前,按句点键,选择一个**。sethotworindicatorgravity(int hotworindicatorgravity)**方法从 IntelliJ 弹出方法帮助器对话框,如图图 8-5 所示。
在参数区输入重力和一个周期,如图图 8-6 所示。
图 8-6 。在参数区域中键入重力对象,并使用句点键调出辅助对象对话框
双击重力。底部常量将 OK Google hotword 放置在表盘设计的底部,如图图 8-6 所示。接下来使用一个竖条 | 字符添加另一个重力。方法调用参数区的 CENTER_HORIZONTAL 常量。这将使热门词汇在表盘设计的底部水平居中。确保在竖线分隔符和重力常量之间没有空格,因为这将被编译器视为多个参数,而不是一个“统一的”重力连接参数。
接下来,让我们添加另一行代码。按下 period 键,从弹出的助手对话框中选择**setShowSystemUiTime(boolean showSystemUiTime)**选项,然后输入 false 的值,因为您将需要使用应用代码来控制所有显示的表盘时间。setWatchFaceStyle()方法结构现在应该类似于下面的 Java 7 编程逻辑:
setWatchFaceStyle( new WatchFaceStyle.Builder(ProWatchFaceService.this)
.setHotwordIndicatorGravity(Gravity.BOTTOM|Gravity.CENTER_HORIZONTAL)
.setShowSystemUiTime(false)
);
接下来,我们来设置 Peek 卡配置设置,从背景可见性方法和常量值开始,如图图 8-7 所示。
图 8-7 。添加一个 setBackgroundVisibility()方法,在里面输入 WatchFaceStyle,选择 BACKGROUND _ VISIBILITY _ INTERRUPTIVE
在下面添加一行代码。setShowSystemUiTime()方法并在您的中添加。setBackgroundVisibility( ) 方法调用。在方法参数区,输入 WatchFaceStyle ,按句点键打开助手对话框。使用此方法的BACKGROUND _ VISIBILITY _ INTERRUPTIVE常量值,当出现 Peek 卡时,允许您的表盘设计至少部分可见。这将使用以下 Java 代码结构来完成:
setWatchFaceStyle( new WatchFaceStyle.Builder(ProWatchFaceService.this)
.setHotwordIndicatorGravity(Gravity.BOTTOM|Gravity.CENTER_HORIZONTAL)
.setShowSystemUiTime(false)
.setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
);
接下来,我们把**加进去。setcardpekmode()和。setpeekocapacitymode()**方法调用完成 Peek 卡如何在交互模式下工作的配置。之后,您可以添加其他四种配置方法。使用表 8-2 中包含的 WatchFaceStyle 常量的 Peek 卡方法调用配置如图 8-8 中的所示,应该类似于下面的 Java 代码:
setWatchFaceStyle( new WatchFaceStyle.Builder(ProWatchFaceService.this)
.setHotwordIndicatorGravity(Gravity.BOTTOM|Gravity.CENTER_HORIZONTAL)
.setShowSystemUiTime(false)
.setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
.setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT)
.setPeekOpacityMode(WatchFaceStyle.PEEK_OPACITY_MODE_TRANSLUCENT)
);
图 8-8 。添加。build()方法调用方法链的末尾来构建 WatchFaceStyle 对象
让我们添加最后四个 Watch Faces style 配置方法和常量,这样您就可以获得一些实践经验,为您的 Watch Faces API 实现和利用所有 Watch Faces style 对象属性。
接下来,您将在表盘处于环境模式时启用 Peek Cards,将状态栏定位在表盘设计的顶部中心,打开状态栏和 Hotword 的视图保护功能,并显示未读 Peek Cards 消息计数器。
必须结束使用 Java 点标记法的方法调用链的最后一个方法调用是。build()方法。请注意,您可以将这些单独的行链接在一起,作为一长行代码。我只是使用点连接符将链中的方法排列起来,以获得更好的可读性,这可以在最终的 Java 构造中看到,如图 8-8 所示:
setWatchFaceStyle( new WatchFaceStyle.Builder(ProWatchFaceService.this)
.setHotwordIndicatorGravity(Gravity.BOTTOM|Gravity.CENTER_HORIZONTAL)
.setShowSystemUiTime(false)
.setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
.setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT)
.setPeekOpacityMode(WatchFaceStyle.PEEK_OPACITY_MODE_TRANSLUCENT)
.setAmbientPeekMode(WatchFaceStyle.AMBIENT_PEEK_MODE_VISIBLE)
.setStatusBarGravity(Gravity.TOP|Gravity.CENTER_HORIZONTAL)
.setViewProtection(WatchFaceStyle.PROTECT_STATUS_BAR|WatchFaceStyle.PROTECT_HOTWORD_INDICATOR)
.setShowUnreadCountIndicator(true)
.build( )
);
接下来,让我们看看 Android(和 Java)计时相关的类,因为这些类提供了获取系统时间和更新表盘设计的功能,所以它可以实时显示正确的时间!
设置表盘时间:与时间相关的类
现在,您已经完全满足了您在第六章中了解到的操作系统功能同化要求,并创建了 Watch Faces SurfaceHolder 和 WatchFaceStyle 对象,它们是形成 WatchFace 设计的核心表面并使用 CanvasWatchFaceService 和 CanvasWatchFaceService 在其上显示所需操作系统功能所必需的。你在第七章中放置的引擎类,是时候学习 Java 7 和 Android 5 类和包中与时间相关的特性了。
这是您需要建立的下一个基础,以便您的表盘设计可以采用系统时间和时区值,并使用您决定实施的任何视觉设计范式将它们转换为表盘设计的表面显示。
Java 时间工具类:时间单位和时区
因为这个 WatchFaces API 旨在创建显示时间的应用,所以让我们仔细看看与时间相关的类。这些可以在 java.util 和 java.util.concurrent 包中找到,它们控制时间单位的转换,如小时、分钟、秒、毫秒、微秒、纳秒等,以及 GMT 时区之间的转换。
在时间单位之间转换:使用时间单位类
公共枚举时间单元类是 java.lang.Enum <时间单元> 类的子类,是 java.util.concurrent 包的一部分。类是一个枚举类,用于枚举 Java 7 应用中使用的时间单位,即提供时间单位的数字表示形式。
这个 TimeUnit 类的 Java 类层次结构如下所示:
java.lang.Object
> java.lang.Enum<TimeUnit>
> java.util.concurrent.TimeUnit
TimeUnit 类(和对象)用于以任何指定的持续时间粒度单位表示持续时间。您将在 WATCH_FACE_UPDATE_RATE 常量中使用它来表示秒和毫秒,在简要讨论了这些 Java 7 TimeUnit 和 TimeZone 类之后,您将把它放入适当的位置。
这个类为开发人员提供了可以在离散时间单位之间转换时间的实用方法,因此得名。您可以使用这些方法来实现使用这些时间单位的计时和延迟操作。
需要注意的是,时间单位本身并不维护时间信息!它只是帮助开发人员组织或使用跨各种上下文对象单独维护的时间表示。因此,TimeUnit 对象可以被认为是一个实时的时间转换过滤器,适用于所有的声音设计师、视频特效编辑或动画师。
支持的时间单位属性在该类中表示为枚举常量。其中包括天、小时、分钟、秒、毫秒、微秒、纳秒。
一纳秒定义为千分之一微秒,一微秒定义为千分之一毫秒,一毫秒定义为千分之一秒,一分钟定义为 60 秒,一小时定义为 60 分钟,一天定义为 24 小时。
TimeUnit 类包含十几个用于控制和转换时间的方法,包括**。托米利斯(长持续时间)**,你将在 ProWatchFaceService.java 课上用到它。
还有半打其他的。此类中的 to()方法,包括。今天( ),。toHours(),。toSeconds(),。toNanos()和。托米克罗斯()。所有的。to( ) TimeUnit 类方法在参数区域采用一个长数据值。
接下来,我们来看看 TimeZone 类,这样你不仅可以将时间转换成不同的单位,还可以转换地球周围的时间!这对表盘设计的国际兼容性非常重要。
时区之间的转换:使用 TimeZone 类
Java 公共抽象时区工具类扩展了一个 java.lang.Object 类。这意味着 TimeZone 工具类是临时编写的,以提供时区支持。该类是 java.util 包的一部分。这个 TimeZone 类有一个已知的直接子类 SimpleTimeZone,但是您将使用这个顶级 TimeZone 类。如果你想知道什么是已知的直接子类,它是 Java API 的一个直接子类。因此,你的直接子类将会是未知的直接子类,直到 Oracle 正式把它们变成 Java 9 的永久部分!
该时区类的 Java 类层次结构如下所示:
java.lang.Object
> java.util.TimeZone
TimeZone 类创建一个对象,用于表示一个时区偏移量。需要注意的是,TimeZone 类还会为您计算出夏令时调整,这很方便。
通常通过调用来获得 TimeZone 对象。getDefault( ) 方法,该方法基于 Watch Face 应用当前运行的时区创建一个 TimeZone 对象。例如,对于在我居住的圣巴巴拉运行的 WatchFace 应用。getDefault()方法基于太平洋标准时间(也称为 PST)创建一个 TimeZone 对象。您可以使用获得一个时区对象的 ID。getID( ) 方法调用,像这样:
TimeZone.getDefault( ).getID( )
如果愿意,还可以通过调用。getTimeZone()方法与。getID()方法调用,使用以下 Java 点链接方法调用结构:
TimeZone.getTimeZone( ).getID( )
为了正确使用这种方法,您需要知道时区 ID 值。例如,太平洋时区的时区 ID 是“美国/洛杉矶”如果您想创建一个加载了太平洋标准时间的 TimeZone 对象,您可以使用下面的 Java 语句:
TimeZone timeZone = TimeZone.getTimeZone("America/Los_Angeles");
如果您不知道所有支持的时区 ID 值,您可以使用**。getAvailableIDs( )** 方法,然后遍历所有支持的时区 ID 值。您可以选择一个受支持的时区 ID,然后获取该时区。如果您想要使用的时区 ID 不是由当前支持的时区 ID 值之一表示的,则开发人员可以指定自定义时区 ID,以便生成自定义时区 ID。自定义时区 ID 的语法是 CustomID : GMT 符号小时:分钟。
当您创建 TimeZone 对象时,它将创建您的自定义时区 ID 属性。此 NormalizedCustomID 将使用以下语法进行配置:GMT 符号(一位数或两位数)小时:(两位数)分钟。其他可接受的格式包括 GMT 标志小时分钟和 GMT 标志小时。
符号可以是加号或减号(连字符),两位数小时的范围在 00 到 23 之间。分钟总是使用两位数字,从 00 到 59。
数字将始终是以下数字之一:0、1、2、3、4、5、6、7、8、9。
小时必须在 0 到 23 之间,分钟必须在 00 到 59 之间。例如,“GMT+10”表示比 GMT 早 10 小时,而“GMT+0010”表示比 GMT 早 10 分钟,因此“GMT+1010”将比 GMT 早 10 小时 10 分钟,依此类推。
时区数据格式与位置无关,数字必须取自 Unicode 标准的基本拉丁块。请注意,不能使用自定义时区 ID 来指定自定义夏令时转换计划,这一点很重要。如果您指定的字符串值与所需的语法不匹配,那么将使用“GMT”字符串值。
当您创建 TimeZone 对象时,它将创建您的自定义时区 ID 属性。这个 NormalizedCustomID 将使用以下语法进行配置:GMT Sign TwoDigitHours : Minutes。
符号可以是加号或减号,一位数小时的范围是从 1 到 9,两位数小时的范围是从 10 到 23。分钟也使用两位数字,范围从 00 到 59。例如, TimeZone.getTimeZone("GMT-8 ")。getID( ) 将返回一个 GMT-08:00 数据值。
TimeZone 类有 21 个方法,我不会在这里详述,但是您将使用。getID()和。Watch Faces 应用代码中的 getDefault()方法。这将允许您获得 Android 用户使用的当前时区及其 id,以便您可以在用户的主机设备(手机或平板电脑)随时切换时区时,在 Watch Faces 应用中切换时区。表盘设计也将反映这一时区的变化。
如果你想更深入地了解所有这些强大的时区方法,你可以访问 docs.oracle.com 网站上的时区 URL:
[`docs.oracle.com/javase/7/docs/api/java/util/TimeZone.html`](http://docs.oracle.com/javase/7/docs/api/java/util/TimeZone.html)
接下来,让我们通过在 ProWatchFaceService 类中使用一些与时间相关的 Java 7 代码,开始实现您已经学习过的一些类。之后,您将了解 Android BroadcastReceiver,以及它如何允许您从主机手机广播(和接收)与时间相关的数据,这些数据可用于设置表盘上的时区(实时)。
保持 Watch Face Time:Watch _ Face _ UPDATE 不变
让我们从添加与计时相关的 Java 代码开始,方法是添加定义表盘更新速率的常数。将使用一个时间单位来完成这项工作,您将为表盘使用一千(毫秒)的计时分辨率,因为您将使用秒针 。
如您所知,TimeUnit 类方法使用 long 数据类型,因为这是一个只在类内部使用的常量,所以让它成为一个私有的静态 final long 变量。您可以将它命名为 WATCH_FACE_UPDATE_RATE,因为这是它将在这个应用的上下文中表示的内容。
您将设置此常量等于时间单位值 1000,这里您将使用时间单位。SECONDS.toMillis(1)构造来设置该值。TimeUnit 类中的 SECONDS 常量表示您将秒作为时间单位来处理,而。toMillis(1)方法调用(和参数)将一秒转换为毫秒值,正如我已经提到的,它是 1000。
这个常量声明语句的 Java 代码应该放在 ProWatchFaceService.java 类的顶部,如图 8-9 所示,应该如下所示:
private static final long WATCH_FACE_UPDATE_RATE = TimeUnit.SECONDS.toMillis(1);
图 8-9 。添加一个私有的静态 final long WATCH _ FACE _ UPDATE _ RATE 变量,并将其转换为毫秒
Android 类:时间、处理器和广播接收器
除了 WatchFaceService 和 WatchFaceService。引擎类,你已经在第七章中对其进行了子类化,我将在第九章的中对其方法进行更详细的探讨,还有一些其他重要的 Android 类也在手表表面设计中使用。我想在本节中详细介绍这些,并在公共 ProWatchFaceService 和私有 Engine 类中实现它们的一些关键方法,以向您展示如何在 Watch Faces 应用设计中使用这些类。
首先从 Android Time 类开始,这个类用来保存当前时间值,精确到秒。接下来,我将讨论 Androidbroadcast receiver类,它用于接收时区变化,最后,我将讨论处理程序类,它用于发送包含更新时区值的消息。
Android 时间类:使用秒的时间处理
Android Time 类是一个公共类,它扩展了 java.lang.Object 类。Time 类的类层次结构如下所示:
java.lang.Object
> android.text.format.Time
这个时间类是 Android 对 java.util.Calendar 和 java.util.GregorianCalendar 类的替代。这就是为什么您要在 Watch Faces API 中使用它。Time 对象(Time 类的一个实例)用于表示时间中的单个时刻,并使用秒来指定时间,就时间精度而言,这对于 Watch Face 应用的使用非常有用。需要注意的是,Time 类不是线程安全的,并且不考虑闰秒。然而,这些特征限制中没有一个给手表表面应用实现带来任何问题。
Time 类有许多日历(特定于日期和日期)问题,如果您将它用于以日历为中心的用途,建议您将 GregorianCalendar 类用于与日历相关的应用。
当使用 time 类执行时间计算时,算法目前使用 32 位整数数字表示。这限制了您从 1902 年到 2037 年的可靠时间范围表示,这意味着如果您将该类用于以日历为中心的表盘设计,您将需要在大约二十年后重写代码。时间对象的许多格式和解析都使用 ASCII 文本,因此,该类也不适合用于非 ASCII 时间处理,这不适用于 WatchFace 应用。
Time 类有三个重载的构造函数方法。其中一个允许您为时间对象指定时区,如下所示:
Time(String timezoneId)
您将使用的构造函数方法,允许您在当前或默认时区中构造一个时间对象,如下所示:
Time( )
第三个构造函数允许您通过将现有时间对象作为参数传递来制作现有时间对象的副本。例如,这应该用于“非破坏性编辑”目的,它看起来像这样:
Time(Time other)
Time 类包含 26 个方法,我不打算在这里详细介绍。因为你只会使用**。和。使用 Watch Faces 应用进行方法调用,我将在这里详细介绍这些方法。如果您有兴趣了解 Time 类中的其他 24 种方法,您可以在 Android 开发者网站上找到这些信息:**
[`developer.android.com/reference/android/text/format/Time.html`](http://developer.android.com/reference/android/text/format/Time.html)
**public void clear(String time zoneid)**方法被添加到 Android 的 API Level 3 中,当被调用时,它重置(清除)所有当前时间值,并将时区设置为 time zone 值,该值是在方法调用参数区域内使用 String 对象及其数据值指定的。
那个。clear()方法调用还会将时间对象的 isDst (是夏令时)属性(或特性)设置为具有一个负值,这表示夏令时是否有效(或无效)是“未知”的。
这个。clear()方法调用通常在。使用 setToNow()或其他 set()方法调用。这将确保在加载时间值之前清除时间对象并将其设置为当前时区。
公共虚空 。setToNow( ) 方法没有参数,当它被调用时,它将时间对象设置为当前时间,使用用户的 Android 操作系统和硬件设备中使用的设置。这个方法也被添加到 API Level 3 中,您将在 clear()方法调用之后使用它,使用下面的 Java 编程语句:
watchFaceTime.clear(intent.getStringExtra("time-zone"));
watchFaceTime.setToNow( );
接下来,让我们在 Watch Face 应用中创建一个时间对象。之后,我将使用 Android BroadcastReceiver 类研究如何在主机(智能手机或平板电脑)和智能手表之间广播时区数据值。然后,我将使用 Handler 类和您将在 updateTimeHandler 对象中创建的自定义消息传递,深入了解系统时间本身是如何发送到智能手表的。
添加手表时间对象:手表时间
添加一个 Time 对象作为引擎类的第一行代码。选择图 8-10 中助手对话框中看到的 android.text.format 时间版本。
图 8-10 。在引擎类的顶部添加一个时间对象声明;选择 android.text.format 版本
将您声明使用的时间对象命名为 watchFaceTime 。一旦 Java 语句就绪,使用 Alt+Enter 快捷键和importTime 类。选择一个 android.text.format.Time 版本,如图图 8-11 所示。
图 8-11 。命名一个时间对象 watchFaceTime,用 Alt+Enter 导入时间类;选择 android.text.format.Time
既然您已经声明了 watchFaceTime 时间对象,那么是时候使用 Java new 关键字来构造对象了。您将在 onCreate()方法中完成这项工作,因为这是创建供 watch face 应用使用的内容的最佳位置。
构建时间对象的 Java 编程语句很简单,如图 8-12 中突出显示的,它应该如下所示:
watchFaceTime = new Time( );
图 8-12 。在 Engine 类中,使用 Java new 关键字构造 watchFaceTime 时间对象
既然保存您的时间值的 Time 对象已经就绪,让我们看看如何广播当前时区设置。这样,如果用户正在旅行,或者手动更改时区,它将立即反映在表盘时间显示中。
Android 的 BroadcastReceiver 类:广播时间消息
Android 的公共抽象 BroadcastReceiver 类扩展了 java.lang.Object,有四个已知的直接子类:WakefulBroadcastReceiver、AppWidgetProvider、DeviceAdminReceiver 和 RestrictionsReceiver。这个 BroadcastReceiver 类的 Java 类层次结构如下所示:
java.lang.Object
> android.content.BroadcastReceiver
BroadcastReceiver 类提供意图广播(传递)方法。这个类提供了一个基础设施,允许你的手表在时区改变时接收 Android 操作系统发送的意图。
您将通过实现一个**private void****registerTimeZoneReceiver()**方法在您的 watch face 应用中动态注册该类的一个实例。每当观察面进入睡眠(不可见)然后醒来时,将调用此方法,设置 updateTimeZoneReceiver 标志并发送一个加载有 ACTION_TIMEZONE_CHANGED 常量的 Intent 对象。
然后,这个 TimeZoneChange 意图将确定用户的时区是否已经更改(自从他们的手表进入睡眠和醒来),然后通过使用。registerReceiver()方法。在本章的下一节中,您将创建这个时区接收器广播接收器。TimeZone receiver broadcast receiver 对象会将您的时间对象设置为 Time zone 对象。这将确保您的表盘使用的时区始终是最新的。
这个 BroadcastReceiver 类有一个基本的 BroadcastReceiver( ) 构造函数方法调用,它有 18 个方法。我不会在这里详细介绍所有这些,但是,我会介绍一下**。onReceive( )** 方法,您将在 Watch Face 应用中使用该方法。如果您想详细研究这个类,可以访问 Android 开发者网站,网址是:
[`developer.android.com/reference/android/content/BroadcastReceiver.html`](http://developer.android.com/reference/android/content/BroadcastReceiver.html)
这个公共抽象 void****on receive(Context Context,Intent intent) 方法带两个参数。第一个是名为 Context 的上下文对象,它包含运行 BroadcastReceiver 的(私有)引擎类的上下文对象。第二个是名为 intent 的 Intent 对象,这是您的 timeZoneReceiver 对象将接收的 Intent 对象。这个方法是源自 API 级别 1 的原始 Android 方法,因为 BroadcastReceiver 对象是 Android 的核心构件。
调用此的方法。onReceive()方法,当你希望你的 BroadcastReceiver 对象接收意图广播时,registerReceiver。**。register receiver(broadcast receiver,IntentFilter)方法也有。unregister receiver(Broadcast receiver)**的对应物,这两者都将在本章中实现,以创建一个时区广播系统。
同样重要的是要注意,你不能在实现中启动任何弹出对话框。onReceive()方法,以及。onReceive()方法应该只响应已知的 操作系统动作 ,比如您将要使用的 ACTION_TIMEZONE_CHANGED 。实现应该忽略任何“意外的”(操作系统未知的)或自定义的意图。onReceive()方法实现可能会收到。
现在让我们实现 time zone receiver broadcast receiver 及其 public void。onReceive( ) 方法放在私有引擎类的内部。
添加时区 BroadcastReceiver 对象:timeZoneReceiver
让我们在名为 watchFaceTime 的时间对象之后添加一个名为 timeZoneReceiver 的最终 BroadcastReceiver 对象,并使用 Java new 关键字,键入 BroadcastReceiver 的前几个字母以显示方法助手对话框。双击 broadcast receiver(Android . content)选项,将该构造方法插入到您的 Java 代码中,如图图 8-13 所示。
图 8-13 。在 watchFaceTime 对象之后添加最后一个名为 timeZoneReceiver 的 BroadcastReceiver 对象
单击 Java 7 代码行左侧红色错误灯泡旁边的下拉箭头指示器。选择实现方法选项,如图 8-14 左侧所示,在选择方法实现对话框中,如图 8-14 右侧所示,选择 onReceive(context:Context,intent:Intent):void ,点击 OK 按钮。
图 8-14 。添加 Java new 关键字和 BroadcastReceiver()构造函数方法;选择实施方法
单击 OK 后,IntelliJ IDEA 将为这个public void on receive(Context Context,Intent intent){ } 方法实现创建一个空的引导方法结构。现在,您需要做的就是清除 watchFaceTime 时间对象,并在名为 intent 的 Intent 对象中加载新的时区信息,该对象被传递到方法中。使用访问时区字符串。getStringExtra( ) 方法使用名为“time-zone”的额外数据字段访问该时区数据,从而被意外调用。
完成之后,您所要做的就是使用将您的 watchFaceTime 对象设置为当前系统时间。setToNow( ) 方法调用,您的 Time 对象现在被设置为正确的时区和时间信息。您的 Java 代码。onReceive()方法实现应该类似于下面的 Java 7 方法结构:
@Override
public void onReceive(Context context, Intent intent) {
watchFaceTime.clear(intent.getStringExtra("time-zone"));
watchFaceTime.setToNow( );
}
正如你在图 8-15 中看到的,代码是无错的,你可以添加一个布尔标志变量,它会告诉你什么时候你的表盘进入睡眠状态,当它再次醒来时需要检查你的操作系统时区设置!接下来我们就这么做,然后我会解释更多关于 Android Handler 类的内容。
图 8-15 。在 onReceive()方法中添加。清除( )和。setToNow()方法调用 watchFaceTime 对象
关于这个 timeZoneReceiver,您需要做的最后一件事是创建一个布尔“flag”变量,它会告诉您何时需要更新这个 timeZoneReceiver。
让我们将标志命名为 updateTimeZoneReceiver 并将其设置为初始值 false ,如图图 8-16 所示,因为假设手表表面最初没有进入睡眠状态,并且。不需要调用 timeZoneReceiver 对象中的 onReceive()方法(如果将其设置为 true,则需要调用)。这个布尔标志需要以假开始。后来,在**。registerReceiver( )** ,这个布尔标志将被设置为 true,并且在中。unRegisterReceiver( ) ,这个布尔标志将被设置回 false 以取消注册 BroadcastReceiver。
boolean updateTimeZoneReceiver = false;
图 8-16 。在 timeZoneReceiver 之后添加一个布尔值 updateTimeZoneReceiver 变量,该变量的值设置为 false
这个布尔 updateTimeZoneReceiver 最初被设置为假值,指示 BroadcastReceiver 没有被注册使用。在下一节中,您将创建**。registerTimeZoneReceiver( )** 方法,该方法将该值设置为 true ,表示正在使用 BroadcastReceiver。
然后,您的 TimeZone receiver broadcast receiver 对象将更新 time zone 对象值,然后更新您的**。unregisterTimeZoneReceiver()**方法,也将在本章中创建,将把这个布尔值设置回 false。
让我们开始创建 register 和 unregister、TimeZoneReceiver 方法,这样我就可以进入 Handler 类和更多与时间相关的代码了!
调用您的时区接收器对象 : registerTimeZoneReceiver()
在布尔型 updateTimeZoneReceiver 变量下,创建**private void registerTimeZoneReceiver()**方法结构,并在其中创建 if()条件语句。在该 if()语句的求值区域内,键入 updateTimeZoneReceiver 对象的前几个字母,然后通过双击它从弹出的帮助器对话框中选择它。该选项在图 8-17 的底部以蓝色突出显示。
图 8-17 。创建一个私有的 void registerTimeZoneReceiver()方法,并通过键入 update 的前几个字母来添加一个 if 条件
完成该方法的初始条件 if()结构,如果您的 updateTimeZoneReceiver 布尔标志已经设置为 true 值,则提供该方法的出口。**if(updateTimeZoneReceiver)**构造等同于 true,而 if(!updateTimeZoneReceiver) 构造等同于 false,因为您将在。unregisterTimeZoneReceiver()方法。
因此,如果 updateTimeZomeReceiver 布尔标志已经设置为 true 值,下面的条件 if()语句结构将“脱离”此方法,也就是说,不做任何其他事情就退出此方法:
if( updateTimeZoneReceiver ) { return; }
所以这里的逻辑是,如果接收者已经注册了,就不需要这个方法,所以让我们通过使用 Java return 关键字来退出。
一旦您确定您的 updateTimeZoneReceiver 布尔标志被设置为 false 值,由于您在上一节中创建的变量和初始化语句,它将出现在应用的开始处,您将通过使用下面的 Java 语句将布尔标志设置为一个 true 值,这可以在图 8-18 中看到:
updateTimeZoneReceiver = true;
图 8-18 。如果 updateTimeZoneReceiver 为真,则返回(退出方法);然后将 updateTimeZoneReceiver 设置为 true
你需要做的下一件事是创建你的 Intent 对象,它装载了ACTION _ time zone _ CHANGEDIntent,这将告诉 Android OS 你希望从用户的系统设置区域确定什么。
这可以通过使用 Android IntentFilter 类来完成,因此您将需要创建一个复杂的 IntentFilter Java 语句来声明该类的用法,将 IntentFilter 对象命名为 intentFilter ,使用 IntentFilter( ) 构造函数方法来构造该对象,最后通过使用 Intent 来加载它,并更改时区意图。ACTION_TIMEZONE_CHANGED 引用意图类(对象)常数值。
键入如图图 8-19 所示的 Java 语句,使用弹出的帮助器对话框查找正确的 Android 操作系统意图常量。当你键入单词 ACTION_ 时,这些填充助手对话框列表的意图常量就是我之前提到的已知操作系统动作,因为它们是使用意图类在 Android 操作系统中“硬编码”的。这就是常量的“引用路径”使用格式意图的原因。ACTION_TIMEZONE_CHANGED,因为常量在该类中。
图 8-19 底部显示的 Java 代码应该是这样的:
IntentFilter intentFilter = new IntentFilter( Intent.ACTION_TIMEZONE_CHANGED );
图 8-19 。添加一个名为 IntentFilter 的 intentFilter,使用新关键字构造它,然后键入 Intent。动作 _T
最后,你需要使用**。registerReceiver( )** 方法调用来注册您的时区接收器 BroadcastReceiver 对象。这将一次性启用 timeZoneReceiver 对象,并将它发送到您刚刚在 IntentFilter 类中创建的 Intent 对象上。
这是通过使用下面的 Java 7 语句完成的,可以在图 8-20 中看到,它是使用 IntelliJ 弹出助手对话框创建的:
ProWatchFaceService.this.registerReceiver( timeZoneReceiver, intentFilter );
图 8-20 。从 ProWatchFaceService 类的上下文(此关键字)中调用. registerReceiver()方法
这就叫**。registerReceiver** 方法,为您的 ProWatchFaceService 类关闭上下文对象,使用 Java this 关键字和点符号引用一个上下文对象,在编程语句的第一部分使用 ProWatchFaceService.this 。在您的方法调用参数区域中,您将传递一个time zone receiverbroadcast receiver 对象,以及您在前面的代码行中使用ACTION _ time zone _ CHANGEDIntent 对象创建和加载的 intentFilter 对象。
要创建它,键入 ProWatchFaceService.this 和一个周期,并选择**。注册 receiver(broadcast receiver receiver,IntentFilter filter)** 选项,将方法调用插入到你的编程语句中,如图图 8-20 底部所示。
现在,您所要做的就是将 timeZoneReceiver 对象指定为该方法的 BroadcastReceiver,并指定您创建的包含 ACTION_TIMEZONE_CHANGED Intent 对象的 intentFilter 对象。在你完成这些之后,你的 Java 代码将是没有错误的,如图 8-21 所示。registerTimeZoneReceiver()方法已准备就绪!
图 8-21 。在。registerReceiver()参数区域,调用带有 intentFilter 意图的 timeZoneReceiver 对象
接下来,您需要创建**。unregisterTimeZoneReceiver()**方法,该方法将注销 BroadcastReceiver 对象并将 updateTimeZone 布尔标志变量设置回其原始的 false 值。
调用您的时区接收器对象:registerTimeZoneReceiver()
在你个人的空虚之下。registerTimeZoneReceiver()方法,创建一个空的**private void unregisterTimeZoneReceiver()**方法结构,用下面的 Java 关键字和代码,如图 8-22 底部所示:
private void unregisterTimeZoneReceiver( ) { your custom method code will go inside here }
图 8-22 。在 registerTimeZoneReceiver 方法后添加一个私有的 void unregisterTimeZoneReceiver()方法
在这个方法内部,创建另一个条件 if()结构 。在 if()语句的求值区域内,为 updateTimeZoneReceiver 对象键入 false 值,如果(!idspninfopath _ NV),该值将是**。updateTimeZoneReceiver)** ,然后在大括号内的 return 语句为条件 if()语句的主体。如果您的布尔标志已经为假,这将退出该方法。否则,下一行代码将该标志设置为 false,这样当您注销接收方时,它将被设置回 false。代码结构应该如下所示,如图 8-23 中的所示:
private void unregisterTimeZoneReceiver( ) {
if(!updateTimeZoneReceiver) { return; }
updateTimeZoneReceiver = false;
}
图 8-23 。添加一个条件 if(!updateTimeZoneReceiver)语句与 return 语句一起使用
接下来键入 ProWatchFaceService.this 上下文对象和一个周期,并选择**unregister receiver(broadcast receiver)**对话框选项,如图 8-24 中所示。
图 8-24 。将方法调用添加到。unregisterReceiver()关闭你的 ProWatchFaceService.this 上下文对象
在里面。unregisterReceiver()方法调用,传递 time zone receiver broadcast receiver 对象。这将取消注册 BroadcastReceiver 对象,从而允许再次使用该 BroadcastReceiver 对象来轮询您的手表界面用户使用的当前时区,如图 8-25 所示。
图 8-25 。将 time zone receiver broadcast receiver 对象作为。unregisterReceiver( )
您将在下一章实现这两个方法,那时我将介绍用于 Watch Faces API 的核心 WatchFaceService 类和方法。
摘要
在本章中,您学习了一些用于 Watch Faces 应用开发的 Java 工具类和 Android 类。它们包括 SurfaceHolder 类,你用来为你的手表表面创建表面,以及手表表面风格。Builder 类,您用它来为手表表面设计所需的 Android UI 元素。
接下来,我深入研究了 Android 时间相关的类,包括时间、时间单位和时区。我还解释了 BroadcastReceiver 类以及 Intent 对象,并且您学习了如何使用。 registerReceiver( ) 和**。unregisterReceiver( )** 控制 BroadcastReceiver 对象的方法。在下一章中,您将使用这个时区广播系统,在每次表盘进入睡眠状态时检查用户的时区。
在下一章中,你将了解核心的 WatchFaceService。发动机方法。
九、实现表盘引擎:核心表盘 API 方法
既然已经为 WatchFace 应用准备好了 Canvas SurfaceHolder、WatchFaceStyle、TimeZone、TimeUnit、Time 和 BroadcastReceiver 对象,就可以开始学习实现 WatchFaces API 的高级核心方法。
本章将深入探讨 CanvasWatchFacesService 和 CanvasWatchFacesService 的超类。ProWatchFaceService 类和 Engine 类所基于的引擎。了解一下 WatchFaceService 和 WatchFaceService 很重要。Engine 类,因为它们包含正确实现 WatchFaces API 所需的方法、属性和常量。
WatchFaceService 类包含您将在 Watch Face 应用中使用的常量,以及 WatchFaceService。引擎嵌套类包含 14 个方法,其中一半以上将在私有引擎类内的 ProWatchFaceService 公共类中实现。
本章的大部分讨论将集中在这些方法上,这些方法需要在核心 Watch Faces API 引擎功能的私有引擎类中实现。首先,我将解释如何使用 Android 处理程序类在交互模式下每秒更新一次手表表面时间。然后我来解释安卓消息和系统。在本章中,你将会学到大量的 Android 类和方法,所以让我们开始实现核心的 WatchFaces API 方法。
WatchFace 秒时间引擎:使用处理程序
在我进入核心的 WatchFaceService 之前。引擎方法,还有一些与秒针时间动画相关的对象和方法需要你去实现。
在 Android 中,每秒更新一次秒针的方法是使用一个 Handler 对象。出于这个原因,我将给你一个 Android Handler 类的概述,然后你将创建一个updateTimeHandlerHandler 对象和一个**。handleMessage( )** 实现表盘秒针时间更新编程逻辑的方法结构。
Android 的 Handler 类:处理时间更新消息
Android Handler 类是一个扩展 java.lang.Object 的公共类,这意味着它是临时编写的,用于实现 Android 操作系统的线程和消息处理。处理程序类的类层次结构如下所示:
java.lang.Object
> android.os.Handler
这个处理程序类有四个已知的直接子类:HttpAuthHandler、AsyncQueryHandler、AsyncQueryHandler。WorkerHandler 和 SslErrorHandler。
Handler 类(object)将允许您发送和处理与应用线程及其消息队列相关联的消息或可运行对象。您将使用一个消息对象来实现 updateTimeHandler 处理程序对象及其**。handleMessage( )** 方法。
每个处理程序对象实例将与一个单独的线程和该线程对象的 MessageQueue 对象相关联。当您创建新的 Handler 对象时,它被“绑定”到一个 Thread 对象和 MessageQueue 对象,这两个对象属于创建 Handler 对象的 Thread 对象。一旦构造了处理程序,该处理程序对象将为 MessageQueue 对象传递消息(和 runnables ),并在它们离开 MessageQueue 时对它们进行处理。
Handler 对象有两个主要用途:调度要处理的消息或要在未来某个时间执行的 runnables,以及将需要在应用主线程之外的线程上执行的操作排队。在本章的下一节,您将在 Watch Faces API 实现中使用前者。调度消息是使用. post(Runnable)方法完成的。postAtTime(Runnable,long)方法。postDelayed(Runnable,long)方法。senemptymessage(int)方法,。sendMessage(消息),。sendMessageAtTime(Message,long)方法或 sendMessageDelayed(Message,long)方法。
这里您将使用**。sendmentymessage()方法在中调用。updateTimer( )** 方法,您将在实现 Handler 对象后编写该方法。
这些方法的 post 版本将允许您在 MessageQueue 对象收到可运行对象后对其进行排队,而这些方法的 sendMessage 版本允许开发人员对包含数据绑定对象的消息进行排队,这些数据绑定对象可以由处理程序对象的 handleMessage(Message) 方法进行处理,您需要在处理程序对象中实现这些方法。你将在这一章的下一节做那件事。
当向一个处理程序对象提交或发送时,您可以允许在 MessageQueue 对象准备就绪时立即处理该项,或者您可以指定一个延迟(在它被处理之前)或它被处理的绝对时间。这里您将使用前者,但是后两者将允许您实现超时、滴答或其他基于时间的行为。
当为 WatchFaces 应用创建一个进程时,主线程专用于托管这个 MessageQueue 对象。该对象管理“顶级”应用对象(Activity、BroadcastReceiver、Service)以及它们可能需要创建的任何显示窗口。
您可以创建自己的线程,并使用处理程序与主应用线程进行通信。在本章的例子中,您将创建一个 updateTimeHandler 。这是通过调用与以前相同的 post 或 sendMessage 方法来完成的,但是是从一个新的线程中。然后,消息(或 Runnable)将被调度到您的处理程序对象的 MessageQueue 对象中,并在使用消息对象请求时进行处理,至少在本章示例的实现中是这样。
Handler 类有一个嵌套类,名为 Handler 的公共静态接口。android.os 包中的回调。这个嵌套类提供了处理程序回调接口,您将在实例化处理程序时使用该接口,以避免必须实现您自己的自定义处理程序子类。这个嵌套类有一个方法,。handle Message(Message Message),您将在 Java 代码中使用它来编写 updateTimeHandler 对象。
Handler 类有四个重载的公共构造函数方法,您将在本章的 Watch Faces API 实现中使用它们。您将使用的最简单的方法如下所示,包括 Java new 关键字:
final Handler updateTimeHandler = new Handler( ) { Message Handling code will go in here }
如果你构造了处理程序。回调对象,可以使用以下构造函数方法格式:
Handler(Handler.Callback callback)
您还可以使用以下处理程序构造函数方法格式提供您自己的自定义消息 Looper 对象:
Handler(Looper looper)
Watch Faces API 实现不需要自定义 Looper 对象,它允许您为自定义线程创建 MessageQueue,因此我不会在本书中深入讨论这一点。也就是说,为了完整起见,我将在这里详细介绍处理程序构造函数方法。
第四个也是最后一个构造函数方法提供消息 Looper 对象并接受一个处理程序。回调对象中的同一个构造函数方法调用,如下面的代码所示:
Handler(Looper looper, Handler.Callback callback)
Handler 类支持 30 种方法,我显然不能在这里详细介绍,所以我将在本章下一节的代码中介绍三种方法。如果您想详细研究所有 30 个,可以访问下面的开发人员网站 URL:
http://developer.android.com/reference/android/os/Handler.html
您将在 updateTimeHandler 对象中实现的处理消息的主要方法是,您猜对了,。handleMessage( ) 方法。这个**公共 void handle Message(Message msg)**方法将采用。handle Message(Message yourmessagename here)通用格式。该方法被添加到 API Level 1 中,因为它是 Android 操作系统的核心特性和功能。
您将使用另外两个 Handler 类方法从 updateTimeHandler 对象的“外部”与 Handler 对象对话,并在不再需要消息对象时将其从 MessageQueue 对象中删除。
**public final boolean senemptymessage(int what)**发送一个带有 what 数据值的空消息对象。这个方法也被添加到 API Level 1 中,如果一个消息对象被成功地放置到 MessageQueue 对象中,它将返回一个 true 值。正如您可能已经猜到的那样,如果失败,它将返回假值。
另一方面,**public final void remove messages(int what)**方法将删除任何和所有对消息对象的 what 属性使用整数代码的消息对象的未决调度。在 watch faces 应用的情况下,这将是零,它当前位于 MessageQueue 对象内部。接下来让我们看一下 Android Message 类,这样您将对将要使用的所有对象类型有一个总体的了解。
Android 的消息类:创建时间更新消息
Android Message 是一个 public final 类,它扩展了 java.lang.Object master 类,这意味着它是临时编写的,用于保存 Android 操作系统使用的消息对象。类的层次结构如下所示:
java.lang.Object
> android.os.Message
Message 类允许您创建、定义和实例化 Message 对象,该对象将包含您的消息描述以及任何需要发送到 Handler 对象的任意数据。在本例中,这将是您的 updateTimeHandler。
消息对象包含两个额外的 int 字段和一个额外的对象字段,允许您在大多数使用中避免使用任何额外的内存分配。鉴于消息类的构造函数方法是公共的,正如 Java 编程语言所要求的,创建消息对象的最佳代码是调用 Message.obtain(),或者在这里的示例中,调用**handler . sendemptymessage()**方法。这将从“回收的”消息对象的“池”中提取消息对象。
消息对象有许多数据字段。它们提供了属性、特性或数据字段(选择一个最适合您使用的术语),您可以用它们来描述消息对象的内容,包括 arg1、arg2、obj、replyTo、sendingUid 和 what。这里您将使用 what 属性来发送消息对象中的更新时间消息代码。
如果只需要存储几个整数值, public int arg1 属性是使用 Message.setData( ) 方法的“低开销”替代方法。一个 public int arg2 也用于提供两个整数数据值。
公共对象 obj 给你一个任意的对象作为消息对象的一部分,这样你就可以发送一个对象给消息对象的接收者。公共信使 replyTo 为你提供了一个(可选的)信使对象。Messenger 对象是另一种类型的对象,它允许实现基于消息的进程间通信。这允许在一个进程中创建指向(引用)处理程序的信使对象,并允许该处理程序跨另一个进程处理信使对象。
public int sendingUid 为您提供了一个可选字段,指示首先发送消息对象的 Uid(用户标识)。 public int what 用于提供开发者定义的消息代码。您将在代码中使用它来允许接收者(Handler 对象)能够识别消息是关于什么的,在本例中,观察面部计时。
Android Message 类有一个公共构造函数方法, Message( ) 构造函数;但是,获取消息对象的首选方式是调用 Message.obtain()或 Message.sendMessage()方法调用之一,这将为您构造消息对象。
这个 Message 类有将近 24 个公共方法,在这里的 watch faces 开发中,您不会用到其中的任何一个,但是如果您有兴趣了解关于这些方法的更多信息,可以在开发人员的网站上找到详细信息:
http://developer.android.com/reference/android/os/Message.html
创建秒针计时器:更新时间处理器
我们通过键入 Java 关键字 final 和类名(对象名) Handler 并在弹出的帮助器对话框中选择 Handler (android.os) 选项来创建最终的updateTimeHandlerHandler 对象,如图图 9-1 所示。注意还有一个 java.util.logging.Handler 类,这就是我使用这个工作流程的原因,所以一定要选择正确的处理程序。
图 9-1 。使用弹出的帮助器对话框添加最终的处理程序对象声明,以找到 android.os 处理程序
一旦双击 android.os.Handler 类将其插入,您就已经将 Handler 对象声明为 final。这确保了它只被分配(到内存)一次,因此在应用运行期间,它被固定在系统内存的相同内存位置。
接下来,您将使用 Java new 关键字和 Handler()构造函数方法调用来完成名为 **updateTimeHandler、**的 Handler 对象。产生的空处理程序对象结构应该类似于下面的 Java 代码:
final Handler updateTimeHandler = new Handler( ) { // An Empty Handler Object Structure };
正如你在图 9-2 中看到的,updateTimeHandler 对象的代码是无错的,你已经准备好编写公共 void 了。handleMessage( ) 方法。此消息处理方法是在最终的 updateTimeHandler 处理程序对象的大括号内创建的,该对象实现了 updateTimeHandler 处理程序对象的消息对象处理功能。如前所述,这是实现所需的方法。一旦这样做了,处理程序对象就是“可行的”,其他方法可以从它调用。
图 9-2 。使用 Java new 关键字和 Handler()构造函数方法创建一个空的 updateTimeHandler
在 updateTimeHandler 对象内部,您将添加一个**@ Override public void****handle Message(Message)**方法结构,在弹出窗口中选择 android.os.Message 选项,如图图 9-3 所示,使用下面的 Java 代码:
@Override
public void handleMethod(Message updateTimeMessage) { // Message object evaluation goes in here }
图 9-3 。编写一个公共的 void handleMessage(Message)方法,使用弹出菜单选择 android.os 消息
一旦你创造了空的。handleMessage()方法结构,如图图 9-4 所示,您将需要创建一些 Java 代码来评估名为 updateTimeMessage 的消息对象。这个消息对象将被自动传递到。每当 handleMessage()方法引用处理程序对象时。sendEmptyMessage()方法调用。这种引用的一个例子可以在下面这行 Java 代码中看到:
updateTimeHandler.sendEmptyMessage(UPDATE_TIME_MESSAGE); // Send TimeZone Update Message to Handler
图 9-4 。带有 handleMessage()处理结构的(空)updateTimeHandler 处理程序对象现在已经就绪
实际上,在本章的后面,您将会创建这一行代码。
这个过程的下一步是评估传递到。handleMessage()方法使用一个 Java 开关评估结构。这个结构将为每个消息对象的什么属性提供 case 语句。
消息对象使用 Java 点标记法可以引用什么属性或特性。对于 updateTimeMessage 消息对象,这将类似于 updateTimeMessage.what ,并且稍后将在 switch()语句的评估参数区域中引用,使用以下 Java 结构:
switch(updateTimeMessage.what) { your case statements evaluating what attributes will go in here }
正如您在图 9-5 中所看到的,一旦您在评估参数区域中键入 switch()语句和 updateTimeMessage 消息对象,并按下句点键,您将看到一个弹出的帮助器对话框,其中包含您在本章上一节中了解到的属性。双击 what 属性,将其添加到交换机中。
图 9-5 。添加一个 Java switch 语句来计算 updateTimeMessage 对象的 what 属性
接下来您需要做的事情是在私有引擎类的顶部创建一个更新时间消息的 what 属性整数数据常量,这样您就可以在添加 case 评估语句之前准备好它。用于定义常数的 Java 关键字是 static 和 final ,因此您的 Java 编程语句声明了一个设置为初始零数据值并命名为 UPDATE_TIME_MESSAGE 的整数常数,如下所示:
static final int UPDATE_TIME_MESSAGE = 0;
创建一个评估 what 属性的 Java switch 语句,从 updateTimeMessage 对象中调用,并在 UPDATE_TIME_MESSAGE 常量事例评估器中调用 invalidate()方法,然后中断 switch 语句。
switch(updateTimeMessage.what) {
case UPDATE_TIME_MESSAGE:
invalidate( );
break;
}
正如你在图 9-6 中看到的,你的 Java 代码现在没有错误,你已经准备好实现更多高级功能,当智能手表进入环境模式时,这些功能就会发挥作用。
图 9-6 。声明一个 UPDATE_TIME_MESSAGE 整数有价值,并为其添加 switch case 语句
现在是时候在 onDraw()方法体中使用 watchface Canvas 对象了。你会叫一个**。drawColor(int color)** 方法关闭此对象将画布的颜色设置为黑色。当然,这优化了表盘设计的功耗!你在第六章学得不错!
观看面孔时间计算:使用系统时间
您将制作的下一个 Java 代码构造将查看表盘模式(可见或开或关,以及环境与交互),然后您将确定如果表盘可见并处于交互模式,是否需要 updateTimeHandler 来间接更新表盘设计。如果需要 updateTimeHandler,将以毫秒为单位的系统时间将用于计算下一整秒(相当于 1,000 毫秒的精确值),并以。onDraw()方法,你将在第十章中编写这个方法。
Java 系统类:以毫秒为单位的访问时间
Javapublic final Systemclass 扩展了 java.lang.Object master class,并提供对系统相关信息和资源的访问,包括您将使用的系统时间函数,以及标准系统 IO(输入和输出)。系统类层次结构如下所示:
java.lang.Object
> java.lang.System
该类中的所有方法都是以静态方式访问的,并且该类本身不能被实例化。所以不能有任何系统对象,但是会通过使用 System.methodCallName()方法引用系统函数。因此,要使用毫秒来引用系统时间,您的方法调用结构应该类似于下面的 Java 代码,您将在本章的下一节中使用它:
System.currentTimeMillis( )
接下来,让我们使用自定义的 isTimerEnabled( ) 方法编写代码,确定何时需要为您的表盘计算秒针或秒值。这样做之后,您可以实现这个方法来告诉您何时需要在 Handler 对象的 handleMessage( ) switch 语句的 Java 编程结构中计算秒针计时。
手表表面秒:计算秒针运动
您需要做的第一件事是创建一个方法,如果手表表面当前可见并且没有使用环境模式(这意味着它处于交互模式并且被用户查看),该方法将返回一个 true 值。在 updateTimeHandler 对象下添加一行代码,声明一个私有布尔 isTimerEnabled( ) 方法,如图图 9-7 所示。在方法体内,键入 Java return 关键字,然后单词就是,然后双击 isVisible( ) method 选项,当它出现在 IntelliJ 弹出的方法选择器帮助器对话框中时,将其插入到您的代码中。
图 9-7 。通过键入 is,然后选择 isVisible(),创建带有 return 语句的私有布尔 isTimerEnabled()方法
接下来,输入 Java 逻辑 AND 运算符(&&)和 Java 逻辑 NOT(!)运算符,然后键入单词 is。这将弹出一个 IntelliJ 弹出方法帮助器对话框,您可以在其中选择一个**isinembientmode()**方法 选项,如图 9-8 中蓝色部分所示。接下来,双击该方法将其插入到代码中,并添加分号以完成 return 语句。你的代码应该没有错误,如图 9-9 所示。
图 9-8 。键入 Java 的&和&逻辑运算符 AND,and the not!符号,并且是,则选择 isInAmbientMode()
现在,您可以在条件 if()结构中调用这个 isTimerEnabled()方法,以确定智能手表是否在交互模式下运行。如果此方法返回一个 true 值,您可以将系统时间计算到最接近的整秒(精确到 1,000 毫秒)值,当秒正好出现在系统时钟中时,移动秒针。
在 UPDATE_TIME_MESSAGE 的 case 语句的 switch 构造中,在 invalidate()方法调用之后和 Java break 关键字之前添加一行代码。您需要首先将空的条件 if()语句放置到位,然后创建完整的第二个计时逻辑。
评估是否需要计算第二个计时的空条件 if()语句应类似于以下 Java 代码:
If( isTimerEnabled( ) ) { Your Whole Second Timer Programming Logic Will Go In Here }
正如你在图 9-9 中看到的,你的代码是没有错误的,你已经准备好添加条件 if()语句来计算系统时间到最接近的整秒值,这将触发秒针在用户的表盘设计上再滴答一次。
图 9-9 。在 invalidate()之后和 break 关键字之前添加 if(isTimerEnabled( )){ }求值构造
对于这个最接近的整秒时间计算,您需要做的第一件事是使用 System.currentTimeMillis()方法调用获得系统时间,并将其赋给一个 long 变量,您将把它命名为 msTime 。
在条件 if()语句体中,声明一个 long msTime 变量,然后键入等于 (=)运算符和系统类名。然后按句号键,弹出 IntelliJ 弹出方法帮助器对话框,如图图 9-10 所示。双击**current time millis()**method 选项,并将其插入到 Java 代码中,以完成声明、命名和设置该 msTime 变量的值的语句。
图 9-10 。通过键入 equals、System 和 period 声明一个名为 msTime 的长变量。然后选择 currentTimeMillis()
接下来,您需要计算一个延迟值,它代表当前系统时间和下一个 1000 毫秒值之间的毫秒数。这将代表下一个偶数(精确)秒,也就是你希望表盘设计中的秒针前进到表盘上的下一个刻度线的精确时间值。
如图 9-11 中的所示,您需要添加另一个名为 msDelay 的 long 变量,它将保存计算出的延迟值。这将表示直到下一个偶数毫秒发生之前的毫秒数。
图 9-11 。声明一个名为 msDelay 的长整型变量,然后键入= WATCH,select WATCH_FACE_UPDATE_RATE
键入 Java long 变量类型和 msDelay 变量名称,以及 equals 运算符和 WATCH_FACE_UPDATE_RATE 的前几个字母,这是在图 9-11 顶部看到的变量,它保存了每秒 1000 毫秒的时间分辨率值。
找到 WATCH_FACE_UPDATE_RATE 选项,IntelliJ 很可能将它放在列表的顶部,双击它将其插入到 Java 代码中。现在你所要做的就是找到当前时间和下一个完整秒之间的“delta”或毫秒差,你就完成了。
找到 delta 或时间延迟值的关键在于 Java 模操作符。为了计算在调用下一个完整的秒针前进之前等待的 msDelay 时间,取 1,000 毫秒的 WATCH_FACE_UPDATE_RATE,并减去当前毫秒时间值和 1,000 毫秒时间分辨率的模(计算剩余或增量时间值),这将给出下一个完整秒之前的毫秒数。这可以使用下面的 Java 代码来完成,如图图 9-12 所示:
long msDelay = WATCH_FACE_UPDATE_RATE - ( msTime % WATCH_FACE_UPDATE_RATE );
图 9-12 。使用 ms time modulo % WATCH _ FACE _ UPDATE _ RATE 计算下一整秒的偏移量
如您所见,代码现在已经完成并且没有错误,您已经准备好开始实现 WatchFaceService 类和 WatchFaceService 的核心方法了。发动机级。我将概述这个类及其嵌套的 Engine 类,然后您将实现其中的一些方法。
WatchFaces API:要实现的核心方法
这一节将介绍 WatchFaceService 和 WatchFaceService 中的核心 WatchFaces API 方法。引擎类。
Android WatchFaceService 类:核心常量
一个公共抽象 WatchFaceService 类扩展了一个 WallpaperService 类,后者扩展了一个 Service 类,后者扩展了一个 ContextWrapper 类,后者扩展了一个 Context 类,后者扩展了一个 java.lang.Object 类。两个已知的直接子类包括您将使用的 CanvasWatchFaceService 和 Gles2WatchFaceService。此类的层次结构如下所示:
java.lang.Object
> android.content.Context
> android.content.ContextWrapper
> android.app.Service
> android.service.wallpaper.WallpaperService
> android.support.wearable.watchface.WatchFaceService
WatchFaceService 和 WatchFaceService。我接下来将介绍的 Engine 是 WallpaperService 和 WallpaperService 的子类。分别是发动机。如果你想为可穿戴设备创建手表脸,你可以使用它来代替更普通的壁纸服务,你可以将它用于智能手机、平板电脑、电子阅读器和 iTV 电视机的壁纸应用。
WatchFaceService 对象,就像一个 WallpaperService,必须实现一个重要的方法 onCreateEngine(),你在第七章中实现了这个方法。正如你在第七章中所做的,这里你还必须创建(内部类)私有类引擎,扩展任一 WatchFaceService。Engine 或其已知的直接子类之一。
这个类还提供了“唤醒锁”,它将被保持,这样设备不会进入睡眠状态,直到一个表盘完成绘制。这是为了在智能手表处于环境模式时更新表盘。
这个类有一个名为 WatchFaceService 的嵌套类。引擎,这是 Watch Faces API 的实际实现。我将在接下来讨论这个问题。
这个类有五个常量,用于控制 watchface 设计中断(显示哪些通知)和硬件功能,如屏幕老化保护和低位(省电)环境模式。其中两个常量是字符串值,三个是整数值:
- INTERRUPTION_FILTER_ALL 整数常量通过调用。getInterruptionFilter()方法。它也可以作为参数在。onInterruptionFilterChanged(int)方法,您将实现该方法。
- 中断 _ 过滤 _ 无整数常量通过调用。getInterruptionFilter()方法。它也可以作为参数在。onInterruptionFilterChanged(int)方法,您将实现该方法。
- 通过调用. getInterruptionFilter()方法返回整数常量**。它也可以作为参数在。onInterruptionFilterChanged(int)方法,您将实现该方法。**
- PROPERTY _ BURN _ IN _ PROTECTION字符串常量会将一个 Bundle 对象传递给。onPropertiesChanged(Bundle)表示智能手表硬件是否支持老化保护。
- PROPERTY_LOW_BIT_AMBIENT 字符串常量会将一个 Bundle 对象传递给。onPropertiesChanged(Bundle)指示是否有硬件设备支持低位环境模式的实现。
接下来,让我们在您的 Watch Face 应用中实现可以保存这些常量的变量。之后,我将介绍 WatchFaceService。引擎嵌套类,并且可以实现基于 Watch Faces API 的应用所需的核心方法。之后就可以重点在表盘表面绘制你的表盘设计了,这个我在第十章中有涉及,以及如何绘制表盘元素 ShapeDrawable 对象。
添加 WatchFaceService 常量:老化和低位
因为你想支持老化保护和低位环境模式,而且由于 WatchFaces API 目前没有这方面的方法(比如。isInAmbientMode()方法),您已经在自定义的 isTimerEnabled()方法中使用了它,让我们添加布尔变量来保存这些标志。
您可以通过使用一个复合声明语句来声明这两个布尔标志变量,因为您没有特别设置任何默认值。需要注意的是,如果不声明任何布尔值,Java 对任何布尔变量的默认初始化值都将是 false 。
让我们为这些布尔标志提供逻辑名称,以表示它们做什么:让我们将它们命名为 lowBitAmbientModeFlag 和burinprotectmodeflag。您的 Java 复合语句应该如下所示,没有错误,并在图 9-13 的下半部分高亮显示:
boolean lowBitAmbientModeFlag, burnInProtectModeFlag;
图 9-13 。在引擎类中为 lowBitAmbientModeFlag 和 burnInProtectModeFlag 添加布尔变量
是时候从 WatchFaceService 实现几个最重要的 Watch Faces API 方法了。引擎嵌套类。您将添加**。onDestroy( )** ,它在 CanvasWatchFaceService 中。引擎类,因为它需要“破坏”(从内存中删除)您的画布和任何相关的。onDraw()组件也在系统内存中。
来自 WatchFaceService。引擎类,我们来实现四个方法:。onTimeTick( ) ,。onVisibilityChanged( ) ,。onAmbientModeChanged( ) 和**。getInterruptionFilter( )** 。记住,你已经实现了**。onCreate( )** ,尽管稍后在学习图形和。你的表盘设计的 onDraw()部分,我将在第十章中详细介绍。
Android WatchFaceService。引擎类:核心方法
Android 的 WatchFaceService 服务。Engine 类是一个公共抽象类,它扩展了 WallpaperService。引擎类,这意味着谷歌 Android 开发者主要通过使用 Wallpaper API 创建了一个 Watch Faces API。
这个类表示 Watch Faces API 的核心 Java 代码实现。您需要实现。onCreateEngine()方法,正如你在第七章中所做的那样(参见图 7-9 ),来返回表脸引擎实现。该类的 Java 类层次结构如下所示:
java.lang.Object
> android.service.wallpaper.WallpaperService.Engine
> android.support.wearable.watchface.WatchFaceService.Engine
Android WatchFaceService。Engine 类有两个已知的直接子类:一个 CanvasWatchFaceService。引擎,您当前正在为 ProWatchFaceService 类使用它,以及 Gles2WatchFaceService。引擎,用于实现OpenGL ES 2.0WatchFaceService。发动机功能。
该类有一个公共构造函数方法, WatchFaceService。Engine( ) 和 14 个公共方法,我将在这里与来自 CanvasWatchFaceService 的一个方法一起讨论。Engine (onDestroy()),这样您就知道所有核心的 Watch Faces API 方法能够提供什么了。
你已经创建了虚空 。onCreate(surface holder)方法,用于在手表上创建变量和内存中的对象面服务引擎创建。
虚空 。onDestroy( ) 方法 实际上是在 CanvasWatchFaceService 的文档中描述的。Engine 类,在 Android 开发者网站上,但我将把它包含在这里,因为它将在本章中实现。此方法将删除 WatchFaceService。引擎,它还可以从 MessageQueue 对象中移除消息对象。请确保在调用 super.onDestroy( ) 语句之前完成此操作。三种无效方法的用途是:
- 虚空 。当表盘处于环境模式时,每分钟调用一次 onTimeTick( ) 方法 来更新表盘的时间(时针和分针)。
- 虚空 **。确定硬件设备的属性时,调用 onPropertiesChanged(Bundle properties)**方法 。此方法将用于设置您在上一节中创建的低位和老化标志。
- 虚空 **。onVisibilityChanged(boolean visible)**方法 被调用来通知你表盘变得可见或隐藏。您将使用它来发送一个消息对象,以确保 TimeZone 对象与 Time 对象同步。该逻辑所做的是将 Time 对象设置为 TimeZone 对象值。这确保了时区总是设置正确。
此类中的两个方法与设置影响中的逻辑的属性有关。onDraw()方法。因为我在第十章中介绍了绘制逻辑,所以你将在那一章中实现接下来的两个方法:
- 一个虚空 **。onAmbientModeChanged(boolean inAmbientMode)**方法 将在用户的硬件设备进入或退出其环境模式时被调用。
- 虚空 **。当用户改变中断过滤器设置(常量)时,将调用 onInterruptionFilterChanged(int interruption filter)**方法 。
您已经在该类中实现了其中的一些方法,例如。isInAmbientMode(),还有一个方法可以用来修改已经创建好的手表表面样式对象。为了彻底起见,我接下来将介绍这两种方法:
- 最后一个布尔 **。isinembientmode()**方法会返回你的布尔值,这个布尔值会告诉应用手表的表盘硬件是否处于环境模式。
- 虚空 **。setWatchFaceStyle(watchface style)watchface style)**方法 将设置 watchface style 属性,以便您可以动态更改 UI 设置。
这个类还有三个 getter 方法,允许开发人员确定中断过滤器、Peek 卡定位和未读 Peek 卡计数的当前状态(设置):
- 最终 int 。getInterruptionFilter( ) 方法 返回用户选择的当前生效的中断过滤器常量。
- 期末考试 。getPeekCardPosition( ) 方法 使用 Rect 对象返回第一张正在看的卡片的屏幕位置 X 和 Y 坐标。
- 一个最终 int 。getUnreadCount( ) 方法
还有另外三个。on()方法 ,这些方法不会被实现,但是为了完整起见,我将在这里提到它们:
- 捆绑**。onCommand(String action,int x,int y,int z,Bundle extras,boolean resultRequested)** 实现 Android 的动态壁纸功能,用于拦截来自表盘设计表面的触摸事件。
- **虚空。onPeekCardPositionUpdate(Rect Rect)**每当第一张 Peek 卡在表盘屏幕上定位时,调用方法,使用 Rect 对象给出其位置。
- **虚空。onUnreadCountChanged(int count)**方法在“待读队列”中未读通知卡的数量发生变化时调用。
我将在这一章的剩余部分讨论尚未添加的核心 watch face 方法,这样您就可以在。onDraw()方法。
正在添加 WatchFaceService。引擎方法:核心函数
在本章的其余部分,我将讨论与时间和内存优化有关的其他核心表面方法。它们允许您检测表盘是否可见,以及用户当前使用的智能手表设备硬件配置可用的省电和屏幕老化保护模式。
一旦所有这些核心的表盘创建、样式、定时和广播接收器对象和方法都在您的代码中就位,您就可以专注于如何绘制您的表盘设计,这将在第十章中介绍。
在环境模式下显示时间。onTimeTick()方法
实现最简单的核心方法是void??。onTimeTick( ) 方法,所以让我们从它开始,并将其添加到私有引擎类的顶部。
添加 Java @Override 关键字,表示将要覆盖超类方法,并键入 public void onTimeTick( ) 。在方法体内部,使用一个 Java 超级关键字来传递**。对 CanvasWatchFaceService 的 onTimeTick( )** 方法调用。引擎超类。如图 9-14 所示,基本方法体的代码应该如下所示:
@Override
public void onTimeTick( ) {
super.onTimeTick( );
}
图 9-14 。添加公共 void onTimeTick()方法;在其中,使用 Java super 关键字调用父方法
使用弹出的帮助器对话框,如图 9-14 所示,选择方法调用。
您还需要在这个方法中使用。invalidate()方法调用,让 Android 知道更新表盘上的时间。确保将其放在 super.onTimeTick()超类方法调用之后。这就是超类需要为。onTimeTick()方法是在调用此方法之前处理的。invalidate()方法调用。那个。onTimeTick()方法结构应该类似于下面的代码,可以在图 9-15 和图 9-16 中看到:
@Override
public void onTimeTick( ) {
super.onTimeTick( );
invalidate( );
}
图 9-15 。添加一个。调用 super.onTimeTick()后,invalidate()方法调用来更新观察面
如果你想让 IntelliJ 为你写代码,你可以简单地输入“I”并从弹出的帮助器方法对话框中选择 invalidate()方法或者你可以双击你想让 IntelliJ 为你写的方法,如图图 9-15 所示。
接下来让我们编写另一个相对简单的方法。onDestroy()方法。
从内存中移除表盘。onDestroy()方法
在。onTimeTick()方法,再次表明您将要覆盖超类方法,并键入 public void onDestroy( ) 。在方法体内部,使用一个 Java 超级关键字来传递这个**。onDestroy( )** 到 CanvasWatchFaceService。引擎超类。如果您还没有在 watchface 应用中实现 updateTimeZoneReceiver 函数,那么这就是您需要做的全部工作。
基本的代码。因此,onDestroy()方法体将类似于下面的 Java 方法结构:
@Override
public void onDestroy( ) {
super.onDestroy( );
}
因为您使用的是 BroadcastReceiver 对象,所以需要使用从 MessageQueue 对象中移除 Message 对象。removeMessages( ) 方法调用。这需要在调用 super.onDestroy()方法之前完成,否则这将永远不会发生,因为 WatchFaceService。引擎对象将不再存在,消息对象仍将在队列中。
所以添加**@ Override public void on destroy(){ }**空方法结构,然后添加对 updateTimeHandler 对象的引用。接下来,按句点键,启动弹出的帮助器对话框,选择 removeMessages(int what) 选项,如图图 9-16 所示。
图 9-16 。添加公共 void onDestroy()方法;在其中,使用 updatetimehandler . remove messages()方法
在得到的UPDATE TIME handler . remove messages()方法调用的参数区内,键入 UPDATE_TIME_MESSAGE 消息对象常量,或者键入“U”,从弹出的帮助器对话框列表中选择。然后可以添加super . on destroy(); Java 代码语句,你就完事了!
那个。onDestroy()方法结构,在图 9-17 中显示为无错误,在您完成后应该看起来像下面的 Java 代码结构:
@Override
public void onDestroy( ) {
updateTimeHandler.removeMessages(UPDATE_TIME_MESSAGE);
super.onDestroy( );
}
图 9-17 。使用 removeMessages()从 MessageQueue 中删除消息对象后,调用 super.onDestroy()
接下来,您将实现**。onPropertiesChanged( )** 方法,它只有 6 行代码,比前两个方法稍微复杂一些。
确定低位和老化模式:。onPropertiesChanged()
你需要做的下一件事是实现一个**。onPropertiesChanged( )** 方法 ,使用 Java @Override 关键字。这可以在图 9-18 的底部看到,它产生了下面的空 Java 方法结构:
@Override
public void onPropertiesChanged(Bundle properties){ // The method implementation will go in here }
图 9-18 。创建一个空的@ Override public void on properties changed(Bundle properties)方法结构
该方法使用名为属性的 Android Bundle 对象,将一组智能手表属性传递给该方法进行处理。如你所见,在图 9-18 的底部,Bundle 类很可能需要被导入,所以在空方法中点击鼠标,使用 Alt+Enter 工作进程,让 IntelliJ 为你编写import Android . OS . Bundleimport 语句。现在,您已经准备好编写方法体了。
在里面。在 PropertiesChanged()方法上,键入 Java super 关键字和一个句点,然后在弹出的帮助器对话框中选择**onproperties changed(Bundle properties)**选项,双击将其插入到 Java 语句中,如图 9-19 底部所示。
图 9-19 。键入 Java super 关键字,并选择 onPropertiesChanged(Bundle properties)选项
这将把名为 properties 的 Bundle 对象传递给超类,并且您将准备好在您在私有(内部)引擎类的顶部声明的低位和老化布尔变量中设置包含在 Bundle 对象中的属性。
在super.onPropertiesChanged(properties); Java 编程语句下,键入 lowBitAmbientModeFlag 布尔变量名、等号,然后是 properties Bundle 对象,您将使用它来调用方法调用。在 properties 之后,按下句点键以弹出方法帮助器对话框。
当弹出的方法帮助器对话框出现时,选择 getBoolean(String key,boolean defaultValue) 选项,或者双击它,这将插入。getBoolean()方法调用结构到你正在编码的 Java 语句中,如图图 9-20 所示。一旦使用完 IntelliJ 弹出助手对话框,Java 语句将如下所示:
lowBitAmbientModeFlag = properties.getBoolean(PROPERTY_LOW_BIT_AMBIENT);
图 9-20 。键入 lowBitAmbientModeFlag 变量、等号、单词 properties 和句点
在里面。getBoolean()方法参数区,键入 a " P ,选择 PROPERTY_LOW_BIT_AMBIENT 常量,如图图 9-21 所示,我在本章前面已经讨论过。双击此选项可将 lowBitAmbientModeFlag 变量设置为此 LOW_BIT 常量的值。
图 9-21 。在。参数区获取 Boolean()方法,并选择 PROPERTY_LOW_BIT_AMBIENT
现在,您已经从 properties Bundle 对象中提取了低位环境模式标志设置,并将其设置为 lowBitAmbientModeFlag 变量,您需要对 burnInProtectModeFlag 变量执行完全相同的操作。
在 lowBitAmbientModeFlag Java 编程语句下面,键入burinprotectmodeflag布尔变量名、等号,然后是 properties Bundle 对象,您将使用它来调用方法调用。在 properties 之后,按句点键启动弹出的方法助手对话框。
当弹出的方法帮助器对话框出现时,选择 getBoolean(String key,boolean defaultValue) 选项,或者双击它,这将插入。getBoolean()方法调用结构到你正在编码的 Java 语句中,如图图 9-20 所示。一旦您使用完 IntelliJ 弹出助手对话框,您的 Java 语句将如下所示:
burnInProtectModeFlag = properties.getBoolean(PROPERTY_BURN_IN_PROTECTION);
在里面。getBoolean()方法参数区,键入 a " P ,选择PROPERTY _ BURN _ IN _ PROTECTION常量,如图图 9-22 所示。双击常量,这会将 burnInProtectModeFlag 变量设置为这个 BURN_IN 常量的值,这样你的应用就知道用户的智能手表是否支持(实际上需要)老化保护。
图 9-22 。键入一个 burnInProtectModeFlag,将其设置为等于 properties . get boolean(BURN _ IN _ PROTECTION)
现在你已经知道用户的智能手表硬件支持什么样的特殊屏幕(显示)模式,接下来你需要实现的最复杂(也是最重要的)方法就是**。onVisibilityChanged( )** 方法。这个方法需要 10 行代码来实现,所以,现在就开始吧!
确定表盘是否可见:。onVisibilityChanged( )
除了。onDestroy(),您将首先使用 Java super 关键字和方法名调用一个超级方法调用,然后实现任何定制的编程逻辑,就像您对前面的方法结构所做的那样。
让我们通过添加 Java @Override 关键字和**public void onvisibility changed(boolean visible){ }**空方法结构来实现这一点。在里面输入 Java super 关键字和句点字符,然后选择 onVisibilityChanged(布尔可见)选项,如图图 9-23 所示。基本方法结构应该类似于下面的 Java 代码:
@Override
public void onVisibilityChanged(boolean visible) {
super.onVisibilityChanged(visible);
}
图 9-23 。创建 onVisibilityChanged()方法,键入 Java super 关键字,并调用 onVisibilityChanged()
这个方法的其余部分将由 if-else 条件结构组成,它将告诉手表表面应用在手表表面可见或不可见的情况下做什么。空的 if-else 条件语句如下所示,在图 9-24 中显示为无错误:
public void onVisibilityChanged(boolean visible) {
super.onVisibilityChanged(visible);
if(visible) { visible=true statements here } else { visible=false statement here }
}
图 9-24 。在 super.onVisibilityChanged()方法后添加一个空的 if(visible){ } else { }条件结构
如果手表表面活跃起来(visible = true ),要做的第一件事是调用**registerTimeZoneReceiver()**方法来检查自手表进入睡眠状态以来时区是否发生了变化。在 if-else 条件构造的一个 if(visible){ } 部分,键入“r”字符并从帮助器对话框中选择一个 registerTimeZoneReceiver()选项,如图图 9-25 所示,并将此方法调用插入到条件 if-else 语句中。
图 9-25 。在 if(visible)结构中,键入“r”并选择 registerTimeZoneReceiver()方法调用
接下来您要做的是通过调用来确保时区在 time 对象中设置正确。clear( ) 方法关闭了 watchFaceTime 对象。在参数区内,键入“Ti”,然后从弹出的帮助器对话框中选择一个时区(java.util) 类,如图图 9-26 所示。
图 9-26 。调用 watchFaceTime 时间对象的. clear()方法 并选择 TimeZone (java.util)选项
时区后,按下周期键,选择 getDefault( ) 方法 ,如图图 9-27 所示。
图 9-27 。在 TimeZone 对象后键入一个句点,并从弹出帮助器中选择 getDefault( ) TimeZone 方法
双击后。getDefault( ) TimeZone 选项在弹出的帮助器对话框中,键入一个句点,这将启动另一个弹出的帮助器对话框,在这里可以选择一个 getID( ) 方法。双击它,这将给出最终的 Java 语句,将时区重新加载到 Time 对象中。最终的 Java 语句,如图 9-28 所示,应该如下所示:
watchFaceTime.clear( TimeZone.getDefault( ).getID( ) );
既然已经将当前时区对象加载到时间对象中,接下来需要做的就是使用**。setToNow( )** 方法调用将时间对象设置为当前时间,使用新加载的时区作为时间值应该设置的指导。在图 9-28 中显示没有错误的 Java 代码应该如下所示:
watchFaceTime.setToNow( );
图 9-28 。键入 unregisterTimeZoneReceiver()方法的前几个字母,并在弹出窗口中双击它
现在,您只需构建 if-else 条件语句的 else{ }部分。这就是当用户的智能手表处于睡眠状态(表盘不可见)时,您希望表盘应用执行的操作。
就像当手表表面可见(true)时调用. registerTimeZoneReceiver()方法一样,您将类似地调用一个**。unregisterTimeZoneReceiver()**方法当表盘不可见(false)时,如图图 9-28 所示。
在 else{ }结构中键入“u ”,当出现方法帮助器弹出窗口时,选择您在第八章中创建的 unregisterTimeZoneReceiver()方法。双击它,让 IntelliJ 为您编写 Java 语句。你的无错方法现在完成了,如图图 9-29 所示。
图 9-29 。对 if(visibility)和 else{ }部分进行编码且无错误的 onVisibilityChanged()方法
恭喜你!现在,您已经实现了 WatchFaces API 应用的所有非图形部分!你现在已经准备好进入**。onDraw( )** 法!
摘要
在本章中,您学习了如何为您的 WatchFace 应用创建核心 Watch Face 服务引擎类方法。还有一些与。onDraw()相关的代码,我将留到下一章,那时你将开始学习矢量图形界面设计。
首先,我讨论了如何使用 Handler 对象和 Message 对象在手表表面实现秒针。您创建了一个 updateTimeHandler,它允许您每秒更新手表上的秒针。
接下来,我讨论了核心的 WatchFaces API 类:WatchFaceService 和 WatchFaceService.Engine。
在下一章中,您将开始向表盘应用添加矢量图形 2D 设计元素,并且您将学习如何仅使用代码创建表盘,因为矢量图形仅使用 SVG 或 Shape 类创建,即仅使用数学或代码。在第十一章中,你将学习如何使用位图图形来设计你的表盘。