安卓可穿戴设备高级教程-三-

81 阅读1小时+

安卓可穿戴设备高级教程(三)

原文:Pro Android Wearables

协议:CC BY-NC-SA 4.0

十、表盘矢量设计:为表盘使用矢量图形

现在,您已经准备好了大部分 WatchFaces API 基础设施,这些基础设施与您将使用矢量图形在其上“绘画”的画布对象没有直接关系,您已经准备好开始实现**。onDraw( )** 方法,学习矢量图形以及实现它们的 Android 类。

本章将深入探讨与画布对象一起使用的 Android 类,主要是 Android Paint 类和嵌套类,你可能已经猜到了,因为人们是在画布上绘画的!

您还将了解 Android Canvas 类和**。** drawLine( ) 它包含的方法,你将使用它来绘制你的表盘设计的所有组件。

矢量图形绘制方法,如。drawLine()方法,使用 Paint 类(和 object)将 vector (Shape)对象绘制到画布上。到目前为止,在您的 watch face 实现中,这是保存画布表面对象的 SurfaceHolder 对象。

我将用本章的大部分时间讨论 onDraw()方法以及需要实现的方法,以便支持您在第六章中了解到的所有不同模式,如低位环境模式或老化保护模式。一旦这些都准备好了,你就可以完成最后几个 WatchFaceService 了。引擎方法实现。

WatchFace 绘画引擎:使用绘画对象

在我开始编写在画布(屏幕)上绘制基于矢量图形的手表表面设计所需的对象和方法的代码之前,让我们深入了解一下 Android Paint 类。

你对手表表面设计组件进行描边、颜色、样式和反锯齿的方式都是使用这个 Android Paint 类及其六个嵌套的“助手”中的常量和方法调用来完成的。

出于这个原因,让我们仔细看看 Paint 类,然后,您将创建用向量绘制手表表面的 Java 代码。

Android 的 Paint 类:在画布上绘制矢量形状

publicAndroidPaint类扩展了 java.lang.Object master 类,并被临时编码以提供 Android 操作系统的所有数字绘画功能。Paint 类的 Java 类层次结构代码如下所示:

java.lang.Object
  > android.graphics.Paint

Paint 类有一个已知的直接子类——text Paint 类。一个 Paint 类创建一个 Paint 对象,它包含关于样式颜色的信息。这些信息指导 Android 图形引擎绘制矢量形状对象,也称为“几何图形”,以及文本对象(和字体对象),它甚至指导如何渲染任何 BitmapDrawable 对象,我将在第十一章中讨论。

Paint 类有六个嵌套类。在本章中,我将只详细介绍其中的两个,因为您将在示例的 Watch Faces 设计中以及在 Watch Faces 应用的 Java 代码中使用它们。

  • 枚举油漆。Align 嵌套类允许开发者指定如何。drawText( ) 方法应该将一个文本对象相对于 X,Y 坐标对齐。
  • 枚举油漆。Cap 嵌套类允许开发者指定描边线条和路径的开始和结束的处理方式(方端或圆端)。这将允许您定义手表的秒针、分针和时针以及刻度线的外观和感觉。
  • 级油漆。FontMetrics 嵌套类描述在文本对象的任何给定浮点大小属性下字体对象实现的度量
  • 级油漆。FontMetricsInt 嵌套类为希望将 FontMetrics 值定义为整数的开发人员提供了方便的方法
  • 枚举油漆。Join 嵌套类允许开发者指定线条或曲线段如何在描边路径上“连接”在一起(直线或曲线连接)。这和油漆差不多。Cap 功能,除了它应用于两条或多条线或路径的连接交叉点,而不是线或路径的开放(未连接)端。
  • 枚举油漆。样式嵌套类指定了如何绘制矢量形状对象,也称为图元。这可以用一个填充、一个冲程填充和冲程来指定。您将在代码中使用填充笔画

Paint 类指定了十几个常量值,尽管 Android 操作系统不再使用其中的一个。我将在这里提到它们,以便您对这个 Paint 类可以实现的功能有一个大致的了解,因为图形设计是 watch faces 应用设计过程的关键组件之一。

  • int ANTI_ALIAS_FLAG
  • int DEV_KERN_TEXT_FLAG 现在已经不用了。
  • int DITHER_FLAG 是在渲染时启用抖动的绘画标志。打开抖动可以在 hicolor (15 位 32,767 色,或 16 位 65,536 色)或索引色** (8 位或 256 色)智能手表设备硬件显示屏上模拟真彩色图形(低颜色支持)。**
  • int EMBEDDED _ BITMAP _ TEXT _ FLAG是一个绘制标志,当您在表盘上绘制文本对象时,它使您能够使用位图字体对象。
  • int FAKE _ BOLD _ TEXT _ FLAG是一个 Paint flag,允许开发者将合成加粗效果应用到文本对象。这用于不支持(附带)字体加粗组件的自定义字体定义。
  • int FILTER_BITMAP_FLAG 如果你想要最高质量的结果,这应该在你的表盘应用中使用。(这将在第十一章中讲述,届时你将学会如何使用 BitmapDrawable。)
  • int HINTING_OFFint HINTING _ ONPaint flag 常量分别禁用或启用字体对象提示选项。字体提示相当于抗锯齿。因此,如果你在一个 Watch Face 应用中渲染字体对象,那么在低位环境模式下关闭 HINTING_OFF!
  • int LINEAR_TEXT_FLAG 是一个绘画标志,能够平滑、线性地缩放文本对象及其字体对象。这类似于 BitmapDrawable 对象的过滤,因此您可能想要打开它。
  • int STRIKE _ THRU _ TEXT _ FLAG是一个绘画标志,如果文本对象正在使用的字体对象不包括字体-删除线字体定义组件,则该绘画标志将删除线装饰应用于文本对象。
  • int SUBPIXEL_TEXT_FLAG
  • int UNDERLINE _ TEXT _ FLAG是一个绘画标志,当被启用时,如果文本对象正在使用的字体对象不包括字体-下划线定义组件,则允许将下划线装饰应用于呈现的文本对象。

Paint 类有三个公共构造函数方法,包括默认的 Paint( ) 构造函数,它使用默认设置创建一个新的 Paint 对象。还有一个“识别标志”的构造函数,它创建新的 Paint 对象,同时设置我刚才提到的那些标志常量。这个构造函数使用格式 Paint(int flags) 来创建启用了特定标志常量(特性)的新 Paint。

还有一个“copycat”Paint object 构造函数方法,它使用一种形式的 Paint(Paint paint) 来创建一个新的 Paint 对象,并通过使用构造函数方法调用的参数列表区域内指定的 Paint 对象,使用另一个 Paint 对象的属性来初始化它。

Paint 类支持 92 种方法,我显然不能在这里详细介绍,所以我将只介绍那些您将在本章的代码中使用的方法。如果您想详细研究所有 92 种方法,可以访问下面的开发者网站 URL:

http://developer.android.com/reference/android/graphics/Paint.html
  • public void setARGB(int a,int r,int g,int b) 方法是。setColor()方法。这个。setARGB()方法采用四个色板(或颜色平面)并使用这些数据配置 32 位颜色对象。A 或 alpha 分量支持 256 级(8 位)透明度(不透明度),R 或红色分量支持 256 级(8 位)红色值,G 或绿色分量支持 256 级(8 位)绿色值,B 或蓝色分量支持 256 级(8 位)蓝色值。
  • public void setAlpha(int a) 方法也是一个. setColor( ) helper 方法,但是,它只分配颜色对象的 Alpha 值,RGB 值不变。那个。setAlpha( ) 8 位(整数)值的范围必须从 0 到 255。
  • **public void setAntiAlias(boolean aa)**方法是。setFlags()方法。这个方法允许您设置或清除一个名为 ANTI_ALIAS_FLAG 的 Paint 常量,我前面提到过。true 值会打开抗锯齿,false 值会关闭抗锯齿。
  • **public void set color(int color)**方法设置一个绘制对象的颜色。integer 参数保存一个数值,其中包含 Alpha 和 RGB 数据值。这个 32 位值不是预乘的,这意味着 alpha 可以通过独立设置为任意值。不管 RGB 数据值如何。你可以研究 Android 的颜色类来获得更多的细节。
  • public void setStrokeCap(油漆。Cap cap) 方法设置绘制对象的帽样式常量。这可以是对接(默认值)、
  • public void setStrokeWidth(float width)方法设置线条或路径形状对象的像素宽度。在发际线模式 中通过零点来描边。发际线总是独立于画布矩阵绘制单个像素。宽度参数设置绘制对象的笔画宽度。每当绘制对象的样式设置为 STROKESTROKE_AND_FILL 时,都会使用该选项。
  • **public void setStyle(Paint。**方法设置绘制对象的样式。这用于控制如何解释矢量形状原始几何图形。drawBitmap 是一个例外,它总是假设填充。

现在您已经了解了基本的 Paint 类方法及其常量,您可以开始在 watch face design 中实现这个类的核心方法和常量,只需要 Java 代码、vector (Shape)对象和 Paint 对象。

表盘绘制:创建表盘绘制对象

您需要做的第一件事是声明那些 Paint 对象,它们将定义表盘组件在屏幕上的外观。基本的表盘部件是刻度和表针。

声明多个绘制对象:使用复合 Java 声明

现在让我们在低位和老化布尔标志变量声明后添加一个画图对象声明,并将四个画图对象命名为视觉标记,如图 10-1 底部所示(高亮显示)。单击红色油漆错误消息内的任意位置,并使用 Alt+Enter 让 IntelliJ 为您编写import android.graphics.Paint;声明。

9781430265504_Fig10-01.jpg

图 10-1 。为 pHourHand、pMinuteHand、pSecondHand 和 pTickMarks 添加复合 Paint 对象声明

创建 WatchFace 组件方法:配置绘画

接下来要做的是创建四个逻辑方法体来保存 Paint 对象构造(实例化)、配置和编程语句。我以这种方式构建 Java 代码,以防您想要返回并使每个 Paint 对象更加复杂。随着应用变得越来越复杂,更有组织的代码结构将会带来好处,这将允许您使用展开和折叠功能。

您将从顶部(小时)到底部(秒和刻度)工作,并从创建一个开始。 createHourHand( ) 方法。Java 代码将如下所示:

public void createHourHand() {  pHourHand = new Paint();  }

在复合 Paint 声明下添加一行代码,并键入 public void create hourhand(){ } empty 方法结构。然后,在其中添加一个pHourHand = new Paint(); Java 对象实例化语句来创建第一个 Paint 对象,您将配置它来绘制时针。正如你在图 10-2 中看到的,你可以从弹出的助手对话框中选择画图(android.graphics) 选项,并在它下面看到嵌套的助手类。双击 Paint 类将其插入到代码中。

9781430265504_Fig10-02.jpg

图 10-2 。创建一个公共的 void createHourHand()方法;实例化一个名为 pHourHand 的新绘制对象

既然已经实例化了名为 pHourHand 的 Paint 对象,就可以开始使用颜色和绘制样式等属性来配置它了。最重要的属性是颜色,所以我们再加上**。** setARGB( ) 方法调用,将配置时针绘制为完全不透明,并使用全蓝色作为颜色。Java 语句如图 10-3 所示,应该是这样的:

pHourHand.setARGB(255, 0, 0, 255);

9781430265504_Fig10-03.jpg

图 10-3 。使用 IntelliJ 弹出帮助器对话框调用 pHourHand Paint 对象的. setARGB()方法

键入手绘对象,然后键入一个句号。当弹出的助手对话框出现时,选择**。setARGB(int a,int r,int g,int b)** 选项,如图图 10-3 ,将此方法调用插入到 Java 语句中,然后输入 255,0,0,255

时针的下一个最重要的特征是它有多厚,这是使用**配置的。****【setStrokeWidth()**方法,你将其中的像素设置为 6(6/320 就是屏幕的. 01875 或者 1.875% )。如图图 10-4 所示,Java 语句在代码库中应该如下所示:

pHourHand.setStrokeWidth(6.f);

9781430265504_Fig10-04.jpg

图 10-4 。使用 IntelliJ 弹出帮助器对话框调用 pHourHand Paint 对象的. setStrokeWidth()方法

您将 Paint 对象配置为交互式表盘模式的默认对象,然后根据用户的手表型号及其在任何给定时间的行为,检测环境温度、低位和老化。因此,为了获得最佳视觉质量,您将在默认模式下打开的抗锯齿功能,并使时针的尖端变圆**。**

完成此任务的 Java 语句如图 10-5 所示,看起来像下面的代码:

pHourHand.setAntiAlias(true);
pHourHand.setStrokeCap(Cap.ROUND);

9781430265504_Fig10-05.jpg

图 10-5 。使用 Cap 调用 pHourHand Paint 对象的. setAntiAlias()方法和. setStrokeCap()方法。一轮

正如你在图 10-5 中看到的,如果你在。调用 setStrokeCap()方法,你将得到该颜料。Cap 此对话框中的嵌套(辅助)类常量。

双击**帽。在弹出的帮助器对话框中选择 ROUND(Android . graphics . paint)**选项,如图图 10-5 所示,完成一个公共 void 的配置代码。createHourhand()方法结构配置一个画图对象。

完了。createHourHand()方法结构如图图 10-6 所示,您将在下面复制并粘贴它来创建其他三个方法,它应该看起来像下面的 Java 方法结构:

public void createHourHand() {
    pHourHand = new Paint();
    pHourHand.setARGB(255, 0, 0, 255);
    pHourHand.setStrokeWidth(6.f);
    pHourHand.setAntiAlias(true);
    pHourHand.setStrokeCap(Cap.ROUND);
}

9781430265504_Fig10-06.jpg

图 10-6 。复制 createHourHand()方法结构;将其粘贴到它的下面,以创建 createMinuteHand()方法

创建分、秒和刻度表盘组件方法

正如你在图 10-6 中看到的,你的 Java 代码现在已经没有错误了,你可以在它下面复制并粘贴一个. createHourHand()方法来创建一个**。createMinuteHand( )** 方法。我在这段之前的 Java 代码清单中用粗体显示了您将要更改的参数。

因为您也将使用圆形的分针,所以您只需要更改六行代码中两行的参数。但是,不要忘记将方法名和绘制对象名从手动改为手动。改变。setARGB()方法调用,通过将绿色数据参数完全打开(255),红色和蓝色参数完全关闭(0),将分针涂成绿色,然后使分针 4 像素宽而不是 6 像素宽( 4/320 将代表显示屏的 1.25% )。. createMinuteHand()方法结构 的代码应该如下所示:

public void createMinuteHand() {
    pMinuteHand = new Paint();
    pMinuteHand.setARGB(255, 0, 255, 0);
    pMinuteHand.setStrokeWidth(4.f);
    pMinuteHand.setAntiAlias(true);
    pMinuteHand.setStrokeCap(Cap.ROUND);
}

因为您将使用方形秒针,所以您需要在这六行代码中的三行中更改三个参数(在上面的代码中以粗体显示)。不要忘记将方法名和绘制对象名从 pMinuteHand 改为PSE second hand

改变。setARGB()方法调用,通过完全打开红色数据参数(255),关闭绿色和蓝色参数(0),将秒针涂成红色。让我们把秒针做成 2 像素宽而不是 4 像素宽( 2/320 将代表显示屏的 0.625% )。的 Java 代码。 createSecondHand( ) 方法 结构应该看起来像下面的代码:

public void createSecondHand() {
    pSecondHand = new Paint();
    pSecondHand.setARGB(255, 255, 0, 0);
    pSecondHand.setStrokeWidth(2.f);
    pSecondHand.setAntiAlias(true);
    pSecondHand.setStrokeCap(Cap.SQUARE);
}

正如你在图 10-7 中看到的,代码没有错误,你已经准备好创建最终的方法,该方法将创建并配置一个 Paint 对象,用于在表盘周围绘制刻度线。

9781430265504_Fig10-07.jpg

图 10-7 。复制 createMinuteHand()方法结构;粘贴到它的下面,创建 createSecondHand()方法

复制并粘贴 CreateSecondHand()方法到它下面,创建一个 createTickMarks( ) 方法。您需要编辑的唯一方法调用是您的**。setARGB( )** 方法调用,将所有值设置为 255 ,使刻度线为白色。方法结构的 Java 代码应该是这样的:

public void createTickMarks() {
    pTickMarks = new Paint();
    pTickMarks.setARGB(255, 255, 255, 255);
    pTickMarks.setStrokeWidth(2.f);
    pTickMarks.setAntiAlias(true);
    pTickMarks.setStrokeCap(Cap.SQUARE);
}

正如你在图 10-8 中看到的,代码是没有错误的,你已经准备好调用你在。需要由 WatchFaces API 实现的 onCreate()方法。

9781430265504_Fig10-08.jpg

图 10-8 。Copy createSecondHand()方法结构;将它粘贴到自身下面以创建 createTickMarks()方法

您已经实现了该方法的一部分,包括一个 super.onCreate()超类方法调用,并使用 WatchFaceStyle 创建了一个 WatchFaceStyle 对象。构建器类,你在第八章中学到的。

从一个调用这些方法之后。onCreate( ) 方法来设置画图对象的小时、分钟、秒针和刻度,就可以开始在上工作了。onDraw( ) 法。通过使用**,您可以创建使用这些 paint 对象在画布上绘制表盘设计组件的逻辑。来自 Android Canvas 类的 drawLine( )** 方法,我将在本章的下一节详细讨论。

调用 WatchFace 组件 Paint 方法。onCreate()

打开。onCreate()方法实现,并在 WatchFaceStyle 对象的构造和配置之前或之后调用. createHourHand()方法。正如你在图 10-9 中看到的,如果你键入单词 create ,你刚刚创建的所有方法现在都是 IntelliJ 弹出助手对话框的一部分。双击每一个,插入所有四个。

9781430265504_Fig10-09.jpg

图 10-9 。在公共 void onCreate()方法中添加 createHourHand()方法调用,以创建第一个 Paint

一旦你输入单词“create”四次并双击前四个方法调用,你的代码将看起来如图 10-10 所示。

9781430265504_Fig10-10.jpg

图 10-10 。在公共 void onCreate()方法的顶部添加所有四个绘制对象创建方法调用

接下来,让我们快速看一下 Android Canvas 类,它是托管(包含)的类。onDraw()方法。之后,您将编写。onDraw()方法来绘制表盘组件。

WatchFace 绘图引擎:onDraw()方法

Android WatchFaces API 使用 Android Canvas 类作为绘图表面,而。onDraw()方法,该方法将 Paint 对象应用于 Canvas 对象。在本节中,您将仔细查看这些类对象,并了解它们是如何相互协作的。

Android Canvas 类:你的画布绘制方法

Android Canvas 公共类扩展了 Java . lang . object 主类,这意味着它是作为绘图画布使用的临时代码。这个类在 android.graphics 包里。Android Canvas 类的 Java 类层次结构如下所示:

java.lang.Object
  > android.graphics.Canvas

Canvas 类旨在创建一个画布绘制表面对象来保存。onDraw()方法调用。为了能够在画布对象上绘图,您需要有四个基本组件。第一个是一个位图对象,它保存代表画布表面的实际像素。

第二个是一个 Canvas 对象本身,它为。onDraw()方法调用,将数据值写入该位图对象。第三个是“绘图原语”,如 vector ( Shape )子类对象,如 Rect、Path、Text 或 Line,或一个光栅 BitmapDrawable 对象。第四个是 Paint 对象,我在本章的前一节已经介绍过了,它用于描述绘制组件的颜色和样式。

这个 Canvas 类有两个嵌套的类,也称为 helper 类。一个是枚举画布。EdgeType ,定义边缘常量 AA(抗锯齿),或者 BW(黑白),不抗锯齿。另一个嵌套类是枚举画布。VertexMode ,它处理 3D OpenGL ES,我不打算在这本书里讨论。

Canvas 类有 90 个方法,几乎和 Paint 类一样多。您将使用public void drawLine(float startX,float startY,float stopX,float stopY,Paint paint) 方法,使用指定的起点和终点 X,Y 坐标绘制线段。将使用您在本章的上一节中创建的指定绘制对象来绘制线条。

绘制你的表盘:使用。drawLine()方法

接下来,让我们折叠。onCreate()方法,方法是单击该方法左侧的号,然后展开。通过点击方法左边的符号,onDraw()方法。你将首先添加基本的整数和浮点变量来设置表盘设计的中心。然后,您将从 time 对象获取系统时间,然后计算所有表盘设计组件的旋转。

寻找表盘设计的中心:centex 和 centey

让我们首先创建一个名为宽度整数变量,并使用一个. width()方法调用 Rect 对象来获得画布的宽度,该对象定义了画布的边界。要确定垂直中心,您需要将这个数字除以 2,如果智能手表使用整个屏幕,这个数字将是 320 像素。您将使用下面的 Java 代码对高度 (Y)值进行同样的操作,这也显示在图 10-11 中:

int width = rect.width();
float centerX = width / 2.f;
int height = rect.height();
float centerY = height / 2.f;

9781430265504_Fig10-11.jpg

图 10-11 。获取 onDraw( ) Rect 对象的宽度和高度,并使用它们来计算 centerX 和 centerY

在本章的这一节中,您将使用这些 centerXcenterY 值。这些值用于提供表盘指针的原点坐标和刻度排列的中心点。接下来您需要做的是创建整数变量,其中包含小时、分钟和秒的时间值。这样做是因为时间对象使用整数值。

查找当前时间:小时、分钟和秒整数

最重要的事情之一。onDraw( ) 方法每秒需要做的是旋转所有这些指针(时、分、秒)指向正确的方向,基于当前的系统时间值。这保存在 watchFaceTime 时间对象中,您已经在代码中将其放置到位。您所要做的就是创建整数变量来保存当前系统时间的小时、分钟和秒部分,这样您就可以使用圆周率、正弦和余弦对它们进行计算,以找出您的每个表盘指针应该指向的方向。

这可以使用下面的 Java 代码来完成,在使用 IntelliJ 构建这三个语句中的第一个时,也显示在图 10-12 中:

int hours = watchFaceTime.hour();
int minutes = watchFaceTime.hour();
int seconds = watchFaceTime.hour();

9781430265504_Fig10-12.jpg

图 10-12 。创建一个名为 hours 的整数变量,并将其设置为 watchFaceTime 对象的 hour 属性

如果您输入 int hours = ,以及 watchFaceTime 时间对象的名称,然后输入 period ,您将访问 IntelliJ 弹出帮助器对话框,在这里您可以看到小时、分钟和秒方法调用。

正如你所看到的,变量声明和初始化代码到目前为止是没有错误的,并且只需要 7 行代码,如图图 10-13 所示。我在 Java 代码中单击了一个 watchFaceTime 对象引用来显示它的用法,IntelliJ 使用紫色来跟踪它。我单击的 watchFaceTime 由 IntelliJ 用淡黄色高亮显示。

9781430265504_Fig10-13.jpg

图 10-13 。为分钟和秒钟创建整数变量,并将其设置为 watchFaceTime 对象属性

我将在本节中一直这样做,以帮助您了解时间对象的用法。现在,您已经准备好通过正弦、余弦和圆周率方法计算旋转。

现在,您已经有了保存表盘中心点坐标的变量以及 Time 对象的当前小时、分钟和秒,是时候(没有双关语)实现 Java 代码了,它将每秒钟将所有基本表盘组件旋转到位。

旋转时针:使用数学类圆周率、正弦和余弦

声明一个名为hourRotfloat 变量来保存时针旋转。时针将显示来自时间对象的小时值,并且还将显示每个小时之间的分数部分。您可以从 Time 对象中取出分钟值,并除以 60,然后使用下面的公式格式将它加回小时值:(小时+(分钟/60))

因为您使用的是 Java Math 类的正弦波函数,而 PI 定义了正弦波的一个完整周期(波朝上,波朝下),所以您需要将一个完整周期中的小时数(12)除以二,然后将精确的小时数除以六,使用以下公式格式:((小时+(分钟/60))/6) 。现在你所要做的就是将旋转角度乘以圆周率,圆周率可以通过数学方法得到。圆周率法。这将是使用(浮点)数学将转换为浮点值。PI ,所以你不会得到一个编译错误。时针旋转的 Java 代码如图图 10-14 所示,应该如下所示:

float hourRot = ((hours + (minutes / 60.f)) / 6.f) * (float) Math.PI;

9781430265504_Fig10-14.jpg

图 10-14 。创建一个 hourRot 来保存时针旋转计算,并创建一个 hourLength 来保存长度

时针长度的代码要简单得多,它包含一个名为 hourLengthfloat 值的声明,该值被设置为 centerX(表盘半径)值减去 80 像素,以使时针更短。

请记住,centerX 值是表盘表面区域的半径,因为它是 Rect.width()除以 2。图 10-14 中的所示的时针长度代码应该类似于下面的 Java 编程语句:

float hourLength = centerX - 80;

接下来,您需要计算线条形状对象的端点,您将从线条的 centerX 端点绘制到 hourX 坐标,以便时针线将准确地指向正确的赤纬(方向)。正弦函数获取 hourRot 值,并将其转换为 X 坐标的旋转矢量值。您需要在下一行代码中对 Y 坐标做同样的事情。Java 语句应该类似于下面的 Java 代码:

float hourX = (float) Math.sin(hourRot) * hourLength;

当你在 IntelliJ 中键入代码行时,你应该使用弹出的帮助器对话框,如图 10-14 所示,选择一个数学类 sin(double d) 选项,并将其插入到代码中,指定 hourRot 为 double 参数。

要找到 hourY 值,只需使用余弦波反转正弦波,然后用负号将该值绕另一个轴翻转,将该值变为负值。您的 Java 语句应该看起来像下面的代码,可以在图 10-15 中看到:

float hourY = (float) -Math.cos(hourRot) * hourLength;

9781430265504_Fig10-15.jpg

图 10-15 。键入 watchface Canvas 对象,并从弹出帮助器中选择 drawLine()方法调用

现在,时针偏移确定了时针线外端的 X 和 Y 坐标。您还拥有调用。drawLine()方法绘制时针线。

的。从名为 watchfaceCanvas 对象中调用 drawLine()方法,该对象在您的public void on draw(Canvas watch face,Rect rect) 中声明,如图图 10-15 所示。如您所见,如果您键入 watchface Canvas 对象,然后键入句点键,您的 IntelliJ 方法弹出助手对话框应该会出现。双击 drawLine(float startX,float startY,float stopX,float stopY,Paint paint) void 方法选项,将其插入到代码中,看起来应该像下面的 Java 语句,如图图 10-16 :

watchface.drawLine( centerX, centerY, centerX+hourX, centerY+hourY, pHourHand );

9781430265504_Fig10-16.jpg

图 10-16 。调用 watchface Canvas 对象的. drawLine()方法;传入小时绘制对象和坐标

此的方法调用结构。drawLine()方法应该包括表盘中心线的起点,用 centerXcenterY 变量表示,线的终点将使用 centerX+hourXcenterY+hourY 数据值,这些数据值在这个方法调用中作为快捷方式进行计算。最后一个参数是您在本章第一节中创建和配置的 pHourHand Paint 对象,它定义了如何绘制时针线。

旋转分针:使用数学类圆周率、正弦和余弦

分针画线逻辑与时针画线逻辑非常相似,只是它不需要调整部分分钟,而时针需要调整小时精度。分针比时针长,所以你只能从半屏值中减去 40 像素,而不是 80 像素。用于绘制分针线的 Java 代码在图 10-17 中显示无误,应该如下所示:

float minuteRot = minutes / 30f * (float) Math.PI
float minuteLength = centerX - 40;
float minuteX = (float) Math.sin(minuteRot) * minuteLength;
float minuteY = (float) -Math.cos(minuteRot) * minuteLength;
watchface.drawLine( centerX, centerY, centerX + minuteX, centerY + minuteY, pMinuteHand );

9781430265504_Fig10-17.jpg

图 10-17 。调用 watchface Canvas 对象的. drawLine()方法;传入微小的绘画对象和坐标

接下来,您将编写二手绘图逻辑。这与时针和分针逻辑非常相似,只是它仅在交互模式下绘制。

旋转秒针:使用。isInAmbientMode()方法

秒针线逻辑与分针线逻辑非常相似,但有两个例外。绘制的线更长,计算为centex-20、,只有智能手表处于环境模式时才会调用 Math.sin、Math.cos 和 Canvas.drawLine()方法。您的 Java 代码为二手行,包括一个条件 **if(!isineambientmode())**语句隔离了 Math 和 Canvas 方法调用的处理,看起来应该像下面的 Java 代码结构,如图 10-18 所示:

float secondRot = seconds / 30f * (float) Math.PI
float secondLength = centerX - 20;
if ( !isInAmbientMode() ) {
     float secondX = (float) Math.sin(secondRot) * secondLength;
     float secondY = (float) -Math.cos(secondRot) * secondLength;
     watchface.drawLine( centerX, centerY, centerX + secondX, centerY + secondY, pSecondHand );
}

9781430265504_Fig10-18.jpg

图 10-18 。使用条件 if(!idspnonenote)实现二手画线逻辑。isInAmbientMode())结构

