由于最近在负责MTK5.1的Android系统开发,所以本文就以5.1的代码为参考。其它版本虽然会略有不同,但是修改思路是大致相同的。

在市面上很多手机都会对原生桌面进行一些修改,比如把时钟、日历修改成动态显示,或者对整个界面风格进行修改。那么我们就来模仿一下,简单地修改原生应用的图标显示,从而达到修改主题样式的目的。

5.1上的Android桌面,其实也就是Launcher3,位置是
alps\packages\apps\Launcher3。


思路

我先说下核心思路:在Launcher3获取应用图标时都会经过一个类:IconCache.java,具体位置是
alps\packages\apps\Launcher3\src\com\android\launcher3\IconCache.java.

在这个类中有一个关键的方法:cacheLocked

    private CacheEntry cacheLocked(ComponentName componentName, LauncherActivityInfoCompat info,            HashMap labelCache, UserHandleCompat user, boolean usePackageIcon) {        ......    }

Launcher3获取应用图标的方法有好几个,但是这些方法最终都会调用cacheLocked获取应用图标,因此,我们主要的切换主题的逻辑就放在这里做好了。


步骤

一:

在IconCache类中定义全局变量:

    private int mThemeCode;    private List mThemePackageNames;    private static final String[] PACKAGE_NAME = {            "com.android.fmradio",            "com.android.email",            "com.android.music",            "com.android.gallery3d",            "com.android.providers.downloads.ui",            "com.android.browser",            "com.mediatek.filemanager",            "com.android.calculator2",            "com.android.calendar",            "com.mediatek.camera",            "com.android.contacts",            "com.android.deskclock",            "com.android.dialer",            "com.android.mms",            "com.android.settings",            "com.android.soundrecorder",            "com.android.stk",            "com.mediatek.notebook"    };

其中PACKAGE_NAME中的包名就是要切换图标的应用包名,mThemeCode是每个主题所对应的值,mThemePackageNames是为了方便处理PACKAGE_NAME中的包名的判断的。


二:

在IconCache的构造函数中初始化成员变量:

    public IconCache(Context context) {        ......        //mikechenmj add        SharedPreferences sharedPreferences = mContext        .getSharedPreferences("theme_code",Context.MODE_PRIVATE);        mThemeCode = sharedPreferences.getInt("theme",0);        mThemePackageNames = Arrays.asList(PACKAGE_NAME);        //end

这里通过SharedPreferences来获取mThemeCode的值,不然当手机重启后主题就会被恢复原样了。然后将PACKAGE_NAME转化成一个List赋值给mThemePackageNames。


三:

在cacheLocked方法中修改指定应用的图标。

    private CacheEntry cacheLocked(ComponentName componentName, LauncherActivityInfoCompat info,            HashMap labelCache, UserHandleCompat user, boolean usePackageIcon) {        ......        CacheKey cacheKey = new CacheKey(componentName, user);        CacheEntry entry = mCache.get(cacheKey);        ......        //mikechenmj add        if(entry.icon != null) {            Bitmap themeBitmap = getThemeBitmap(componentName ,entry.icon.getWidth(),entry.icon.getHeight());            if(themeBitmap != null) {                entry.icon = themeBitmap;            }        }        //end        return entry;......    private static class CacheEntry {        public Bitmap icon;        public CharSequence title;        public CharSequence contentDescription;    }

在cacheLocked中,通过方法getThemeBitmap获取新的Bitmap,并赋值给entry。getThemeBitmap方法是一个自己编写的获取指定Bitmap的方法。


在IconCache.java中定义需要的方法

    public void setThemeCode(int code) {        mThemeCode = code;        flush();        SharedPreferences sharedPreferences =         mContext.getSharedPreferences("theme_code",Context.MODE_PRIVATE);        SharedPreferences.Editor editor = sharedPreferences.edit();        editor.putInt("theme",mThemeCode);        editor.commit();    }    private Bitmap getThemeBitmap(ComponentName componentName, int reqWidth, int reqHeight) {                + mThemeCode);        if (mThemeCode > 0) {            String packageName = componentName.getPackageName();            if (mThemePackageNames.contains(packageName)) {                StringBuilder identifierName = new StringBuilder();                int resId;                if (packageName.equals("com.android.gallery3d")                        && componentName.getClassName().equals("com.android.camera.CameraLauncher")) {                    identifierName.append("com_mediatek_camera_theme_").append(mThemeCode);                    resId = mContext.getResources().getIdentifier(identifierName.toString(),                     "drawable", mContext.getPackageName());                } else {                    identifierName.append(packageName.replace(".", "_"))                            .append("_theme_")                            .append(mThemeCode);                    resId = mContext.getResources().getIdentifier(identifierName.toString(),                     "drawable", mContext.getPackageName());                }                if (resId == 0) {                    return null;                }                return decodeResource(mContext, resId, reqWidth, reqHeight);            }        }        return null;    }    public static Bitmap decodeResource(Context context, int resId, int reqWidth, int reqHeight) {        Resources resources = context.getResources();        BitmapFactory.Options options = new BitmapFactory.Options();        options.inJustDecodeBounds = true;        BitmapFactory.decodeResource(resources, resId, options);        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);        options.inJustDecodeBounds = false;        Bitmap bitmap = BitmapFactory.decodeResource(resources, resId);        return bitmap;    }    public static int calculateInSampleSize(BitmapFactory.Options options,                                     int reqWidth, int reqHeight) {        final int height = options.outHeight;        final int width = options.outWidth;        int inSampleSize = 1;        if (height > reqHeight || width > reqWidth) {            final int heightRatio = Math.round((float) height / (float) reqHeight);            final int widthRatio = Math.round((float) width / (float) reqWidth);            inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;        }        return inSampleSize;    }

