创建表盘

本指南介绍了如何为 Android Wear 1.0 实现自定义表盘服务。 提供了有关构建精简数字表盘服务的分步说明,随后提供了更多代码来创建模拟风格的表盘。

概述

本演练将创建一个基本的表盘服务,以演示创建自定义 Android Wear 1.0 表盘的基本要素。 初始表盘服务显示一个简单的数字手表,以小时和分钟显示当前时间:

屏幕截图显示初始数字表盘。

在开发和测试此数字表盘之后,将添加更多代码,以将其升级为更复杂的三指针模拟表盘:

屏幕截图显示最终的模拟表盘。

表盘服务作为 Wear 1.0 应用的一部分进行捆绑和安装。 在以下示例中,MainActivity 仅包含 Wear 1.0 应用模板中的代码,以便可以将表盘服务打包并作为应用的一部分部署到智能手表。 实际上,此应用纯粹用作将表盘服务加载到 Wear 1.0 设备(或模拟器)中进行调试和测试的工具。

要求

若要实现表盘服务,需要满足以下条件:

  • Wear 设备或模拟器上的 Android 5.0(API 级别 21)或更高版本。

  • 必须将 Xamarin Android Wear 支持库添加到 Xamarin.Android 项目中。

虽然 Android 5.0 是实现表盘服务的最低 API 级别,但建议使用 Android 5.1 或更高版本。 运行 Android 5.1 (API 22) 或更高版本的 Android Wear 设备允许 Wear 应用在设备处于低功耗环境模式时控制屏幕上显示的内容。 当设备退出低功耗环境模式时,它将处于交互模式。 有关这些模式的详细信息,请参阅保持应用可见

启动应用项目