根据 Android Watch Face API 规则,如果手表表面处于环境模式,秒针画线代码根本不会画线,如果手表处于环境模式,也不会调用任何数学函数,因此该逻辑还用于优化涉及秒针计算的处理器使用。

创建表盘刻度线:使用 Java for 循环结构

就数学而言,在屏幕周围绘制每小时刻度线的代码类似于手绘代码。您仍将使用 PI 常量(3.14159)和 Math.sin-Math.cos 来定位表盘周边的刻度,使用 for 循环来定位所有 12 个刻度。图 10-19 底部显示的代码如下:

float innerTicksRadius = centerX - 10;
for (int ticksIndex = 0; ticksIndex < 12; ticksIndex++) {
   float ticksRot = (float) (ticksIndex * Math.PI * 212);
   float innerX = (float) Math.sin(ticksRot) * innerTicksRadius;
   float innerY = (float) -Math.cos(ticksRot) * innerTicksRadius;
   float outerX = (float) Math.sin(ticksRot) * centerX;
   float outerY = (float) -Math.cos(ticksRot) * centerX;
   watchface.drawLine(centerX+innerX, centerY+innerY, centerX+outerX, centerY+outerY, pTickMarks);
}

9781430265504_Fig10-19.jpg

图 10-19 。创建一个 for 循环,使用内刻度半径和 centerX 作为外半径来绘制刻度线

现在,您已经准备好实现一些代码,这些代码控制当不同的智能手表硬件模式投入使用时,将如何修改 Paint 对象。这将实现你在第六章中了解到的所需的特殊绘图模式。本章的下一节将向您展示如何仅使用绘制常量设置代码来支持这些模式。

高级模式支持:动态绘制方法

本节将解释如何创建几个方法,这些方法可以根据用户智能手表硬件支持的不同模式动态设置绘制对象特性**。**

**这些模式包括环境模式、低位环境模式和老化保护模式,它们提供不同的图形设计特征,如第六章中的所述。互动模式使用全(亮)色,环境模式使用暗淡的颜色或灰度,具体取决于硬件支持,低位模式使用黑白,老化保护仅使用“边缘像素”来定义表盘设计,以便尽可能少的像素开启(老化)。

控制抗锯齿:创建 setAntiAlias()方法

现在在。onCreate()方法并创建一个**私有的 void setAntiAlias(boolean antiAliasFlag)方法。需要注意的是,Java 足够聪明,允许你在 watchface 应用 ProWatchFaceService.java 类中使用这个方法名,因为它通过在包、类和方法之间使用点符号来引用事物,所以它知道这个 ProWatchFaceService。您将要创建的 Engine.setAntiAlias( )Android . graphics . canvas . setantialias()**完全不同。

这个事实允许您调用这个方法 setAntiAlias(),即使 android.graphics 包中已经有一个 Android Canvas.setAntiAlias()方法。有趣的是,在这里您将实际使用这个(画布)。中的 setAntiAlias()方法调用。setAntiAlias()方法代码。这就是我这样设置它的原因,以表明它是可以做到的,并且您的 IntelliJ (Java + Android)编译器可以很好地处理它!

如果这影响了您的感受力,您可以将该方法命名为不同的名称,例如。setAntiAliasingMode(),如果你喜欢的话。在方法内部,调用 Canvas 类的。setAntiAlias()方法关闭 pHourHand Paint 对象,如图图 10-20 所示。您的 Java 代码应该如下所示:

private void setAntiAlias(boolean antiAliasFlag){
    pHourHand.setAntiAlias(antiAliasFlag);
}

如果要使用 IntelliJ 弹出的帮助器对话框,输入 pHourHand.set,如图图 10-20 所示,双击 setAntiAlias(boolean aa) 选项将此方法调用插入 Java 语句中。

9781430265504_Fig10-20.jpg

图 10-20 。使用传入该方法的布尔 antiAliasFlag 参数创建一个私有 void setAntiAlias

加上这个**。setAntiAlias(antiAliasFlag)** 方法调用所有的 Paint 对象,这将在所有的 Paint 对象上设置 on/off (true/false)设置,允许您打开或关闭手表表面的抗锯齿功能。如图图 10-21 所示的 Java 代码应该是这样的:

private void setAntiAlias(boolean antiAliasFlag){
    pHourHand.setAntiAlias(antiAliasFlag);
    pMinuteHand.setAntiAlias(antiAliasFlag);
    pSecondHand.setAntiAlias(antiAliasFlag);
    pTickMarks.setAntiAlias(antiAliasFlag);
}

9781430265504_Fig10-21.jpg

图 10-21 。调用每个绘制对象的. setAntiAlias()方法,并传入布尔抗锯齿标志

现在,您已经创建了允许您为表盘设计打开和关闭抗锯齿的方法,您可以创建另一种方法,只绘制老化保护模式的时针和分针的边缘。最后,让我们创建第三种方法来实现低位环境模式(仅黑白像素),然后您将与您在第六章中学到的所有手表面孔 API 保持一致。

控制老化:创建 setBurnInProtect()方法

您需要创建的下一个方法是处理需要屏幕老化保护的智能手表硬件,这是使用边缘渲染代码实现的,该代码仅渲染(使用自定义绘画选项绘制到画布上)手表表面组件的边缘。

创建private void setBurnInProtect(boolean enabled)方法结构,并声明一个画图。样式对象命名为 paintStyle ,并将其设置为等于默认值 Paint。Style.FILL 因为这是表盘矢量形状在除了老化保护模式之外的所有其他模式中需要使用的。

当您输入这条语句时,将出现一个 IntelliJ 弹出方法助手对话框,您可以选择画图。Style.FILL 选项,或者双击它,会将其插入到 Java 语句中。

一旦您使用完 IntelliJ 弹出助手对话框,如图 10-22 所示的 Java 方法结构应该是这样的:

private void setBurnInProtect(boolean enabled) {
    Paint.Style paintStyle = Paint.Style.FILL;
}

9781430265504_Fig10-22.jpg

图 10-22 。使用传递到方法中的布尔启用参数创建私有 void setBurnInProtect

接下来您需要添加的是一个条件 if-else 结构,它将处理老化保护是否需要打开(启用)或关闭(!启用)如果不需要的话。if 结构的 if(enabled) 部分应该设置画图。将名为 paintStyle 的样式对象添加到 STROKE 常量,如图 10-23 中弹出的帮助器对话框所示。if()结构的第一部分应该类似于下面的 Java 代码:

private void setBurnInProtect(boolean enabled) {
    Paint.Style paintStyle = Paint.Style.FILL;
    if(enabled) {
        paintStyle = Paint.Style.STROKE;
    } else {
        // an empty else structure for now
    }
}

9781430265504_Fig10-23.jpg

图 10-23 。添加一个 if(enabled) { } else { }空条件结构,并在其中将 paintStyle 设置为 STROKE

if-else 条件语句的 else 部分将设置默认绘画。 FILL 的样式值,你也可以在方法的头部(开始)设置,这样如果不需要这个烧屏模式(false),矢量组件将被填充而不是描边,使它们用颜色填充而不是用黑色填充。这确保了只有在使能时才会使用 STROKE。到目前为止,该方法的 Java 代码应该是这样的,如图 10-24 所示:

private void setBurnInProtect(boolean enabled) {
    Paint.Style paintStyle = Paint.Style.FILL;
    if(enabled) {
        paintStyle = Paint.Style.STROKE;
    } else {
        paintStyle = Paint.Style.FILL;
    }
}

9781430265504_Fig10-24.jpg

