Android Launcher图标定制

原理

利用Android多用户的特性,对于不同的用户展示不同的桌面。所以需要在Launcher初始化的时候判断当前user。这个需要在framework中添加接口判断,不详说。
这样,就有了两套桌面主题,一套的要求是六边形的图标展示,文件夹预览是3个图标拼接(见下图),另一套就是比较常见的圆角图标展示加上文件夹9个预览图排列。

先上效果图压阵

六边形图标桌面
圆角图标桌面

需求分析

  1. 首先是系统预置应用,这个UI的同事会提供相关的美化过的图片,我们需要做的就是用这些图标替换apk中的图片。
  2. 其次是用户安装的第三方应用,那么我们需要把第三方的应用转换成和主题(圆角或者六角)匹配的样子。

具体实现

桌面图标的加载

通过Launcher源码分析,加载桌面图标在IconCache(packages/apps/Launcher3/src/com/android/launcher3/IconCache.java)文件中的cacheLocked函数,这个函数做的是:1.先查询IconCache的缓存中是否存在,如果存在就直接从缓存中取出图标,如果不存在,则读取Launcher数据库,如果数据库存在,则把相关图标从数据库中读出来并保存到IconCache的缓存中,如果数据库不存在,则从系统中的packageManager取相关的图标信息。
根据我们的需求,我们需要在从系统中取相关图标信息的地方改动。分两种情况:

  1. 预置应用:我们会把UI改动好的图标放到Launcher res目录下面,通过包名匹配,直接替换(当然也要区分user,不用用户取不同的图标)。
private Bitmap getImdataDefaultThemeIcon(ComponentName componentName, UserHandleCompat user) {
    // We don't keep icons for other profiles in persistent cache.
    // We should change the icon according to cloudminds ui design.
    defaultThemeIcon = null;
    Resources r = mContext.getResources();
    String resName = getImdataDefaultThemeResId(componentName, user);
    String packageName = mContext.getPackageName();
    final int resId = r.getIdentifier(resName, "drawable", packageName);
    defaultThemeIcon = BitmapFactory.decodeResource(r, resId);
    if (defaultThemeIcon == null) {
        Log.w(TAG, "getImdataDefaultThemeIcon no find default theme icon for " + resName);
    }    
    return defaultThemeIcon;
}

private static String getImdataDefaultThemeResId(ComponentName component, UserHandleCompat user) {
    String resourceName = component.flattenToShortString();
    String filename = resourceName.replace(File.separatorChar, '_');
    String filePrefix = RESOURCE_FILE_PREFIX;
    filename = filename.replace('.', '_');
    filename = filename.toLowerCase();
    if (user.isEodUser()) {
        filePrefix = EOS_RES_FILE_PREFIX;
    }
    return filePrefix + filename;
}
  1. 第三方应用:
    通过在Utilities.java中添加createNonPreloadAppIconBitmap函数来统一处理。思路是在原apk的图标上添加蒙版,针对两个用户,分别是圆角蒙版或者六角蒙版。
六角蒙版

圆角蒙版

圆角的比较简单,网上也有好几种版本,我们这里采用的是利用PorterDuff.Mode.SRC_IN来叠加生成新的图标。

mask = BitmapFactory.decodeResource(context.getResources(), R.drawable.mask);
int width = mask.getWidth();
int height = mask.getHeight();
Bitmap bitmapScale = Bitmap.createScaledBitmap(bitmap, width, height, true);
result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
canvas.setBitmap(result);
canvas.drawBitmap(mask, 0, 0, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(bitmapScale, 0, 0, paint);

六角的话则要麻烦点,因为现在的应用图标大部分是方形的,不能只是简单的叠加,所以我们采用的方法是先在原图标中截取一个圆形区域出来,然后把这个圆形区域放到六角形蒙蔽中间合成一个新的图标。

final int iconBitmapSize = DEFAULT_ICON_SIZE;
result = Bitmap.createBitmap(iconBitmapSize, iconBitmapSize,
                    Bitmap.Config.ARGB_8888);
canvas.setBitmap(result);
mask = BitmapFactory.decodeResource(context.getResources(),
                    R.drawable.thirdapp_bg_gray);
int backWidth = MASK_ICON_WIDTH;
int backHeight = MASK_ICON_HEIGHT;

Rect dst = new Rect();
dst.left = ICON_BACKGROUND_PADDING_LEFT;
dst.top = 0;
dst.right = backWidth;
dst.bottom = backHeight - ICON_BACKGROUND_PADDING_BOTTOM;
canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG));
            canvas.drawBitmap(mask, null, dst, null);

