Launcher图标角标

前言

桌面图标的角标,看着是个很简单的功能,就是在应用的右上角显示当前有几个未读消息;在网上查了资料之后,发现很多同行说,Android原生是没有此功能,平时使用的手机都有该功能,其实是国内手机厂商自己定制的桌面图标角标,且不同厂商之间,方案还不尽相同;但是此次我还是要看Android源生代码究竟是怎么显示应用有新的未读消息,此篇文章是以为记。
在这里插入图片描述

很直观,它是显示在应用的右上角的,那么我们要查看它,得先在代码中找到单个应用显示的类,根据上篇文章介绍,应用是在CellLayout平均分配的矩形中显示,可以先查看CellLayout的源码,在CellLayout的源码中,找到了下面的方法:

    public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
            boolean markCells) {
    
    
        final LayoutParams lp = params;

        // Hotseat icons - remove text
        if (child instanceof BubbleTextView) {
    
    
            BubbleTextView bubbleChild = (BubbleTextView) child;
            bubbleChild.setTextVisibility(mContainerType != HOTSEAT);
        }

        child.setScaleX(mChildScale);
        child.setScaleY(mChildScale);

        // Generate an id for each view, this assumes we have at most 256x256 cells
        // per workspace screen
        if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
    
    
            // If the horizontal or vertical span is set to -1, it is taken to
            // mean that it spans the extent of the CellLayout
            if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
            if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;

            child.setId(childId);
            if (LOGD) {
    
    
                Log.d(TAG, "Adding view to ShortcutsAndWidgetsContainer: " + child);
            }
            mShortcutsAndWidgets.addView(child, index, lp);

            if (markCells) markCellsAsOccupiedForView(child);

            return true;
        }
        return false;
    }

该方法是将子View添加到CellLayout中;而我们知道CellLayout中要么显示应用的快捷方式,要不显示桌面小部件,如果是Hotseat里么的CellLayout,那么它就只能显示应用的快捷方式,且不显示它的名称,从代码得知应用的快捷方式的类就是BubbleTextView。

        // Hotseat icons - remove text
        if (child instanceof BubbleTextView) {
    
    
            BubbleTextView bubbleChild = (BubbleTextView) child;
            bubbleChild.setTextVisibility(mContainerType != HOTSEAT);
        }

在BubbleTextView源码中,确实有看到在该View的右上角画标记,但是没有发现有设置未读消息的代码,经过修改代码运行得知,此标记仅表示有新消息,但是没有消息的条数,具体修改代码如下:

    protected void drawBadgeIfNecessary(Canvas canvas) {
    
    
        //设置mBadgeScale以满足条件,画出右上角标记信息
        mBadgeScale = 1.0f;
        if (!mForceHideBadge && (hasBadge() || mBadgeScale > 0)) {
    
    
            getIconBounds(mTempIconBounds);
            mTempSpaceForBadgeOffset.set((getWidth() - mIconSize) / 2, getPaddingTop());
            final int scrollX = getScrollX();
            final int scrollY = getScrollY();
            canvas.translate(scrollX, scrollY);
            mBadgeRenderer.draw(canvas, mBadgeColor, mTempIconBounds, mBadgeScale,
                    mTempSpaceForBadgeOffset);
            canvas.translate(-scrollX, -scrollY);
        }
    }

运行结果如下:在这里插入图片描述
所以得出结论,原生图标角标与国内手机的图标角标有出入。

接下来要学习一下国内手机的图标角标都是怎么实现的。

小米

本人有部红米手机,所以就以小米手机为例,进行学习,通过查找,在小米开发平台找到相关资料;小米桌面角标

  1. 默认逻辑
    当应用向通知栏发送了一条通知 (除了进度条样式和常驻通知外),应用图标的右上角就会显示「1」。角标的数字代表应用的通知数,即应用发送了「x」条通知,角标就会显示为「x」。
  1. 开发者如何设置桌面角标
    2.1 MIUI6-MIUI11桌面应用角标适配方法
    可通过反射调用设置桌面角标,参考代码如下:
try {
    
    
    Field field = notification.getClass().getDeclaredField(“extraNotification”);
    Object extraNotification = field.get(notification);
    Method method = extraNotification.getClass().getDeclaredMethod(“setMessageCount”, int.class);
    method.invoke(extraNotification, mCount);
} catch (Exception e) {
    
    
    e.printStackTrace();
}