图 10-24 。添加 paintStyle 对象集以填充 enabled = false 的条件 if 语句的 else 部分

该方法需要做的最后一件事是为表盘的时针和秒针组件设置 Paint 对象,以使用该 paintStyle。这是通过调用. setStyle()方法 并传递已经在 if(enabled)条件结构中设置为默认填充或更改为 STROKE 的 paintStyle 来完成的。

完成后,Java 方法结构应该类似于下面的代码:

private void setBurnInProtect(boolean enabled) {
    Paint.Style paintStyle = Paint.Style.FILL;
    if(enabled) {
        paintStyle = Paint.Style.STROKE;
    } else {
        paintStyle = Paint.Style.FILL;
    }
    pHourHand.setStyle(paintStyle);
    pMinuteHand.setStyle(paintStyle);
}

最终方法在图 10-25 中显示无误。我选择了 paintStyle 属性,这样你就可以通过这个方法体看到它的用法。

9781430265504_Fig10-25.jpg

图 10-25 。在 if-else 之后,使用。setStyle()

需要注意的是,如果**,这个方法将只改变绘制对象的值。****on properties changed()**方法检测用户的智能手表硬件是否设置了屏幕老化保护标志。此方法将在。onAmbientModeChanged()方法,您将在本章的后面创建它(这就是为什么您现在在这里创建它,以便在以后需要时可以使用它)。

下一件你想做的事情是确保画图对象被正确配置为低位环境和环境模式,所以让我们创建**。ensureModeSupport( )** 方法 next,该方法控制颜色和 Alpha 值,进而控制表盘组件在黑色背景色下在屏幕上的显示亮度。

确保模式支持:一个 ensureModeSupport()方法

在下面添加一行代码。setBurnInProtect()方法并创建一个私有 void ensureModeSupport( ) 方法。声明名为enablelowtambientmode的布尔标志变量,并将其设置为等于lowtambientmode flag布尔标志变量的逻辑以及从is ambientmode()方法调用返回的值。其代码如图 10-26 中的所示。

9781430265504_Fig10-26.jpg

图 10-26 。创建一个私有 void ensureModeSupport,并添加一个布尔 enableLowBitAmbientMode 参数

一旦创建了这个 enableLowBitAmbientMode 布尔标志变量,通过使用下面的 Java 代码结构创建一个空的 if{}else-if{}else{} 结构,它也显示在图 10-27 中:

private void ensureModeSupport() {
    boolean enableLowBitAmbientMode = isInAmbientMode() && lowBitAmbientModeFlag
    if( enableLowBitAmbientMode )  {   // Low-Bit Ambient Mode Java Statements
    } else if( isInAmbientMode() ) {   // Ambient Mode Java Statements
    } else {                           // Interactive Mode Java Statements
    }
}

9781430265504_Fig10-27.jpg

图 10-27 。添加一个 if(enableLowBitAmbientMode){ } else if(is ambientmode){ } else { }条件结构

低位环境模式的特点是每像素只使用一位的数据,也就是全黑上全白。因此,你将不得不设置阿尔法为 255 (这样白色就没有黑色,从背景色)和颜色值为颜色。白色也一样。

setAlpha( ) 方法调用 Paint 对象,如图图 10-28 所示,看起来应该像下面的 Java 语句:

private void ensureModeSupport() {
    boolean enableLowBitAmbientMode = isInAmbientMode() && lowBitAmbientModeFlag
    if( enableLowBitAmbientMode )  {
        pHourHand.setAlpha(255);
        pMinuteHand.setAlpha(255);
        pSecondHand.setAlpha(255);
        pTickMarks.setAlpha(255);
    } else if( isInAmbientMode() ) {  // Ambient Mode Java Statements
    } else {                         // Interactive Mode Java Statements
    }
}

9781430265504_Fig10-28.jpg

图 10-28 。添加。setAlpha(255)方法调用,将所有绘制对象设置为使用其 100%的颜色值进行绘制

接下来你需要做的是用**将所有表盘设计元素的颜色设置为白色。setColor(颜色。**白色)调用掉绘制对象。如图图 10-29 所示的 Java 代码是这样的:

pHourHand.setAlpha(255);
pMinuteHand.setAlpha(255);
pSecondHand.setAlpha(255);
pTickMarks.setAlpha(255);
pHourHand.setColor(Color.WHITE);
pMinuteHand.setColor(Color.WHITE);
pSecondHand.setColor(Color.WHITE);
pTickMarks.setColor(Color.WHITE);

9781430265504_Fig10-29.jpg

图 10-29 。添加。setColor(颜色。WHITE)方法调用将所有绘制对象设置为使用白色值进行绘制

现在,您已经确保在低位环境模式下,表盘屏幕上仅使用黑白值,您需要实现条件逻辑的 else-if 部分,将表盘组成部分中使用的颜色调暗 50%。这是使用 127 的阿尔法值完成的,由于黑色(零值)背景色,这与变暗颜色值 50% 具有相同的效果。如图图 10-30 所示,Java 代码如下所示:

private void ensureModeSupport() {
    boolean enableLowBitAmbientMode = isInAmbientMode() && lowBitAmbientModeFlag
    if( enableLowBitAmbientMode )  {
        pHourHand.setAlpha(255);
        pMinuteHand.setAlpha(255);
        pSecondHand.setAlpha(255);
        pTickMarks.setAlpha(255);
        pHourHand.setColor(Color.WHITE);
        pMinuteHand.setColor(Color.WHITE);
        pSecondHand.setColor(Color.WHITE);
        pTickMarks.setColor(Color.WHITE);
    } else if( isInAmbientMode() ) {
        pHourHand.setAlpha(127);
        pMinuteHand.setAlpha(127);
        pSecondHand.setAlpha(127);
        pTickMarks.setAlpha(127);
    } else {
        // Interactive Mode Java Statements
    }
}

9781430265504_Fig10-30.jpg

图 10-30 。添加。setAlpha(127)调用 else-if(),将所有绘制对象设置为使用其颜色值的 50%进行绘制

使用 Alpha 值作为调光器(由于黑色背景色)将允许您支持在环境模式下支持颜色的智能手表并且仍然允许您在代码中为环境模式支持将屏幕调暗 50%。

条件 if-else 语句的最后一个 else 部分是如果智能手表根本不处于环境模式(不处于低位环境模式,甚至不处于环境模式)时将执行的代码,这意味着它处于交互模式

在交互模式下,您要做的是重新调高 Alpha 值,以便使用最大亮度(因为没有与黑色背景颜色混合,这会使颜色值变暗并降低亮度)。

为此,您将使用在条件语句的 if(enableLowBitAmbientMode)部分开头使用的四个语句,在这里您调用了。setAlpha()方法,全开值为 255。

您完成的 ensureModeSupport()方法结构,如图 10-31 所示,应该类似于下面的 Java 代码:

private void ensureModeSupport() {
    boolean enableLowBitAmbientMode = isInAmbientMode() && lowBitAmbientModeFlag
    if(enableLowBitAmbientMode)  {
        pHourHand.setAlpha(255);
        pMinuteHand.setAlpha(255);
        pSecondHand.setAlpha(255);
        pTickMarks.setAlpha(255);
        pHourHand.setColor(Color.WHITE);
        pMinuteHand.setColor(Color.WHITE);
        pSecondHand.setColor(Color.WHITE);
        pTickMarks.setColor(Color.WHITE);
    } else if(isInAmbientMode()) {
        pHourHand.setAlpha(127);
        pMinuteHand.setAlpha(127);
        pSecondHand.setAlpha(127);
        pTickMarks.setAlpha(127);
    } else {
        pHourHand.setAlpha(255);
        pMinuteHand.setAlpha(255);
        pSecondHand.setAlpha(255);
        pTickMarks.setAlpha(255);
    }
}

9781430265504_Fig10-31.jpg

图 10-31 。添加。setAlpha(255)为所有绘制对象调用 else { }设置,以使用它们的 100%颜色值进行绘制

正如您所看到的,在大多数情况下,此方法控制 alpha 通道数据,您可以使用它在环境模式下充当调光器,并分别确保低位环境和交互模式下的全白(或全彩色)像素值。您正在利用手表的黑色背景颜色来使用 Alpha 值使像素变暗或变亮,因此您可以保留颜色值(低位环境模式除外)。

现在,您已经有了所需的方法(并从中调用)。来自 WatchFaceService 的 onAmbientModeChanged()方法。Engine 类,您接下来将实现该类,以确保智能手表硬件进入环境模式时支持所有这些不同的模式。

调用模式方法:onAmbientModeChanged( )

在。onVisibilityModeChanged()方法并添加了**@ Override public void on ambientmodechanged(boolean ambientModeFlag)**方法结构,这样就可以实现表盘编程逻辑,它涵盖了当用户的智能手表硬件从交互模式切换到环境模式(以节省电池电量)时你希望发生的情况。

在空方法结构中,使用 Java super 关键字调用超类的。onAmbientModeChanged()方法,传递一个 ambientModeFlag 布尔变量,该变量将被传递到您正在编码的方法中。到目前为止的方法结构如图图 10-32 所示,代码如下:

@Override
public void onAmbientModeChanged(boolean ambientModeFlag) {
    super.onAmbientModeChanged(ambientModeFlag);
}

9781430265504_Fig10-32.jpg

图 10-32 。创建一个 public void onAmbientModeChanged()方法,并调用 Java super 关键字

接下来,添加一个条件**if(lowbitAmbientModeFlag){ }**结构。如果您愿意,让 IntelliJ 为您编写,方法是在 If()中键入“l”,然后选择 lowBitAmbientModeFlag 布尔选项,如图图 10-33 所示,双击它让 IntelliJ 为您编写条件语句。您的 Java 条件 if()结构应该如下所示:

if(lowBitAmbientModeFlag) {
    // Java statements to be processed for low-bit ambient mode will go in here
}

9781430265504_Fig10-33.jpg

图 10-33 。添加一个 if(lowBitAmbientModeFlag){}条件结构,并使用帮助器对话框编写代码

在条件 if 结构中,调用 setAntiAlias()方法,如图图 10-34 所示,使用传入该方法的 ambientModeFlag 值的相反值。这使用了 setAntiAlias(!ambientModeFlag) 方法调用格式。到目前为止,Java 代码应该是这样的:

if(lowBitAmbientModeFlag) {
    setAntiAlias(!ambientModeFlag);
}

9781430265504_Fig10-34.jpg

图 10-34 。在条件结构中,键入 set 并双击 setAntiAlias(antiAliasFlag)方法

创建另一个if(burnInProtectModeFlag)条件结构,该结构调用您刚刚编写的 setBurnInProtect( ) 方法,使用与 ambientModeFlag 相同的值,该方法将打开老化保护,如果该标志被设置为告诉手表表面智能手表硬件需要该功能。

到目前为止,onAmbientModeChanged()方法确保了如果智能手表支持环境模式下的低位,抗锯齿被关闭,如果用户的智能手表需要烧屏保护,手表表面的组件被描边(仅显示边缘)而不是填充。到目前为止的 Java 代码,如图图 10-35 所示,应该看起来像下面的方法结构:

@Override
public void onAmbientModeChanged(boolean ambientModeFlag) {
    super.onAmbientModeChanged(ambientModeFlag);
    if(lowBitAmbientModeFlag) {
        setAntiAlias(!ambientModeFlag);
    }
    if(burnInProtectModeFlag) {
        setBurnInProtect(ambientModeFlag);
    }
}

9781430265504_Fig10-35.jpg

图 10-35 。创建两个条件 if 结构,它们将调用 setAntiAlias()和 setBurnInProtect()方法

接下来的事情。onAmbientModeChanged()方法需要做的是调用 ensureModeSupport( ) 方法,如图图 10-36 所示,这是您刚刚创建的。在所有的绘画属性被重新配置(重置)后,你可以通过调用重绘表盘设计。 invalidate( ) 方法。

9781430265504_Fig10-36.jpg

图 10-36 。调用 ensureModeSupport()方法,然后通过调用 invalidate()方法刷新观察图面

您需要做的最后一件事是创建一个**。** checkTimer( ) 方法。这将重置 updateTimeHandler 并重置计时器逻辑,以便它知道要等待多长时间才能读取下一个偶数(1,000 毫秒)秒值来设置表盘秒针,因为环境模式可能已变回交互模式!

返回交互模式:checkTimer()方法

还有最后一件适用于。onVisibilityChanged()和。onAmbientModeChanged()方法。你需要确保使用 isTimerEnabled()方法重置秒针计时器逻辑,以防可见性或环境模式变回交互模式。

在 onAmbientModeChanged()方法下添加一行代码,并创建一个 public void checkTimer( ) 方法结构。在这个结构里面,键入 updateTimehandler 对象,然后按下周期键。然后在弹出的帮助器对话框中双击remove messages(int what)void选项,如图 10-37 所示。

9781430265504_Fig10-37.jpg

图 10-37 。创建一个公共的 void checkTimer()方法,键入 updateTimeHandler,选择 removeMessages(int what)

添加一个 UPDATE_TIME_MESSAGE 常量作为参数,以删除当观察面进入环境模式或不可见(不可见)模式时可能留在 MessageQueue 中的任何消息对象。

到目前为止,Java 方法结构应该类似于下面的代码:

public void checkTimer() {
    updateTimeHandler.removeMessages(UPDATE_TIME_MESSAGE);
}

该方法的下一部分需要检查。具有条件 if 结构的 isTimerEnabled()布尔返回值。如果值等于 true,它需要调用**。sendEmptyMessage( )** 方法关闭 updateTimeHandler 对象,使用下面的 Java 代码结构,如图图 10-38 所示:

If( isTimerEnabled ) { updateTimehandler.sendEmptyMessage(UPDATE_TIME_MESSAGE); }

9781430265504_Fig10-38.jpg

图 10-38 。添加一个 if(isTimerEnabled)条件并调用。updateTimeHandler 的 sendEmptyMessage()

现在你需要做的就是在 onAmbientModeChanged()方法的末尾添加一个 checkTimer()方法调用,如图 10-39 中突出显示的。

9781430265504_Fig10-39.jpg

图 10-39 。打开 onAmbientModeChanged()方法,并在末尾添加 checkTimer()方法调用

最后,你还需要在 onVisibilityChanged()方法的末尾添加一个 checkTimer()方法调用,如图 10-40 中突出显示的。当手表重新开机时,将需要此呼叫。要获取该值,计时器必须等到下一个偶数秒(甚至 1000 毫秒)。

9781430265504_Fig10-40.jpg

图 10-40 。打开 onVisibilityChanged()方法,并在最后添加 checkTimer()方法调用

现在你已经有了渲染表盘的基本方法!

摘要

在这一章中,你学习了如何使用 Android Paint 和 Canvas 类中的方法来创建“矢量”表盘设计。首先,我深入介绍了 Paint 类、嵌套类、构造函数以及您将在 watch face design Java 代码中使用的方法。之后,您为时针、分针、秒针和刻度线创建了绘制对象。然后我讨论了画布类和。drawLine()方法,用于绘制所有的表盘组件。

接下来,您创建了。onDraw()方法逻辑将表盘组件部分绘制到画布上,然后您开发了高级模式支持,使用 Paint 方法实现特殊的表盘 API 模式,使用基于布尔标志值动态调整绘制特征的方法。之后,您在 onAmbientModeChanged()和 onVisibilityChanged()方法中实现了这些新方法,它们是核心的 WatchFaceService 方法。

在下一章中,您将开始向手表表面应用添加位图图形和 2D 设计元素,并且您将学习如何创建使用新媒体资源和矢量代码的手表表面。您还将开始使用 AVD 仿真器测试您的代码。**

十一、表盘位图设计:为表盘使用光栅图形

现在,您已经有了足够的 WatchFaces API 代码来测试您的 Java 代码,这意味着这将是一个繁忙的章节。您将学习如何让模拟器工作,测试代码,并进行任何添加,在您有了一个可工作的 vector watch face 应用后,您将了解如何合并 BitmapDrawable 素材来为 watch face 应用创建背景图像。大多数表盘设计将利用位图资源和矢量绘图代码的组合来创建设计。

在让 AVD 模拟器工作并测试了目前已经准备好的代码库之后,您将确保让 watch face 工作所需的每个 Java 语句都准备好了。高级警告,我漏掉了一两个,所以您可以看到如何使用 AVDs 来测试 WatchFaces API Java 代码!

完成基本的表盘代码后,您将添加一个方法来检测表盘是圆的还是方的,然后您将进入不同的位图图像相关的类,这些类是实现矢量表盘设计背后的背景图像所需要的。

我将讨论用于访问插图信息的 Android WindowInsets 类,以及获取数字图像素材所需的 Android Bitmap 和 Resources 类。我还将讨论 Android Drawable 和 BitmapDrawable 类,它们需要将您的数字图像资源和原始位图数据包装成一种格式,onDraw()方法可以利用这种格式将位图图像素材写入您的手表表面背景。

测试表盘设计:使用圆形 AVD

既然现在已经有足够的代码来测试 watch face 应用,那么打开 Android Studio ProAndroidWearable 项目,让我们通过 Watch Face Round 2 (ARM)仿真器运行代码。我已经折叠了所有的代码,以显示大约 60 行顶级代码,如图 11-1 左侧所示(标有 1)。使用 IDE 顶部的运行菜单访问运行...子菜单(标有 2),这将打开运行浮动菜单(标有 3)。如果您是第一次选择此项,您的编辑配置对话框将会打开,这正是您想要的,因为您将需要选择显示的不启动活动选项(标有 4)。

9781430265504_Fig11-01.jpg

图 11-1 。使用运行image穿菜单序列;在编辑配置对话框中,选择不启动活动

一旦您选择了“不启动活动”选项,因为 WatchFace 应用不使用活动对象(因为它是一个壁纸对象),请单击“应用”按钮(位于对话框的右下角),然后单击“运行”按钮。

这将启动一个等待 adb 进度条对话框,如图 11-2 顶部的所示。最终会出现你的 Android_Wear_Round AVD 模拟器。

9781430265504_Fig11-02.jpg

图 11-2 。启动 Android Wear Round AVD,尝试测试应用。AVD 崩溃,单击确定关闭

