news 2026/10/7 2:11:32

Android Wear 自定义表盘开发完全指南:从设计、服务实现到性能优化(android-training-course-in-chinese 实战教程)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Android Wear 自定义表盘开发完全指南:从设计、服务实现到性能优化(android-training-course-in-chinese 实战教程)
  • 文档
  • 教程
  • 移动开发

【免费下载链接】android-training-course-in-chinese

Android官方培训课程中文版

项目地址:https://gitcode.com/gh_mirrors/an/android-training-course-in-chinese
点击查看免费下载

本文以 android-training-course-in-chinese 仓库的 wearables/watch-faces 课程为主线,系统讲解在 Android Wear 设备上创建自定义表盘(watch face)的全流程。表盘本质上是运行在可穿戴设备上的动态数字画布,用颜色、动画与上下文信息表达时间。阅读完本文,你将掌握表盘工程配置、CanvasWatchFaceService服务实现、绘制与系统 UI 协调、配置 Activity 接入以及节能优化等完整实战技能。

一、表盘开发的整体蓝图

Android Wear 的表盘是一个动态的数字画布,它通过颜色、动画和相关的上下文信息来表示时间。Android Wear 配套应用(companion app)为用户提供了不同风格和形状的表盘,当用户选择可穿戴设备应用或配套应用上可用的表盘时,可穿戴设备会提供表盘的预览并让用户设置选项。

Android Wear 允许开发者创建自定义表盘。当用户安装一个包含表盘的可穿戴应用的手持式应用时,表盘会出现在手持设备上的 Android Wear 配套应用中,以及可穿戴设备上的表盘选择器里。这正是 创建可穿戴的应用 课程中提到的打包机制——用户不会直接将应用下载到可穿戴设备,而是将可穿戴应用打包进手持应用,安装手持应用时系统自动安装可穿戴部分。

本节课程由以下 6 个 Lesson 组成,本文将其完整展开:

Lesson主题核心内容
设计表盘设计准则与实现策略屏幕形状适配、显示模式、实现方案选型
构建表盘服务工程配置与服务实现依赖、权限、CanvasWatchFaceService回调
绘制表盘初始化与绘制资源加载、定时器、系统 UI、模式切换、onDraw()
在表盘上显示信息上下文数据集成数据获取任务、自定义定时器、重绘
提供配置 Activity用户客制化Intent 元数据、可穿戴/配套配置界面
定位常见的问题兼容性适配屏幕形状检测、Card 容纳、系统指示图标、相对尺寸
优化性能和电池使用时间性能与节能位图裁剪、滤镜、帧率、CPU 睡眠

开发工具建议:本课程推荐使用 Android Studio 进行 Android Wear 开发,因为它提供了工程初始配置、库包含和方便的打包流程,这些功能在 ADT(Eclipse)中是没有的。课程中的全部教程均假定使用 Android Studio。

二、设计表盘:准则与实现策略

设计 Android Wear 表盘,类似于设计传统表盘,是一场"清晰地显示时间"的练习。但 Wear 设备为表盘提供了更多高级功能:鲜艳的色彩、动态的背景、动画和数据整合。因此必须同时考虑更多的设计因素。

2.1 六大设计准则

为方形和圆形的设备作出规划:设计应该可以运行在方形和圆形的 Android Wear 设备上,包括那些使用感知形状的 Layout(BoxInsetLayout)的设备。在 wearables/ui/layouts.md 中介绍的BoxInsetLayout继承自FrameLayout,通过app:layout_box属性(取值如"left|top"、"all")让子 view 自动落在圆形屏幕的安全区域内;方形屏幕上窗口间隔为 0,layout_box会被忽略。

支持所有的显示模式:表盘必须同时支持有限颜色的环境模式(ambient mode)和全彩色动画的交互模式(interactive mode)。

优化特殊屏幕的技术:在环境模式下,表盘应让大部分像素保持黑色。根据屏幕技术,需要避免使用大块白色像素,仅使用黑色和白色,并禁用抗锯齿(anti-aliasing)。

容纳系统 UI 组件:设计应确保系统指示图标可见;当 notification cards 出现在屏幕上时,用户依然能看到时间。

整合数据:表盘可以利用配套手机设备上的传感器和蜂窝数据连接,显示天气、下一个日程事件等上下文数据。

提供设置选项:让用户可在可穿戴应用或 Android Wear 配套应用上配置表盘的某些设计特征(如颜色和尺寸)。

2.2 创建实现策略

