Android 动态时钟图标实现机制:config.xml 与 ClockDrawableWrapper.java
Android 系统支持为特定应用(如系统时钟)提供动态图标,使其能够实时反映当前状态(例如,显示当前时间)。此功能主要依赖于 iconloaderlib 库中的配置和代码实现。本文将解析两个关键文件:config.xml 和 ClockDrawableWrapper.java,阐述它们在动态时钟图标实现中的作用。
1. 配置目标应用:frameworks/libs/systemui/iconloaderlib/res/values/config.xml
系统需要识别哪个应用程序的图标需要进行动态化处理。此识别通过 config.xml 文件中的字符串资源完成。
- 关键配置项:<string name="clock_component_name" translatable="false">...</string>
- 作用:定义需要应用动态时钟图标逻辑的应用组件名称。
-
<?xml version="1.0" encoding="utf-8"?> <resources> <!-- 关键配置:定义动态时钟图标对应的应用组件 --> <!-- 系统会查找这个名字,如果匹配,则使用 ClockDrawableWrapper --> <string name="clock_component_name" translatable="false">com.google.android.deskclock/com.android.deskclock.DeskClock</string> <!-- (可选) 动态日历的配置 --> <string name="calendar_component_name" translatable="false">com.google.android.calendar/com.android.calendar.AllInOneActivity</string> </resources>
此配置告知图标加载系统(例如 Launcher),当加载 com.google.android.deskclock.DeskClock 组件的图标时,应采用特殊的渲染逻辑。
2. 实现动态渲染:frameworks/libs/systemui/iconloaderlib/src/com/android/launcher3/icons/ClockDrawableWrapper.java
package com.android.launcher3.icons;
// ... 其他 import ...
import android.graphics.drawable.LayerDrawable;
import java.util.Calendar;
import java.util.concurrent.TimeUnit;
@TargetApi(Build.VERSION_CODES.O)
public class ClockDrawableWrapper extends AdaptiveIconDrawable implements BitmapInfo.Extender {
// ...
// 关键常量:控制是否显示和更新秒针
// 修改为 false 以启用秒针
private static final boolean DISABLE_SECONDS = false;
// 关键常量:时钟更新的频率(毫秒)
// 如果秒针禁用,通常是1分钟;如果启用,则更频繁(例如 200ms)
public static final long TICK_MS = DISABLE_SECONDS ? TimeUnit.MINUTES.toMillis(1) : 200L;
// 用于从元数据读取图层索引的键名
private static final String HOUR_INDEX_METADATA_KEY = LAUNCHER_PACKAGE + ".HOUR_LAYER_INDEX";
private static final String MINUTE_INDEX_METADATA_KEY = LAUNCHER_PACKAGE + ".MINUTE_LAYER_INDEX";
private static final String SECOND_INDEX_METADATA_KEY = LAUNCHER_PACKAGE + ".SECOND_LAYER_INDEX";
// ... 其他元数据键名 ...
// 存储时钟动画相关信息的内部类实例
private final AnimationInfo mAnimationInfo = new AnimationInfo();
// ...
// 内部类,存储图层索引、默认时间等信息
private static class AnimationInfo {
public ConstantState baseDrawableState;
public int hourLayerIndex; // 时针图层索引
public int minuteLayerIndex; // 分针图层索引
public int secondLayerIndex; // 秒针图层索引
public int defaultHour;
public int defaultMinute;
public int defaultSecond;
// 关键方法:根据当前时间更新 Drawable 图层的 level
boolean applyTime(Calendar time, LayerDrawable foregroundDrawable) {
time.setTimeInMillis(System.currentTimeMillis()); // 获取当前时间
// 计算需要旋转的小时、分钟、秒 (考虑默认时间偏移)
int convertedHour = (time.get(Calendar.HOUR) + (12 - defaultHour)) % 12;
int convertedMinute = (time.get(Calendar.MINUTE) + (60 - defaultMinute)) % 60;
int convertedSecond = (time.get(Calendar.SECOND) + (60 - defaultSecond)) % 60;
boolean invalidate = false; // 标记是否有任何图层被更新
// 更新时针图层的 level
if (hourLayerIndex != INVALID_VALUE) {
final Drawable hour = foregroundDrawable.getDrawable(hourLayerIndex);
// level 通常基于分钟数 (0-719)
if (hour.setLevel(convertedHour * 60 + time.get(Calendar.MINUTE))) {
invalidate = true;
}
}
// 更新分针图层的 level
if (minuteLayerIndex != INVALID_VALUE) {
final Drawable minute = foregroundDrawable.getDrawable(minuteLayerIndex);
// level 通常基于分钟数 (0-719 or 0-59)
if (minute.setLevel(time.get(Calendar.HOUR) * 60 + convertedMinute)) {
invalidate = true;
}
}
// 更新秒针图层的 level (如果 !DISABLE_SECONDS)
if (secondLayerIndex != INVALID_VALUE) {
final Drawable second = foregroundDrawable.getDrawable(secondLayerIndex);
// level 通常基于秒数 * 一个倍数 (例如 0-599)
if (second.setLevel(convertedSecond * LEVELS_PER_SECOND)) {
invalidate = true;
}
}
return invalidate; // 返回是否需要重绘
}
}
// 内部 Drawable 类,实际处理绘制和定时任务
private static class ClockIconDrawable extends FastBitmapDrawable implements Runnable {
private final Calendar mTime = Calendar.getInstance();
private final AnimationInfo mAnimInfo;
private final LayerDrawable mFG; // 前景 LayerDrawable (包含时/分/秒针)
// ... 其他字段 ...
ClockIconDrawable(ClockConstantState cs) {
// ... 初始化 ...
mAnimInfo = cs.mAnimInfo;
// 获取前景 LayerDrawable
mFullDrawable = (AdaptiveIconDrawable) mAnimInfo.baseDrawableState.newDrawable().mutate();
mFG = (LayerDrawable) mFullDrawable.getForeground();
// 初始应用一次时间
mAnimInfo.applyTime(mTime, mFG);
// ...
}
@Override
public void drawInternal(Canvas canvas, Rect bounds) {
// ... 绘制背景 ...
// 关键:绘制前再次应用时间,确保指针位置最新
mAnimInfo.applyTime(mTime, mFG);
// ... 绘制前景 (mFG) ...
// 重新安排下一次更新
reschedule();
}
// 关键方法:定时器回调
@Override
public void run() {
// 应用时间,如果需要重绘则调用 invalidateSelf()
if (mAnimInfo.applyTime(mTime, mFG)) {
invalidateSelf();
} else {
// 如果时间没变,仍然需要重新调度以备下次检查
reschedule();
}
}
// 关键方法:设置或取消定时器
private void reschedule() {
if (!isVisible()) {
return;
}
unscheduleSelf(this); // 取消之前的调度
final long upTime = SystemClock.uptimeMillis();
final long step = TICK_MS; // 使用定义的更新间隔
// 安排下一次 run() 的执行
scheduleSelf(this, upTime - ((upTime % step)) + step);
}
@Override
public boolean setVisible(boolean visible, boolean restart) {
// 控制定时器的启动和停止
boolean result = super.setVisible(visible, restart);
if (visible) {
reschedule(); // 可见时启动/重新调度定时器
} else {
unscheduleSelf(this); // 不可见时停止定时器
}
return result;
}
// ...
}
// ... 其他方法和内部类 ...
}
该 Java 类是动态时钟图标渲染的核心实现。
- 基类:继承自 AdaptiveIconDrawable,允许其在自适应图标框架内运作,同时覆写绘制逻辑。
- 核心机制:
- 图层管理 (LayerDrawable):通常,动态时钟图标由背景、表盘、时针、分针、秒针等图层组成。ClockDrawableWrapper 使用 LayerDrawable 来管理这些组成部分,特别是前景中的指针图层。
- 定时更新:
- 内部实现了一个 Runnable,通过 scheduleSelf 和 unscheduleSelf 方法进行周期性调度。
- 更新周期由 TICK_MS 常量定义。
- 时间同步 (applyTime 方法):
- 定时器触发时,调用 applyTime 方法。
- 该方法获取当前系统时间 (System.currentTimeMillis())。
- 根据当前时间计算时、分、秒对应的角度或状态。
- 通过设置 LayerDrawable 中相应图层(时针、分针、秒针)的 level 来更新指针位置。
- 秒针控制 (DISABLE_SECONDS 常量):
- 此布尔常量决定是否渲染和更新秒针。
- 修改为 false (private static final boolean DISABLE_SECONDS = false;) 即可启用秒针的动态显示。当为 false 时,applyTime 会计算并更新秒针图层的 level。
- 绘制 (drawInternal 方法):
- 在绘制图标时,此方法负责:
- 绘制背景。
- 调用 applyTime 确保指针状态是最新。
- 绘制包含更新后指针的前景 LayerDrawable。
总结
动态时钟图标的实现流程如下:
- 图标加载器通过 config.xml 中的 clock_component_name 识别出需要动态处理的时钟应用。
- 加载器使用 ClockDrawableWrapper 而不是标准 Drawable 来渲染该应用的图标。
- ClockDrawableWrapper 内部定时器周期性触发 applyTime 方法,根据系统时间更新时、分、(若 DISABLE_SECONDS 为 false)秒针图层的 level。
- 图标绘制时,ClockDrawableWrapper 渲染出反映当前时间的、指针位置正确的图标。
通过 config.xml 的配置和 ClockDrawableWrapper.java 的动态渲染逻辑相结合,Android 系统实现了功能性与美观性兼具的动态时钟图标。修改 DISABLE_SECONDS 为 false 是启用秒针动态效果