美文网首页Launcher桌面开发
Android Launcher图标定制

Android Launcher图标定制

作者: wbxjack | 来源:发表于2017-05-17 17:39 被阅读289次

    原理

    利用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. 其实就是一个公式值,最后还是要在这个基础上,根据实际的图微调一下偏移,但是思路是这样的。有了思路就慢慢调整即可。

    相关文章

      网友评论

        本文标题:Android Launcher图标定制

        本文链接:https://www.haomeiwen.com/subject/frfdxxtx.html