完成设计后,需要决定如何获得数据并完成绘制。大部分实现方案由三部分组成:

  • 一幅或多幅背景图片
  • 接收所需数据的应用代码
  • 在背景图片上绘制文本和形状的应用代码

实现策略的关键决策点如下:

  • 双背景策略:一般在交互模式和环境模式使用两幅不同的背景图片,环境模式背景通常为全黑。
  • 屏幕密度:Android Wear 设备的屏幕密度(hdpi)应设计为320×320 像素,以同时兼容方形和圆形设备。背景图片的四角在圆形设备上不可见。
  • 按需缩放:代码中可以检测设备屏幕尺寸,若分辨率低于图片则按比例缩小背景。为了提升性能,只对背景图片缩放一次并保存缩放后的 bitmap(这正是后续 绘制表盘 中mBackgroundScaledBitmap缓存机制的由来)。
  • 数据获取节流:仅在需要时运行代码检索上下文数据并保存结果,使每次绘制时可重用数据——例如不必每隔一分钟刷新一次天气。
  • 模式差异化绘制:为延长电池寿命,环境模式下的绘制代码应相对简单,通常用一组有限颜色绘制形状轮廓;交互模式才使用全色彩、复杂形状、渐变和动画。

三、构建表盘服务:工程配置与核心回调

表盘在可穿戴应用中实现为服务(Service)并随应用打包。当用户安装包含表盘的手持式应用后,表盘在配套应用和可穿戴表盘选择器中可用;用户选择后,可穿戴设备显示表盘并按需调用其服务的回调方法。

3.1 在 Android Studio 中创建工程

按以下步骤创建包含wear和mobile两个模块的工程:

  1. 打开 Android Studio。
  2. 创建新工程(Welcome界面点击New Project,或在File菜单选择New Project)。
  3. 填写应用名,点击Next。
  4. 选择Phone and Tablet尺寸系数。
  5. 在Minimum SDK下拉菜单选择API 18。
  6. 选择Wear尺寸系数。
  7. 在Minimum SDK下拉菜单选择API 21,点击Next。
  8. 选择Add No Activity,在接下来的两个界面点击Next。
  9. 点击Finish。
  10. 在 IDE 窗口点击View > Tool Windows > Project。

工程创建完成后,Android Studio 会自动向build.gradle添加所需依赖条目,其中包含:

  • Wearable Support 库:提供可继承以实现表盘的基类(CanvasWatchFaceService等);
  • Google Play services client 库(play-services与play-services-wearable):用于在配套设备和可穿戴应用之间通过 可穿戴数据层 API 同步数据项。

若使用 Eclipse ADT,需单独下载 Wearable Support 库并将其作为依赖包含进工程。

3.2 声明权限

表盘需要PROVIDE_BACKGROUND和WAKE_LOCK两项权限,需在可穿戴应用和手持式应用的 manifest 文件manifest节点下同时声明:

<manifest ...> <uses-permission android:name="com.google.android.permission.PROVIDE_BACKGROUND" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> ... </manifest>

Caution:手持式应用必须包含所有在可穿戴应用中声明的权限。

3.3 实现服务和回调方法

表盘实现为服务:当表盘处于活动状态时,系统会在时间改变或出现重要事件(如切换到环境模式、收到新通知)时调用服务方法,服务实现据此将表盘绘制到屏幕上。

实现一个表盘需要继承CanvasWatchFaceService与CanvasWatchFaceService.Engine,并重写 Engine 的回调方法(这些类都包含在 Wearable Support 库中)。核心骨架如下:

public class AnalogWatchFaceService extends CanvasWatchFaceService { @Override public Engine onCreateEngine() { /* provide your watch face implementation */ return new Engine(); } /* implement service callback methods */ private class Engine extends CanvasWatchFaceService.Engine { @Override public void onCreate(SurfaceHolder holder) { super.onCreate(holder); /* initialize your watch face */ } @Override public void onPropertiesChanged(Bundle properties) { super.onPropertiesChanged(properties); /* get device features (burn-in, low-bit ambient) */ } @Override public void onTimeTick() { super.onTimeTick(); /* the time changed */ } @Override public void onAmbientModeChanged(boolean inAmbientMode) { super.onAmbientModeChanged(inAmbientMode); /* the wearable switched between modes */ } @Override public void onDraw(Canvas canvas, Rect bounds) { /* draw your watch face */ } @Override public void onVisibilityChanged(boolean visible) { super.onVisibilityChanged(visible); /* the watch face became visible or invisible */ } } }

Note:Android SDK 中的WatchFace示例示范了如何通过继承CanvasWatchFaceService实现模拟和数字表盘,示例位于android-sdk/samples/android-21/wearable/WatchFace目录。