2.2 MIUI12及以后桌面应用角标适配方法
由于Google屏蔽了hideAPI的反射调用,因此MIUI12及以后可以使用notification.number,可参照Android开发者文档https://developer.android.google.cn/reference/android/app/Notification#number,参考代码如下:

Notification notification = new Notification.Builder(context, CHANNEL_ID)
            .setSmallIcon(R.drawable.notification_icon)
            .setContentTitle(textTitle)
            .setContentText(textContent)
            .setNumber(int number)
            .build();
  1. 如何判断MIUI版本
    可参考文档https://dev.mi.com/console/doc/detail?pId=1312,其中6.1节有具体方法说明。

由于本人的手机版本是MIUI12.5.6,所以只能参考上述文档中2.2的适配方法,写了如下测试代码:

 private void createShortcutBadger() {
    
    
        //获取通知管理类
        NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        //通知在Android8.0之后需要创建通道,才能弹出来
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    
    
            NotificationChannel channel = new NotificationChannel("1", "name", NotificationManager.IMPORTANCE_HIGH);
            mNotificationManager.createNotificationChannel(channel);
        }
        Notification notification = new Notification.Builder(this, "1")
                .setSmallIcon(R.drawable.ic_launcher_background)
                .setContentTitle("通知标题")
                .setContentText("通知内容")
                .setNumber(1)
                .build();
        //告诉系统要出现哪个通知
        mNotificationManager.notify(0, notification);
    }

通知可以创建成功,可应用的角标却没有,在网上查阅资料后,得知原因是,当我们创建通知时,已经在应用内部了,所以创建的通知,应用认为是已读状态,所以并不会在应用图标的右上角显示未读消息个数,所以对代码做了改动如下:

 mHandler.postDelayed(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                createShortcutBadger();
            }
        },3000);

经过测试,发现此时图标的角标还是未能显示,后想是否是因为手机里的某些权限未开启,经过查找,找到了设置->通知与控制中心->桌面角标,发现自己写的应用的角标权限确实未开启,开启之后,再次验证,角标正常显示。在这里插入图片描述
但是此代码只适用于MIUI12及以后桌面的图标角标显示,为了能够适配所有小米手机的角标显示,根据文档对源码做了如下改动;

    private void createShortcutBadger() {
    
    
        String code = PropUtils.get("ro.miui.ui.version.code", "7");
        Integer intCode = Integer.valueOf(code);
        /**
         * 当前手机code为11,但是不清楚MIUI12的code是多少,所以就以11为临界点。
         */
        if (intCode >= 11){
    
    
            createHighShortcutBadger();
        } else {
    
    
            createLowShortcutBadger();
        }
    }

    private void createLowShortcutBadger() {
    
    
        //获取通知管理类
        NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        //通知在Android8.0之后需要创建通道,才能弹出来
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    
    
            NotificationChannel channel = new NotificationChannel("1", "name", NotificationManager.IMPORTANCE_HIGH);
            mNotificationManager.createNotificationChannel(channel);
        }
        Notification notification = new Notification.Builder(this, "1")
                .setSmallIcon(R.drawable.ic_launcher_background)
                .setContentTitle("通知标题")
                .setContentText("通知内容")
                .build();
        try {
    
    
            Field field = notification.getClass().getDeclaredField("extraNotification");
            Object extraNotification = field.get(notification);
            Method method = extraNotification.getClass().getDeclaredMethod("setMessageCount", int.class);
            method.invoke(extraNotification, 1);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        //告诉系统要出现哪个通知
        mNotificationManager.notify(0, notification);
    }

    private void createHighShortcutBadger() {
    
    
        //获取通知管理类
        NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        //通知在Android8.0之后需要创建通道,才能弹出来
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    
    
            NotificationChannel channel = new NotificationChannel("1", "name", NotificationManager.IMPORTANCE_HIGH);
            mNotificationManager.createNotificationChannel(channel);
        }
        Notification notification = new Notification.Builder(this, "1")
                .setSmallIcon(R.drawable.ic_launcher_background)
                .setContentTitle("通知标题")
                .setContentText("通知内容")
                .setNumber(1)
                .build();
        //告诉系统要出现哪个通知
        mNotificationManager.notify(0, notification);
    }
package com.yangshuangyue.myshortcutbadger;

