5.1 Launcher3 修改總結

最近比較忙 有段時間沒寫博客了   趁着有空 把最近修改launcher的心得總結一下


一、 修改和替換特定應用圖標

   1   allApps界面

    allApps界面就是所謂的二級菜單,抽屜。

   Launcher3 生成二級菜單的圖標分爲初次加載和初次加載之後。

   初次加載時 修改 IconCache.java 中的 updateCacheAndGetContentValues()方法

public synchronized void getTitleAndIcon(AppInfo application,
            LauncherActivityInfoCompat info, boolean useLowResIcon) {
        UserHandleCompat user = info == null ? application.user : info.getUser();
        CacheEntry entry = cacheLocked(application.componentName, info, user,
                false, useLowResIcon);
        application.title = Utilities.trim(entry.title);
//        Bitmap icon = null;
//        icon = getNonNullIcon(entry, user);

        application.iconBitmap =  replaceFirstShownIcon(application,entry,user);
        application.contentDescription = entry.contentDescription;
        application.usingLowResIcon = entry.isLowResIcon;
    }
其中  AllApps頁面初次加載時替換系統圖標的方法如下,就是讀取包名,然後替換
private Bitmap replaceFirstShownIcon(AppInfo info,CacheEntry entry,UserHandleCompat user){

        String pkgName = info.componentName.getPackageName();
        String className = info.componentName.getClassName();
        Resources resources = mContext.getResources();
        if(pkgName.equals("com.android.soundrecorder")){
            return BitmapFactory.decodeResource(resources,R.drawable.icon_record);
        }
        if(pkgName.equals("com.android.deskclock")){
            return BitmapFactory.decodeResource(resources,R.drawable.icon_clock);
        }
        if(pkgName.equals("com.android.qworldclock")){
            return BitmapFactory.decodeResource(resources,R.drawable.icon_world);
        }
        if(pkgName.equals("com.android.gallery3d")){
            return BitmapFactory.decodeResource(resources,R.drawable.icon_gallery);
        }
        if(pkgName.equals("com.android.music")){
            if(className.equals("com.android.music.VideoBrowserActivity")){
                return BitmapFactory.decodeResource(resources,R.drawable.icon_play);
            }
            if(className.equals("com.android.music.MusicBrowserActivity")){
                return BitmapFactory.decodeResource(resources,R.drawable.icon_music);
            }

        }

            if(pkgName.equals("com.android.providers.downloads.ui")){
                return BitmapFactory.decodeResource(resources,R.drawable.icon_download);
            }
            if(pkgName.equals("com.android.email")){
                return BitmapFactory.decodeResource(resources,R.drawable.icon_email);
            }
        if(pkgName.equals("com.android.mms")){
            return BitmapFactory.decodeResource(resources,R.drawable.icon_sms);
        }
            return getNonNullIcon(entry, user);
        }

  從IconCache.java的類名就可以看出 這個類是用作緩存系統已安裝軟件的圖標的,在第一次加載成功後會將Icons緩存在一個app_icons.db的數據庫

文件中,下次再讀取就是從數據庫中讀取

   所以如果要修改AllApps界面圖標,除了在replaceFirstShownIcon() 方法中做替換外,還要在寫入數據庫的方法中同樣替換一次

該方法爲IconCahce類的addIconToDb方法

private void addIconToDB(ContentValues values, ComponentName key,
            PackageInfo info, long userSerial) {
        values.put(IconDB.COLUMN_COMPONENT, key.flattenToString());
        values.put(IconDB.COLUMN_USER, userSerial);
        values.put(IconDB.COLUMN_LAST_UPDATED, info.lastUpdateTime);
        values.put(IconDB.COLUMN_VERSION, info.versionCode);
        replaceIcon(key,values);
        mIconDb.getWritableDatabase().insertWithOnConflict(IconDB.TABLE_NAME, null, values,
                SQLiteDatabase.CONFLICT_REPLACE);
    }
同樣,替換方法爲

