为Android Wear创建表盘






4.33/5 (7投票s)
本文介绍如何为您的 Android Wear 创建新的表盘,并结合一些个人经验解释了具体操作方法。
引言和背景
我很好奇为什么设计师们还不涉足这些科技领域,而事实上,大多数科技产品都需要设计和美术方面的人才。我认识一些朋友,他们非常有品味,懂得构图、对比等“美术”相关的东西,但却缺乏良好的编程技能,无法有效地为移动设备编写应用程序。对我来说也是如此,虽然我(经验还算丰富,但算不上优秀)有编写应用程序的经验,这些程序可以很好地节省资源,但我却缺乏良好的设计原则品味。在台式机上,你可以创建一个庞大的应用程序,但一旦转移到小型设备(如手持设备)上,你就必须更关注资源,因为它们资源有限。我所说的资源,例如电池、内存和其他硬件组件。
在为手持设备和其他类似迷你设备编写出色的应用程序时,您必须考虑电池消耗、CPU 使用率、内存消耗以及其他因素……哦,我有没有提到 **您还必须确保用户注意周围环境**,而不是浪费时间点击您的“下一步”、“下一步”按钮?在编写手持设备和 **可穿戴设备**(*我可以用这个词来指代手表吗?*)的应用程序时,这些是必须遵循的一些原则。这基本上是不言而喻的,您必须确保您的应用程序不会消耗过多的 CPU,不会持续执行计算线程,并且是否对电池友好?
在接下来的文章中,您将学习如何创建一个 Android Wear 应用程序,该应用程序可以为列表添加一个新的表盘,并且遵循基本的编程设计模式,以确保资源不会被过多使用。在何处使用 CPU 以及使用多少,何时保存用于绘制表盘的资源以及在哪里保存,以及如何在屏幕上绘制内容。如果您需要向屏幕添加更多组件,该如何操作。所有这些都将收集并汇编在一篇文章中为您呈现!
在下一部分之前,请阅读我在我们一次 谈话 中从 OriginalGriff 那里听到的内容。
我写过一些运行在电池上的微控制器应用程序,您必须非常、非常小心。电池寿命很重要——如果您增加了过多的处理,可能会大大缩短电池寿命。对于嵌入式设备来说,这可能意味着客户会转向竞争对手……
目前市面上的手表太糟糕了,因为它们没有做到这一点:它们试图成为您手腕上的“整部手机”——这会消耗大量电量。一个除非您非常小心使用,否则就无法持续一整天的手表有什么意义?人们已经足够恼火于现代手机电量不够用了……
我相信您已经明白“**小心谨慎**”地开发应用程序的重要性。 :-)
关于 Android Wear—我们主题中的智能手表一点介绍。
在进一步介绍之前,我想先简单介绍一下 Android Wear 本身。Android Wear 是 Android 在智能手表类别下提供的一个新平台。Android,这个领先的智能手机操作系统,很久以前就推出了其智能手表版本。它正在迅速获得关注,很快也会获得一定的市场份额!
关于 Wear 应用一点介绍
我已经准备了另一篇文章,供您入门 Android Wear 应用程序,您可以去阅读,因为它包含了在创建实际 Android Wear 应用程序之前所需的所有知识。 Android Wear 应用程序 ABC。
要实际在手表上使用应用程序,您需要将其连接到您的 Android 设备,然后将该应用程序安装到您的设备上。设备会将 Wear 应用推送到 Wear 设备,然后 Wear 设备上运行您的应用程序。
为何有此行为?
在我看来,我实际上喜欢这种框架,因为它将大部分工作留给了手持设备。例如,如果您想下载文件、获取 Web 服务的回复或将数据存储在互联网上,您可以通过手持设备完成,并将数据传递给手持设备。
您还可以向手持设备发送网络资源请求,手持设备无论如何都会比 Wear 设备本身拥有更多的电池电量。因此,如果您在手持设备上运行大部分计算并存储大部分资源,这可以在 **许多方面** 使您的 Wear 应用程序更好!
前往工作室
在本节中,我将使用 Android Studio 来创建 Wear 应用程序以及包含表盘的应用程序。本文将仅关注应用程序的表盘部分,即表盘是什么、如何创建以及如何修改。此外,它在每个设备上的外观以及如何根据设备的硬件(方形或圆形)修改表盘。到最后,您将能够将您的想法移植到真正的智能手表上!
首先,创建一个新的 Android Wear 项目(*请阅读以上文章,因为它提供了有关创建可移植到 Android Wear 的项目的信息*)。确保您选择了“空活动”,因为我将逐步指导您创建新的表盘。因此,不使用模板。创建空项目后,请继续阅读。在此阶段,您只需要打开 IDE 并创建一个项目。
创建表盘
如果您曾经编写过 Android 应用程序,那么您一定很熟悉 Android 服务。Android 服务是在后台运行以执行任务的组件。它们可以启动和停止,因为它们没有 UI,所以它们应该在工作完成后停止。例如,您可以使用服务在后台播放音乐文件,一旦文件播放完毕,服务必须自行停止(*除非重复播放*)。同样,Android Wear 的表盘也是一个服务组件。它在后台运行,并持续在您的手表上绘制表盘。在本节中,我将向您介绍表盘的许多内容,包括表盘的“是什么”和“如何”操作。
什么是表盘
简单来说,表盘是一个在后台运行的服务,为您提供在画布上绘制图形的功能。这些图形用于创建不同的表盘、编写文本、构建动画以及执行开发者在其图形应用程序中可能想要的其他活动。但是,有些事情是表盘无法做到的,或者限制了开发者的操作。它们是专门为系统功能设计的。例如,虽然允许与表盘交互,但只提供单击。捏、拖动以及手持设备拥有的其他功能在表盘中都不提供。
Android 开发者在这方面有 优秀的文档 资源,他们还为您提供视觉反馈和设计方法以应用于您的手表。在本指南中,我将教您一些关于这些原则的基本知识,以及如何在您的应用程序中充分利用它们。
现在,如果表盘的目的对您来说很清楚,我认为我们应该继续下一步,为本指南创建一些东西。在整个过程中,我会尽我所能解释每一个细节,以便您以后想自己构建应用程序时能够理解该过程。
开始—空项目
首先创建一个新项目,确保它是空的。表盘基本上是一个“绘图画布”服务。因此,在 Android API 中,有一个服务 CanvasWatchFaceService
和 CanvasWatchFaceService.Engine
,您需要在您的应用程序中扩展它们来创建表盘。Engine
类允许您实际绘制表盘,该类提供的方法包括:
onCreateEngine
此函数用于获取 Engine 对象的一个实例。然后该实例可用于不同的目的,例如计时器和绘图等。invalidate
此函数类似于View.invalidate
函数。它会提示系统重新绘制您的表盘。onDraw
此函数是我们感兴趣的。它允许我们- 在画布上绘制对象。
- 提供设备的边界,以便在画布上绘制时使用。
- 绘制形状时可以使用 Canvas 参数。
- 与其他 API 一样,Android
Canvas
对象也为我们提供了绘制形状的方法,例如- 圆形
- 路径
- 弧形
- 位图
它们只是图形,可以用 Android 语言绘制。我会向您展示一个代码片段,将您的可绘制对象转换为Bitmap
,请继续阅读。 - 文本
- 执行其他基于 Canvas 的函数,可在 Android API 中找到。
- 还有一些函数,在解释完服务本身之前,我不会讨论它们。
这只是对服务的概述,接下来我将展示如何实现它。
注意:对于使用 Eclipse 的用户,请在继续之前下载并安装 支持库和插件。它们是必需的。在 Android Studio 中,一旦配置好 IDE,它们就会自动安装。
创建一个新服务,实现创建表盘的基础服务。然后,这些服务将提供接口(*我指的是函数*),供您在设备上执行函数并确定事件何时发生。覆盖这些方法将允许您执行自己的操作。
我创建了以下 Java 类,它将作为我们表盘的服务。
import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.Bundle;
import android.support.wearable.watchface.CanvasWatchFaceService;
import android.view.SurfaceHolder;
/**
* Created by AfzaalAhmad on 09/22/2015.
*/
public class CodeProjectWearFaceService extends CanvasWatchFaceService {
@Override
public Engine onCreateEngine() {
return new Engine();
}
private class Engine extends CanvasWatchFaceService.Engine {
@Override
public void onCreate(SurfaceHolder holder) {
super.onCreate(holder);
}
@Override
public void onPropertiesChanged(Bundle properties) {
super.onPropertiesChanged(properties);
}
@Override
public void onTimeTick() {
super.onTimeTick();
}
@Override
public void onAmbientModeChanged(boolean inAmbientMode) {
super.onAmbientModeChanged(inAmbientMode);
}
@Override
public void onDraw(Canvas canvas, Rect bounds) {
}
@Override
public void onVisibilityChanged(boolean visible) {
super.onVisibilityChanged(visible);
}
}
}
该类实现了服务,它**几乎**准备好了。我们需要提供 Wear 伴侣应用程序和设备将用来确定我们表盘资源的元数据。在 manifest 中,您需要将活动注册为服务,Android 将在 manifest 中查找任何此类进程,因此,请编辑 manifest 并添加以下 service
元素。
<service android:name=".CodeProjectWearFace" android:label="@string/my_digital_name" android:permission="android.permission.BIND_WALLPAPER" > <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_digital" /> <meta-data android:name="com.google.android.wearable.watchface.preview_circular" android:resource="@drawable/preview_digital_circular" /> <intent-filter> <action android:name="android.service.wallpaper.WallpaperService" /> <category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" /> </intent-filter> </service>
这样就会注册您的服务以便被找到。虽然这相当清楚,但我还是在这里解释一下。现在,在我们深入分析之前,我将讨论另一个元素,即 wallpaper
元素。Android Wear 表盘是绘制在 wallpaper 元素上的。您可以创建一个新的 wallpaper。在 XML 文件夹下创建一个新文件并随意命名(在上面的例子中,它是 watch_face
)。代码只有两行,
<?xml version="1.0" encoding="UTF-8"?>
<wallpaper xmlns:android="http://schemas.android.com/apk/res/android" />
现在我将讨论更多关于服务定义的内容。
android:name
此属性指定代码文件,Android 使用此名称的代码文件并运行其中的代码。然后,您需要覆盖并实现该代码文件中所需的功能。*我们已经做过了*。android:label
- 此属性用于在列表中命名表盘。您可以使用任何名称,我使用了“**CodeProject Wear Face**”,这是一个字面字符串。
meta-data
这些元素为您的服务,特别是表盘服务,指定了元数据。Android Wear 应用或 Wear 设备将使用此处指定的数据,并显示您表盘的数据。android.service.wallpaper
这将指定 wallpaper 元素,我上面已经描述了如何创建它。您需要创建它,因为这些是必需的。com.google...watchface.preview
这是您在矩形 Wear 设备中表盘的默认预览。com.google...watchface.preview_circular
这是您在圆形 Wear 设备中表盘的默认预览。
intent-filter
此应用程序感兴趣的意图。它们不言自明。:-)
完成这些之后,我们就可以开始编写实际在手表上绘制内容的 C代码了。我们的表盘已在 Android 应用中注册,并将执行服务中的代码来执行操作,例如处理事件、在画布上绘制以及隐藏或显示数据。
实时预览示例
我想向您展示我们的应用程序现在的样子,所以我想显示 CodeProject 在设备上就足够了。因此,我修改了 onDraw 函数中的代码,并在此处编写了以下代码。
@Override
public void onDraw(Canvas canvas, Rect bounds) {
final Typeface typeface = Typeface.DEFAULT;
Paint paint = new Paint()
{{
setTextSize(40);
setARGB(255, 255, 255, 255);
setTypeface(typeface);
}};
canvas.drawText("CodeProject", 50, 100, paint);
}
这段代码很简单,而且非常紧凑,只做一件事,“*在手表上显示 CodeProject*!” 它确实做到了,请看下图。它使用索引 (50, 100) 和传递给它的 paint 对象打印了 CodeProject。paint 对象将包含我们想要的图形细节,例如
- 我们想要的文本大小。
- 文本颜色。
- 绘制文本时要使用的字体(*font-family*?)
该函数是 drawText。还有其他函数可用,例如 drawBitmap
和 drawArc
等。我不会讨论其中大部分,但很快就会在这里使用 drawBitmap
。
图 1:(方形)Android Wear 显示渲染的 CodeProject 文本以及左上角的图标。
为其添加一些功能—中级技能
到目前为止,我希望您现在已经了解了画布、Wear 服务、CanvasWatchFaceService 以及它提供的功能。我将指出其中一些服务,然后我们将进入主要部分,在画布上绘制有用的东西!
收集数据
在创建某些东西之前,您应该问自己。“**这有什么区别?**” 它为用户提供了更多什么数据,它考虑了哪些因素,它是否值得上传应用程序供用户使用?这些问题将帮助您了解应用程序“做什么”。
虽然 Wear 的功能不如手持设备多,但您仍然可以使用您的应用程序来
- 显示日期和时间。
我个人建议您使用Calendar
对象,但使用Time
可以让您使用更多功能,我仍然建议使用Calendar
实例来获取日期和时间。 - 显示附加信息。
这是有趣的部分,因为这是 Wear 与普通手表不同之处。您可以在屏幕上显示额外的数据,而普通手表则不允许。*因此是智能手表*!;-)- 显示提醒的详细信息,或即将到来的特定提醒。
- 显示天气详情。
- 未接来电或可供阅读的消息数量。
- 总卡片数量。
- 来自任何 API 或数据源的任何其他数据!
收集完数据后,您可以像绘制任何其他内容一样将其显示在画布上。写下数据,绘制图标,用户就可以在路上看到它了。
默认情况下,Android 为我们提供了许多 API 合约,例如 Calendar。在那里,您可以获取有关提醒、事件等的详细信息。这些合约允许您的应用程序获取有关用户自己的内容数据库的数据。然后,使用它们,您可以以非常用户友好的方式设计自己的应用程序。这正是其他应用程序获取用户数据的方式。如果您感兴趣,可以随时查看支持 API 中的 WearableCalendarContact
类。
创建成员
正如已经多次提到的,您绝不能在 Wear 设备本身执行大部分任务,它的资源不像其他任何手持设备那样多。这就是为什么不建议一直让 CPU 或 RAM 忙碌。在您的 onCreateEngine 函数中,您可以在不经常执行的某个地方初始化成员。
这样做的一个非常合乎逻辑的原因是,对于您尝试渲染的每个 Bitmap,您想要创建的每个 Paint 对象都需要驻留在 RAM 中,并且也会占用 CPU。例如,要渲染的 Bitmap,每个像素都需要 CPU 时间来渲染。因此,您需要的像素越多,消耗的 CPU 时间就越多,同时电池消耗也越多,这就是为什么总是建议不要一次又一次地创建(和/或初始化)成员。一次性明智地完成它们。
我将考虑我要做什么?以下是它们列表
- 日期时间文本
- 背景颜色
- Bob 的图片(*我希望管理员不介意!*)
因此,为了使用它们,我需要提前初始化它们,以便当 CPU 开始渲染所有内容时,它已经拥有所有东西,并且这个过程不会一遍又一遍地循环。
// The paints that we are going to use in our application.
Paint textPaint, boldTextPaint, boldTimePaint, normalTimePain, datePaint, backGround;
Bitmap bob;
这些是我将在应用程序中使用的变量。为了给它们提供有效值,我将在 onCreate
函数中初始化它们。
// Let us initialize them all here
bob = BitmapFactory.decodeResource(getResources(), R.drawable.CodeProject_Bob_Sticker);
backGround = new Paint() {{ setARGB(255, 255, 140, 0); }};
datePaint = createPaint(false, 25);
textPaint = createPaint(false, 40);
boldTextPaint = createPaint(true, 40);
boldTimePaint = createPaint(true, 40);
normalTimePaint = createPaint(false, 40);
提示:要将可绘制资源转换为 Bitmap
对象,您可以使用 BitmapFactory
并对其调用 decodeResource
。它需要要使用的资源和可绘制资源。
我用来创建 Paint 对象的函数定义如下:
private Paint createPaint(boolean bold, final int fontSize) {
Typeface typeface = Typeface.create(Typeface.DEFAULT, Typeface.NORMAL);
if(bold) {
typeface.create(Typeface.DEFAULT, Typeface.BOLD);
}
return new Paint()
{{
setARGB(255, 255, 255, 255);
setTextSize(fontSize);
}};
}
这会创建一个新的 paint,并允许我们稍后使用它来渲染图像。我将在解释一些其他内容之后再尝试它。
响应用户交互
用户可以与 Android Wear 交互,但只提供一种交互方法。单击,其他点击和手势默认被阻止,因为系统想自己专注于它们,为什么?我不知道,我不代表 Android。:-)
您只能使用单击来与用户交互,然后向他显示响应。当然,您可以忽略它,但这里不是。您应该考虑响应他的请求。为此,您需要覆盖 onTapCommand
函数,并且还需要更改样式并确保您的 Wear 应用程序正在接收点击事件。首先,在 onCreate 函数中更改应用程序的样式,并添加捕获点击事件的请求。
setWatchFaceStyle(new WatchFaceStyle.Builder(CodeProjectWearFace.this)
.setAcceptsTapEvents(true)
.build());
这样,我们的应用程序就可以处理点击事件了。接下来,我们需要覆盖函数,以便在用户点击我们的应用程序时实际提供功能和服务。
@Override
public void onTapCommand(
@TapType int tapType, int x, int y, long eventTime) {
switch (tapType) {
case WatchFaceService.TAP_TYPE_TAP:
// Handle the tap
break;
// There are other cases, not mentioned here. <a href="https://developer.android.com.cn/training/wearables/watch-faces/interacting.html">Read Android guide</a>
default:
super.onTapCommand(tapType, x, y, eventTime);
break;
}
}
这样您就可以获得用户交互。此外,如果您查看函数的签名,您会发现还有两个坐标参数;x
和 y
。这些参数将告诉我们用户在屏幕上的哪个位置点击,以便我们可以触发一个函数。由于画布只是一个绘图板,我们无法叠加任何应用程序、超链接或任何类型的触发器。因此,我们必须确定当我们的表盘渲染时,我们的特定功能将在何处呈现,以便我们知道用户点击了它!
同时满足两种设备尺寸
由于 Android Wear 有方形和圆形两种尺寸,因此您有责任确保您的表盘在这两种尺寸上看起来都一样,并遵循这两种情况的模式。大多数为方形设计的 UI 在圆形设备上会失败,而为圆形设计的 UI 在方形设备上也会失败。因此,您必须确保小心绘制。
为了实际获取视图详细信息,您没有提供任何原生 Wear 功能或 API,在这种情况下,您将使用 Android wallpaper 服务函数并覆盖它们以提供额外的基于视图的 C内容。我指的是 WallpaperService.Engine
的 onApplyWindowInsets(WindowInsets)
函数。WindowInsets 允许您获取设备是否为圆形。因此,您可以创建一个成员来确定是必须绘制圆形数据还是方形数据。这取决于设备类型。
@Override
public void onApplyWindowInsets(WindowInsets insets) {
super.onApplyWindowInsets(insets);
boolean isRound = insets.isRound();
if(isRound) {
// Render the Face in round mode.
} else {
// Render the Face in square (or rectangular) mode.
}
}
我现在不会深入探讨所有这些内容。我只会略过它们,并在文章末尾的高级部分分享一些代码技巧。
管理手表样式
另一个主要概念是管理手表样式,您想绘制什么样的表盘,以及系统在哪里绘制自己的数据。例如,当用户打开设备时,“Ok Google”热词会一直显示在屏幕上。您可以完全自主决定它绘制在何处。峰值卡片如何显示,系统是否显示系统时间,还是您自己处理?所有这些都包含在 Android API 的 WatchFaceStyle
对象中。
您创建一个新对象,然后为其提供一些设置。这些设置将应用于渲染器(Engine),并且表盘将根据这些设置进行绘制。
WatchFaceStyle style = new WatchFaceStyle.Builder(CodeProjectWatchFaceService.this)
.setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT)
.setBackgroundVisibility(WatchFaceStyle
.BACKGROUND_VISIBILITY_INTERRUPTIVE)
.setHotwordIndicatorGravity(Gravity.TOP)
.setAcceptsTapEvents(true)
.setShowSystemUiTime(false)
.build();
setWatchFaceStyle(style);
上述样式(属性)在创建表盘时应用。系统将使用这些来为您提供服务,例如事件处理机制,或在画布上绘制时间,或渲染热词。
现在,如果所有这些点都已理解,那么是时候继续做其他事情了。例如创建表盘,因为您只需要理解这些概念。
创建一个简单的表盘
在本节中,我将向您展示创建简单表盘的代码,代码将足够简单,但之后我会尝试分块解释它。:-)
我为本次演示创建了一个 CodeProject Wear Face。首先,看看创建了什么。
图 2:在两种设备尺寸上显示 Wear Face。
显示完这些之后,我想我应该展示用于相同过程的代码了,代码量不多,是一小段代码用于绘制数据。
注意:请原谅时间冲突,这是因为模拟器启动的时间。仅此而已。
@Override
public void onDraw(Canvas canvas, Rect bounds) {
calendar = Calendar.getInstance(); // Re-instance the Calendar, to update the time
canvas.drawRect(0, 0, bounds.width(), bounds.height(), whiteBackground); // Entire background Canvas
canvas.drawRect(0, 60, bounds.width(), 240, backGround); // Orange color
// Day
canvas.drawText(new SimpleDateFormat("cccc").format(calendar.getTime()), 130, 120, textPaint);
// String time = String.format("%02d:%02d", mTime.hour, mTime.minute);
String time = new SimpleDateFormat("hh:mm a").format(calendar.getTime());
canvas.drawText(time, 130, 170, boldTextPaint);
String date = new SimpleDateFormat("MMMM dd, yyyy").format(calendar.getTime());
canvas.drawText(date, 150, 200, darkText);
canvas.drawBitmap(bob, 0, 40, null);
}
这段代码就可以完成任务。如两种情况所示,表盘绘制得很顺利。但在大多数情况下,您可能需要降低几个像素以留出边距。例如,看看“**星期四**”。在方形尺寸中,它很好,但在圆形形状中,渲染“**星期三**”时文本的最后一个“**y**”可能会超出屏幕。始终确保 UI 没有问题。
一个通用技巧是,首先确定屏幕尺寸,然后获取像素尺寸的一部分用于渲染。好了,Bob 在两个屏幕上看起来都很开心。:-)
服务代码
这是服务的完整代码(*服务是通过 Android Studio 提供的模板生成的,然后我对其进行了修改以创建 CodeProject 主题的表盘,因此大部分代码由 Android 团队自己编写*)
package com.afzaalahmadzeeshan.wearfaces;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.wearable.watchface.CanvasWatchFaceService;
import android.support.wearable.watchface.WatchFaceService;
import android.support.wearable.watchface.WatchFaceStyle;
import android.text.format.Time;
import android.view.SurfaceHolder;
import java.lang.ref.WeakReference;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
/**
* CodeProject Wear face, which shows Day, time (HH:MM A)
*/
public class CodeProjectWear extends CanvasWatchFaceService {
private static Paint textPaint, boldTextPaint, backGround, whiteBackground, darkText;
private static Bitmap bob;
private static Calendar calendar;
private static final long INTERACTIVE_UPDATE_RATE_MS = TimeUnit.SECONDS.toMillis(1);
private static final int MSG_UPDATE_TIME = 0;
@Override
public Engine onCreateEngine() {
return new Engine();
}
private class Engine extends CanvasWatchFaceService.Engine {
final Handler mUpdateTimeHandler = new EngineHandler(this);
final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
calendar = Calendar.getInstance();
}
};
boolean mRegisteredTimeZoneReceiver = false;
/**
* Whether the display supports fewer bits for each color in ambient mode. When true, we
* disable anti-aliasing in ambient mode.
*/
boolean mLowBitAmbient;
@Override
public void onCreate(SurfaceHolder holder) {
super.onCreate(holder);
setWatchFaceStyle(new WatchFaceStyle.Builder(CodeProjectWear.this)
.setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT)
.setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
.setShowSystemUiTime(false)
.build());
// Let us initialize them all here
bob = BitmapFactory.decodeResource(getResources(), R.drawable.codeproject_bob_sticker);
backGround = new Paint() {{ setARGB(255, 255, 140, 0); }};
textPaint = createPaint(false, 40);
boldTextPaint = createPaint(true, 40);
whiteBackground = createPaint(false, 0);
darkText = new Paint() {{ setARGB(255, 50, 50, 50); setTextSize(18); }};
setWatchFaceStyle(new WatchFaceStyle.Builder(CodeProjectWear.this)
.setAcceptsTapEvents(true)
.build());
calendar = Calendar.getInstance();
}
private Paint createPaint(final boolean bold, final int fontSize) {
final Typeface typeface = (bold) ? Typeface.DEFAULT_BOLD : Typeface.DEFAULT;
return new Paint()
{{
setARGB(255, 255, 255, 255);
setTextSize(fontSize);
setTypeface(typeface);
setAntiAlias(true);
}};
}
@Override
public void onDestroy() {
mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
super.onDestroy();
}
@Override
public void onPropertiesChanged(Bundle properties) {
super.onPropertiesChanged(properties);
mLowBitAmbient = properties.getBoolean(PROPERTY_LOW_BIT_AMBIENT, false);
}
@Override
public void onTimeTick() {
super.onTimeTick();
invalidate();
}
@Override
public void onAmbientModeChanged(boolean inAmbientMode) {
super.onAmbientModeChanged(inAmbientMode);
if (inAmbientMode) {
if (mLowBitAmbient) {
}
invalidate();
}
// Whether the timer should be running depends on whether we're visible (as well as
// whether we're in ambient mode), so we may need to start or stop the timer.
updateTimer();
}
@Override
public void onDraw(Canvas canvas, Rect bounds) {
calendar = Calendar.getInstance();
canvas.drawRect(0, 0, bounds.width(), bounds.height(), whiteBackground); // Entire background Canvas
canvas.drawRect(0, 60, bounds.width(), 240, backGround);
canvas.drawText(new SimpleDateFormat("cccc").format(calendar.getTime()), 130, 120, textPaint);
// String time = String.format("%02d:%02d", mTime.hour, mTime.minute);
String time = new SimpleDateFormat("hh:mm a").format(calendar.getTime());
canvas.drawText(time, 130, 170, boldTextPaint);
String date = new SimpleDateFormat("MMMM dd, yyyy").format(calendar.getTime());
canvas.drawText(date, 150, 200, darkText);
canvas.drawBitmap(bob, 0, 40, null);
}
@Override
public void onVisibilityChanged(boolean visible) {
super.onVisibilityChanged(visible);
if (visible) {
registerReceiver();
// Update time zone in case it changed while we weren't visible.
calendar = Calendar.getInstance();
} else {
unregisterReceiver();
}
// Whether the timer should be running depends on whether we're visible (as well as
// whether we're in ambient mode), so we may need to start or stop the timer.
updateTimer();
}
private void registerReceiver() {
if (mRegisteredTimeZoneReceiver) {
return;
}
mRegisteredTimeZoneReceiver = true;
IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
CodeProjectWear.this.registerReceiver(mTimeZoneReceiver, filter);
}
private void unregisterReceiver() {
if (!mRegisteredTimeZoneReceiver) {
return;
}
mRegisteredTimeZoneReceiver = false;
CodeProjectWear.this.unregisterReceiver(mTimeZoneReceiver);
}
/**
* Starts the {@link #mUpdateTimeHandler} timer if it should be running and isn't currently
* or stops it if it shouldn't be running but currently is.
*/
private void updateTimer() {
mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
if (shouldTimerBeRunning()) {
mUpdateTimeHandler.sendEmptyMessage(MSG_UPDATE_TIME);
}
}
/**
* Returns whether the {@link #mUpdateTimeHandler} timer should be running. The timer should
* only run when we're visible and in interactive mode.
*/
private boolean shouldTimerBeRunning() {
return isVisible() && !isInAmbientMode();
}
/**
* Handle updating the time periodically in interactive mode.
*/
private void handleUpdateTimeMessage() {
invalidate();
if (shouldTimerBeRunning()) {
long timeMs = System.currentTimeMillis();
long delayMs = INTERACTIVE_UPDATE_RATE_MS
- (timeMs % INTERACTIVE_UPDATE_RATE_MS);
mUpdateTimeHandler.sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs);
}
}
}
private static class EngineHandler extends Handler {
private final WeakReference<CodeProjectWear.Engine> mWeakReference;
public EngineHandler(CodeProjectWear.Engine reference) {
mWeakReference = new WeakReference<>(reference);
}
@Override
public void handleMessage(Message msg) {
CodeProjectWear.Engine engine = mWeakReference.get();
if (engine != null) {
switch (msg.what) {
case MSG_UPDATE_TIME:
engine.handleUpdateTimeMessage();
break;
}
}
}
}
}
此代码也存在于文章随附的源代码中。最后,清单被编辑如下。清单包含要绘制的表盘的信息,并将用于在我们 Android Wear 应用伴侣和设备本身的面孔列表中提供我们的 Wear Face。
<service android:name=".CodeProjectWear" android:label="CodeProject Wear" android:permission="android.permission.BIND_WALLPAPER" > <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_analog" /> <meta-data android:name="com.google.android.wearable.watchface.preview_circular" android:resource="@drawable/preview_analog" /> <intent-filter> <action android:name="android.service.wallpaper.WallpaperService" /> <category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" /> </intent-filter> </service>
继续下载提供的源代码包,并在您自己的环境中进行测试!:-)
关注点
在本文中,您了解了如何创建表盘。表盘只是一个在屏幕上渲染对象的服务。功能不是很多,但提供了 Canvas,可用于绘制对象。
文章没有 TL;DR,希望您读完不会觉得无聊!:-)