在图 11-2 左侧可以看到,模拟器会将其组件加载优化到内存中,告诉你 Android 正在启动,然后你的 Android OS 就会出现。在我的情况下,Android 似乎知道我在写这本书,AVD 在我身上崩溃了,所以我借此机会向你展示,在图 11-2 的右侧,Android 开发环境并不比操作系统更防弹!这在新版本的软件中尤其如此,如 Android 5 (Android Studio 1.x),因为它们尚未完善。你应该预料到打嗝,像 AVDs 不能正常工作,不要让他们阻止你!

如果你曾经得到这个“Android Wear 没有响应”的屏幕,如右边的图 11-2 所示,只需点击 OK 按钮,进行任何更改,然后再试一次!我在使用方形和圆形 AVD 仿真器时都遇到了一些问题,所以我将在本章的第一部分向您展示我为纠正这些问题所做的一些事情。

当我使用运行image运行磨损菜单序列再次尝试时,我得到了在图 11-3 左下方看到的磨损选项卡。这个想法的这一部分向您展示了 AVD 正在发生的事情,包括位置和任何问题。

9781430265504_Fig11-03.jpg

图 11-3 。尝试使用运行image佩戴菜单序列再次启动 AVD 安卓 DDMS 面板显示流程

这次当我运行 AVD 模拟器时, Android DDMS 也弹出一个面板,在左侧窗格显示进程信息,在右侧窗格显示 logcat ,是错误日志目录的简称,如图图 11-3 所示。logcat 窗格当前是空的,因此没有记录任何错误,至少在包含代码启动的时间段内没有。一旦您开始使用 Android 应用,该窗格中可能会出现错误,通知您代码中的任何问题。

一旦你的 AVD 启动,如图 11-4 所示,找到设置选项,点击它,并找到 改变手表表面选项,然后点击它并横向滚动手表表面,直到你找到你的专业手表表面选项。一旦你点击了你的预览图片,Android 就会启动 Pro 表盘设计,它显示在图 11-4 的最右侧。似乎 onDraw()方法正在正确绘制表盘设计,但是秒针没有前进,因此您需要检查计时代码。

9781430265504_Fig11-04.jpg

图 11-4 。找到设置image改变表盘顺序,选择专业表盘,运行你的表盘

将整个秒延迟发送到您的处理程序对象

因为秒针固定在原位,所以开始查看代码的逻辑位置是名为 updateTimeHandler 的处理程序对象,因为这段代码是表盘应用每秒启动计时器逻辑的位置。

里面。 handleMessage( ) 方法,会注意到,在条件 if(isTimerEnabled( )) 结构中你已经计算出了 msDelay (直到下一个整秒偏移值的时间)。但是,您没有将那个 msDelay 数据值发送给 Handler 对象,因此它可以触发下一个与时间相关的消息对象。

这是通过使用**完成的。****sendEmptyMessageDelayed()**方法 调用,完成向 handler 对象发送 msDelay 值的 Java 编程语句将在 msDelay 计算后立即执行。Java 代码可以在图 11-5 的中看到,看起来应该像下面的语句:

updateTimeHandler.sendEmptyMessageDelayed(UPDATE_TIME_MESSAGE, msDelay);

9781430265504_Fig11-05.jpg

图 11-5 。添加对 updateTimeHandler 对象的. sendEmptyMessageDelayed()方法的调用

它所做的是使用方法调用将 UPDATE_TIME_MESSAGE 值和以毫秒为单位的延迟值发送到 updateTimeHandler,该方法调用(通过其名称)指定它在确切的 msDelay 值(表示下一个完整的(1,000 毫秒)秒发送一条空消息(触发器)。

使用 Run image Run Wear 菜单序列启动 AVD 测试表盘。秒针还冻着,肯定还少了什么!

在绘制逻辑中将时间对象设置为当前时间

既然 Handler 对象正在广播正确的整秒时间,那么检查基于时序的逻辑的下一个逻辑位置是在中。onDraw( ) 法。请注意,您使用了 watchFaceTime 时间对象来计算小时、分钟和秒针角度位置。在执行这些计算之前,您需要确保时间对象设置准确。因此,你需要打电话给**。** setToNow( ) 方法,关闭 watchFaceTime 时间对象,在顶部。onDraw()方法。

我把它放在 super.onDraw()方法调用和。将背景色设置为黑色的 drawColor()方法调用。Java 编程语句应该如下所示:

watchFaceTime.setToNow( );

正如你在图 11-6 中看到的,Java 代码是没有错误的,你现在已经准备好使用 Round AVD 再次测试代码了。我在 Java 代码中单击了 watchFaceTime 对象引用,以跟踪它在 onDraw()方法中的使用。

9781430265504_Fig11-06.jpg

图 11-6 。向添加一个调用。方法后关闭 watchFaceTime 对象。drawColor()调用

将 Java 代码的 watchFaceTime 时间对象更新行添加到代码中后,在回合 AVD 中再次测试手表表面。现在秒针应该在滴答走了,您已经准备好在 Square AVD 模拟器中测试代码,以获得一些使用 Android_Wear_Square AVD 的经验。

测试表盘设计:使用方形 AVD

使用运行image编辑配置菜单序列(如图图 11-1 所示)将仿真器设置为 Android Wear Square AVD,如图图 11-7 所示。

9781430265504_Fig11-07.jpg

图 11-7 。使用编辑配置对话框选择方形 AVD

我进入设置image换表面找了一个方形的表面预览,没有收录!我检查了我的 AndroidManifest.xml 文件,以确保引用了正确的图像素材,然后我使用谷歌查看是否有其他人在使用 square watch face 模拟器时遇到了这个问题。我看到的一个建议与 AVD 使用主机 GPU 选项有关。有人建议取消这个选项,有人建议选择它!

所以我两个都试过了;两种设置都不起作用!所以我尝试了 横向,这在模拟器中将我的内容转向侧面,但它没有显示方形的表盘预览,所以我尝试了将内存增加到 1GB 以及将内存增加到 500MB ,所有这些的结果都可以在图 11-8 中看到。

9781430265504_Fig11-08.jpg

图 11-8 。尝试改变使用主机 GPU 和方向设置,并增加 RAM 和内部存储

这些设置都不起作用,我花了几天时间试图让 square 预览出现在模拟器中,以便测试代码。我想确保这些 AVD 模拟器工作正常,因为不是所有人都有智能手表硬件来测试!我不断尝试不同的东西,以找出为什么 square Pro 表盘预览没有在 Square AVD 模拟器中显示出来。

因为这可能在某个时候发生在你身上(不仅仅是模拟器),我将告诉你一些我尝试过的事情和最终成功的事情!

在建议使用主机 GPU 不起作用并且给 AVD 更多系统资源也不起作用之后,我想知道 320 像素的正方形预览对于标准的 280 度倾斜正方形表盘来说是否太大了,因为 Android 的文档建议 280 度倾斜尺寸用于正方形表盘设计。

当我在所有这些不同的 AVD 设置和图像素材尺寸之间迭代时,我遇到了几次 Android 方形臂 AVD 的崩溃,这给了我如图图 11-9 所示的对话框。我并没有因此而气馁,因为 Android Studio 和 Android 5 都是新平台,肯定会有很多漏洞,至少在一段时间内是这样。我只是关闭了 AVD(如果它没有因为 emulator-arm.exe 的错误对话框而消失的话)并继续尝试。

9781430265504_Fig11-09.jpg

图 11-9 。在显示此对话框的过程中,方形 AVD 崩溃

我在运行/调试(编辑)配置对话框中的仿真器选项卡下找到了一个解决方案,至少对于我的硬件设置和安装是这样的。该选项卡在图 11-10 中显示为选中状态,在该选项卡下面是一个在我的安装中被默认选中的选项,名为附加命令行选项。我觉得奇怪的是这个选项被选中了,而且是空的,所以我取消选择了它。我还选择了擦除用户数据选项,以确保每次启动时,我都将这个 ARM AVD“干净”地加载到我的系统内存中。

9781430265504_Fig11-10.jpg

图 11-10 。取消选择其他命令行选项并选择擦除用户数据

我在这里的想法是,有什么东西被加载到系统内存中的 AVD 仿真器代码中,这阻止了 square watch face 预览加载到 Android Square ARM AVD 仿真器中,或在 Android Square ARM AVD 仿真器的 UI 中显示。事实证明,这解决了问题,这很好,因为我需要这个 AVD 仿真器为那些没有物理智能手表硬件但想了解 Android Wear 的人工作。

高级模拟器选项显示在图 11-10 中间,包括擦除用户数据选项,我现在已经选中了(如图)。

还有一个禁用引导动画你可以选择,如果你愿意,以加快 AVD 加载序列,也显示在图 11-10 。

现在,当我启动方形 AVD 并选择设置image更改手表表面选项时,我可以滚动并找到 Pro 手表表面方形手表表面预览图像,可以在图 11-11 中从左数第三个窗格中看到。需要注意的是,这个工作过程如果你碰巧遇到,也可以解决 Round AVD 仿真器中同样的问题。

9781430265504_Fig11-11.jpg

图 11-11 。选择设置image改变手表表面对话框,找到(方形)专业手表表面,运行并测试它

当我点击 Pro 手表表面预览图像时,我现在获得了手表表面设计,秒针正在滴答作响,因此基本的手表表面代码有效!

现在,您可以测试已经在程序逻辑中实现的各种特殊模式(低位、老化),这些模式在表盘进入环境模式时启动。在开始测试自定义硬件模式之前,我想了解一下如何使用 AVD 仿真器,因为它们相对较新,所以容易崩溃,以及如何使用 F7 键,该键在 AVD 仿真器中打开和关闭环境模式。

AVD 崩溃:无法连接和没有响应的面板

当我在写这一章的时候测试 watch faces 代码的时候,我遇到了大量的崩溃和上一节提到的问题,我甚至不能选择一个正方形的 watch faces 预览来测试我的 Java 代码。一旦我想通了这一点,我仍然有很多关于“悬挂”AVD 仿真器软件的问题,所以让我们看看我在这一部分遇到的两个场景,这样你就知道如何解决这种情况,如果你碰巧遇到它。希望在这本书出版时这些问题会被解决,但是你永远不知道,所以我在这里把它包括进来只是为了彻底。

这些崩溃的形式要么是“无法连接到手机上的 Android Wear 应用”屏幕,要么是“Android Wear 没有响应”屏幕。“无法连接”错误,可在左侧的图 11-12 中看到,通常允许您“重试”或“重置设备”并继续测试。要进入这两个屏幕,使用“滑动获取一些提示”选项,如图 11-12 中的第一个屏幕(左侧)所示。这将显示您可以单击的按钮;确保在使用重置(红色)按钮作为最后手段之前,先尝试重试(绿色)按钮。

9781430265504_Fig11-12.jpg

图 11-12 。如果你得到一个不能连接到 Android Wear 应用的屏幕,向左滑动几次以获得重试和重置窗格

我已经成功地使用了这两个按钮来“保存”AVD 会话,这样我就不必退出(在对话窗口的右上角使用一个红色的方形 X)AVD 仿真器软件并重新开始。

如果重试或重置设备按钮起作用,您将看到图 11-13 的中间窗格中显示的启动屏幕,然后您可以继续测试您的表盘应用。

9781430265504_Fig11-13.jpg

图 11-13 。如果你看到一个 Android Wear 没有响应的窗格,点击等待,你会看到开始屏幕。按 F7 进入环境模式

你可能会遇到的另一个显示停止错误屏幕是“ Android Wear 没有响应”屏幕,它可以在图 11-13 的最左侧看到。如果您点击等待按钮,您可能最终会得到图 11-13 中间的所示的启动屏幕。如果你点击 OK 按钮,AVD 模拟器将关闭,就像你使用了对话框左上角的红色方块 X 一样。

图 11-13 的右侧窗格中还显示了 AVD 环境模式,您可以通过按下 F7 键(键盘顶部的功能键 7)来调用该模式。正如你所看到的,代码通过使用将颜色调暗到亮度的 50%** 。setAlpha(127)** 技术,运转良好。如果想在环境模式下用灰度代替颜色,就要加上**。setColor(颜色。【WHITE)方法调用里面那段 Java 代码。**

现在,您已经知道如何使用 F7 键将 AVD 仿真器置于环境模式,您可以继续测试低位环境模式代码。

特殊屏幕模式:测试低位环境模式

有多种方法可以测试低位环境模式代码,方法是通过在应用编程逻辑内部的某个位置添加执行此操作的代码行,强制打开 lowBitAmbientModeFlag。

在逻辑上,lowBitAmbientModeFlag=true; toggle 语句将进入 onAmbientModeChanged( ) 方法或 onPropertiesChanged( ) 方法;在这一章中你将会用到这两个工具,所以你可以看到它们是如何工作的。

在超类方法调用后的 onAmbientModeChanged()方法中添加一个lowBitAmbientModeFlag=true; Java 语句,如图图 11-14 所示。

9781430265504_Fig11-14.jpg

图 11-14 。将 lowBitAmbientModeFlag=true 设置添加到 onAmbientModeChanged()方法中,以测试低位环境模式

一旦您人工将低位标志的值设置为真,您将使用 Run image Run Wear 菜单序列来启动 AVD 仿真器。接下来,使用设置image更改手表表面image专业手表表面序列,并启动你的手表表面应用。在图 11-15 的最右边可以看到处于低位环境模式的表盘,它使用白色,任何线条都没有抗锯齿(如锯齿状边缘所示)。

9781430265504_Fig11-15.jpg

图 11-15 。使用设置image改变手表表面image Pro 手表表面系列,和 F7 测试低比特环境模式

因为老化通常与低位模式一起使用,所以我们接下来添加老化标志设置,您可以在它们的“开”设置中测试这两个标志。

特殊屏幕模式:测试低位和老化模式

在评估该标志的 if()语句之前添加一个burnInProtectModeFlag=true;语句,如图 11-16 中的所示。IntelliJ 中弹出了一个灯泡图标,所以我下拉建议菜单,双击“从‘if’语句中删除大括号”建议。这使得 IntelliJ IDEA 稍微简化了 Java 代码。如果 IntelliJ 给你一个合理的代码优化建议,往往明智的做法是采纳它,看看效果如何!

9781430265504_Fig11-16.jpg

图 11-16 。添加一个 burnInProtectModeFlag=true,并采用 IntelliJ 建议来简化 if()构造

在这种情况下,它将该方法的代码行从 12 行减少到 6 行,或者说减少了 50%的代码,一旦您删除了强制代码行的标志,您将把它放入**。onPropertiesChanged( )** 方法下一步。

修改后的 onAmbientModeChanged()方法的 Java 代码(没有 true 标志设置)可以在图 11-17 的中看到没有错误,看起来像下面的 Java 方法结构:

@Override
public void onAmbientModeChanged(boolean ambientModeFlag) {
    super.onAmbientModeChanged(ambientModeFlag);
           if(lowBitAmbientModeFlag) setAntiAlias(!ambientModeFlag);
           if(burnInProtectModeFlag) setBurnInProtect(ambientModeFlag);
    ensureModeSupport;
    invalidate;
    checkTimer;
}

9781430265504_Fig11-17.jpg

图 11-17 。一旦实现了 IntelliJ if()优化建议,该方法就会减少到 6 行代码

接下来,让我们将这些特殊模式标志“强制真”设置放入 onPropertiesChanged()方法中,在方法的最后,如图图 11-18 所示。这个方法结构的新 Java 代码应该如下所示:

@Override
public void onPropertiesChanged(Bundle properties) {
    super.onPropertiesChanged(properties);
    lowBitAmbientModeFlag = properties.getBoolean(PROPERTY_LOW_BIT_AMBIENT, false);
    burnInProtectModeFlag = properties.getBoolean(PROPERTY_BURN_IN_PROTECTION, false);
    lowBitAmbientModeFlag = true;
    burnInProtectModeFlag = true;
}

9781430265504_Fig11-18.jpg

图 11-18 。将设置为 true 值的特殊模式标志移到 onPropertiesChanged()方法的底部

现在,让我们使用设置image更改表盘image Pro 表盘序列再次测试表盘。正如你在图 11-19 中从左边第三个窗格看到的,当你按下 F7 键将表盘切换到环境模式时,低位标志逻辑正在工作,你已经知道了。

9781430265504_Fig11-19.jpg

图 11-19 。通过使用 Android_Wear_Square_API_21 AVD 模拟器测试您的 watch face Java 代码

然而,您的老化编程逻辑并没有像您预期的那样将轮廓放置在表盘的指针周围。再次按下 F7 键切换回交互模式,如图 11-19 最右边的第四个窗格所示。如您所见,表盘指针的 RBG 颜色值没有被恢复,因此您还需要了解一下 ensureModeSupport( ) 方法以及 setBurnInProtect( ) 方法。让我们先将开关恢复到交互式 RGB 颜色模式。

因为您在中配置了表盘指针颜色。onCreate()方法,方法是调用自定义的。createHand()方法,您需要在 ensureModeSupport()方法的最终 else 部分中设置颜色。

您已经使用将 alpha 值设置回 255(完全不透明)。setAlpha()方法调用,所以,需要使用**。setColor( )** 方法调用来重新配置 Paint 对象以使用蓝色、绿色、红色和白色的颜色类常量。我以这种方式编写代码,使用不同的方法调用,向您展示设置颜色值的不同方式。请记住,您使用了。setARGB()方法设置。createHand()方法。在图 11-20 中显示没有错误的 Java 代码应该如下所示:

} else {
    pHourHand.setAlpha(255);
    pMinuteHand.setAlpha(255);
    pSecondHand.setAlpha(255);
    pTickMarks.setAlpha(255);
    pHourHand.setColor(Color.BLUE);
    pMinuteHand.setColor(Color.GREEN);
    pSecondHand.setColor(Color.RED);
    pTickMarks.setColor(Color.WHITE);
}

9781430265504_Fig11-20.jpg

图 11-20 。添加。setColor()方法调用 ensureModeSupport()方法中 if-else 循环的 else 部分

现在我们来看看为什么?setStyle(画图。Style.STROKE)方法不是在时针和分针表盘部件周围描绘单个像素轮廓。从图 11-19 中可以看到。setBurnInProtect()方法显然对屏幕上显示的内容没有任何影响。