private void replaceIcon(ComponentName name,ContentValues values){
        if(name.getPackageName().equals("com.android.soundrecorder")){
            values.put(IconDB.COLUMN_ICON, Utilities.flattenBitmap(BitmapFactory.decodeResource(mContext.getResources(),R.drawable.icon_record)));
        }
        if(name.getPackageName().equals("com.android.qworldclock")){
            values.put(IconDB.COLUMN_ICON, Utilities.flattenBitmap(BitmapFactory.decodeResource(mContext.getResources(),R.drawable.icon_world)));
        }
        if(name.getPackageName().equals("com.android.gallery3d")){
            values.put(IconDB.COLUMN_ICON, Utilities.flattenBitmap(BitmapFactory.decodeResource(mContext.getResources(),R.drawable.icon_gallery)));
        }
        if(name.getPackageName().equals("com.android.music")){
            if(name.getClassName().equals("com.android.music.VideoBrowserActivity")){
                values.put(IconDB.COLUMN_ICON, Utilities.flattenBitmap(BitmapFactory.decodeResource(mContext.getResources(),R.drawable.icon_play)));
            }
            if(name.getClassName().equals("com.android.music.MusicBrowserActivity")){
                values.put(IconDB.COLUMN_ICON, Utilities.flattenBitmap(BitmapFactory.decodeResource(mContext.getResources(),R.drawable.icon_music)));

            }
        }
        if(name.getPackageName().equals("com.android.providers.downloads.ui")){
            values.put(IconDB.COLUMN_ICON, Utilities.flattenBitmap(BitmapFactory.decodeResource(mContext.getResources(),R.drawable.icon_download)));
        }
		if(name.getPackageName().equals("com.android.deskclock")){
            values.put(IconDB.COLUMN_ICON, Utilities.flattenBitmap(BitmapFactory.decodeResource(mContext.getResources(),R.drawable.icon_clock)));
        }
        if(name.getPackageName().equals("com.android.email")){
            values.put(IconDB.COLUMN_ICON, Utilities.flattenBitmap(BitmapFactory.decodeResource(mContext.getResources(),R.drawable.icon_email)));
        }
        if(name.getPackageName().equals("com.android.mms")){
            values.put(IconDB.COLUMN_ICON, Utilities.flattenBitmap(BitmapFactory.decodeResource(mContext.getResources(),R.drawable.icon_sms)));
        }

    }

  2   workspace界面

爲了達到替換圖標的目的,在workspace同樣需要做一些操作。

workspace生成圖標的途徑有很多,從allApps界面拖拽,從workspace中拖拽,或者從default_workspace.xml中讀取,每一個途徑,最終要在workspace界面生成圖標,都必須會走Launcher.java中的creatShortCut()方法

public View createShortcut(ViewGroup parent, ShortcutInfo info) {
        BubbleTextView favorite = (BubbleTextView) mInflater.inflate(R.layout.app_icon,
                parent, false);
        favorite.applyFromShortcutInfo(info, mIconCache);
        favorite.setCompoundDrawablePadding(mDeviceProfile.iconDrawablePaddingPx);
        favorite.setOnClickListener(this);
        favorite.setOnFocusChangeListener(mFocusHandler);
        return favorite;
    }

跟進favorite.applyFromShortcutInfo(info, mIconCache)中,

該方法在BubbleTextView中,這個BubbleTextView是一個TextView,代表桌面上的每一個icon,一個icon 其實就是一個textView(應用名稱)和imageview的組合。

public void applyFromShortcutInfo(ShortcutInfo info, IconCache iconCache,
            boolean promiseStateChanged) {
//        Bitmap b = info.getIcon(iconCache);
        Bitmap b= replaceIcon(info,iconCache);

        FastBitmapDrawable iconDrawable = mLauncher.createIconDrawable(b);
        iconDrawable.setGhostModeEnabled(info.isDisabled != 0);

        setIcon(iconDrawable, mIconSize);
        if (info.contentDescription != null) {
            setContentDescription(info.contentDescription);
        }
        setText(info.title);
        setTag(info);

        if (promiseStateChanged || info.isPromise()) {
            applyState(promiseStateChanged);
        }
    }
我們在這裏面做替換系統圖標的工作

 private Bitmap replaceIcon(ShortcutInfo info,IconCache cache){
        Resources resources = getContext().getResources();
        String pkgName = info.getTargetComponent().getPackageName();
        String className = info.getTargetComponent().getClassName();
        if(pkgName.equals("com.android.soundrecorder")){
            return BitmapFactory.decodeResource(resources,R.drawable.icon_record);
        }
        if(pkgName.equals("com.android.deskclock")){
            return BitmapFactory.decodeResource(resources,R.drawable.icon_clock);
        }
        if(pkgName.equals("com.android.qworldclock")){
            return BitmapFactory.decodeResource(resources,R.drawable.icon_world);
        }
        if(pkgName.equals("com.android.gallery3d")){
            return BitmapFactory.decodeResource(resources,R.drawable.icon_gallery);
        }
        if(pkgName.equals("com.android.music")){
            if(className.equals("com.android.music.VideoBrowserActivity")){
                return BitmapFactory.decodeResource(resources,R.drawable.icon_play);
            }
            if(className.equals("com.android.music.MusicBrowserActivity")){
                return BitmapFactory.decodeResource(resources,R.drawable.icon_music);
            }
        }

        if(pkgName.equals("com.android.providers.downloads.ui")){
            return BitmapFactory.decodeResource(resources,R.drawable.icon_download);
        }
        if(pkgName.equals("com.android.email")){
            return BitmapFactory.decodeResource(resources,R.drawable.icon_email);
        }
        if(pkgName.equals("com.android.mms")){
            return BitmapFactory.decodeResource(resources,R.drawable.icon_sms);
        }
        return info.getIcon(cache);

    }