CanvasWatchFaceService还提供了类似View.invalidate()的重绘机制:在主 UI 线程中调用invalidate(),从其它线程调用postInvalidate(),即可请求系统重新绘制表盘。

3.4 注册表盘服务

在可穿戴应用的 manifest 文件中注册服务实现,系统会在安装后使用该服务信息,使表盘在配套应用和表盘选择器中可用。完整注册代码:

<service android:name=".AnalogWatchFaceService" android:label="@string/analog_name" 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_analog" /> <meta-data android:name="com.google.android.wearable.watchface.preview_circular" android:resource="@drawable/preview_analog_circular" /> <intent-filter> <action android:name="android.service.wallpaper.WallpaperService" /> <category android:name= "com.google.android.wearable.watchface.category.WATCH_FACE" /> </intent-filter> </service>

关键元数据说明:

  • com.google.android.wearable.watchface.preview:定义表盘预览图。配套应用和表盘选择器在展示表盘时使用该预览图,可运行 Wear 设备或模拟器上的表盘并截图获取。hdpi 屏幕的 Wear 设备上预览图通常为320×320 像素。
  • com.google.android.wearable.watchface.preview_circular:圆形预览图。若表盘同时包含圆形和方形预览图,配套应用和表盘选择器会按手表形状选择合适的一张;若没有圆形预览图,方形和圆形设备都会使用方形预览图(圆形设备上会被圆形裁剪)。
  • android.service.wallpaper:指定包含wallpaper节点的watch_face.xml资源文件:
<?xml version="1.0" encoding="UTF-8"?> <wallpaper xmlns:android="http://schemas.android.com/apk/res/android" />

可穿戴应用可以包含多个表盘——必须为每个表盘实现分别添加一个 service 节点。

四、绘制表盘:初始化、绘制与系统 UI 协调

配置好工程并添加表盘服务类后,就可以编写初始化和绘制代码了。本节以 Android SDKWatchFace示例中的AnalogWatchFaceService为例讲解系统如何调用表盘服务方法。示例中的大部分服务实现(如初始化、检测设备功能)可复用到任意表盘。

4.1 初始化表盘

系统加载服务时,应分配并初始化表盘需要的大部分资源:加载位图资源、创建自定义动画定时器、配置颜色风格等。这些操作通常只执行一次并复用结果,以提升性能、方便维护。

初始化分三步:声明变量 → 在Engine.onCreate()中初始化组件 → 在Engine.onVisibilityChanged()中初始化自定义定时器。

第一步:声明变量。需要在Engine实现中为可复用资源声明成员变量,包括:

  • 图形对象:背景位图、时钟指针等位图资源;
  • 自定义定时器:系统每分钟通知一次时间变化,但自定义动画需要按更高频率刷新,需用Handler实现定时器;
  • 时区变化接收器:用户旅行调整时区时系统会广播事件,服务必须注册广播接收器以更新时间。

WatchFace示例的变量声明(自定义定时器通过Handler每秒计数一次,计数时调用invalidate()触发重绘):

private class Engine extends CanvasWatchFaceService.Engine { static final int MSG_UPDATE_TIME = 0; /* a time object */ Time mTime; /* device features */ boolean mLowBitAmbient; /* graphic objects */ Bitmap mBackgroundBitmap; Bitmap mBackgroundScaledBitmap; Paint mHourPaint; Paint mMinutePaint; ... /* handler to update the time once a second in interactive mode */ final Handler mUpdateTimeHandler = new Handler() { @Override public void handleMessage(Message message) { switch (message.what) { case MSG_UPDATE_TIME: invalidate(); if (shouldTimerBeRunning()) { long timeMs = System.currentTimeMillis(); long delayMs = INTERACTIVE_UPDATE_RATE_MS - (timeMs % INTERACTIVE_UPDATE_RATE_MS); mUpdateTimeHandler .sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs); } break; } } }; /* receiver to update the time zone */ final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { mTime.clear(intent.getStringExtra("time-zone")); mTime.setToNow(); } }; /* service methods (see other sections) */ ... }

第二步:在Engine.onCreate()中初始化组件。包括加载背景图片、创建绘制图形对象的风格和色彩、分配保存时间的对象、配置系统 UI:

@Override public void onCreate(SurfaceHolder holder) { super.onCreate(holder); /* configure the system UI (see next section) */ ... /* load the background image */ Resources resources = AnalogWatchFaceService.this.getResources(); Drawable backgroundDrawable = resources.getDrawable(R.drawable.bg); mBackgroundBitmap = ((BitmapDrawable) backgroundDrawable).getBitmap(); /* create graphic styles */ mHourPaint = new Paint(); mHourPaint.setARGB(255, 200, 200, 200); mHourPaint.setStrokeWidth(5.0f); mHourPaint.setAntiAlias(true); mHourPaint.setStrokeCap(Paint.Cap.ROUND); ... /* allocate an object to hold the time */ mTime = new Time(); }

背景位图在系统初始化表盘时只加载一次,图形风格用Paint实例化,之后在onDraw()中复用这些风格绘制组件。

第三步:初始化自定义定时器。通过定时器的计数频率,开发者决定交互模式下表盘的更新频率,从而支持自定义动画和视觉效果。

Note:在环境模式下,系统不会可靠地调用自定义定时器。环境模式的更新应依赖onTimeTick()。

定时器的启动条件是:表盘可见 且 设备处于交互模式:

private void updateTimer() { mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME); if (shouldTimerBeRunning()) { mUpdateTimeHandler.sendEmptyMessage(MSG_UPDATE_TIME); } } private boolean shouldTimerBeRunning() { return isVisible() && !isInAmbientMode(); }

在Engine.onVisibilityChanged()中启动定时器并注册时区接收器:

@Override public void onVisibilityChanged(boolean visible) { super.onVisibilityChanged(visible); if (visible) { registerReceiver(); // Update time zone in case it changed while we weren't visible. mTime.clear(TimeZone.getDefault().getID()); mTime.setToNow(); } else { unregisterReceiver(); } // Whether the timer should be running depends on whether we're visible and // whether we're in ambient mode), so we may need to start or stop the timer updateTimer(); }

registerReceiver()/unregisterReceiver()的实现如下(注册Intent.ACTION_TIMEZONE_CHANGED过滤器):

private void registerReceiver() { if (mRegisteredTimeZoneReceiver) { return; } mRegisteredTimeZoneReceiver = true; IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED); AnalogWatchFaceService.this.registerReceiver(mTimeZoneReceiver, filter); } private void unregisterReceiver() { if (!mRegisteredTimeZoneReceiver) { return; } mRegisteredTimeZoneReceiver = false; AnalogWatchFaceService.this.unregisterReceiver(mTimeZoneReceiver); }

4.2 在环境模式下更新表盘

环境模式下系统每分钟调用一次Engine.onTimeTick(),通常每分钟更新一次已足够。大部分表盘实现只需在onTimeTick()中重绘:

@Override public void onTimeTick() { super.onTimeTick(); invalidate(); }

4.3 配置系统 UI

表盘不应干涉系统 UI 组件。若表盘背景较亮或在屏幕底部附近显示信息,可能需要配置 notification cards 的尺寸或启用背景保护。Android Wear 允许表盘配置以下几个方面:

  • 指定第一个 notification card 离屏幕的距离;
  • 指定系统是否将时间绘制在表盘上;
  • 在环境模式下显示或隐藏 notification card;
  • 用纯色背景保护系统指针;
  • 指定系统指针的位置。

创建WatchFaceStyle实例并传入Engine.setWatchFaceStyle()方法:

@Override public void onCreate(SurfaceHolder holder) { super.onCreate(holder); /* configure the system UI */ setWatchFaceStyle(new WatchFaceStyle.Builder(AnalogWatchFaceService.this) .setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT) .setBackgroundVisibility(WatchFaceStyle .BACKGROUND_VISIBILITY_INTERRUPTIVE) .setShowSystemUiTime(false) .build()); ... }

上述配置将 card 设为一行高、背景仅对中断性通知显示、不显示系统时间(因为表盘自己绘制时间)。系统 UI 风格可以在表盘实现的任意时刻配置,例如用户选择白色背景时可为系统指针添加背景保护。

4.4 获得设备屏幕信息

系统确定设备屏幕属性时调用Engine.onPropertiesChanged(),例如设备是否使用低比特率环境模式、屏幕是否需要烧毁保护:

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

绘制时应考虑这两个属性:

  • 低比特率环境模式:屏幕在环境模式下为每种颜色提供更少的比特,切换到环境模式时应禁用抗锯齿和位图滤镜;
  • 烧毁保护:环境模式下避免使用大块白色像素,且不要将内容放在离屏幕边缘 10 像素范围内(系统会周期性移动内容防止像素烧毁)。

4.5 响应两种模式间的变化

设备在环境模式和交互模式间转换时,系统调用Engine.onAmbientModeChanged(),实现应做出必要调整并调用invalidate()重绘:

@Override public void onAmbientModeChanged(boolean inAmbientMode) { super.onAmbientModeChanged(inAmbientMode); if (mLowBitAmbient) { boolean antiAlias = !inAmbientMode; mHourPaint.setAntiAlias(antiAlias); mMinutePaint.setAntiAlias(antiAlias); mSecondPaint.setAntiAlias(antiAlias); mTickPaint.setAntiAlias(antiAlias); } invalidate(); updateTimer(); }

4.6 绘制表盘

系统调用Engine.onDraw(),参数为Canvas实例和绘制区域bounds。bounds说明任意内插的区域,如圆形设备底部的"下巴"。绘制步骤:

  1. 首次调用onDraw()时缩放背景以匹配画布;
  2. 检查设备处于环境模式还是交互模式;
  3. 处理任何图形计算;
  4. 在画布上绘制背景位图;
  5. 使用Canvas方法绘制表盘。
@Override public void onDraw(Canvas canvas, Rect bounds) { // Update the time mTime.setToNow(); int width = bounds.width(); int height = bounds.height(); // Draw the background, scaled to fit. if (mBackgroundScaledBitmap == null || mBackgroundScaledBitmap.getWidth() != width || mBackgroundScaledBitmap.getHeight() != height) { mBackgroundScaledBitmap = Bitmap.createScaledBitmap(mBackgroundBitmap, width, height, true /* filter */); } canvas.drawBitmap(mBackgroundScaledBitmap, 0, 0, null); // Find the center. Ignore the window insets so that, on round watches // with a "chin", the watch face is centered on the entire screen, not // just the usable portion. float centerX = width / 2f; float centerY = height / 2f; // Compute rotations and lengths for the clock hands. float secRot = mTime.second / 30f * (float) Math.PI; int minutes = mTime.minute; float minRot = minutes / 30f * (float) Math.PI; float hrRot = ((mTime.hour + (minutes / 60f)) / 6f ) * (float) Math.PI; float secLength = centerX - 20; float minLength = centerX - 40; float hrLength = centerX - 80; // Only draw the second hand in interactive mode. if (!isInAmbientMode()) { float secX = (float) Math.sin(secRot) * secLength; float secY = (float) -Math.cos(secRot) * secLength; canvas.drawLine(centerX, centerY, centerX + secX, centerY + secY, mSecondPaint); } // Draw the minute and hour hands. float minX = (float) Math.sin(minRot) * minLength; float minY = (float) -Math.cos(minRot) * minLength; canvas.drawLine(centerX, centerY, centerX + minX, centerY + minY, mMinutePaint); float hrX = (float) Math.sin(hrRot) * hrLength; float hrY = (float) -Math.cos(hrRot) * hrLength; canvas.drawLine(centerX, centerY, centerX + hrX, centerY + hrY, mHourPaint); }

该实现按当前时间计算指针位置,使用onCreate()中初始化的图形风格在背景位图上绘制指针。其中秒针只在交互模式绘制,环境模式不显示。

五、在表盘上显示上下文信息

创建自定义表盘不仅可以用丰富的方式显示时间,还可以在用户扫视设备时显示相关信息。表盘可以通过 可穿戴数据层 API 与可穿戴设备上的应用通信。某些情况下,需要在工程的手持式应用模块中创建 activity,从互联网或用户配置文件检索数据,再分享给表盘。

5.1 创建丰富的体验

设计和实现上下文感知表盘前,先回答四个问题:

  • 想要包含什么类型的数据?
  • 可以从哪里获得数据?
  • 数据多久会显著变化?
  • 如何表达数据,使用户瞥一眼就明白含义?

Wear 设备通常与带 GPS 或蜂窝数据连接的配套设备配对,因此可以整合位置、日历事件、社交媒体、图片、股票报价、新闻事件、体育比分等数据。但并非所有类型的数据都适合表盘,需要权衡与用户的相关性;当没有配对设备或互联网断开时,表盘应优雅降级处理。

由于表盘是一直在运行的应用,必须使用高效节能的方法获取数据。例如每 10 分钟而非每 1 分钟获取一次天气并缓存结果;在设备从环境模式切换到交互模式时刷新上下文数据(此时用户最可能瞥一眼手表)。

屏幕空间有限且用户通常只看一两秒,因此应归纳上下文信息。有时用图形和颜色表达信息效果最好——例如表盘根据天气改变背景图片。

5.2 添加数据到表盘

WatchFace示例的CalendarWatchFaceService从用户配置文件获取日程数据,显示接下来 24 小时有多少个会议。实现包含上下文数据的表盘需要三步:提供检索数据的任务 → 创建自定义定时器周期调用任务或监听外部数据变化 → 用更新数据重绘表盘。

第一步:提供任务检索数据。在CanvasWatchFaceService.Engine实现中创建继承AsyncTask的类:

/* Asynchronous task to load the meetings from the content provider and * report the number of meetings back using onMeetingsLoaded() */ private class LoadMeetingsTask extends AsyncTask<Void, Void, Integer> { @Override protected Integer doInBackground(Void... voids) { long begin = System.currentTimeMillis(); Uri.Builder builder = WearableCalendarContract.Instances.CONTENT_URI.buildUpon(); ContentUris.appendId(builder, begin); ContentUris.appendId(builder, begin + DateUtils.DAY_IN_MILLIS); final Cursor cursor = getContentResolver() .query(builder.build(), null, null, null, null); int numMeetings = cursor.getCount(); if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Num meetings: " + numMeetings); } return numMeetings; } @Override protected void onPostExecute(Integer result) { /* get the number of meetings and set the next timer tick */ onMeetingsLoaded(result); } }

Wearable Support 库的WearableCalendarContract类可直接访问配套设备用户的日历事件。任务检索完数据后调用回调方法。

第二步:创建自定义定时器。用Handler通过线程消息队列发送和处理延时消息,周期性更新数据:

private class Engine extends CanvasWatchFaceService.Engine { ... int mNumMeetings; private AsyncTask<Void, Void, Integer> mLoadMeetingsTask; /* Handler to load the meetings once a minute in interactive mode. */ final Handler mLoadMeetingsHandler = new Handler() { @Override public void handleMessage(Message message) { switch (message.what) { case MSG_LOAD_MEETINGS: cancelLoadMeetingTask(); mLoadMeetingsTask = new LoadMeetingsTask(); mLoadMeetingsTask.execute(); break; } } }; ... }

在onVisibilityChanged()中根据可见性启动/停止定时器:

@Override public void onVisibilityChanged(boolean visible) { super.onVisibilityChanged(visible); if (visible) { mLoadMeetingsHandler.sendEmptyMessage(MSG_LOAD_MEETINGS); } else { mLoadMeetingsHandler.removeMessages(MSG_LOAD_MEETINGS); cancelLoadMeetingTask(); } }

第三步:用更新的数据重绘表盘。任务检索完数据后调用invalidate(),将数据保存为Engine成员变量供onDraw()访问:

private void onMeetingsLoaded(Integer result) { if (result != null) { mNumMeetings = result; invalidate(); } if (isVisible()) { mLoadMeetingsHandler.sendEmptyMessageDelayed( MSG_LOAD_MEETINGS, LOAD_MEETINGS_DELAY_MS); } }

回调方法将结果存入成员变量、销毁 view(触发重绘)并调度下一个定时器再次运行任务。

六、提供配置 Activity:让用户客制化表盘

一些表盘提供配置参数让用户客制化外观和行为,例如选择背景颜色、在两个时区中选择感兴趣的时区。配置界面可以是可穿戴应用的 activity、手持应用的 activity,或两者皆有——用户可启动可穿戴设备上的配置界面,也可启动 Android Wear 配套应用中的配套配置界面。

6.1 指定配置 Activity 的 Intent

在可穿戴应用 manifest 的服务声明部分添加元数据项,声明配置 activity 的 Intent:

<service android:name=".DigitalWatchFaceService" ... /> <!-- companion configuration activity --> <meta-data android:name= "com.google.android.wearable.watchface.companionConfigurationAction" android:value= "com.example.android.wearable.watchface.CONFIG_DIGITAL" /> <!-- wearable configuration activity --> <meta-data android:name= "com.google.android.wearable.watchface.wearableConfigurationAction" android:value= "com.example.android.wearable.watchface.CONFIG_DIGITAL" /> ... </service>

元数据值必须在应用的包名之前定义。配置 activity 为此 intent 注册 intent filter,系统在用户想配置表盘时启动该 intent。若表盘只包含配套或可穿戴配置 activity 之一,只需包含对应的元数据项。

6.2 创建可穿戴配置 Activity

可穿戴配置 activity 只提供有限的客制化选择(复杂的菜单在小屏幕上很难导航),应提供二元选择和少量选项,覆盖表盘的主要方面。在可穿戴应用 manifest 中声明如下 intent filter:

<activity android:name=".DigitalWatchFaceWearableConfigActivity" android:label="@string/digital_config_name"> <intent-filter> <action android:name= "com.example.android.wearable.watchface.CONFIG_DIGITAL" /> <category android:name= "com.google.android.wearable.watchface.category.WEARABLE_CONFIGURATION" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity>

该 intent filter 的 action 名称必须与 6.1 节定义的 intent 一致。在配置 activity 中构建简单 UI 提供选择,用户做出选择后使用可穿戴数据层 API将配置变化传达给表盘。

6.3 创建配套配置 Activity

配套配置 activity 允许用户访问全套客制化选择(手持设备屏幕更大,更容易与复杂菜单互动),例如显示复杂的颜色选择器。在手持应用 manifest 中声明:

<activity android:name=".DigitalWatchFaceCompanionConfigActivity" android:label="@string/app_name"> <intent-filter> <action android:name= "com.example.android.wearable.watchface.CONFIG_DIGITAL" /> <category android:name= "com.google.android.wearable.watchface.category.COMPANION_CONFIGURATION" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity>

6.4 创建监听器服务

为接收配置 activity 更新的配置参数,需要在可穿戴应用中创建实现可穿戴数据层 API 的WearableListenerService服务。表盘实现可在配置参数改变时重新绘制表盘。可参考WatchFace示例中的DigitalWatchFaceConfigListenerService与DigitalWatchFaceService类。

七、定位常见问题与兼容性适配

创建自定义表盘与创建 notification 和可穿戴特有 activity 的方法不同,本节解决实现第一个表盘时常见的问题。

7.1 检测屏幕形状

部分 Wear 设备屏幕为方形,部分为圆形,圆形设备可能在底部包含插入部分("下巴")。在运行时通过重写onApplyWindowInsets()检测屏幕形状:

private class Engine extends CanvasWatchFaceService.Engine { boolean mIsRound; int mChinSize; @Override public void onApplyWindowInsets(WindowInsets insets) { super.onApplyWindowInsets(insets); mIsRound = insets.isRound(); mChinSize = insets.getSystemWindowInsetBottom(); } ... }

重新绘制表盘时检查mIsRound和mChinSize的值来适配设计。

7.2 容纳 Card

收到 notification 时,card 可能遮盖屏幕很大一部分(取决于 系统 UI 风格)。表盘应确保 card 出现时用户仍能看到时间:

  • 模拟表盘通常需要调整(如缩小表盘避免被 card 覆盖);
  • 数字表盘的显示时间区域通常不会被 card 覆盖,一般无需调整;
  • 使用WatchFaceService.getPeekCardPosition()方法确定 card 上方可用于调整表盘的空间。

在环境模式下 card 背景是透明的。若表盘在环境模式下 card 附近包含详细信息,可考虑在 card 上方绘制黑色方块,确保用户能读到 card 内容。

7.3 配置系统指示图标

创建WatchFaceStyle实例时可配置系统指示图标的位置与背景保护:

  • setStatusBarGravity():设置状态栏的位置;
  • setHotwordIndicatorGravity():设置热词的位置;
  • setViewProtection():用灰色半透明背景保护状态栏和热词。系统指示图标是白色的,若表盘背景明亮则尤其必要。

7.4 使用相对尺寸

不同厂商的 Wear 设备屏幕尺寸和分辨率不同,表盘应使用相对尺寸而非绝对像素值:

  • 绘制时用Canvas.getWidth()/Canvas.getHeight()获得画布尺寸,以屏幕尺寸比例定位图片;
  • 若重新绘制组件响应 card,则根据屏幕里 card 上方剩余空间的比例重新绘制。

八、优化性能与电池使用时间

表盘在设备上一直运行,高效使用电池十分重要。除良好的 notification cards 和系统指示图标外,还需确保动画流畅、服务不执行多余计算。

8.1 减小位图资源的尺寸

每次重绘时,onDraw()中的图像组件(时钟指针等)往往需要旋转甚至缩放。资源越大,转换运算量越大,大图形资源的转换会大幅降低动画帧率。提升性能需要:

  • 不要使用比需要更大的图像组件;
  • 删除边缘周围多余的透明像素(有时可减小 97% 的大小)。

这不仅能提升动画性能,也能节省电量。

8.2 合并位图资源

经常一起绘制的位图可以合并到同一个图形资源中。例如交互模式下将背景图片和计数标记组合,避免每次重绘时绘制两个全屏位图。

8.3 绘制可缩放位图时禁用抗锯齿

使用Canvas.drawBitmap()绘制可缩放位图时,可通过Paint实例设置选项。用setAntiAlias()禁用抗锯齿可提升性能——该设置对位图绘制没有任何视觉效果。

使用位图滤镜

对于绘制在其它组件上的位图资源,可在同一Paint实例上用setFilterBitmap()打开位图滤镜,放大后的时钟指针边缘会更平滑。

Note:在低比特率环境模式下,系统不能可靠渲染图片颜色,无法保证滤镜效果。因此在环境模式下应禁用位图滤镜。

8.4 将复杂操作移到 onDraw() 外

系统每次重绘都会调用onDraw(),应只放置更新表盘的关键操作,避免在其中:

  • 加载图片和其它资源;
  • 调整图片大小;
  • 分配对象;
  • 运行帧与帧之间不会改变的计算。

这些操作通常放在Engine.onCreate()中;图片大小调整可在Engine.onSurfaceChanged()之前进行(该方法提供画布大小)。性能分析可使用 Android Device Monitor,重点确保onDraw()实现运行时间短且调用一致。

8.5 节能最佳做法

降低动画帧频:动画消耗大量计算资源和电量。大部分动画在每秒 30 帧下看起来流畅,应避免超过 30 fps。

让 CPU 睡眠:动画和内容的小变化会唤醒 CPU。表盘应在动画之间让 CPU 睡眠——例如交互模式下每隔一秒用动画短脉冲,下一秒让 CPU 睡眠。即使短暂的睡眠也能有效降低电量消耗。为最大化电池寿命应谨慎使用动画,闪烁动画在闪烁时也会唤醒 CPU 并耗电。

监控电量消耗:在 Android Wear 配套应用的Settings > Watch battery下可查看可穿戴设备中不同进程的电量消耗。Android 5.0 的电池优化可参考 Project Volta 计划。

九、课程脉络速览

本课程的 6 个 Lesson 构成了一条从设计到发布的完整链路:

  1. 设计表盘:先定设计准则与实现策略(双背景、320×320 画布、数据节流);
  2. 构建表盘服务:工程配置、权限声明、CanvasWatchFaceService骨架与 manifest 注册;
  3. 绘制表盘:初始化资源、定时器、系统 UI 配置、屏幕属性、模式切换与onDraw();
  4. 在表盘上显示信息:AsyncTask检索数据 +Handler定时刷新 +invalidate()重绘;
  5. 提供配置 Activity:声明配置 Intent、可穿戴/配套配置界面、WearableListenerService监听;
  6. 定位常见的问题 与 优化性能和电池使用时间:屏幕形状适配、Card 容纳、相对尺寸、位图裁剪与节能。

配套的可穿戴 UI 指南见 创建自定义布局(BoxInsetLayout感知形状布局),数据同步机制见 可穿戴数据层,应用打包与运行见 创建并运行可穿戴应用。

  • 文档
  • 教程
  • 移动开发

【免费下载链接】android-training-course-in-chinese

Android官方培训课程中文版

项目地址:https://gitcode.com/gh_mirrors/an/android-training-course-in-chinese
点击查看免费下载

相关推荐

上一篇:从0到1:Loggifly与Vaultwarden集成实现登录异常实时告警
下一篇:copymanga下载管理器深度剖析:PausableDownloader与AutoDownloadHandler的工作原理

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 2:10:11

Wand-Enhancer:本地 3 分钟解锁 Pro

Wand-Enhancer&#xff1a;本地 3 分钟解锁 Pro 【免费下载链接】Wand-Enhancer Advanced UX and interoperability extension for Wand (WeMod) app 项目地址: https://gitcode.com/GitHub_Trending/we/Wand-Enhancer Wand-Enhancer 是一个开源工具&#xff0c;通过本地…

作者头像 李华
网站建设 2026/10/7 2:09:50

Eve REST API 自定义 ID 字段实战:为资源接入 UUID 唯一标识

后端Web框架 【免费下载链接】eve REST API framework designed for human beings 项目地址&#xff1a; https://gitcode.com/gh_mirrors/ev/eve 点击查看 免费下载 Eve 默认以 MongoDB 的 ObjectId 作为文档唯一标识&#xff0c;但当业务集合使用 UUID 等自定义主键时&#…

作者头像 李华
网站建设 2026/10/7 2:08:03

C# OPC UA客户端开发实战:连接、订阅与SQL Server数据存储

简介&#xff1a;这是一份面向工业自动化、物联网及企业级数据集成方向开发者的C#实战项目源码&#xff0c;核心是用C#构建OPC UA客户端&#xff0c;连接OPC UA服务器完成数据读写&#xff0c;并将采集数据存入SQL Server数据库。项目借助OpcUaHelper开源库简化协议实现&#x…

作者头像 李华