65.9K
CodeProject 正在变化。 阅读更多。
Home

为Android Wear创建表盘

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.33/5 (7投票s)

2015 年 9 月 24 日

CPOL

20分钟阅读

viewsIcon

28939

downloadIcon

259

本文介绍如何为您的 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 中,有一个服务 CanvasWatchFaceServiceCanvasWatchFaceService.Engine,您需要在您的应用程序中扩展它们来创建表盘。Engine 类允许您实际绘制表盘,该类提供的方法包括:

  1. onCreateEngine
    此函数用于获取 Engine 对象的一个实例。然后该实例可用于不同的目的,例如计时器和绘图等。
  2. invalidate
    此函数类似于 View.invalidate 函数。它会提示系统重新绘制您的表盘。
  3. onDraw
    此函数是我们感兴趣的。它允许我们
    • 在画布上绘制对象。
    • 提供设备的边界,以便在画布上绘制时使用。
    • 绘制形状时可以使用 Canvas 参数。
    • 与其他 API 一样,Android Canvas 对象也为我们提供了绘制形状的方法,例如
      • 圆形
      • 路径
      • 弧形
      • 位图
        它们只是图形,可以用 Android 语言绘制。我会向您展示一个代码片段,将您的可绘制对象转换为 Bitmap,请继续阅读。
      • 文本
    • 执行其他基于 Canvas 的函数,可在 Android API 中找到。
  4. 还有一些函数,在解释完服务本身之前,我不会讨论它们。

这只是对服务的概述,接下来我将展示如何实现它。

注意:对于使用 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" />

现在我将讨论更多关于服务定义的内容。

  1. android:name
    此属性指定代码文件,Android 使用此名称的代码文件并运行其中的代码。然后,您需要覆盖并实现该代码文件中所需的功能。*我们已经做过了*。
  2. android:label
  3. 此属性用于在列表中命名表盘。您可以使用任何名称,我使用了“**CodeProject Wear Face**”,这是一个字面字符串。
  4. meta-data
    这些元素为您的服务,特别是表盘服务,指定了元数据。Android Wear 应用或 Wear 设备将使用此处指定的数据,并显示您表盘的数据。
    • android.service.wallpaper
      这将指定 wallpaper 元素,我上面已经描述了如何创建它。您需要创建它,因为这些是必需的。
    • com.google...watchface.preview
      这是您在矩形 Wear 设备中表盘的默认预览。
    • com.google...watchface.preview_circular
      这是您在圆形 Wear 设备中表盘的默认预览。
  5. 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 对象将包含我们想要的图形细节,例如

  1. 我们想要的文本大小。
  2. 文本颜色。
  3. 绘制文本时要使用的字体(*font-family*?)

该函数是 drawText。还有其他函数可用,例如 drawBitmapdrawArc 等。我不会讨论其中大部分,但很快就会在这里使用 drawBitmap


图 1:(方形)Android Wear 显示渲染的 CodeProject 文本以及左上角的图标。

为其添加一些功能—中级技能

到目前为止,我希望您现在已经了解了画布、Wear 服务、CanvasWatchFaceService 以及它提供的功能。我将指出其中一些服务,然后我们将进入主要部分,在画布上绘制有用的东西!

收集数据

在创建某些东西之前,您应该问自己。“**这有什么区别?**” 它为用户提供了更多什么数据,它考虑了哪些因素,它是否值得上传应用程序供用户使用?这些问题将帮助您了解应用程序“做什么”。

虽然 Wear 的功能不如手持设备多,但您仍然可以使用您的应用程序来

  1. 显示日期和时间。
    我个人建议您使用 Calendar 对象,但使用 Time 可以让您使用更多功能,我仍然建议使用 Calendar 实例来获取日期和时间。
  2. 显示附加信息。
    这是有趣的部分,因为这是 Wear 与普通手表不同之处。您可以在屏幕上显示额外的数据,而普通手表则不允许。*因此是智能手表*!;-)
    • 显示提醒的详细信息,或即将到来的特定提醒。
    • 显示天气详情。
    • 未接来电或可供阅读的消息数量。
    • 总卡片数量。
    • 来自任何 API 或数据源的任何其他数据!

收集完数据后,您可以像绘制任何其他内容一样将其显示在画布上。写下数据,绘制图标,用户就可以在路上看到它了。

默认情况下,Android 为我们提供了许多 API 合约,例如 Calendar。在那里,您可以获取有关提醒、事件等的详细信息。这些合约允许您的应用程序获取有关用户自己的内容数据库的数据。然后,使用它们,您可以以非常用户友好的方式设计自己的应用程序。这正是其他应用程序获取用户数据的方式。如果您感兴趣,可以随时查看支持 API 中的 WearableCalendarContact 类。

创建成员

正如已经多次提到的,您绝不能在 Wear 设备本身执行大部分任务,它的资源不像其他任何手持设备那样多。这就是为什么不建议一直让 CPU 或 RAM 忙碌。在您的 onCreateEngine 函数中,您可以在不经常执行的某个地方初始化成员。

这样做的一个非常合乎逻辑的原因是,对于您尝试渲染的每个 Bitmap,您想要创建的每个 Paint 对象都需要驻留在 RAM 中,并且也会占用 CPU。例如,要渲染的 Bitmap,每个像素都需要 CPU 时间来渲染。因此,您需要的像素越多,消耗的 CPU 时间就越多,同时电池消耗也越多,这就是为什么总是建议不要一次又一次地创建(和/或初始化)成员。一次性明智地完成它们。

我将考虑我要做什么?以下是它们列表

  1. 日期时间文本
  2. 背景颜色
  3. 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;
    }
}

这样您就可以获得用户交互。此外,如果您查看函数的签名,您会发现还有两个坐标参数;xy。这些参数将告诉我们用户在屏幕上的哪个位置点击,以便我们可以触发一个函数。由于画布只是一个绘图板,我们无法叠加任何应用程序、超链接或任何类型的触发器。因此,我们必须确定当我们的表盘渲染时,我们的特定功能将在何处呈现,以便我们知道用户点击了它!

同时满足两种设备尺寸

由于 Android Wear 有方形和圆形两种尺寸,因此您有责任确保您的表盘在这两种尺寸上看起来都一样,并遵循这两种情况的模式。大多数为方形设计的 UI 在圆形设备上会失败,而为圆形设计的 UI 在方形设备上也会失败。因此,您必须确保小心绘制。

为了实际获取视图详细信息,您没有提供任何原生 Wear 功能或 API,在这种情况下,您将使用 Android wallpaper 服务函数并覆盖它们以提供额外的基于视图的 C内容。我指的是 WallpaperService.EngineonApplyWindowInsets(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,希望您读完不会觉得无聊!:-)

© . All rights reserved.