3  更換系統語言之後


 當更換系統語言之後,圖標和標籤會重新加載生成一次,而且在AllApps列表裏,圖標的排列次序也會發生變化,跟進到IconCache.java的updateCacheAndGetContentValues()方法中,在這裏做替換圖標工作

@Thunk ContentValues updateCacheAndGetContentValues(LauncherActivityInfoCompat app,
            boolean replaceExisting) {
        final ComponentKey key = new ComponentKey(app.getComponentName(), app.getUser());
        CacheEntry entry = null;
        if (!replaceExisting) {
            entry = mCache.get(key);
            // We can't reuse the entry if the high-res icon is not present.
            if (entry == null || entry.isLowResIcon || entry.icon == null) {
                entry = null;
            }
        }
        if (entry == null) {
            entry = new CacheEntry();
            Bitmap icon = replaceIconAfterLanguageChanged(app);
            entry.icon = icon;
        }
        entry.title = app.getLabel();
        entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, app.getUser());
        mCache.put(new ComponentKey(app.getComponentName(), app.getUser()), entry);

        return newContentValues(entry.icon, entry.title.toString(), mActivityBgColor);
    }

其中 replaceIconAfterLanguageChanged()如下

private Bitmap replaceIconAfterLanguageChanged(LauncherActivityInfoCompat app){

    return IconReplaceUtil.replaceIcon(mContext,app,mIconDpi);

}

  public static  Bitmap replaceIcon(Context mContext,LauncherActivityInfoCompat app,int mIconDpi){
        String pkgName = app.getComponentName().getPackageName();
        String className = app.getComponentName().getClassName();
        Resources resources = mContext.getResources();
       Bitmap icon = replaceIcon(pkgName,className,resources);
        if(icon!=null){
            return icon;
        }
        else return Utilities.createIconBitmap(app.getBadgedIcon(mIconDpi), mContext);
    }