正如你在图 10-25 中看到的,你正在尝试使用笔画绘画。样式常量在时针和分针矢量对象周围创建轮廓效果,并使用填充绘制。样式来创建实心或填充效果。虽然这种方法可以很好地处理 Android 中的任何 2D ShapeDrawable 对象,包括文本、圆形、矩形和其他“封闭”的线条或曲线形状,但它不能处理 1D “开放”的线条或曲线形状的对象,因为它们没有内部**!因此,在 1D 矢量“射线”对象的情况下,描边和填充将产生完全相同的效果!**

你需要做的就是使用来完成这项工作。setStrokeWidth( )** 方法调用线条形状对象,这将允许您通过不必声明画图来优化老化方法。名为 paintStyle 的样式对象,并简化 if-else 条件结构,对时针使用三个像素的**,它需要比分针粗,对分针使用两个像素的**,这与您对刻度元素使用的值相同,因此它们可以很容易地在表盘显示上看到。****

****使用单个像素绘制表盘元素将无法让最终用户轻松读取表盘时间,即使这是屏幕老化保护的最佳设置。如果你想看看它看起来怎么样,你可以在你的表盘应用中使用 1.f 笔画宽度数据值进行实验!在这种情况下,时针需要 2.f 才能区分,分针和刻度线将使用 1.f 设置。在图 11-21 中可以看到一个新的方法结构,它为绘制对象设置了 StrokeWidth 属性,应该是这样的:

private void setBurnInProtect(boolean enabled) {
    if(enabled) {
        pHourHand.setStrokeWidth(3.f);
        pMinuteHand.setStrokeWidth(2.f);
    } else {
        pHourHand.setStrokeWidth(6.f);
        pMinuteHand.setStrokeWidth(4.f);
    }
}

9781430265504_Fig11-21.jpg

图 11-21 。去除油漆。请使用 setBurnInProtection 方法中的样式逻辑,而不是使用。setStrokeWidth()

现在,当您使用 Run image Run Wear 并启动 AVD 仿真器并使用设置imageChange Watch FaceimagePro Watch FaceimageF7 键时,您将获得启用低位和老化模式的环境模式,如图 11-22 中最右侧窗格所示。

9781430265504_Fig11-22.jpg

图 11-22 。使用设置image改变手表表面image Pro 手表表面系列,和 F7 测试低比特环境模式

正如你所看到的,手表表面仍然是有吸引力的,可读的,完全可用的,即使使用零抗锯齿。表盘设计使用非常少的像素来绘制表盘设计元素,这有助于提供屏幕老化保护,这是该模式的目标。

接下来你需要做的是学习如何检测用户的手表表面是方形还是圆形,使用 Android 的 WindowInsets 类。在您将表盘形状检测代码放置到位后,您将了解 Android 位图DrawableBitmapDrawable 类,以及如何使用这些来实现矢量表盘组件的位图背景图像,将您的 Android 表盘提升到全新的水平。

Android WindowInsets 类:轮询屏幕形状

Android WindowInsets 类是一个公共 final 类,它扩展了 java.lang.Object 类。阶级阶层看起来像这样:

java.lang.Object
  > android.view.WindowInsets

这个类是 android.view 包的一部分,因为它与视图对象一起使用。WindowInsets 对象可用于描述应用窗口内容的一组插入。在这种情况下,这个对象保存了表盘的特征,例如表盘的形状以及它是否有一个架子,就像摩托罗拉 MOTO 360 目前所做的那样。

这些 WindowInsets 对象是“不可变的”(固定的或不可变的)。他们可能会被扩展(由谷歌的 Android 团队),在未来包括其他插入类型。这里您将结合使用 WindowInsets 对象和**。onapplywindowsets(window insets)**方法,这是您接下来要编写的代码。

这个 WindowInsets 类有一个公共构造函数方法,它采用格式WindowInsets(WindowInsets insets)并构造一个(新的)WindowInsets 对象。它通过从源 WindowInsets 定义中复制数据值来实现这一点,在本例中,这将来自每个智能手表制造商。

这个 WindowInsets 类有 18 个方法,对于 WatchFaces API 开发,您可能想了解其中的两个。 isRound( ) 方法会告诉你表盘是圆的(或者不是圆的,会是方的),还有**。getSystemWindowInsetBottom()**方法会告诉你 MOTO 360 用来连接其手表表面屏幕的“架子”的大小(整数)。

接下来,让我们创建**onapplywindowsets()**方法,这是 WatchFaceService 的一个方法。引擎类的实现我一直保存到现在,当你学习一些实际上可以“利用”它所提供的东西的时候!

检测表盘形状:使用窗口镶嵌

在引擎类中 onTimerTick()方法之后添加一个**public void on applywindowsets(window insets insets){ }**空方法结构。正如你在图 11-23 中看到的,你必须使用 Alt+Enter 并让 IntelliJ 在你的类的顶部为你编写import android.view.WindowInsets;语句。

9781430265504_Fig11-23.jpg

图 11-23 。添加 public void on applywindowsets(window insets insets){ } empty 方法结构

下一步是使用下面的 Java 变量声明语句将 roundFlag 布尔变量添加到复合布尔语句的末尾(在私有引擎类的顶部),如图 11-24 顶部所示(高亮显示):

boolean lowBitAmbientModeFlag, burnInProtectModeFlag, roundFlag;

9781430265504_Fig11-24.jpg

图 11-24 。将名为 roundFlag 的布尔变量添加到 Engine 类中,并在 onApplyWindowInsets()方法中将其设置为等于 insets.isRound()

在空的 onApplyWindowInsets()方法中,使用 Java super 关键字调用超类,并将名为 insets 的 WindowInsets 对象向上传递给父类。接下来,将 roundFlag 布尔变量设置为等于的布尔值。 isRound( ) 方法调用掉 insets WindowInsets 对象。

如 Android Studio 底部的图 11-24 所示,Java 方法应该类似于下面的 Java 方法结构:

@Override
private void onApplyWindowInsets(WindowInsets insets) {
    super.onApplyWindowInsets(insets);
    roundFlag = insets.isRound( );
}

现在你有办法知道用户的智能手表使用的是圆形还是方形显示硬件。在本章的下一部分中,当您学习如何将位图图像放置在矢量观察面组件的后面时,将会用到这个布尔值。然而,首先让我们看看 Android 的位图和资源类,您将使用它们来加载您的图像数据。

Android 位图类:使用数字图像资源

Android Bitmap 类是一个 public final 类,实现了 Java Parcelable 接口,扩展了 java.lang.Object master 类。位图类层次结构如下所示:

java.lang.Object
  > android.graphics.Bitmap

Android Bitmap 类有两个嵌套的(helper)类。第一个是枚举位图。CompressFormat 类,它指定了这些位图对象可以使用编解码器压缩成的已知图像文件格式,编解码器是 Android 操作系统的一部分。枚举值包括 JPEGWEBPPNG

第二个是枚举位图。Config 类,它指定可能的位图配置。其中包括 alpha_8 ,这是 8 位 256 透明度级别 ALPHA 通道专用格式,以及 ARGB_8888 ,这是 32 位格式,每色彩平面(和 ALPHA 通道)使用 8 位数据。

还有一种叫做 RGB_565 的 16 位位图格式,很有意思,因为 Android 目前还没有 16 位编解码支持(BMP、TIF、TGA 都支持 16 位颜色)。还有被弃用(意味着不再支持)的 ARGB_4444 格式,你不应该使用,因为它在 Android API Level 13 中被弃用了。

Bitmap 类有 50 多个方法,所以很明显,我不能在这里详细介绍这些方法,但我会在本章以及下一章中介绍那些您将在 WatchFaces API 设计中用来实现位图素材的方法,在下一章中,我将讨论用于创建其他 watch face 模式位图素材的数字图像技术。

getWidth( ) 方法调用将返回位图对象的宽度属性,同样的,也是如此。 getHeight( ) 方法调用,将返回位图对象的高度属性。

Android API Level 1 中添加的public static Bitmap createScaledBitmap(Bitmap src,int dstWidth,int dstHeight,boolean filter) 方法,通过缩放源位图对象的图像数据来创建新的位图对象。方法参数包括一个 src ,源位图对象,一个 dstWidth ,或目标位图对象的目标宽度,一个 dstHeight ,目标位图对象的目标高度,以及一个布尔过滤器数据值,如果您希望 Android 将双线性插值应用于图像缩放算法,该值将被设置为 true 。您将使用此选项来获得最高质量的图像缩放结果。如果要进行上采样(从较低的分辨率到较高的分辨率),这一点尤其重要。

该方法返回一个缩放的位图对象,或者如果没有执行缩放,则逻辑上返回源位图对象。如果源位图对象的宽度小于(或等于)零,或者如果位图对象的高度小于或等于零,该方法将引发 IllegalArgumentException。您将在后面的章节中使用这个方法。onDraw()方法来确保您的位图对象适合您的表盘显示。

接下来,让我们看看 Android Resources 类,因为您将不得不使用它来将数字图像资源加载到 Bitmap 对象中。

Android 资源类:使用你的 Res 文件夹

公共 Android Resources 类扩展了 java.lang.Object,是 android.content.res 包的一部分。它有一个已知的直接子类,称为 MockResources。Java 类的层次结构如下所示:

java.lang.Object
  > android.content.res.Resources

Resources 类用于创建对象,这些对象允许您访问存储在 /res 文件夹中的应用资源。这个 Android“R”资源系统(例如,存储在 /res/drawable-hdpi 中并被引用为 R.drawable.imagename 的图像)跟踪与您的 Android 应用相关联的所有非代码素材。您可以使用这个类来访问这些应用资源,就像您将在下一节中所做的那样。

通过使用**,您可以获得一个 Resources 对象,该对象加载了对您的应用中的外部项目素材(资源)的所有引用。getResources( )** 方法调用应用的主上下文,使用 Java this 关键字访问该上下文。在这个应用场景中,它看起来像下面的 Java 语句:

Resources watchFaceResources = ProWatchFaceService.this.getResources( );

IntelliJ IDEA 中安装的 Android SDK 工具在构建时将应用资源编译成应用二进制文件,它们从/res/drawable 引用路径进入 R.drawable 引用路径。这就是在 Java 方法调用中使用 R.drawable.image_asset_name 而不是/res 的原因。

为了能够将外部资源用作资源,您必须将它定位到项目 res/目录中正确的源子文件夹中,例如,图像形状将进入**/RES/drawable**(r . drawable)矢量动画将进入 /res/anim (R.anim)。

作为应用构建(编译)过程的一部分,Android SDK 工具将为每个素材资源生成 r .符号。然后,您可以在应用的 Java 代码中使用这些 R. references 来访问资源。确保在运行时使用 Java 代码中的 R. references 来访问素材。

使用外部应用资源使开发人员能够在不修改 Java 代码或 XML 标记的情况下更改应用的可视特征。此外,提供这些替代资源将允许开发者优化他们的应用,跨越异常广泛(并且快速增长)的消费电子设备硬件配置集合。动态访问不同新媒体资源的能力将允许开发人员适应不同的场景,如不同的最终用户语言、不同的屏幕尺寸、形状和密度,以及在这种情况下,圆形表盘与方形表盘!

能够在运行时动态访问各种资源是开发兼容各种不同类型硬件设备的 Android Watch Faces(和其他)应用的一个重要方面。

Resources 类有两个嵌套的(也称为 helper)类。这里有资源。NotFoundException 类,该类处理在找不到请求的资源(或其路径)时引发资源 API 异常的情况。还有一个资源。主题类,保存应用中实现的特定操作系统主题的当前属性值。

创建资源对象有一个公共的构造函数方法,采用这个参数格式:Resources(asset manager assets,DisplayMetrics metrics,Configuration config) 。请注意,如果您使用。getResources(),这个资源对象将会为您创建,您不必显式地使用这个构造函数方法调用和格式化。

这是一个实现的情况,您将在本章后面的 Java 语句中使用它,这在本节前面已有概述。现在让我们为 watch face 应用创建位图和资源对象。

访问图像:使用位图和资源

在私有引擎类的顶部(声明区域) 您需要做的第一件事是创建声明和命名两个位图对象的复合语句。如果由于某种原因,智能手表硬件使用的分辨率不是 320 x 320 像素,则这些位图对象中的一个将保存图像资源(原始图像数据),另一个将保存图像数据的缩放版本。

这种方法将允许您使用更高分辨率的数字图像素材,如果更高分辨率的表盘是在以后几年开发的。复合语句的 Java 代码可以在图 11-25 中看到,应该如下所示:

Bitmap watchFaceBitmap, scaleWatchFaceBitmap;

9781430265504_Fig11-25.jpg

图 11-25 。添加一个名为 watchFaceBitmap 和 scaleWatchFaceBitmap 的复合位图对象声明

点击代码行的任意位置,如图 11-25 中突出显示的,使用 Alt+Enter 击键序列告诉 IntelliJ 为您编写导入语句。

接下来要做的事情是创建资源对象。最合理的方法是 onCreate()方法,因为它只需要在应用启动时执行一次。你可以用其他声明在引擎类的顶部声明资源 watchFaceResources,或者你可以在 onCreate()中声明、命名并本地加载这个对象**,使用下面的 Java 语句,这也可以在图 11-26 中看到:**

Resources watchFaceResources = ProWatchFaceService.this.getResources( );

9781430265504_Fig11-26.jpg

图 11-26 。创建一个名为 watchFaceResources 的资源对象;使用。getResources()方法

点击代码的资源行,使用 Alt+Enter 工作流程,指导 IntelliJ 编写导入语句,如图图 11-26 所示。

在您继续编码之前,让我们花点时间了解一下 Android Drawable 类,因为您将在代码中实现 Drawable 对象来保存这些位图对象中的一个。

Android 可绘制类:创建可绘制对象

Android 公共抽象 Drawable 类是为在 Android 中创建 Drawable 对象而临时编写的,因此它直接扩展了 java.lang.Object,如以下代码所示:

java.lang.Object
  > android.graphics.drawable.Drawable

Drawable 是一个 Android 术语,指可以在屏幕上绘制的东西。Drawable 类提供了处理底层可视资源的通用 API,这些可视资源可能是大量与图形相关的绘图元素类型中的任何一种,如九色、矢量、颜色、形状、渐变、插入、图层、剪辑或位图,这是您将在本章的下一节中使用的内容。

Drawable 对象没有任何接收或处理事件或直接与用户交互的能力,因此您必须用某种类型的视图对象(小部件)来“包装”Drawable 对象才能做到这一点。

drawable 有 15 个已知的直接子类。您将使用 BitmapDrawable 子类,但您也可以在应用开发中使用其他子类,包括:VectorDrawable、GradientDrawable、NinePatchDrawable、AnimatedVectorDrawable、PictureDrawable、LayerDrawable、ClipDrawable、ColorDrawable、ScaleDrawable、RotateDrawable、ShapeDrawable、InsetDrawable、RoundedBitmapDrawable,还有一个 DrawableContainer。

有七个已知的间接亚类;这些是直接子类的子类,包括:AnimatedStateListDrawable、RippleDrawable、AnimationDrawable、PaintDrawable、LevelListDrawable、StateListDrawable 和 TransitionDrawable。

Drawables 在被包装到视图中之前,对 Android 应用来说是不可见的。Drawables 采用多种图形元素格式:

  • 您将在这里使用的可绘制对象是BitmapDrawable;它是“位”或像素的映射,使用 GIF、PNG、WEBP 或 JPEG 数字图像“编解码器”将数字图像数据素材压缩和解压缩到系统内存中。
  • NinePatchDrawable 是对 PNG 数据格式的扩展,允许图像指定如何拉伸和缩放周界区域。
  • ShapeDrawable 包含简单的矢量绘图命令,而不是原始位图对象,允许矢量作品“渲染”到任何屏幕大小。
  • LayerDrawable 允许开发者创建一个图像合成的 Drawable。这种类型的 drawable 就像 Android 中的 mini-GIMP,你可以使用 z-order 层将多个位图 drawable 堆叠在另一个之上。
  • statedravable是另一种类型的复合 Drawable,它根据 statedravable 的状态设置从一组给定的 drawable 中选择一个。多状态 Android ImageButton 小部件就是一个很好的例子。
  • LevelDrawable 是另一种类型的复合 Drawable,它根据 LevelDrawable 对象的级别设置,从一组给定的 drawable 中选择一个。状态栏上的信号电平图标就是一个很好的例子。

Android 中有许多其他可绘制的对象类型,所以如果你有兴趣更详细地了解所有这些,请查看 ApressPro Android Graphics(2013)标题。

现在是时候创建手表表面 Drawable 对象,并使用它来加载一个位图对象,这样您就可以开始将背景图像添加到手表表面设计中。您还将合并 roundFlag 布尔值,以便如果您对方形和圆形表盘有不同的设计,您将知道如何设置表盘代码以使用正确的版本。

加载 Drawable:使用 roundFlag 布尔值

你需要做的第一件事,在私有的引擎类的顶部,是声明并命名你的可绘制对象。让我们为这个 Drawable 对象使用逻辑名 watchFaceDrawable ,这样当你在代码中使用它时,你就能确切地知道它是什么了。可绘制对象声明可以在图 11-27 的中看到,看起来应该像下面的 Java 对象声明:

Drawable watchFaceDrawable;

9781430265504_Fig11-27.jpg

图 11-27 。在 Engine 类中添加一个 Drawable 对象声明,并将该对象命名为 watchFaceDrawable

接下来您要做的是在 onCreate()方法的顶部,在 Resources 对象声明和实例化代码行之后添加一个空的条件 if-else 结构。

条件结构将评估 roundFlag 布尔变量,并用正确的数字图像资源加载 watchFaceDrawable 对象。在图 11-28 中可以看到正在构建的空语句,它应该看起来像下面的 Java 条件 if-else(空)结构:

if(roundFlag) {
         // Round Watch Face Java Statements
} else {
         // Square Watch Face Java Statements
}

9781430265504_Fig11-28.jpg

图 11-28 。在 onCreate()方法中添加 if(roundFlag)条件结构;从弹出菜单中选择圆形标志

在条件语句的 if 部分,设置watchfacedravable对象等于getDrawable(r . drawable . preview _ pro _ circular)方法调用watchfacesource对象。在图 11-38 中,可以看到预览 _pro_circular PNG 以蓝色突出显示。一旦你完成了整个条件 if-else 结构的编码,如图 11-29 所示的 Java 代码应该如下所示:

if(roundFlag) {
    watchFaceDrawable = watchFaceResources.getDrawable(R.drawable.preview_pro_circular);
} else {
    watchFaceDrawable = watchFaceResources.getDrawable(R.drawable.preview_pro_square);
}

