安卓15桌面时钟图标为动态时间

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。

总结

动态时钟图标的实现流程如下:

  1. 图标加载器通过 config.xml 中的 clock_component_name 识别出需要动态处理的时钟应用。
  1. 加载器使用 ClockDrawableWrapper 而不是标准 Drawable 来渲染该应用的图标。
  1. ClockDrawableWrapper 内部定时器周期性触发 applyTime 方法,根据系统时间更新时、分、(若 DISABLE_SECONDS 为 false)秒针图层的 level。
  1. 图标绘制时,ClockDrawableWrapper 渲染出反映当前时间的、指针位置正确的图标。

通过 config.xml 的配置和 ClockDrawableWrapper.java 的动态渲染逻辑相结合,Android 系统实现了功能性与美观性兼具的动态时钟图标。修改 DISABLE_SECONDS 为 false 是启用秒针动态效果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值