public static Bitmap replaceIcon(String pkgName, String className,Resources resources){
        if(pkgName.equals("com.android.soundrecorder")){
            return BitmapFactory.decodeResource(resources,R.drawable.icon_record);
        }
        if(pkgName.equals("com.android.deskclock")){
            return BitmapFactory.decodeResource(resources,R.drawable.icon_clock);
        }
        if(pkgName.equals("com.android.qworldclock")){
            return BitmapFactory.decodeResource(resources,R.drawable.icon_world);
        }
        if(pkgName.equals("com.android.gallery3d")){
            return BitmapFactory.decodeResource(resources,R.drawable.icon_gallery);
        }
        if(pkgName.equals("com.android.music")){
            if(className.equals("com.android.music.VideoBrowserActivity")){
                return BitmapFactory.decodeResource(resources,R.drawable.icon_play);
            }
            if(className.equals("com.android.music.MusicBrowserActivity")){
                return BitmapFactory.decodeResource(resources,R.drawable.icon_music);
            }

        }

        if(pkgName.equals("com.android.providers.downloads.ui")){
            return BitmapFactory.decodeResource(resources,R.drawable.icon_download);
        }
        if(pkgName.equals("com.android.email")){
            return BitmapFactory.decodeResource(resources,R.drawable.icon_email);
        }
        if(pkgName.equals("com.android.calendar")){
            return BitmapFactory.decodeResource(resources,R.drawable.icon_calendar);
        }
        if(pkgName.equals("com.android.mms")){
            return BitmapFactory.decodeResource(resources,R.drawable.icon_sms);
        }
        if(pkgName.equals("com.android.calculator2")){
            return BitmapFactory.decodeResource(resources,R.drawable.icon_calculator2);
        }


到這裏,基本上替換圖標的工作就完成了  完成AllApps界面,拖拽到workspace,更改系統語言都不會失效。

二 、修改主頁默認佈局

 1 主頁佈局包括 行列數, 默認桌面圖標和widget,hotseat 默認圖標。

 行列數 修改InvariantDeviceProfile.java中 幾個屬性值就好

這裏大概說明一下

public int numRows; // workspace 行
    public int numColumns; //workspace 列
public int numFolderRows; // 文件夾中圖標行
    public int numFolderColumns; //文件件中圖標的列
    float iconSize; 圖標大小
    float iconTextSize;  圖標標題大小 
float numHotseatIcons; 熱區圖表數
    float hotseatIconSize; 熱區圖表大小
    int defaultLayoutId;  默認加載的桌面佈局屬性xml文件的資源id

修改相應屬性值即可生效 

比如如果要讓系統強制加載默認的5*5佈局,就在 defaultLayoutId賦值的兩個方法中,將id寫死爲R.xml.default_workspace5*5即可。

其他配置方法同上。

id寫死爲該xml文件,就要在該xml中配置默認的一些屬性,比如你想默認放置哪些圖標和插件(4.4之後默認放置插件的權限被谷歌提高,普通應用設置該屬性無效,必須打包進system/priv-app中纔可以設置默認小插件)在桌面上,就配置工程中的res/xml/default_workspace_4*4 5*5 5*6 這幾個xml文件。

配置方法如下所示

<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">

    <!-- Hotseat -->
    <include launcher:workspace="@xml/dw_tablet_hotseat" />  //這是hotseat的默認配置 我放在下面

<resolve
        launcher:screen="0"
        launcher:x="0"
        launcher:y="3" >
        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_EMAIL;end" />
        <favorite launcher:uri="mailto:" />
    </resolve>
    <resolve
        launcher:screen="0"     默認爲第0屏
        launcher:x="1"          1列 4行 
        launcher:y="4" >
        <favorite
            launcher:packageName="com.android.gallery3d"                          包名 
            launcher:className="com.android.gallery3d.app.GalleryActivity"        類名(也可以用intent的方式配置 如上 )
            />

    </resolve>
    <resolve
        launcher:screen="0"
        launcher:x="2"
        launcher:y="4" >
        <favorite
            launcher:packageName="com.android.calendar"
            launcher:className="com.android.calendar.AllInOneActivity"
            />

    </resolve>
    <resolve
        launcher:screen="0"
        launcher:x="3"
        launcher:y="4" >
        <favorite
            launcher:packageName="com.android.soundrecorder"
            launcher:className="com.android.soundrecorder.SoundRecorder"
            />

    </resolve>

    <resolve
        launcher:screen="0"
        launcher:x="4"
        launcher:y="4" >
        <favorite
            launcher:packageName="com.android.email"
            launcher:className="com.android.email.activity.ComposeActivityEmail"
            />
    </resolve>

    <appwidget           桌面小圖標 
    launcher:packageName="com.android.deskclock"
    launcher:className="com.android.alarmclock.DigitalAppWidgetProvider"
    launcher:screen="0"
    launcher:x="1"
    launcher:y="0"
    launcher:spanX="3"         小圖標占位大小
    launcher:spanY="2" />


    </favorites>




hotseat的配置和上面基本是一樣的,注意有兩個xml文件,代表手機和平板的。

<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
    <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
    <!-- Dialer, Messaging, [All Apps], Browser, Camera -->
   

    <resolve
        launcher:container="-101"
        launcher:screen="1"
        launcher:x="1"
        launcher:y="0" >
        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MESSAGING;end" />
        <favorite launcher:uri="sms:" />
        <favorite launcher:uri="smsto:" />
        <favorite launcher:uri="mms:" />
        <favorite launcher:uri="mmsto:" />
    </resolve>

    <!-- All Apps -->

   

    <!--<resolve-->
    <!--launcher:container="-101"-->
    <!--launcher:screen="4"-->
    <!--launcher:x="4"-->
    <!--launcher:y="0" >-->
    <!--<favorite launcher:uri="#Intent;action=android.media.action.STILL_IMAGE_CAMERA;end" />-->
    <!--<favorite launcher:uri="#Intent;action=android.intent.action.CAMERA_BUTTON;end" />-->
    <!--</resolve>-->
   

</favorites>


三 過濾特定圖標不在Allapps界面顯示

 這個主要在LaunchModel.java的 loadAllApps方法中

 private void loadAllApps() {
            final long loadTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;

            final Callbacks oldCallbacks = mCallbacks.get();
                              ....
 
                // Create the ApplicationInfos
                for (int i = 0; i < apps.size(); i++) {
                    if("com.android.dialer".equals(apps.get(i).getApplicationInfo().packageName)
                            ||"com.caf.fmradio".equals(apps.get(i).getApplicationInfo().packageName)){
                          continue;    //過濾操作就在這裏  所有app信息會加載後存入mBgAllAppsList 中

                    }
                  //  replaceIcon(apps.get(i));

                        LauncherActivityInfoCompat app = apps.get(i);

                        // This builds the icon bitmaps.
                        mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache));
                 }

                final ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user);
                if (heuristic != null) {
                    final Runnable r = new Runnable() {

                        @Override
                        public void run() {
                            heuristic.processUserApps(apps);
                        }
                    };
                    runOnMainThread(new Runnable() {

                        @Override
                        public void run() {
                         ......
            }
        }


Launcher3的修改基本都只是一些小修改,整體難度不高



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章