其中setThemeCode方法是提供给Launcher3的主活动:Launcher.java调用的。
在这个方法中更新了mThemeCode并保存到了SharedPreferences当中,并且清除了缓存,以免被以前的主题影响。

getThemeBitmap方法主要是根据componentName中携带的包名判断是否需要更图标以及合成需要的图标的名称,并通过decodeResource将其解析出来。当mThemeCode不大于0时,代表使用的是默认的风格,所以不往下执行代码。
而图片的命名都是与包名对应的,比如com_android_browser_theme_1.png对应的就是主题值为1,包名为com.android.browser的应用。
除此之外,由于5.1中的相机和图库的包名是同一个,所以判断相机时加上了类名作为判断条件:
componentName.getClassName().equals(“com.android.camera.CameraLauncher”)

decodeResource方法主要是通过calculateInSampleSize方法获取到合适的options.inSampleSize值,然后根据这个值将图标的大小调整合适,再解析出来。


IconCache.java修改完了,接下来就是要想办法去选择并传递ThemeCode。我们可以在主活动Launcher中的OverviewMode模式(既长按桌面进入的调整桌面的模式)中添加一个Button来启动一个新的活动,在新活动中选择主题,并把主题的值返回给Launcher,Launcher再调用IconCache的setThemeCode方法把ThemeCode传递给IconCache,然后通知桌面刷新。代码如下:

在res/layout/overview_panel.xml中新加一个Button

    ......    "gone"        android:id="@+id/themes_button"        android:layout_width="0dp"        android:layout_height="wrap_content"        android:layout_weight="1"        android:drawablePadding="4dp"        android:drawableTop="@drawable/theme_button"        android:fontFamily="sans-serif-condensed"        android:gravity="center_horizontal"        android:text="@string/theme_button_text"        android:textAllCaps="true"        android:textSize="12sp" />

其中android:drawableTop属性中的@drawable/theme_button是自己定义的一个xml文件,里面定义了按键被按时和平时显示的图片。


在Launcher初始化时初始化新加的Button,并设置按键事件。

            View themeButton = findViewById(R.id.themes_button);            themeButton.setVisibility(View.VISIBLE);            themeButton.setOnClickListener(new OnClickListener() {                @Override                public void onClick(View arg0) {                    Intent intent = new Intent("mikechenmj.android.activity.LAUNCHER_THEME_PICKER");                    startActivityForResult(intent,THEME_PICKER_REQUEST_CODE);                }            });            themeButton.setOnTouchListener(getHapticFeedbackTouchListener());