9781430265504_Fig11-29.jpg

图 11-29 。在 if 中添加一个 watchFaceDrawable 对象,用 watchFaceResources.getDrawable()加载它

正如你在图 11-29 中看到的,当你编码。getDrawable()方法调用时,IntelliJ 会为你列出你的 Resources (R.) object assets,就像你键入的每个句点字符一样。键入 R 和句点,然后选择可绘制类型(文件夹)。接下来输入另一个句号,然后选择 preview_pro_circular PNG 图片引用,完成 Java 编程语句。现在你要做的就是在 else 结构中重复这个。

一旦你完成创建这个 if(roundFlag)-else 结构,用正确的资源对象引用加载你的 watchFaceDrawable 对象,你的代码应该是没有错误的,如图图 11-30 所示,正方形手表表面数字图像素材引用在适当的位置(并突出显示)。

9781430265504_Fig11-30.jpg

图 11-30 。在 if-else 语句的 else 部分添加不同的 watchFaceResources.getDrawable()方法调用

接下来,让我们快速查看一下 Android BitmapDrawable 类,您将在下一行 Java 代码中使用该类来“转换”Drawable 对象,该对象现在已经根据 roundFlag 布尔变量的设置加载了正确的图像资源引用。isRound()方法调用。咻!

我想给你一个这个类的概述,因为 BitmapDrawables 是 Android 应用开发中最强大和最常用的可绘制对象类型之一,既用于 UI 设计,也用于应用的图形设计。如果您正在寻找更多关于 2D Android UI 设计的高级资料,有机会的话,请查看 Apress titlePro Android UI(2014)。

Android 的 BitmapDrawable 类:图像绘制

Android 的公共 BitmapDrawable 类扩展了 Drawable 类,包含在Android . graphics . Drawable包中。Java 类的层次结构如下所示:

java.lang.Object
  > android.graphics.drawable.Drawable
    > android.graphics.drawable.BitmapDrawable

BitmapDrawable 是一个包含 Bitmap 对象的 Drawable 对象,该对象可以平铺、拉伸、旋转、着色、褪色或对齐。您可以使用 Android API Level 1 中引入的八种原始构造函数方法中的三种重载构造函数方法之一来创建 BitmapDrawable:

BitmapDrawable( )                                // This constructor was deprecated in API 4 and can be ignored

BitmapDrawable(Resources res)                   // This constructor was deprecated in API 18 and can be ignored

BitmapDrawable(Bitmap bitmap)                   // This constructor was deprecated in API level 4 and can be ignored

BitmapDrawable(Resources res, Bitmap bitmap)    // Creates Drawable using an external bitmap resource

BitmapDrawable(String filepath)                 // This constructor was deprecated in API level 5 and can be ignored

BitmapDrawable(Resources res, String filepath)  // Create a Drawable by decoding from a file path

BitmapDrawable(InputStream is)                  // This constructor was deprecated in API level 5 and can be ignored

BitmapDrawable(Resources res, InputStream is)  // Create Drawable decoding bitmap from input stream

因为您将强制转换 BitmapDrawable 对象,所以我不会在这里涵盖所有这些构造函数方法;不过,可以说,您可以通过使用图像文件路径、使用输入流、使用另一个位图对象、使用 XML 定义膨胀、使用另一个位图对象或使用资源对象来创建 BitmapDrawable 对象,就像您将要做的那样。

如果你想使用一个 XML 定义文件定义一个 BitmapDrawable,使用一个< bitmap > XML 标签来定义这个元素。BitmapDrawable 将与 Bitmap 对象一起使用,该对象处理原始位图图形的管理和转换,并且最终将是在绘制到 Canvas 对象时使用的对象,您将在本章使用的代码中注意到这一点。

有许多 XML 属性、参数或特性可用于位图对象,在表 11-1 中有所概述。

表 11-1 。使用 XML 标记参数可访问的 BitmapDrawable 属性

|

位图属性

|

位图属性功能描述

| | --- | --- | | 反别名 | 启用或禁用抗锯齿(边缘平滑算法) | | 发抖 | 针对颜色深度不匹配启用位图抖动(ARGB8888 至 RGB565) | | 过滤器 | 为高质量缩放启用或禁用位图双线性过滤 | | 重力 | 定义用于位图对象的重力常数设置 | | 米帕普 | 启用或禁用 mipMap 提示功能 | | 科学研究委员会 | 位图资源文件标识符资源路径 | | 平铺模式 | 定义整体位图对象平铺模式 | | 平铺模式 | 明确定义水平平铺模式 | | 平铺模式 | 明确定义垂直平铺模式 |

使用 BitmapDrawable 对象:提取和缩放

接下来,让我们实现一个 BitmapDrawable 对象,从包含它的 Drawable 对象中获取所需的位图对象数据。Java 语句用下面的代码将watchFaceDrawable Drawable 转换为 (BitmapDrawable) ,在图 11-31 : 中可以看到没有错误

watchFaceBitmap = ( (BitmapDrawable) watchFaceDrawable ).getBitmap( ); // Cast to a (BitmapDrawable)

9781430265504_Fig11-31.jpg

图 11-31 。添加一个 watchFaceBitmap 对象,并将其设置为等于 watchFaceDrawable 对象中的位图资源

这一行简洁的 Java 代码包含了 watchFaceBitmap Bitmap 对象、watchFaceDrawableDrawable 对象(包含您的图像资源)、一个未声明的 BitmapDrawable 对象,该对象充当 APK 文件中的 Drawable 素材和需要驻留在最终用户的 Android 硬件设备系统内存中的原始位图之间的桥梁。

该语句所做的是将 watchFaceBitmap 位图对象设置为等于一个的结果。getBitmap( ) 方法调用,该方法调用自转换结构,其中使用**(BitmapDrawable)watchFaceDrawable**转换结构将 watchFaceDrawable 对象转换为 BitmapDrawable,该转换结构神奇地将 Drawable 对象转换为 BitmapDrawable 对象。

一旦 Drawable 被转换成 BitmapDrawable,这个。getBitmap()方法调用将工作,也就是说,它将是一个有效的方法调用,不会抛出异常。

现在,您已经准备好了方法,这些方法是。来自 WatchFaceService 的 onAmbientModeChanged()方法。引擎类,当智能手表硬件进入环境模式时,你需要确保所有这些不同的模式都得到支持。

缩放位图:使用。createScaledBitmap()方法

下一个需要放置代码的地方是 onDraw()方法内部,在这里需要将一个位图对象插入到绘制管道中。在计算宽度和高度值之后,创建一个空的条件 if 结构,因为您将使用这些值来确定是否需要缩放。在图 11-32 的中显示的正在构建中的代码应该是这样的:

if(scaleWatchFaceBitmap) {
    // an empty conditional if statement thus far
}

9781430265504_Fig11-32.jpg

图 11-32 。添加一个 if(scaleWatchFaceBitmap)条件结构,以确定是否需要缩放位图

在条件 if 结构中,评估 scaledWatchFaceBitmap 是否未使用(空的或 null ),或者它是否与源位图具有不同的维度。如图图 11-33 所示,Java 代码应该如下所示:

if ( scaleWatchFaceBitmap == null ||
     scaleWatchFaceBitmap.getWidth( ) != width ||
     scaleWatchFaceBitmap.getWidth( ) != height  ) { // Java code to be processed will go in here }

9781430265504_Fig11-33.jpg

图 11-33 。添加用于确定 scaleWatchFaceBitmap 对象是空的还是需要设置的布尔 OR 逻辑

在条件 if 结构中,如果位图需要缩放,调用**。createScaledBitmap( )** 方法,关闭 Bitmap 类,如图图 11-34 所示,用这个结果加载 scaleWatchFaceBitmap 对象。到目前为止,Java 代码应该是这样的:

if ( scaleWatchFaceBitmap == null ||
     scaleWatchFaceBitmap.getWidth( ) != width ||
     scaleWatchFaceBitmap.getWidth( ) != height  ) {
     scaleWatchFaceBitmap = Bitmap.createScaledBitmap(watchFaceBitmap, width, height, true);
}

9781430265504_Fig11-34.jpg

图 11-34 。在条件 if 中,调用 scaleWatchFaceBitmap 的 Bitmap.createScaledBitmap()方法

正如你在图 11-34 中看到的,如果你输入位图类名并按下句号键,IntelliJ 会弹出一个帮助器对话框,里面有所有适用的方法。您可以选择适用于您想要做的事情的选项,在本例中为.createScaledBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter)。到目前为止,Java 代码是没有错误的,如图 11-35 所示。

9781430265504_Fig11-35.jpg

图 11-35 。将 watchFaceBitmap 对象和 watchface 宽度和高度传递到。createScaledBitmap()

请注意,如果 scaleWatchFaceBitmap 尚未使用(空),则此。createScaledBitmap()方法调用会将 watchFaceBitmap 对象转移到 scaleWatchFaceBitmap 对象中,即使宽度和高度相同!正如您在上一节中了解到的,这就是该方法的工作方式,因为如果不需要缩放,它将返回原始位图对象。

接下来你需要做的是绘制位图作为背景,在手表表面屏幕的左上角(0,0)。这是使用。drawBitmap()方法调用。如图图 11-36 所示,Java 语句应该如下所示:

watchface.drawBitmap(scaleWatchFaceBitmap, 0, 0, null);

9781430265504_Fig11-36.jpg

图 11-36 。打电话给。drawBitmap()方法,使用 scaleWatchFaceBitmap

如果您想知道,方法调用参数区域末尾的 null 引用了一个 Paint 对象。如果没有定义画图对象,要将更多的屏幕绘制选项应用于位图,那么它将为空,如 empty 或 undefined。如果您想定义更多关于如何在屏幕上绘制位图对象的选项,您可以创建这个 Paint 对象,命名它,加载它(配置它),并在方法调用的最后一个参数槽中使用它的名称。

测试背景位图:圆形与方形

让我们使用两个 AVD 模拟器来测试应用,以确保您的位图资源现在呈现在它们应该在表盘设计后面的地方。正如你在图 11-37 的右侧看到的,你的圆形表盘的位图背景似乎使用了方形表盘位图资源,这意味着圆形标志没有被设置为

9781430265504_Fig11-37.jpg

图 11-37 。在两个 AVD 仿真器中测试位图代码

当然,位图对象缩放也可能有一些问题,因为这个圆形的表盘 AVD 似乎被放大了不少。让我们确保并创建两个测试图像,其中的文本为圆形和方形。我将在 PNG32 图像资源中使用 alpha 通道透明度,这样您将能够看到画布对象的背景(黑色)颜色,因为那行代码被留在了。onDraw()方法,并且当前正被位图对象覆盖。这将向你展示另一个层次的灵活性;也就是说,无缝结合位图和矢量的方法来设计手表的脸。我将在圆形手表表面图像中包含一个圆形周边环。

