活动地址:CSDN21天学习挑战赛
1,机缘
Android 9.0 10.0 Launcher3 时钟动态图标的定制化(时钟动态图标)
2,收获
1.概述
在定制化10.0的项目开发中,在Launcher3的app列表页,有时钟app的图标,由于图标是静态的
开发的需要要求调成动态图标,时刻显示时间,所以要把时钟图标替换成动态图标如图:
2. 9.0 10.0时钟动态图标的定制化主要核心代码
packages/apps/Launcher3/src/com/android/launcher3/BubbleTextView.java packages/apps/Launcher3/src/com/android/launcher3/Launcher.java
3,日常
3 9.0 10.0时钟动态图标的定制化主要核心代码和功能实现
在Launcher3 workspace 第二屏app列表页中 默认的时钟app图标是静态图标 就是时间不会变化的
但这也显然不符合要求,时间是时刻都在变化的 下面就来定制化时钟动态图标3.1 定义绘制时间图标类IconUtil.java
package com.android.launcher3; import android.content.Context; import android.graphics.*; import com.android.launcher3.R; public class IconUtil { private static final String TAG = "IconUtil"; private static Bitmap getBitmap(Context context, int res) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Bitmap.Config.ARGB_4444; return BitmapFactory.decodeResource(context.getResources(), res, options); } public static Bitmap getDeskClockIcon(Context context) { Bitmap empty = getBitmap(context, R.drawable.icon_time); int x = empty.getWidth(); int y = empty.getHeight(); Bitmap deskClock = Bitmap.createBitmap(x, y, Bitmap.Config.ARGB_4444); Canvas canvas = new Canvas(deskClock); Paint paint = new Paint(); paint.setAntiAlias(true); canvas.drawBitmap(empty, 0, 0, paint); paint.setStrokeCap(Paint.Cap.ROUND); paint.setStrokeWidth(30); paint.setColor(Color.parseColor("#FFA500")); int radius = 200; int cx = x / 2; int cy = y / 2; int hour = Integer.parseInt(DateUtils.getCurrentHour()); int min = Integer.parseInt(DateUtils.getCurrentMin()); //时针的角度,这里是整点的角度。因为0°是从3点开始,所以这里减90°,从9点开始计算角度 int drgeeHour = hour * 30 - 90; if (drgeeHour < 0) { drgeeHour += 360; } // 加上时针在两个整点之间的角度,一分钟在分针上是6°,在时针上是min * 6 / 12 drgeeHour += min * 6 / 12; //时针 针尖的x y坐标,相当于已知圆心坐标和半径,求圆上任意一点的坐标 int xHour = (int) (cx + radius * Math.cos(drgeeHour * 3.14 / 180)); int yHour = (int) (cy + radius * Math.sin(drgeeHour * 3.14 / 180)); canvas.drawLine(xHour, yHour, cx, cy, paint); //分针的长度 radius = 260; paint.setStrokeWidth(20); paint.setColor(Color.RED); //分针的角度 int drgeeMin = min * 6 - 90; if (drgeeMin < 0) { drgeeMin += 360; } //分针 针尖的x y坐标 int x1 = (int) (cx + radius * Math.cos(drgeeMin * Math.PI / 180)); int y1 = (int) (cy + radius * Math.sin(drgeeMin * Math.PI / 180)); canvas.drawLine(x1, y1, cx, cy, paint); return deskClock; } }
3.2.计算当前时间工具类 DateUtils.java
package com.android.launcher3; import java.text.SimpleDateFormat; public class DateUtils { public static String getCurrentDay() { SimpleDateFormat format = new SimpleDateFormat("dd"); Long t = new Long(System.currentTimeMillis()); String d = format.format(t); return d; } public static String getCurrentSecond() { SimpleDateFormat format = new SimpleDateFormat("ss"); Long t = new Long(System.currentTimeMillis()); String d = format.format(t); return d; } public static String getCurrentMin() { SimpleDateFormat format = new SimpleDateFormat("mm"); Long t = new Long(System.currentTimeMillis()); String d = format.format(t); return d; } public static String getCurrentHour() { SimpleDateFormat format = new SimpleDateFormat("HH"); Long t = new Long(System.currentTimeMillis()); String d = format.format(t); return d; } }
3.3时钟工具类DeskClockUtil.java
package com.android.launcher3; import android.content.Context; import android.graphics.Bitmap; import android.os.Handler; import android.os.Message; import com.android.launcher3.LauncherSettings; public class DeskClockUtil { private OnDeskClockIconChangeListener mListener; private boolean mIsResume; private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); if (msg.what == 100) { Message msg1 = new Message(); msg1.what = 100; msg1.obj = msg.obj; mHandler.sendMessageDelayed(msg1, 60000); if (mListener != null) { mListener.onChange(IconUtil.getDeskClockIcon((Context) msg.obj)); } } } }; private static DeskClockUtil sInstance; private DeskClockUtil() { } public static DeskClockUtil getInstance() { if (sInstance == null) { sInstance = new DeskClockUtil(); } return sInstance; } private void refresh(Context context) { if (mListener != null) { mListener.onChange(IconUtil.getDeskClockIcon(context)); } if (mHandler.hasMessages(100)) { mHandler.removeMessages(100); } Message msg = new Message(); msg.what = 100; msg.obj = context; mHandler.sendMessageDelayed(msg, 1000 * (60 - Integer.parseInt(DateUtils.getCurrentSecond()))); } public void onResume(Context context) { mIsResume = true; refresh(context); } public void onPause() { mIsResume = false; mHandler.removeMessages(100); } public void setListener(OnDeskClockIconChangeListener listener,Context context) { mListener = listener; if (mIsResume) { refresh(context); } } public interface OnDeskClockIconChangeListener { void onChange(Bitmap icon); } }
3.4.BubbleTextView.java中设置动态图标
public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, OnResumeCallback { private static final int DISPLAY_WORKSPACE = 0; private static final int DISPLAY_ALL_APPS = 1; private static final int DISPLAY_FOLDER = 2; private static final int[] STATE_PRESSED = new int[] {android.R.attr.state_pressed}; private static final Property
DOT_SCALE_PROPERTY = new Property (Float.TYPE, "dotScale") { @Override public Float get(BubbleTextView bubbleTextView) { return bubbleTextView.mDotParams.scale; } @Override public void set(BubbleTextView bubbleTextView, Float value) { bubbleTextView.mDotParams.scale = value; bubbleTextView.invalidate(); } }; public static final Property TEXT_ALPHA_PROPERTY = new Property (Float.class, "textAlpha") { @Override public Float get(BubbleTextView bubbleTextView) { return bubbleTextView.mTextAlpha; } @Override public void set(BubbleTextView bubbleTextView, Float alpha) { bubbleTextView.setTextAlpha(alpha); } }; private final ActivityContext mActivity; private Drawable mIcon; private final boolean mCenterVertically; private final CheckLongPressHelper mLongPressHelper; private final StylusEventHelper mStylusEventHelper; private final float mSlop; private final boolean mLayoutHorizontal; private final int mIconSize; private float mIconTextSize; @ViewDebug.ExportedProperty(category = "launcher") private boolean mIsIconVisible = true; @ViewDebug.ExportedProperty(category = "launcher") private int mTextColor; @ViewDebug.ExportedProperty(category = "launcher") private float mTextAlpha = 1; @ViewDebug.ExportedProperty(category = "launcher") private DotInfo mDotInfo; private DotRenderer mDotRenderer; @ViewDebug.ExportedProperty(category = "launcher", deepExport = true) private DotRenderer.DrawParams mDotParams; private Animator mDotScaleAnim; private boolean mForceHideDot; @ViewDebug.ExportedProperty(category = "launcher") private boolean mStayPressed; @ViewDebug.ExportedProperty(category = "launcher") private boolean mIgnorePressedStateChange; @ViewDebug.ExportedProperty(category = "launcher") private boolean mDisableRelayout = false; private IconLoadRequest mIconLoadRequest; public BubbleTextView(Context context) { this(context, null, 0); } public BubbleTextView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public BubbleTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mActivity = ActivityContext.lookupContext(context); mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.BubbleTextView, defStyle, 0); mLayoutHorizontal = a.getBoolean(R.styleable.BubbleTextView_layoutHorizontal, false); int display = a.getInteger(R.styleable.BubbleTextView_iconDisplay, DISPLAY_WORKSPACE); final int defaultIconSize; if (display == DISPLAY_WORKSPACE) { DeviceProfile grid = mActivity.getWallpaperDeviceProfile(); setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx); setCompoundDrawablePadding(grid.iconDrawablePaddingPx); defaultIconSize = grid.iconSizePx; mIconTextSize = grid.iconTextSizePx; } else if (display == DISPLAY_ALL_APPS) { DeviceProfile grid = mActivity.getDeviceProfile(); setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.allAppsIconTextSizePx); setCompoundDrawablePadding(grid.allAppsIconDrawablePaddingPx); defaultIconSize = grid.allAppsIconSizePx; mIconTextSize = grid.allAppsIconTextSizePx; } else if (display == DISPLAY_FOLDER) { DeviceProfile grid = mActivity.getDeviceProfile(); setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.folderChildTextSizePx); setCompoundDrawablePadding(grid.folderChildDrawablePaddingPx); defaultIconSize = grid.folderChildIconSizePx; mIconTextSize = grid.folderChildTextSizePx; } else { defaultIconSize = mActivity.getDeviceProfile().iconSizePx; mIconTextSize = mActivity.getWallpaperDeviceProfile().iconTextSizePx; } mCenterVertically = a.getBoolean(R.styleable.BubbleTextView_centerVertically, false); mIconSize = a.getDimensionPixelSize(R.styleable.BubbleTextView_iconSizeOverride, defaultIconSize); a.recycle(); mLongPressHelper = new CheckLongPressHelper(this); mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this); mDotParams = new DotRenderer.DrawParams(); setMaxLines(mActivity.getDeviceProfile().maxIconLabelLines); setEllipsize(TruncateAt.END); setAccessibilityDelegate(mActivity.getAccessibilityDelegate()); setTextAlpha(1f); } /** * Applies the item info if it is same as what the view is pointing to currently. */ @Override public void reapplyItemInfo(ItemInfoWithIcon info) { if (getTag() == info) { mIconLoadRequest = null; mDisableRelayout = true; // Optimization: Starting in N, pre-uploads the bitmap to RenderThread. info.iconBitmap.prepareToDraw(); if (info instanceof AppInfo) { //加载app图标 applyFromApplicationInfo((AppInfo) info); } else if (info instanceof WorkspaceItemInfo) { applyFromWorkspaceItem((WorkspaceItemInfo) info); mActivity.invalidateParent(info); } else if (info instanceof PackageItemInfo) { applyFromPackageItemInfo((PackageItemInfo) info); } mDisableRelayout = false; } } reapplyItemInfo()负责加载appInfo workspaceitem PackageItemInfo的相关信息 public void applyFromApplicationInfo(AppInfo info) { applyIconAndLabel(info); // We don't need to check the info since it's not a WorkspaceItemInfo super.setTag(info); // Verify high res immediately verifyHighRes(); if (info instanceof PromiseAppInfo) { PromiseAppInfo promiseAppInfo = (PromiseAppInfo) info; applyProgressLevel(promiseAppInfo.level); } applyDotState(info, false /* animate */); } public void applyFromPackageItemInfo(PackageItemInfo info) { applyIconAndLabel(info); // We don't need to check the info since it's not a WorkspaceItemInfo super.setTag(info); // Verify high res immediately verifyHighRes(); } private void applyIconAndLabel(ItemInfoWithIcon info) { FastBitmapDrawable iconDrawable = DrawableFactory.INSTANCE.get(getContext()) .newIcon(getContext(), info); mDotParams.color = IconPalette.getMutedColor(info.iconColor, 0.54f); setIcon(iconDrawable); setText(info.title); if (info.contentDescription != null) { setContentDescription(info.isDisabled() ? getContext().getString(R.string.disabled_app_label, info.contentDescription) : info.contentDescription); } }
最终在applyIconAndLabel(ItemInfoWithIcon info)加载app的图标 名称等信息
所以实现时钟动态图标就是在这里
4. 9.0 10.0时钟动态图标的定制化功能实现
private void applyIconAndLabel(ItemInfoWithIcon info) { String pkgname = ""; if (info.getIntent() != null && info.getIntent().getComponent() != null) { pkgname = info.getIntent().getComponent().getPackageName(); } android.util.Log.e("Launcher3","pkgname:"+pkgname); FastBitmapDrawable iconDrawable = null; if(pkgname.equals("com.android.deskclock")){ DeskClockUtil.getInstance().setListener(new DeskClockUtil.OnDeskClockIconChangeListener() { @Override public void onChange(Bitmap icon) { FastBitmapDrawable deskiconDrawable = new FastBitmapDrawable(icon); android.util.Log.e("Launcher3","deskiconDrawable:"+deskiconDrawable+"--icon:"+icon); if(deskiconDrawable!=null)setIcon(deskiconDrawable); } }, getContext()); }else{ iconDrawable = DrawableFactory.INSTANCE.get(getContext()) .newIcon(getContext(), info); } //FastBitmapDrawable iconDrawable = DrawableFactory.INSTANCE.get(getContext()) //.newIcon(getContext(), info); mDotParams.color = IconPalette.getMutedColor(info.iconColor, 0.54f); if(iconDrawable!=null)setIcon(iconDrawable); setText(info.title); if (info.contentDescription != null) { setContentDescription(info.isDisabled() ? getContext().getString(R.string.disabled_app_label, info.contentDescription) : info.contentDescription); } }
5.在Launcher.java的onResume()和onPause()中分别开始和暂停
protected void onResume() { ...... /* begin /DeskClockUtil.getInstance().onResume(this);/ end */ if (mLauncherCallbacks != null) { mLauncherCallbacks.onResume(); } } @Override protected void onPause() { /* begin /DeskClockUtil.getInstance().onPause();/ end */ .... }