当点击这个按钮的时候,会启动一个接收
mikechenmj.android.activity.LAUNCHER_THEME_PICKER这个Action的活动,在这个活动中选择主题风格,并且通过类似下面的方法来返回选择的themeCode。

    private void returnThemeCode(int theme) {        Intent intent = new Intent();        intent.putExtra("theme_code", theme);        setResult(RESULT_OK, intent);        finish();    }

选择主题的活动的代码不重要就不贴了,反正大致就这个思路。


然后修改Launcher.java的onActivityResult,调用方法handleThemePicker。

    @Override    protected void onActivityResult(            final int requestCode, final int resultCode, final Intent data) {        //mikechenmj add        handleThemePicker(requestCode,resultCode,data);        //end        ......    }
    private void handleThemePicker(int requestCode, int resultCode, Intent data) {        if (resultCode == RESULT_OK) {            if(requestCode == THEME_PICKER_REQUEST_CODE && resultCode == RESULT_OK) {                int themeCode = data.getIntExtra("theme_code", 0);                Resources resources = getResources();                WallpaperManager manager = WallpaperManager.getInstance(this);                Bitmap wallpager;                if(themeCode == 0) {                    Resources sysRes = Resources.getSystem();                    int resId = sysRes.getIdentifier("default_wallpaper", "drawable", "android");                    wallpager = IconCache.decodeResource(this, resId,                    resources.getDisplayMetrics().widthPixels,                    resources.getDisplayMetrics().heightPixels);                }else {                    StringBuilder stringBuilder = new StringBuilder("launcher_theme_wallpaper_");                    stringBuilder.append(themeCode);                    wallpager = IconCache.decodeResource(this,                    resources.getIdentifier(stringBuilder.toString(), "drawable", getPackageName()),                    resources.getDisplayMetrics().widthPixels,                    resources.getDisplayMetrics().heightPixels);                }                try {                    manager.setBitmap(wallpager);                } catch (IOException e) {                    e.printStackTrace();                }                if (mState != State.WORKSPACE || mWorkspace.isInOverviewMode()) {                    showWorkspace(true);                }                mIconCache.setThemeCode(themeCode);                mModel.forceReload();            }        }    }

在handleThemePicker方法中对themeCode进行了判断,如果themeCode等于0,则代表用的是系统默认的风格,所以壁纸设置成系统默认壁纸。
如果themeCode不等于0,则根据themeCode合成对应壁纸的名称并加载出来,然后将其设置成壁纸。这里的壁纸名称也是有要求的,都是launcher_theme_wallpaper_ 然后加上themeCode。

然后通过mIconCache.setThemeCode(themeCode)方法将themeCode传递给IconCache,再调用
mModel.forceReload()方法通知桌面刷新,调用showWorkspace(true)退出OverviewMode模式。就这样,整个客制化流程就完成了。

更多相关文章

  1. Android"挂逼"修炼之行---微信摇骰子和猜拳作弊器原理解析
  2. Android系统版本与AspectJ
  3. Android(安卓)Studio中新建assets文件的两种方法
  4. 安卓开机logo和开机动画的几种实现方法
  5. android canvas常用的方法解析(一)
  6. 《第一行代码》读完总结
  7. Android(安卓)Api Demo学习之Activity
  8. Android(安卓)Develop Training——管理Activity的生命周期(Manag
  9. MIUI评测:iOS身,Android心

随机推荐

  1. Android(安卓)view 的cache
  2. android NetWorkHelper 网络工具类
  3. 【Android】网络状态
  4. GridView 加载并显示本地图片
  5. Android简单计算器(已消除常见bug)
  6. 解决问题:IllegalStateException - config
  7. android使用同一个RecyclerView实现两种
  8. Android(安卓)让按钮闪烁(点击的时候停止
  9. 【android错误收集】android.view.Inflat
  10. android读取工程里文件并显示在界面