Bitmap bmp = toRoundBitmap(drawableToBitmap(icon));
Drawable drawableIcon = new BitmapDrawable(context.getResources(), bmp);

drawableIcon.setBounds(ICON_LEFT_APP, ICON_TOP_APP, ICON_RIGHT_APP, ICON_BOTTOM_APP);
drawableIcon.draw(canvas);

toRoundBitmap函数主要是截取圆形区域,主要用canvas的drawCircle和之前圆角的PorterDuff.Mode.SRC_IN来实现,剩下的就是根据图标的宽高来定圆的半径。(参看效果图中的讯飞输入法图标)

文件夹的显示

Launcher中FolderIcon.java负责文件夹的显示,针对我们的场景,我们定义了两种不同的folder layout(folder_icon_3x3 -- 9图标圆角文件夹, folder_icon_eod_3 -- 3图标六角文件夹)
3x3这个布局的核心就是3个LinearLayout,每个里面横行3个图标。
eod_3这个布局的核心是2个LinearLayout纵向排列,第一个LinearLayout居中放置一个图标,第二个LinearLayout水平放置两个图标。
当然,在核心布局外面要包裹一个设置了背景图片的LinearLayout,来实现文件夹是圆角边框还是六角边框。

对于3x3的圆角显示这个比较简单不做详说,下面具体说一下六角这个预览3图标的展示。这个需要把3个图标拼装到一起,达到效果图的样子。
FolderIcon中dispatchDraw方法负责复制文件夹图标,其中调用针对文件夹预览图标的个数,循环调用drawPreviewItem方法,我们要做的就是针对不同的图标的index,调整显示图标的坐标。即调整预览图标的其实x/y,最后调用translate函数移动到画布的合的位置canvas.translate(transX, transY)。

//margin between hexagons
float margin = mViewHeight * (1 - CUSTOM_HEXAGON_W_H_RATIO);
//offset to the centre
float offset = (float) (margin/2 / Math.sin(Math.PI/3));
if (index == 0) {//上面的一个图标的x,y偏移
    transX = mTotalWidth/2 - mPreviewIconSize/2;
    transY = (int) (getPaddingTop() + mLauncher.getDeviceProfile().iconSizePx/2 - mViewHeight -  offset);
} else {//下面两个图标的x,y偏移
    transX = mTotalWidth/2 + mPreviewIconSize * (index - 2);
    transY = (int) (getPaddingTop() + mLauncher.getDeviceProfile().iconSizePx/2 - mViewHeight/4 + offset);
}

说明:

  1. 这个CUSTOM_HEXAGON_W_H_RATIO = 0.9137f是UI给我们的六级文件夹的宽高比,为了显示效果,给的不是一个正方形的图,而是高要比宽大些,即瘦长些。所以margin主要是算垂直y方向的偏移。
  2. offset的计算:在预览图中3个图标相交的边,我们把它们认为是包裹在一个圆中的3个连接圆心的点,那么offset就是这个圆的半径,margin是横向宽少对高的长度,因为是按照中间对称,所以除以2,在除上sin60就是半径,画个图就清楚了,我这里偷懒了哈。
  3. 其实就是一个公式值,最后还是要在这个基础上,根据实际的图微调一下偏移,但是思路是这样的。有了思路就慢慢调整即可。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270

推荐阅读更多精彩内容