创建一个名为 WatchFace 的新 Android Wear 1.0 项目(有关创建新 Xamarin.Android 项目的详细信息,请参阅 Hello, Android

将包名称设置为 com.xamarin.watchface

此外,向下滚动并启用 INTERNET 和 WAKE_LOCK 权限

所需的权限

接下来,下载 preview.png – 稍后将在本演练中将其添加到 drawables 文件夹

添加 Xamarin.Android Wear 包

启动 NuGet 包管理器(在 Visual Studio 中,右键单击“解决方案资源管理器”中的“引用”,然后选择“管理 NuGet 包...”)。将项目更新到最新稳定版本的 Xamarin.Android.Wear

NuGet 包管理器添加

接下来,如果已安装 Xamarin.Android.Support.v13,请将其卸载

NuGet 包管理器移除

在 Wear 设备或模拟器上生成并运行应用(有关如何执行此操作的详细信息,请参阅入门指南)。 应会在 Wear 设备上看到以下应用屏幕:

应用屏幕截图

此时,基本的 Wear 应用还没有表盘功能,因为它还没有提供表盘服务实现。 接下来添加此服务。

CanvasWatchFaceService

Android Wear 通过 CanvasWatchFaceService 类实现表盘。 CanvasWatchFaceService 派生自 WatchFaceService,而后者本身又派生自 WallpaperService,如下图所示:

继承关系图

CanvasWatchFaceService 包含嵌套的 CanvasWatchFaceService.Engine;它实例化一个 CanvasWatchFaceService.Engine 对象,该对象执行绘制表盘的实际工作。 如上图所示,CanvasWatchFaceService.Engine 派生自 WallpaperService.Engine

此图中未显示由 CanvasWatchFaceService 用来绘制表盘的 Canvas - 此 Canvas 是通过 OnDraw 方法传入的,如下所述。

在以下部分,将通过以下步骤创建自定义表盘服务:

  1. 定义一个名为 MyWatchFaceService 的类,该类派生自 CanvasWatchFaceService

  2. MyWatchFaceService 中,创建一个派生自 CanvasWatchFaceService.Engine 的名为 MyWatchFaceEngine 的嵌套类。

  3. MyWatchFaceService 中实现一个 CreateEngine 方法,该方法实例化并返回 MyWatchFaceEngine

  4. MyWatchFaceEngine 中实现 OnCreate 方法,以创建表盘样式并执行任何其他初始化任务。

  5. 实现 MyWatchFaceEngineOnDraw 方法。 每当表盘需要重新绘制(即失效)时,就会调用此方法OnDraw 是绘制(和重新绘制)表盘元素(例如时针、分针和秒针)的方法。

  6. 实现 MyWatchFaceEngineOnTimeTick 方法。 每分钟至少调用 OnTimeTick 一次(在环境模式和交互模式下),或者在日期/时间发生更改时调用。

有关 CanvasWatchFaceService 的详细信息,请参阅 Android CanvasWatchFaceService API 文档。 同样,CanvasWatchFaceService.Engine 解释了表盘的实际实现。

添加 CanvasWatchFaceService

添加名为 MyWatchFaceService.cs 的新文件(在 Visual Studio 中,右键单击“解决方案资源管理器”中的“WatchFace”并单击“添加”>“新项...”,然后选择“类”)

将此文件的内容替换为以下代码:

using System;
using Android.Views;
using Android.Support.Wearable.Watchface;
using Android.Service.Wallpaper;
using Android.Graphics;

namespace WatchFace
{
    class MyWatchFaceService : CanvasWatchFaceService
    {
        public override WallpaperService.Engine OnCreateEngine()
        {
            return new MyWatchFaceEngine(this);
        }

        public class MyWatchFaceEngine : CanvasWatchFaceService.Engine
        {
            CanvasWatchFaceService owner;
            public MyWatchFaceEngine (CanvasWatchFaceService owner) : base(owner)
            {
                this.owner = owner;
            }
        }
    }
}

MyWatchFaceService(派生自 CanvasWatchFaceService)是表盘的“主程序”。 MyWatchFaceService 仅实现一个方法 (OnCreateEngine),该方法实例化并返回 MyWatchFaceEngine 对象(MyWatchFaceEngine 派生自 CanvasWatchFaceService.Engine)。 实例化的 MyWatchFaceEngine 对象必须作为 WallpaperService.Engine 返回。 MyWatchFaceService 封装对象将传入构造函数中。

MyWatchFaceEngine 是实际的表盘实现 - 它包含绘制表盘的代码。 它还处理系统事件,例如屏幕更改(环境/交互模式、屏幕关闭等)。

实现引擎 OnCreate 方法

OnCreate 方法初始化表盘。 将以下字段添加到 MyWatchFaceEngine

Paint hoursPaint;

Paint 对象用于在表盘上绘制当前时间。 接下来,将以下方法添加到 MyWatchFaceEngine

public override void OnCreate(ISurfaceHolder holder)
{
    base.OnCreate (holder);

    SetWatchFaceStyle (new WatchFaceStyle.Builder(owner)
        .SetCardPeekMode (WatchFaceStyle.PeekModeShort)
        .SetBackgroundVisibility (WatchFaceStyle.BackgroundVisibilityInterruptive)
        .SetShowSystemUiTime (false)
        .Build ());

    hoursPaint = new Paint();
    hoursPaint.Color = Color.White;
    hoursPaint.TextSize = 48f;
}

启动 MyWatchFaceEngine 后不久就会调用 OnCreate。 它设置 WatchFaceStyle(控制 Wear 设备与用户交互的方式)并实例化用于显示时间的 Paint 对象。

SetWatchFaceStyle 的调用执行以下操作:

  1. 将“速览模式”设置为 PeekModeShort,这会使通知在显示器上显示为较小的“速览”卡。

  2. 将背景可见性设置为 Interruptive,这会导致速览卡的背景仅在代表中断通知时短暂显示。

  3. 禁止在表盘上绘制默认系统 UI 时间,以便自定义表盘可以显示时间。

有关这些和其他表盘样式选项的详细信息,请参阅 Android WatchFaceStyle.Builder API 文档。

SetWatchFaceStyle 完成后,OnCreate 实例化 Paint 对象 (hoursPaint),并将其颜色设置为白色,将文本大小设置为 48 像素(TextSize 必须以像素为单位指定)。

实现引擎 OnDraw 方法

OnDraw 方法可能是最重要的 CanvasWatchFaceService.Engine 方法 - 它是实际绘制表盘元素(例如数字和表盘指针)的方法。 在以下示例中,它在表盘上绘制时间字符串。 将以下方法添加到 MyWatchFaceEngine

public override void OnDraw (Canvas canvas, Rect frame)
{
    var str = DateTime.Now.ToString ("h:mm tt");
    canvas.DrawText (str,
        (float)(frame.Left + 70),
        (float)(frame.Top  + 80), hoursPaint);
}

当 Android 调用 OnDraw 时,它会传入 Canvas 实例以及可以绘制表盘的边界。 在上面的代码示例中,DateTime 用于计算当前时间(以小时和分钟为单位,12 小时格式)。 使用 Canvas.DrawText 方法将生成的时间字符串绘制在画布上。 该字符串将显示在距左边缘上方 70 像素、距上边缘下方 80 像素的位置。

有关 OnDraw 方法的详细信息,请参阅 Android onDraw API 文档。

实现引擎 OnTimeTick 方法

Android 定期调用 OnTimeTick 方法来更新表盘显示的时间。 此方法每分钟至少调用一次(在环境模式和交互模式下),或者当日期/时间或时区发生更改时调用。 将以下方法添加到 MyWatchFaceEngine

public override void OnTimeTick()
{
    Invalidate();
}

OnTimeTick 的此实现仅调用 InvalidateInvalidate 方法计划 OnDraw 以重新绘制表盘。

有关 OnTimeTick 方法的详细信息,请参阅 Android onTimeTick API 文档。

注册 CanvasWatchFaceService

必须在关联 Wear 应用的 AndroidManifest.xml 中注册 MyWatchFaceService。 为此,请将以下 XML 添加到 <application> 节:

<service
    android:name="watchface.MyWatchFaceService"
    android:label="Xamarin Sample"
    android:allowEmbedded="true"
    android:taskAffinity=""
    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" />
    <intent-filter>
        <action android:name="android.service.wallpaper.WallpaperService" />
        <category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />
    </intent-filter>
</service>

此 XML 执行以下操作:

  1. 设置 android.permission.BIND_WALLPAPER 权限。 此权限授予表盘服务更改设备上的系统壁纸的权限。 请注意,必须在 <service> 节而不是外部的 <application> 节中设置此权限。

  2. 定义 watch_face 资源。 此资源是一个简短的 XML 文件,用于声明 wallpaper 资源(此文件将在下一部分中创建)。

  3. 声明一个名为 preview 的可绘制图像,该图像将由手表选取器选择屏幕显示。

  4. 包含一个 intent-filter,让 Android 知道 MyWatchFaceService 将显示表盘。

基本 WatchFace 示例的代码到此结束。 下一步是添加所需的资源。

添加资源文件

在运行手表服务之前,必须添加 watch_face 资源和预览图像。 首先,在 Resources/xml/watch_face.xml 中创建新的 XML 文件,并将其内容替换为以下 XML

<?xml version="1.0" encoding="UTF-8"?>
<wallpaper xmlns:android="http://schemas.android.com/apk/res/android" />

将此文件的生成操作设置为 AndroidResource

此资源文件定义了一个用于表盘的简单 wallpaper 元素。

如果尚未下载 preview.png,现在请下载。 安装它,安装路径为 Resources/drawable/preview.png。 请务必将此文件添加到 WatchFace 项目中。 此预览图像会在 Wear 设备上的表盘选取器中向用户显示。 若要为自己的表盘创建预览图像,可以在表盘运行时抓取表盘的屏幕截图。 (有关从 Wear 设备获取屏幕截图的详细信息,请参阅抓取屏幕截图)。

试试看!

生成应用并将其部署到 Wear 设备。 应会看到 Wear 应用屏幕像以前一样出现。 执行以下操作以启用新表盘:

  1. 向右轻扫,直到出现手表屏幕的背景。

  2. 触摸并按住屏幕背景上的任意位置两秒。

  3. 从左向右轻扫以浏览各种表盘。

  4. 选择“Xamarin 示例”表盘(如右侧所示)

    WatchFace 选取器

  5. 点击“Xamarin 示例”表盘将其选中

这会将 Wear 设备的表盘更改为使用迄今为止实现的自定义表盘服务:

屏幕截图显示在 Wear 设备上运行的自定义数字手表。

这是一个相对粗糙的表盘,因为应用实现非常少(例如,它不包含表盘背景,并且不调用 Paint 抗锯齿方法来改善外观)。 但是,它确实能够实现创建自定义表盘所需的基本功能。

在下一部分,该表盘将升级为更复杂的实现。

升级表盘

在本演练的其余部分,MyWatchFaceService 已升级为显示模拟风格的表盘,并进行了扩展以支持更多功能。 将添加以下功能来创建升级后的表盘:

  1. 用模拟时针、分针和秒针指示时间。

  2. 对可见性变化做出反应。

  3. 对环境模式和交互模式之间的变化做出响应。

  4. 读取基础 Wear 设备的属性。

  5. 发生时区更改时自动更新时间。

在实现以下代码更改之前,请下载 drawable.zip,将其解压缩,然后将解压缩的 .png 文件复制到 Resources/drawable(覆盖之前的 preview.png)。 将新的 .png 文件添加到 WatchFace 项目。

更新引擎功能

下一步是将 MyWatchFaceService.cs 升级为绘制模拟表盘并支持新功能的实现。 将 MyWatchFaceService.cs 的内容替换为 MyWatchFaceService.cs 中表盘代码的模拟版本(可以将此源剪切并粘贴到现有的 MyWatchFaceService.cs)

此版本的 MyWatchFaceService.cs 向现有方法添加更多代码,并包含其他重写方法以添加更多功能。 以下部分提供了源代码的指导教程。

OnCreate

更新的 OnCreate 方法与以前一样配置表盘样式,但它包括一些额外的步骤

  1. 将背景图像设置为 Resources/drawable-hdpi/xamarin_background.png 中的 xamarin_background 资源

  2. 初始化用于绘制时针、分针和秒针的 Paint 对象。

  3. 初始化一个用于在表盘边缘绘制小时刻度的 Paint 对象。

  4. 创建一个调用 Invalidate(重新绘制)方法的计时器,以便每秒重新绘制秒针。 请注意,此计时器是必需的,因为 OnTimeTick 每分钟仅调用一次 Invalidate

此示例仅包含一张 xamarin_background.png 图像;但是,你可以为自定义表盘支持的每种屏幕密度创建不同的背景图像

OnDraw

更新的 OnDraw 方法使用以下步骤绘制模拟风格的表盘

  1. 获取当前时间,该时间现在于 time 对象中维护。

  2. 确定绘图表面及其中心的边界。

  3. 绘制背景,在绘制背景时进行缩放以适合设备。

  4. 在表盘周围绘制十二个刻度(对应于表盘上的小时)

  5. 计算每个手表指针的角度、旋转和长度。

  6. 在手表表面上绘制每个指针。 请注意,如果手表处于环境模式,则不会绘制秒针。

OnPropertiesChanged

调用此方法是为了向 MyWatchFaceEngine 告知 Wear 设备的属性(例如低位环境模式和老化保护)。 在 MyWatchFaceEngine 中,此方法仅检查低位环境模式(在低位环境模式下,屏幕支持每种颜色的更少位数)。

有关此方法的详细信息,请参阅 Android onPropertiesChanged API 文档。

OnAmbientModeChanged

当 Wear 设备进入或退出环境模式时调用此方法。 在 MyWatchFaceEngine 实现中,表盘在环境模式下会禁用抗锯齿功能。

有关此方法的详细信息,请参阅 Android onAmbientModeChanged API 文档。

OnVisibilityChanged

每当手表可见或隐藏时,就会调用此方法。 在 MyWatchFaceEngine 中,此方法根据可见性状态注册/取消注册时区接收器(如下所述)。

有关此方法的详细信息,请参阅 Android onVisibilityChanged API 文档。

时区功能

新的 MyWatchFaceService.cs 还包含用于在时区发生变化时(例如跨时区旅行时)更新当前时间的功能。 在 MyWatchFaceService.cs 末尾附近,定义了时区更改 BroadcastReceiver,用于处理时区更改的 Intent 对象

public class TimeZoneReceiver: BroadcastReceiver
{
    public Action<Intent> Receive { get; set; }
    public override void OnReceive (Context context, Intent intent)
    {
        if (Receive != null)
            Receive (intent);
    }
}

RegisterTimezoneReceiverUnregisterTimezoneReceiver 方法由 OnVisibilityChanged 方法调用。 当表盘的可见性状态更改为隐藏时,将调用 UnregisterTimezoneReceiver。 当表盘再次可见时,将调用 RegisterTimezoneReceiver(请参阅 OnVisibilityChanged 方法)。

引擎 RegisterTimezoneReceiver 方法为此时区接收器的 Receive 事件声明一个处理程序;每当跨越时区时,此处理程序就会使用新的时间更新 time 对象:

timeZoneReceiver = new TimeZoneReceiver ();
timeZoneReceiver.Receive = (intent) => {
    time.Clear (intent.GetStringExtra ("time-zone"));
    time.SetToNow ();
};

为时区接收器创建并注册意向筛选器:

IntentFilter filter = new IntentFilter(Intent.ActionTimezoneChanged);
Application.Context.RegisterReceiver (timeZoneReceiver, filter);

UnregisterTimezoneReceiver 方法取消注册时区接收器:

Application.Context.UnregisterReceiver (timeZoneReceiver);

运行改进的表盘

再次生成应用并将其部署到 Wear 设备。 像以前一样从表盘选取器中选择表盘。 左侧会显示手表选取器中的预览,右侧会显示新表盘:

屏幕截图显示选取器和设备上改进的模拟表盘。

在此屏幕截图中,秒针每秒移动一次。 在 Wear 设备上运行此代码时,秒针会在手表进入环境模式时消失。

总结

在本演练中,实现并测试了一个自定义 Android Wear 1.0 表盘。 引入了 CanvasWatchFaceServiceCanvasWatchFaceService.Engine 类,并实现了引擎类的基本方法来创建简单的数字表盘。 使用更多功能更新了此实现以创建模拟表盘,并实现了其他方法来处理可见性、环境模式和设备属性差异的变化。 最后,实现了时区广播接收器,以便手表在跨越时区时自动更新时间。