- 文档
- 教程
- 移动开发
【免费下载链接】android-training-course-in-chinese
Android官方培训课程中文版
本文以 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两个模块的工程:
- 打开 Android Studio。
- 创建新工程(
Welcome界面点击New Project,或在File菜单选择New Project)。 - 填写应用名,点击Next。
- 选择Phone and Tablet尺寸系数。
- 在Minimum SDK下拉菜单选择API 18。
- 选择Wear尺寸系数。
- 在Minimum SDK下拉菜单选择API 21,点击Next。
- 选择Add No Activity,在接下来的两个界面点击Next。
- 点击Finish。
- 在 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说明任意内插的区域,如圆形设备底部的"下巴"。绘制步骤:
- 首次调用
onDraw()时缩放背景以匹配画布; - 检查设备处于环境模式还是交互模式;
- 处理任何图形计算;
- 在画布上绘制背景位图;
- 使用
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 构成了一条从设计到发布的完整链路:
- 设计表盘:先定设计准则与实现策略(双背景、320×320 画布、数据节流);
- 构建表盘服务:工程配置、权限声明、
CanvasWatchFaceService骨架与 manifest 注册; - 绘制表盘:初始化资源、定时器、系统 UI 配置、屏幕属性、模式切换与
onDraw(); - 在表盘上显示信息:
AsyncTask检索数据 +Handler定时刷新 +invalidate()重绘; - 提供配置 Activity:声明配置 Intent、可穿戴/配套配置界面、
WearableListenerService监听; - 定位常见的问题 与 优化性能和电池使用时间:屏幕形状适配、Card 容纳、相对尺寸、位图裁剪与节能。
配套的可穿戴 UI 指南见 创建自定义布局(BoxInsetLayout感知形状布局),数据同步机制见 可穿戴数据层,应用打包与运行见 创建并运行可穿戴应用。
- 文档
- 教程
- 移动开发
【免费下载链接】android-training-course-in-chinese
Android官方培训课程中文版
相关推荐
Android Wear 表盘配置 Activity 实战指南:从 Intent 元数据到数据层同步(android-training-course-in-chinese)
Android Wear 表盘配置 Activity 实战指南:从 Intent 元数据到数据层同步(android training course in ch
文档教程移动开发Android 自定义 View 性能优化实战:从 60fps 渲染目标到硬件加速(android-training-course-in-chinese 解读)
Android 自定义 View 性能优化实战:从 60fps 渲染目标到硬件加速(android training course in chinese 解读)
文档教程移动开发Android 可穿戴应用开发实战:从 Notification 同步到数据层与表盘(android-training-course-in-chinese)
Android 可穿戴应用开发实战:从 Notification 同步到数据层与表盘(android training course in chinese) 本
文档教程移动开发
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考