round_face_text.pngsquare_face_test.png 图片素材复制到**/AndroidStudioProjects/ProAndroidWearable/wear/src/main/RES/drawable-hdpi/**文件夹中,结果可以在图 11-38 右侧看到。

9781430265504_Fig11-38.jpg

图 11-38 。使用操作系统文件管理工具将测试位图添加到 wear/src/main/RES/drawable-hdpi 文件夹中

接下来,使用以下 Java 代码,将用于测试 if(roundFlag)代码的手表表面预览图像的引用更改为 watch_face_test 图像,如图图 11-39 所示(突出显示):

if(roundFlag) {
    watchFaceDrawable = watchFaceResources.getDrawable(R.drawable.round_face_test);
} else {
    watchFaceDrawable = watchFaceResources.getDrawable(R.drawable. square_face_test);
}

9781430265504_Fig11-39.jpg

图 11-39 。添加 if-else 条件结构,该结构评估是否正在使用圆形观察面并获取正确的资源

让我们在两个 AVD 仿真器中运行这个新配置,以确认发生了什么。正如你在图 11-40 中看到的,缩放正在正确地执行,因为 320 像素源在圆形 AVD 像素中,方形 AVD 正在向下采样到 Android 称方形表盘使用的 280 像素。你可以通过查看文本(字体)大小来判断。

9781430265504_Fig11-40.jpg

图 11-40 。迄今为止,在方形和圆形 AVD 模拟器中测试您的 Java 代码,并确保代码工作正常

我谷歌了一下这个。isRound()设置不正确”的问题,并发现许多开发人员在 AVDs 和实际智能手表硬件上都遇到了这个问题。其中一个解决方案涉及使用 XML 的 UI 布局设计工作区,但是这些不适用于您在这里所做的事情,即直接写入 Canvas 对象(公认的高级方法)。因此,我要试着自己解决这个问题!

解决 roundFlag 问题:onCreate()到 onDraw()

我将假设 onApplyWindowInsets()方法 正在工作,并且正在正确地设置 roundFlag 变量,并且我遇到的真正问题是这个方法的执行时间。如果首先调用 onCreate()方法,则 roundFlag 不设置为默认值(false)以外的任何值。所以,我要做的第一件事是,在宽度和高度变量计算之后,将 onCreate()方法中的代码放入 onDraw()方法中。

正如你在图 11-41 中看到的,我在我移动的代码周围留了一些空间,这样如果需要的话,我可以把它移回来或者进一步优化它。

9781430265504_Fig11-41.jpg

图 11-41 。将位图和 Drawable 相关代码从 onCreate()方法移动到 onDraw()方法 ?? 内

我在两个 avd 中测试了应用,看看这是否是问题所在,确实是问题所在,手表表面现在使用了正确的位图素材,如图图 11-42 所示。

9781430265504_Fig11-42.jpg

图 11-42 。测试位于。圆形和方形 AVD 仿真器中的 onDraw()方法

这是一个好消息,因为您将希望使用 Android Canvas 和 Java 7 代码来完成所有的手表表面设计,但这也导致了一些新的优化问题,因为 onDraw()方法被频繁调用,并且您只想在第一次绘制之前做一次这些事情,这就是为什么我最优地尝试将这些代码语句放在 onCreate()方法中。

希望 Android OS 开发人员将来会改变这种方法调用顺序,以便在 onCreate()之前调用 onapplywindowsets()。我将向您展示如何确保这些位图设置语句只执行一次,以便您的应用不会多次执行所有这些操作,这不是非常理想的,因为您只想加载一个 Resources 对象,确定要使用的图像资源(圆形或方形),然后在应用启动时一次性缩放该资源。

让我们优化的位图部分。onDraw()方法来实现 firstDraw 布尔变量,这样这些操作只在第一次 onDraw()方法调用时执行。

优化您的 onDraw():第一次抽牌与每次抽牌

在引擎类的顶部,创建一个名为 firstDraw 的布尔变量,并将其设置为等于一个 true 值,因为如果不显式设置这个布尔变量,它将默认为 false 值。在图 11-43 中显示(突出显示)的 Java 代码应该如下所示:

boolean firstDraw = true;

9781430265504_Fig11-43.jpg

图 11-43 。在引擎类的顶部创建一个布尔 firstDraw 变量,并将其设置为 true 值

接下来您需要做的是将 if(firstDraw) 条件语句放在位图相关的代码周围,这些代码是从。onCreate()方法添加到 onDraw()方法中。这将确保这些语句只执行一次,这样就不会浪费内存或 CPU 周期。

因为这在你第一次运行应用时是正确的,所以你所要做的就是把你想要执行的代码放在这个结构中,然后在条件语句结束时,在退出语句之前,把 firstDraw 布尔值设置为 false。这将锁定该语句以供将来使用,从而有效地允许您模拟。onCreate()函数。onDraw()方法结构,如图图 11-44 所示。

9781430265504_Fig11-44.jpg

图 11-44 。在位图代码周围添加一个 if(firstDraw)结构,以便它只在第一次绘制时执行

因为我是一个优化狂,我立即开始想知道这里面是否有其他语句。onDraw()方法,该方法应锁定在此“仅处理一次”框中。

我决定将 if(firstDraw)语句之后的 if(scaleWatchFaceBitmap)条件结构也放在该语句中,这样所有与位图对象相关的图像处理都只进行一次,因为硬件特性(屏幕分辨率和形状)在运行时不会改变,所以您可以在第一个 onDraw()周期完成所有这些处理。

将 if(scaleWatchFaceBitmap)条件结构从 if(firstDraw)条件结构的外部复制到 if(roundFlag) in-else 结构的内部。确保将它粘贴在将 firstDraw 布尔变量设置为 false 的 Java 语句之前,在结构的末尾。一定要缩进 if(scalewatchFaceBitmap)结构,如图图 11-45 所示。如果你在 AVD 中测试这个,你会看到你的秒针在滴答走,这意味着这个代码在工作。

9781430265504_Fig11-45.jpg

图 11-45 。将 if(scaleWatchFaceBitmap)结构放在 firstDraw=false 之前的 if(firstDraw)结构中

现在你所要做的就是为其他的表盘模式开发位图资源,你将会在第十二章中了解到,你将会从矢量和位图的角度掌握表盘设计!

摘要

在本章中,您使用 AVD 模拟器测试了到目前为止开发的 Java 代码。您了解了 avd 的来龙去脉,然后开始实现 onApplyWindowInsets()方法,以便能够检测圆形表盘类型。

您已经了解了实现位图图像作为表盘设计的装饰背景所需的 Android 类。其中包括位图类、资源类和可绘制类及其子类位图可绘制类。

之后,您学习了如何在 onDraw()方法中实现这些类以在手表表面设计的背景中加载和显示位图素材,使用条件 if 结构来优化智能手表和智能手机设备硬件上的处理负载。

在下一章中,您将使用 GIMP 图像编辑软件来创建不同的数字图像背景,以支持 WatchFaces API 要求您支持的不同模式。********

十二、表盘数字图像:开发多模式素材

现在,您已经安装了一个位图素材作为手表表面设计的背景,是时候进入数字图像和创建位图的不同模式兼容版本的工作过程了。在本章中你将使用 GIMP (GNU 图像处理程序),因为每个人都可以免费使用它,但是你也可以使用 Photoshop CS。

首先,我将讨论 GIMP 为我们提供了哪些数字图像处理算法,用于将交互模式素材(我将它用作 PNG 索引颜色(256 色)素材)转换为灰度和黑白模式,以便与环境模式、低位环境模式和老化保护模式一起使用。

一旦您为交互式(PNG8)、环境(PNG3)、环境低位(PNG1)和环境老化(PNG1)模式创建了位图资源,您将回到 Java 编码。您将修改自定义方法以合并这些位图资源之间的切换,以便矢量资源和位图资源都符合模式要求(或接近),并且矢量和光栅设计组件无缝地一起渲染(工作)。

你将返工。onDraw()方法结构,以便每当模式发生变化时,位图对象将被重新缩放,因为您现在将跨所有模式实现大范围的位图图像素材。

您还将检查 ensureModeSupport()方法以优化处理,添加位图对象(背景)支持,添加老化保护模式支持,并使用另一个 else-if 部分扩展 if-else-if 结构。

位图模式环境 : GIMP 灰度图像模式

如果你还没有,在gimp.org下载并安装最新版本的 GIMP,然后启动它。使用文件image打开菜单序列,进入打开图像对话框,如图图 12-1 所示,打开prowatchfaceint.png文件,该文件应该在本书的文件库中。

9781430265504_Fig12-01.jpg

图 12-1 。使用文件image打开菜单序列来访问打开图像对话框

正如您在对话框中看到的,GIMP 将允许您在对话框的左侧位置窗格中导航磁盘驱动器文件夹层次结构,并使用对话框右侧的预览窗格显示您选择的数字图像文件的信息。我选择的文件显示在对话框的中间部分。点击对话框右下角的打开按钮,打开您选择的文件。

请注意,我已经使用 PNG8 文件格式、优化了这个交互模式源图像,它使用 256 色加抖动。使用抖动可以让你模拟超过 256 种颜色。这样,当您也使用 256 级灰度创建灰度环境模式图像时,这也将有适当的抖动,使其看起来好像在环境模式图像中有超过 256 级的灰度。通过使用这种数字图像优化方法,您将能够为环境模式以及其他所需模式“预优化”图像素材。

通过使用抖动预优化 8 位图像,您将能够在环境模式下模拟超过 256 级的灰度,使其看起来像超过 256 种颜色(或对于环境模式,灰度级)。这是优化 8 位索引彩色抖动图像素材的优势之一;当您对它们应用灰度算法(对于环境模式)时,抖动也会出现,为环境模式资源提供与抖动为交互模式资源提供的相同的视觉升级效果,并具有完全相同的数据足迹优化(甚至可能更好)。

为了创建这个预优化的环境模式灰度图像,在 GIMP 的图像菜单下找到模式子菜单。单击模式菜单选项右侧向右箭头,并下拉子菜单。选择灰度选项,如图 12-2 顶部所示。

9781430265504_Fig12-02.jpg

图 12-2 。使用图像image模式image灰度菜单序列将彩色图像转换为灰度图像

正如您所看到的,GIMP 已经确定所选择的图像是一个索引的 8 位图像。如果图像是 24 位(PNG24)或 32 位(PNG32),那么 RGB 选项将带有项目符号。一旦您选择了这个灰度选项,它将被加上项目符号,并将应用 GIMP 算法从图像中移除色调(颜色),仅保留光度(亮度)值。

现在你所要做的就是使用文件image导出为菜单序列,如图 12-3 左侧所示,进入 GIMP 导出图像对话框。

9781430265504_Fig12-03.jpg

图 12-3 。使用 GIMP 文件image导出为菜单序列访问导出图像对话框(像在 Photoshop 中另存为)

一旦你进入导出图像对话框,如图 12-4 所示,你可以选择中间区域的 prowatchint.png,这样你就不必再输入一遍,然后将“int”改为“amb”一旦你点击导出按钮,你会得到导出图像为 PNG 的选项对话框。取消选中所有选项以产生最小的文件大小。

9781430265504_Fig12-04.jpg

图 12-4 。要使用灰度环境模式,将文件命名为 prowatchfaceamb.png,并选择最大 PNG 压缩

这将为您提供 8 位灰度环境模式图像,,它使用了您的灰度环境模式图像所能支持的最大数量的灰色值(256)。当然,一些智能手表可以支持 8 位灰度,但是,一些智能手表会将图像减少到更少的灰度,因此,我将向您展示如何优化更低的位级灰度图像,以防您的目标智能手表在环境模式下仅使用 16 种灰度。我还将向您展示如何创建仅使用八种灰度但仍然看起来很棒的位图图像(我甚至将向您展示如何使四种灰度看起来可以接受)。我还将介绍华硕 ZenWatch 使用的 1 位(两种灰度或黑白)低位环境模式。

低位模式位图:GIMP 的色调分离算法

你需要做的下一件事是创建低位环境模式图像。一些智能手表在不活跃使用(被观看)时会切换到“环境”低功耗使用模式。例如,索尼智能手表 3 (SW3)使用了透反射屏幕(一种可以在阳光下轻松阅读的技术),在互动模式下使用 16 位(RGB 565)颜色(以及背光),在环境模式下关闭背光以节省电能。所以你可以在索尼 SW3 环境模式下使用彩色,尽管谷歌 WatchFaces API 文档建议使用低位灰度。

这就是为什么我在本章的这一节详细介绍这种低位灰度优化的工作过程。一些智能手表制造商最终将使用比我之前提到的 256 级更少的灰度级。有些可能使用 16 ( PNG4 4 位)灰度,但也可能更少。请咨询您的智能手表制造商,了解他们的环境模式到底有多低!

掌握了目标智能手表制造商在环境模式下支持多少灰度(或颜色)的知识,以及您将在本章的这一部分学习的工作流程,您甚至可以优化八个灰度级( PNG3 ),甚至一个微薄的四个灰度级( PNG2 ),并且仍然使生成的位图图像素材看起来很好,特别是在较小的智能手表表面上,它使用相对精细的点间距(即小像素大小)。

要减少 8 位灰度图像中的灰度等级,您需要访问 GIMP 颜色菜单,该菜单位于图 12-5 的顶部。找到色调分离选项并选择它进入一个对话框,允许你应用颜色(或灰度)减少算法。

9781430265504_Fig12-05.jpg

图 12-5 。使用 GIMP Colors image色调分离菜单序列访问色调分离对话框,以减少灰色的数量

您要选择颜色的偶数位数 : 二色1 位四色2 位八色3 位16 色4 位灰度。

是的,即使是灰色的阴影,或者黑色和白色,也被认为是颜色!接下来让我们启动色调分离对话框,创建一个 PNG4 4 位灰度图像。

正如你在图 12-6 中看到的,如果你选择了预览复选框,你将能够实时看到该算法的滑块设置对灰度图像的影响。

9781430265504_Fig12-06.jpg

图 12-6 。将色调分离设置为 16 级灰色,以适应 4 位灰度环境模式显示

如果您将滑块值设置为 16 (4 位)颜色值,您将看到视觉效果几乎与 256 色版本一样好,使用的灰度值数据少 16 倍!然而,存在一些可见的“条带”,这并不令人满意。稍后,在我解释了色调分离算法之后,我将讨论如何使用抖动技术来减轻索引色带化。我将解释这两个工作过程,这样你就会知道使用 GIMP 降低色彩值的所有主要方法。

目前,很难从智能手表制造商那里获得关于环境模式下灰度(甚至颜色)所用的位级的物理规格。希望制造商在未来为 Android 可穿戴设备开发者发布一份涵盖这一内容的技术信息白皮书。

这种环境模式能达到的最“低位”是 1 位低位环境模式。在这里,您将使用 1 位图像来创建老化图像。

接下来,让我们看一个不同的工作过程,它允许你通过使用索引颜色转换对话框来访问 GIMP 2 抖动算法。

抖动低位图像:索引模式转换

因为你不能在色调分离对话框中访问 Floyd-Steinberg 抖动算法 ,我觉得这是 GIMP 2 开发者的疏忽,让我们看看另一个工作过程(和产生的对话框),它在颜色(这里是灰度)减少方面提供了更多的选择。

要访问弗洛伊德-斯坦伯格抖动算法,你需要使用索引颜色转换对话框。使用图像image模式image索引菜单序列进入,如图图 12-7 所示。这将改变一个图像回到索引颜色模式,你可能记得这是它开始的地方。进入灰度模式去除了色调值,只留下亮度值。

9781430265504_Fig12-07.jpg

图 12-7 。使用图像image模式image索引菜单序列,并选择 Floyd-Steinberg 抖动算法

返回到索引图像模式(在这种情况下,由于源图像数据,它是灰度的)将触发索引颜色转换对话框。这是你可以找到弗洛伊德-斯坦伯格抖动算法选项的地方,你可以在图 12-7 的右下角看到它被选中。

选择生成最佳调色板单选按钮,设置 16842最大色数值,点击转换按钮。正如你在图 12-8 中所看到的,将要应用的抖动可以在生成的低位灰度环境图像中产生显著的质量差异。右下角的 1 位环境模式(无失真)图像已经过反转(我将在“老化模式”部分讨论这一工作过程)和算法处理,以便最大限度地减少使用的白色像素数量。

9781430265504_Fig12-08.jpg

图 12-8 。正如你所看到的,抖动将改善低比特环境模式图像质量一个数量级

一旦您确定了目标智能手表制造商支持的低位灰度级别,您就可以将文件导出为文件名prowatchfacelow.png。如果你对支持的灰度值的数量有任何疑问,使用 1 位方法,反转它(如果需要),然后导出你的文件。您希望屏幕的大部分区域使用黑色(关闭)像素值。我将在老化模式部分讲述反转

在您拥有最适合目标智能手表低位环境模式(或环境模式)的低位灰度环境模式图像后,使用 GIMP 文件image导出为菜单序列,如图图 12-3 所示,并将文件命名为prowatchfacelow.png,如图图 12-9 所示,在对话框顶部的名称字段中。点击导出按钮,导出索引色 PNG 文件(不选择选项),16 灰度 PNG4 ,8 灰度 PNG3 ,4 灰度 PNG2 ,黑白 PNG1

9781430265504_Fig12-09.jpg

图 12-9 。使用文件image导出为菜单序列使用导出图像对话框保存 prowatchfacelow.png

创建老化模式位图:使用反转算法

接下来你需要创建的是黑白(1 位)低位环境模式图形,也将用于老化模式。

这个过程的第一步是撤销你最后做的任何事情,无论是色调分离对话框还是索引颜色转换对话框。

GIMP Edit image Undo 菜单根据你的最后一次操作进行定制,所以如果你一直在使用色调分离对话框,那么这个菜单序列就是EditimageUndo Posterization。这可以在 GIMP 的左上角看到,如图图 12-10 所示。使用 Edit image Undo 的原因是,这样你就可以让完整的 8 位 256 级灰度图像进入任何算法。

9781430265504_Fig12-10.jpg

图 12-10 。使用编辑image撤销工作过程返回到 8 位(256 色)原始灰度图像数据

您可能希望在将图像“重新存储”为两种颜色(1 位或 PNG1)之前执行此操作,因为您希望为该色调分离算法提供尽可能多的原始灰度,因为该算法需要处理的数据越多,产生的结果就越好。

使用 GIMP **颜色image ** 色调分离菜单序列(如图图 12-5 所示)再次进入色调分离对话框。这一次,你需要选择 1 位颜色(两种颜色),这是最低可能的色调分离设置。

使用色调分离算法的原因是因为你特别不想要 1 位颜色抖动效果,如图 12-8 右上角所示。这是因为你试图得到一个线条绘制效果 ,一旦你阿尔法混合表盘黑色背景色,这将使白色像素变暗为中灰色,这将提供所需的结果。这是为了确保在老化保护模式下不会发生老化。

设置及其结果可以在图 12-11 中看到。正如你所看到的,结果看起来不错,在智能手表显示屏上看起来会更好,因为像素间距点大小屏幕密度更精细。如果你想看看这是什么样子?setAlpha(127),看一下图 12-15 中最右边的面板。

9781430265504_Fig12-11.jpg

图 12-11 。使用色调分离对话框,将色调分离级别设置为两种颜色,然后单击确定按钮

如果你想要更细(更细)的线条,你可以使用缩放工具 (它是一个放大镜)和橡皮擦工具(看起来像你以前学校的橡皮擦)手动擦除线条较粗部分的一些像素。

正如你可能已经注意到的,即使图形现在只使用黑白颜色值,没有防走样,正如老化模式所要求的那样,现在的问题是你的表盘是打开的(白色),而不是关闭的(黑色)。你最终需要做的是与你现在在屏幕上看到的完全相反的结果。幸运的是,GIMP 有一种算法可以“翻转”或“反转”像素值,幸运的是,正如你可能想象的那样,它最适合黑白图像。

你将在算法上处理翻转白色背景颜色值以使背景变成黑色,同时使黑色线条变成白色的方法是使用 GIMP Invert 算法。这也位于颜色菜单下,在菜单上比色调分离选项更靠下一点,你可以在图 12-12 的中间看到。

9781430265504_Fig12-12.jpg

图 12-12 。要将白色背景上的黑线反转为黑色背景,使用颜色image反转菜单

当您选择此选项时,没有对话框,因为 GIMP 将简单地为您反转像素颜色值,并且您的图像将立即变成黑背景上的白线,这是老化保护模式所需的结果。

正如你在图 12-13 中看到的,你非常接近你需要的屏幕老化保护的结果,即只有在你绝对需要的地方是白色像素,其他地方都是黑色像素。重要的是要注意,如果您希望在老化保护模式下打开较少的像素,您可以在这些反转模式下编辑线条,使线条变细。

9781430265504_Fig12-13.jpg

图 12-13 。GIMP 2.8.14 右预览区看到的颜色image反转菜单序列的结果

事实上,这可能是获得一些 GIMP 2 数字图像编辑实践的好方法。要编辑这些线条,请使用放大镜工具放大图像,然后使用 GIMP 橡皮擦工具删除像素,直到图像中的所有线条都是一到两个像素宽。

为了谨慎起见,你也可以去掉拖鞋内部的颜色(白色);我会把这项工作留给你自己去做,这样你就可以得到一些使用 GIMP 的练习,因为这是一个重要的 Android watch face 开发工具,需要你去掌握。

需要指出的是,这些 BagelToons LLC 图片属于我的客户 Ira H. Harrison-Rubin,它们应该仅用于在本书的背景下学习和实践,因为 BagelToons 将发布所有 BagelToons 作品,包括这一张,作为 Watch Faces 应用。

细化完成后,使用文件image导出为菜单序列进入导出图像对话框,如图图 12-14 所示,然后将此数字图像命名为prowatchfacelow.png。点击导出按钮(不选择选项),将该烧屏保护图像素材以 PNG1 图像文件格式导出。

9781430265504_Fig12-14.jpg

图 12-14 。使用文件【导出为】菜单序列使用导出图像对话框保存 prowatchfacelow.png

如果您打算将 1 位灰度用于环境低位和老化模式,那么您将把文件保存为【prowatchfacelow.png】的,并且仅使用三个位图来覆盖四种模式。我已经展示了一幅图像的四个位图——交互(8 位颜色)、环境(3 位、八级灰度)、低位环境(黑白)和老化保护(黑白),如图图 12-15 所示,这样你就可以直观地比较所有这些。****

**9781430265504_Fig12-15.jpg

图 12-15 。四种位图资源—8 位彩色、3 位灰度、黑白、黑白和灰色—涵盖了您的所有模式

现在让我们换个话题,开始一些 Java 编码,这样您就可以实现前三个位图文件了。稍后,您将实现老化模式,这可以通过位图资源或代码中的 alpha 混合来实现。

多模式位图:使用 Java 更改位图

要使 watch face 应用位图素材与您的模式检测代码兼容,您需要做的下一件事是更改 onDraw()和 ensureModeSupport()方法,以添加更改位图素材的代码。

首先,将 onDraw()方法中的Resources watchFaceResources = ProWatchFaceService.this.getResources(); Java 语句移动到私有引擎类声明区域的顶部,如图 12-16 中的所示。因为不止一个方法将使用这个资源对象,所以您必须使它更加“可见”

9781430265504_Fig12-16.jpg

图 12-16 。将资源对象声明、命名和加载 Java 语句移到引擎顶部

因为你将不止一次地缩放多个位图对象,即从交互模式到环境模式再回到交互模式,你需要将 if(scaleWatchFaceBitmap)结构移回到if(first draw)条件 if()结构之外,如图图 12-17 所示。

9781430265504_Fig12-17.jpg

图 12-17 。将 if(scaleWatchFaceBitmap)结构移到 if(firstDraw)结构之外(在每次绘制时评估)

请注意,现在 if(scaleWatchFaceBitmap)是它自己的结构,您可以通过将 scaleWatchFaceBitmap 对象设置为 null (清除或清空它的值)来触发它进行位图评估和缩放。

这是因为 if()条件赋值器的逻辑或结构的一部分是scaleWatchFaceBitmap = null,所以如果您想要调用这个构造中的内容,只需将对象设置为 null,就可以为您创建的任何位图资源调用这个重新缩放逻辑。

在开始重写。ensureModeSupport()方法,您需要将您在前面使用 GIMP 一章中创建的素材安装到正确的 Android Studio 项目 HDPI 可绘制资源文件夹中。

将 prowatchfaceamb.png、prowatchfaceint.png 和 prowatchfacelow.png 位图素材从保存它们的文件夹中复制到**/ProAndroidWearable/wear/src/main/RES/drawable-hdpi/**文件夹中,如图图 12-18 所示。

9781430265504_Fig12-18.jpg

图 12-18 。将 8 位交互式、3 位环境和 1 位低位环境位图复制到项目的/drawable-hdpi 文件夹中

如您所见,我使用了三种优化程度最高的位图素材,以便使用最少的颜色、灰度级别和黑白级别,并以智能手表显示图像所用的最少功率实现最小的文件大小。在图 12-18 中可以看到,视觉质量不错,这里我只使用 256 色(交互模式)八灰度(环境模式)或黑白(低位或烧屏模式)。

现在,您已经准备好修改环境模式和低位环境模式的 Java 代码,以便使用正确的位图资源。在黑色背景下工作良好的白色表盘元素将被重置为使用黑色,以获得与白色位图资源的最大对比度,但老化保护位图资源除外,在该资源中,您将使用白色表盘设计元素。让我们从灰度位图开始,然后实现索引颜色和预烧保护模式位图素材的 Java 代码。

将位图对象安装到低位环境模式

将带有 prowatchfacelow 素材引用的watchfacedrable配置语句和来自 onDraw()方法的 watchFaceBitmap 语句复制到 if(enableLowBitAmbientMode)构造的顶部。颜色值保持白色,因为背景是黑色的。在图 12-19 中可以看到 if 结构的 Java 代码,没有错误,应该如下所示:

if( enableLowBitAmbientMode ) {
    watchFaceDrawable = watchFaceResources.getDrawable(R.drawable.prowatchfacelow);
    watchFaceBitmap = ((BitmapDrawable) watchFaceDrawable).getBitmap();
    scaleWatchFaceBitmap = null;
    pHourHand.setAlpha(255);
    pMinuteHand.setAlpha(255);
    pSecondHand.setAlpha(255);
    pTickMarks.setAlpha(255);
    pHourHand.setColor(Color.WHITE);
    pMinuteHand.setColor(Color.WHITE);
    pSecondHand.setColor(Color.WHITE);
    pTickMarks.setColor(Color.WHITE);

9781430265504_Fig12-19.jpg

图 12-19 。复制 watchFaceDrawable 和 watchFaceBitmap 代码,并添加 scaleWatchFaceBitmap = null

如你所见,第三行代码是一个scaleWatchFaceBitmap = null;语句,它将触发if(scaleWatchFaceBitmap)条件语句。触发此方法的模式更改也将触发图像重新缩放代码(如果需要),因为此语句在前两个语句之后出现。

因为背景图像现在是灰度的,主要是白色的,所以您需要更改。setColor()方法调用来引用颜色。黑色常量,以便刻度线和表盘指针相对于背景位图对象(灰度数字图像素材)具有最大对比度。

接下来,您将对。setAlpha()方法调用,添加。setColor()方法调用,并将与位图对象相关的语句添加到第二个else-if(is ambientmode())结构中。由于在环境模式中使用了大量白色背景图像,您将再次需要使用黑色颜色值常量,并且您还将想要使表盘设计元素全黑,以获得最大的可读性,以及关闭这些表盘设计元素的屏幕像素。出于这个原因,你也可以将当前的 127 Alpha 值设置为 255 的完全不透明值。我把这个方法结构中被改动过的 Java 语句都加粗了。如图 12-20 所示,Java 代码现在应该是这样的:

} else if( isInAmbientMode() ) {
    watchFaceDrawable = watchFaceResources.getDrawable(R.drawable.prowatchfaceamb);
    watchFaceBitmap = ((BitmapDrawable) watchFaceDrawable).getBitmap();
    scaleWatchFaceBitmap = null;
    pHourHand.setAlpha(255);
    pMinuteHand.setAlpha(255);
    pSecondHand.setAlpha(255);
    pTickMarks.setAlpha(255);
    pHourHand.setColor(Color.BLACK);
    pMinuteHand.setColor(Color.BLACK);
    pSecondHand.setColor(Color.BLACK);
    pTickMarks.setColor(Color.BLACK);

9781430265504_Fig12-20.jpg

图 12-20 。添加与位图相关的代码,将 Alpha 值更改为 255,将颜色值更改为 color。黑色

接下来,让我们修改 if-else-if-else 结构的 else 部分,以添加与位图对象相关的语句,这些语句将设置当手表表面处于交互模式时,您将为其使用的索引彩色图像素材。

优化交互模式:将刻度线颜色设置为黑色

条件 if-else 结构的最后一个 else 部分构成了 ensureModeSupport()方法的大部分,它设置交互模式特征(如果没有设置其他模式标志的话)。这段代码改动最少,只把光学标记对象改成了颜色。黑色并添加您在其他部分添加的位图相关的 Java 语句。你的代码,如图图 12-21 所示,应该是这样的:

} else {
    watchFaceDrawable = watchFaceResources.getDrawable(R.drawable.prowatchfaceint);
    watchFaceBitmap = ((BitmapDrawable) watchFaceDrawable).getBitmap();
    scaleWatchFaceBitmap = null;
    pHourHand.setAlpha(255);
    pMinuteHand.setAlpha(255);
    pSecondHand.setAlpha(255);
    pTickMarks.setAlpha(255);
    pHourHand.setColor(Color.BLUE);
    pMinuteHand.setColor(Color.GREEN);
    pSecondHand.setColor(Color.RED);
    pTickMarks.setColor(Color.BLACK);
}

9781430265504_Fig12-21.jpg

图 12-21 。在 else 结构的顶部添加与位图对象相关的代码,并将光学标记更改为颜色。黑色

现在,您已经在 ensureModeSupport()方法中添加了与位图相关的内容并更改了颜色,从而最大限度地提高了现有 Java 代码的可读性,接下来让我们在 Square AVD 模拟器中测试交互式、环境和低位环境模式。

在 Square AVD 中测试交互和环境模式

确保您已经删除了私有引擎类中所有出现的强制标志设置,换句话说,删除lowBitAmbientModeFlag=true; and burnInProtectModeFlag=true;代码片段。接下来,使用运行image运行磨损菜单序列并启动 Square AVD 仿真器。如果您想确保您的 Square AVD 仿真器是当前选择的仿真器,您可以使用运行image编辑配置菜单序列。

一旦模拟器启动并加载 Pro 手表表面,您将看到方形手表表面测试模式,因为您将该代码留在了。onDraw()方法。如果你想知道我为什么这样做,这是为了向你展示如何在 watch face 启动时显示不同的位图(比如法律免责声明,在手表表面后面,第一次启动时)。

在第十三章介绍表盘配置对话框中添加圆形表盘装饰支持时,知道如何实现是非常有用的。最理想的情况是,您可以完全删除这些代码,只使用正方形位图资源作为表盘背景。

但是,我想向您展示,通过在。onDraw()方法结构。在任何情况下,一旦你看到正方形测试屏幕,它仍然是从 if(firstDraw)代码调用的,按下 F7 键,你将看到环境模式位图素材,它显示在图 12-22 的左侧。

9781430265504_Fig12-22.jpg

图 12-22 。使用设置image改变手表表面image专业手表表面系列,和 F7 测试环境模式

如果您再次切换 F7 键,您将触发。ensureModeSupport()方法。颜色结果可以在图 12-22 的右侧看到,正如你所看到的,状态栏图标和 Peek 卡与背景艺术作品的定位是完美的。

为了测试低位(在此优化素材的情况下为 1 位颜色)模式,您需要在 onAmbientModeChanged()方法中,紧接在**super . onAmbientModeChanged()**方法调用语句之后,再次安装一个lowBitAmbientModeFlag=true;强制模式开关,如图图 12-23 所示。

9781430265504_Fig12-23.jpg

图 12-23 。在 onAmbientModeChanged()方法中将 lowBitAmbientModeFlag 布尔值设置为 true 值

强制将这个低位环境模式标志设置为“开”状态,如果(lowBitAmbientModeFlag) 关闭代码中的抗锯齿,这应该在之前完成,正如你在图 12-24 中看到的,也调用 ensureModeSupport()方法,该方法安装低位(1 位颜色)图形。

9781430265504_Fig12-24.jpg

图 12-24 。使用设置image改变手表表面image Pro 手表表面系列,和 F7 测试低比特环境模式

现在,当你使用 Run image Run Wear 并启动 AVD 仿真器,然后使用SettingsimageChange Watch FaceimagePro Watch FaceimageF7 键时,你将得到启用低位模式的环境模式,如图 12-24 左侧所示。

你需要做的下一件事是创建低比特环境模式位图的老化模式版本,它将使用灰色颜色值而不是白色值。您将使用 GIMP 将白色像素更改为 50%灰色颜色值,这将匹配 Android 颜色。灰色恒定完美,提供了一个灰色“老化”版本的低位环境模式。

之后,您将把**if(enableBurnInAmbientMode)**构造添加到 enableModeSupport()方法中,该方法设置正确的位图和颜色值。

Android Wear 老化模式:位图和 Java 代码

为了彻底了解表盘设计,让我们实现一个老化模式图像和 Java 代码,通过这种低位设计创建老化保护!

创建老化模式位图 : GIMP 亮度-对比度

如果 GIMP 没有打开,打开它,使用文件image打开菜单序列打开你的prowatchfacelow.png文件。你要将位图的白光强度“调暗”50%,以匹配 Android 的颜色。灰色常数。

正如你在图 12-25 中看到的,你在 GIMP 中实现这一点的方法是使用颜色image亮度对比菜单序列,这将打开一个对话框,允许你调暗表盘设计中白色像素发出的光线(亮度)。相当酷!

9781430265504_Fig12-25.jpg

图 12-25 。打开 prowatchfacelow.png 文件并调用颜色image亮度-对比度菜单序列

在图 12-26 的右侧显示的亮度-对比度对话框将允许您在不同的亮度(或对比度)设置下设置预置。当您设置好想要保存的设置后,点击加号( + )图标即可。例如,你现在可以尝试将设置保存为“Android 50%灰度老化保护模式预设”。

9781430265504_Fig12-26.jpg

图 12-26 。将亮度滑块调整到最左侧,将亮度降低 50%(或 127/255)

亮度滑块向左拖动到 -127 值,实际上是 128,因为从零开始计数,正好是 8 位灰度范围内 256 个值的一半。确保您的预览复选框被选中,这样您可以实时看到修改,然后单击确定按钮,这将完成操作并应用算法。

接下来,您需要使用 GIMP 文件image导出为工作流程保存老化模式数字图像素材,如图图 12-27 所示,并使用相同的 15 字符格式将文件命名为 prowatchfacebur.png

9781430265504_Fig12-27.jpg

图 12-27 。使用文件【导出为】菜单序列使用导出图像对话框保存 prowatchfacebur.png

现在,您已经准备好返回 Java 编码,并通过插入一个新的 if-else 部分,在 enableModeSupport( ) 方法中实现老化保护模式。

Java 中的老化保护:if(enableburniambientmode)

在 ensureModeSupport()方法结构中,您需要做的第一件事是在方法结构的顶部添加一个布尔 enableBurnInAmbientMode 变量。您将把它设置为一个逻辑与条件,如果**is ambientmode()**返回 true 并且 burnInProtectModeFlag 被设置为 true 值,那么它将返回一个 true 值。否则,这将评估为假,因为老化保护要求环境模式开启,并且老化制造商支持常数到位并指定。

在私有方法中创建了这个新的布尔标志之后,您将需要在低位环境部分之后和仅环境模式部分之前添加一个 if-else 条件部分。方法的结尾是最后的 else 部分,它涵盖了交互模式设置。

这个if(enableBurnInAmbientMode)构造应该使用您刚刚创建的prowatchfacebur.png图像资源加载一个 watchFaceDrawable 对象,然后使用从 Drawable 中提取新的老化模式位图对象。getBitmap( ) 方法,将它放在 watchFaceBitmap 对象中。一旦完成,您可以将 scaleWatchFaceBitmap 对象设置为 null 。这将触发 onDraw()方法中的重缩放计算,因为您已经为新模式更改了位图资源,这可能是必要的。

作为一种优化技术,我将使用 255(完全打开)的 alpha 值和 Android OS 颜色,而不是使用 127 alpha 通道值来创建 50%的灰度值,就像您之前所做的那样。设定表盘指针和刻度线颜色值的灰色常数。

如果你想知道为什么使用这种安卓颜色。灰色常数是一种优化,如果你使用的 alpha 通道值为 255,这是因为 Android 不会调用其混合算法,这可能是处理密集型的。此外,您不希望通过将白色与您创建的烧屏保护图像中使用的灰色混合来创建亮像素。

您会注意到,在 ensureModeSupport()方法的 Java 代码清单中,我将完整地包括它,因为您现在已经实现了所有这些模式及其位图对象,您没有对这些位图图像素材使用任何 alpha 混合,因此您已经在整个方法中应用了这种优化技术。如果您打算使用背景位图,您可以决定删除这些调用!

完了。ensureModeSupport()方法可以在图 12-28 中看到,Java 代码应该是这样的方法结构:

private void ensureModeSupport(){
    boolean enableLowBitAmbientMode = isInAmbientMode() && lowBitAmbientModeFlag;
    boolean enableBurnInAmbientMode = isInAmbientMode() && burnInProtectModeFlag;
    if (enableLowBitAmbientMode) {
        watchFaceDrawable = watchFaceResources.getDrawable(R.drawable.prowatchfacelow);
        watchFaceBitmap = ((BitmapDrawable) watchFaceDrawable).getBitmap();
        scaleWatchFaceBitmap = null;
        pHourHand.setAlpha(255);
        pMinuteHand.setAlpha(255);
        pSecondHand.setAlpha(255);
        pTickMarks.setAlpha(255);
        pHourHand.setColor(Color.WHITE);
        pMinuteHand.setColor(Color.WHITE);
        pSecondHand.setColor(Color.WHITE);
        pTickMarks.setColor(Color.WHITE);
    } else if (enableBurnInAmbientMode) {
        watchFaceDrawable = watchFaceResources.getDrawable(R.drawable.prowatchfacebur);
        watchFaceBitmap = ((BitmapDrawable) watchFaceDrawable).getBitmap();
        scaleWatchFaceBitmap = null;
        pHourHand.setAlpha(255);
        pMinuteHand.setAlpha(255);
        pSecondHand.setAlpha(255);
        pTickMarks.setAlpha(255);
        pHourHand.setColor(Color.GRAY);
        pMinuteHand.setColor(Color.GRAY);
        pSecondHand.setColor(Color.GRAY);
        pTickMarks.setColor(Color.GRAY);
    } else if ( isInAmbientMode() ) {
        watchFaceDrawable = watchFaceResources.getDrawable(R.drawable.prowatchfaceamb);
        watchFaceBitmap = ((BitmapDrawable) watchFaceDrawable).getBitmap();
        scaleWatchFaceBitmap = null;
        pHourHand.setAlpha(255);
        pMinuteHand.setAlpha(255);
        pSecondHand.setAlpha(255);
        pTickMarks.setAlpha(255);
        pHourHand.setColor(Color.BLACK);
        pMinuteHand.setColor(Color.BLACK);
        pSecondHand.setColor(Color.BLACK);
        pTickMarks.setColor(Color.BLACK);
    } else {
        watchFaceDrawable = watchFaceResources.getDrawable(R.drawable.prowatchfaceint);
        watchFaceBitmap = ((BitmapDrawable) watchFaceDrawable).getBitmap();
        scaleWatchFaceBitmap = null;
        pHourHand.setAlpha(255);
        pMinuteHand.setAlpha(255);
        pSecondHand.setAlpha(255);
        pTickMarks.setAlpha(255);
        pHourHand.setColor(Color.BLUE);
        pMinuteHand.setColor(Color.GREEN);
        pSecondHand.setColor(Color.RED);
        pTickMarks.setColor(Color.BLACK);
    }
}

9781430265504_Fig12-28.jpg

图 12-28 。添加带有颜色的 else if(enableBurnInAmbientMode)构造。灰度和 prowatchfacebur 图像

现在是时候测试预烧保护模式的 Java 代码,以及位图数字资源,这是您在本节前面使用 GIMP 亮度-对比度对话框创建的。

测试老化保护模式位图和 Java 代码

打开。onAmbientModeChanged()方法构造并移除强制lowBitAmbientModeFlag=true;布尔标志语句,该语句用于测试低位环境模式。相反,这里您将添加强制burnInProtectModeFlag=true;布尔标志语句,现在您将利用它来测试老化保护模式。

那个。onAmbientModeChanged()方法结构,可以在图 12-29 中看到,应该看起来像下面的 Java 方法结构:

@Override
public void onAmbientModeChanged(boolean ambientModeFlag) {
    super.onAmbientModeChanged(ambientModeFlag);
    if(lowBitAmbientModeFlag) setAntiAlias(!ambientModeFlag);
    burnInProtectModeFlag = true;
    if(burnInProtectModeFlag) setBurnInProtect(ambientModeFlag);
    ensureModeSupport();
    invalidate();
    checkTimer();
}

9781430265504_Fig12-29.jpg

图 12-29 。将 burnInProtectModeFlag 设置为 true,强制老化保护模式测试位图

使用 IntelliJ 中的 Run image Run Wear 工作流程,并使用 Android Wear Square AVD 仿真器测试代码和位图对象。正如你在图 12-30 中看到的,你已经实现了 50%暗烧模式版本的低位模式。

9781430265504_Fig12-30.jpg

图 12-30 。使用设置image改变表盘image Pro 表盘系列,和 F7 键测试环境老化模式

值得注意的是,我将在下一章中通过添加额外的 Java 代码来介绍圆形 AVD 仿真器和圆形表盘设计原则和技术,这将涉及如何允许用户定制表盘设计。其原因主要是优化驱动的决策。为什么要为表盘设计提供圆形和方形图像资源,而方形资源可以用于两者?圆形表盘装饰,例如装饰性的轮圈,最终用户可以添加用于他们自己定制的装饰。这样,如果用户希望,即使是方形表盘也可以具有圆形表盘设计装饰。这通过用户界面增加了表盘设计的价值。

摘要

在本章中,您学习了如何使用 GIMP 创建,以及如何使用 enableModeSupport()和 onDraw()方法实现大量不同的表盘设计模式。智能手表制造商将需要其中一些低功耗模式,具体取决于显示技术。

首先,我讨论了如何将优化的交互模式位图图像素材转换为灰度图像,这是环境模式通常需要的,也是 Android 推荐的。

之后,您学习了如何减少图像资源使用的灰色或灰度数量,因此您可以针对某些制造商智能手表产品要求的某些较低位模式进行优化。例如,索尼智能手表 3 有一个 3 位或 8 级灰度的环境模式,现在你知道如何使用弗洛伊德-斯坦伯格抖动算法来优化你的位图。

在创建了几个数字图像素材之后,您还学习了如何将 Java 代码添加到。enableModeSupport()方法结构,以及如何修改 onDraw()方法结构,以便每次模式改变时,如果位图资源的分辨率与图像资源不同,位图资源将被重新评估和重新缩放,以适应智能手表屏幕。

在下一章,你将学习如何使用 Android Wear 应用的另一面(或组件),即 Android Studio (IntelliJ)中所称的“移动”面。对于这个主题,Java 编码和 XML 标记变得更加复杂。因此,下一章将起到将本书的这一手表表面设计部分与本书的其他 Android(非 WatchFaces API)穿戴主题章节(第十七章)进行“桥接”的作用。**