import java.lang.reflect.Method;

class PropUtils {
    
    
    /**
     * 设置属性值
     *
     * @param key   长度不能超过31,key.length <= 30
     * @param value 长度不能超过91,value.length<=90
     */
    public static void set(String key, String value) {
    
    
        // android.os.SystemProperties
        // public static void set(String key, String val)
        try {
    
    
            Class<?> cls = Class.forName("android.os.SystemProperties");
            Method method = cls.getMethod("set", String.class, String.class);
            method.invoke(null, key, value);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }

    /**
     * 获取属性值
     *
     * @param key 长度不能超过31,key.length <= 30
     * @param defValue
     * @return
     */
    public static String get(String key, String defValue) {
    
    
        // android.os.SystemProperties
        // public static String get(String key, String def)
        try {
    
    
            Class<?> cls = Class.forName("android.os.SystemProperties");
            Method method = cls.getMethod("get", String.class, String.class);
            return (String) method.invoke(null, key, defValue);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        return defValue;
    }
}

没有MIUI12以下的手机,未能验证。

参考资料:

Android开发:史上最全Android应用角标适配方法
MIUI6&7桌面角标开源代码简介
桌面应用角标适配说明
关于小米 角标不显示问题
使用SystemProperties的几种方式

华为

1、声明权限

    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="com.huawei.android.launcher.permission.CHANGE_BADGE"/>

2、在需要进行角标显示地方,采用如下方法传递数据给华为桌面应用。

Bundle extra = new Bundle();
extra.putString("package", "xxxxxx");
extra.putString("class", "yyyyyyy");
extra.putInt("badgenumber", i);
context.getContentResolver().call(Uri.parse("content://com.huawei.android.launcher.settings/badge/"), "change_badge", null, extra);

关键参数说明:

  • package:应用包名
  • class:桌面图标对应的应用入口Activity类
  • badgenumber:角标数字

示例

boolean mIsSupportedBade = true;
if (mIsSupportedBade) {
    
    
    setBadgeNum(num);
}
/** set badge number*/
public void setBadgeNum(int num) {
    
    
    try {
    
    
        Bundle bunlde = new Bundle();
        bunlde.putString("package", "com.test.badge"); // com.test.badge is your package name
        bunlde.putString("class", "com.test. badge.MainActivity"); // com.test. badge.MainActivity is your apk main activity
        bunlde.putInt("badgenumber", num);
        this.getContentResolver().call(Uri.parse("content://com.huawei.android.launcher.settings/badge/"), "change_badge", null, bunlde);
    } catch (Exception e) {
    
    
        mIsSupportedBade = false;
    }
}

参考资料

Android开发:史上最全Android应用角标适配方法
华为桌面角标开发指导书

三星

可以通过广播机制直接设置应用角标,且应用在前台和被杀掉后仍可显示

    private static boolean setSamsungBadge(int count, Context context) {
    
    
        try {
    
    
            String launcherClassName = getLauncherClassName(context);
            if (TextUtils.isEmpty(launcherClassName)) {
    
    
                return false;
            }
            Intent intent = new Intent("android.intent.action.BADGE_COUNT_UPDATE");
            intent.putExtra("badge_count", count);
            intent.putExtra("badge_count_package_name", context.getPackageName());
            intent.putExtra("badge_count_class_name", launcherClassName);
            context.sendBroadcast(intent);
            return true;
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return false;
        }
    }

参考资料

Android开发:史上最全Android应用角标适配方法

联想

    private static boolean setZukBadge(int count, Context context) {
    
    
        try {
    
    
            Bundle extra = new Bundle();
            ArrayList<String> ids = new ArrayList<>();
            // 以列表形式传递快捷方式id,可以添加多个快捷方式id
//        ids.add("custom_id_1");
//        ids.add("custom_id_2");
            extra.putStringArrayList("app_shortcut_custom_id", ids);
            extra.putInt("app_badge_count", count);
            Uri contentUri = Uri.parse("content://com.android.badge/badge");
            Bundle bundle = context.getContentResolver().call(contentUri, "setAppBadgeCount", null,
                    extra);
            return bundle != null;
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return false;
        }
    }

参考资料

Android开发:史上最全Android应用角标适配方法

猜你喜欢

转载自blog.csdn.net/yangshuangyue/article/details/122499660