讀書筆記之Launcher圖標排序小結

本文記錄下以前修改Launcher需要涉及到的地方,方便後續查詢。

1、默認XML文件以及意義

在這裏插入圖片描述

default_workspace.xml默認會加載這個佈局文件。那打開該文件看一下:

<resolve
    launcher:screen="-401"
    launcher:x="5"
    launcher:y="1" >
    <favorite
        launcher:className="com.hzbhd.bluetooth.a2dp.A2dpMainActivity"
        launcher:packageName="com.hzbhd.bluetooth" />
</resolve>
<!-- second page -->
<resolve
    launcher:screen="1"//應用所處屏幕
    launcher:x="0"//應用圖標所處x位置
    launcher:y="0" >//應用圖標所處y位置 
    <favorite
        launcher:className="com.hzbhd.media.Image"//點擊圖標啓動的類
        launcher:packageName="com.hzbhd.media" />//應用包名
</resolve>

2、解析過程

位於LauncherProvider類中的loadDefaultFavoritesIfNecessary()方法用於加載佈局

synchronized public void loadDefaultFavoritesIfNecessary() {
        String spKey = LauncherAppState.getSharedPreferencesKey();
        SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE);
		//如果沒有佈局對應的數據庫,則開始加載
        if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)){
            Log.d(TAG, "loading default workspace");

            AutoInstallsLayout loader = createWorkspaceLoaderFromAppRestriction();
            if (loader == null) {
                loader = AutoInstallsLayout.get(getContext(),
                        mOpenHelper.mAppWidgetHost, mOpenHelper);
            }
            if (loader == null) {
                final Partner partner = Partner.get(getContext().getPackageManager());
                if (partner != null && partner.hasDefaultLayout()) {
                    final Resources partnerRes = partner.getResources();
                    //加載默認佈局
                    int workspaceResId = partnerRes.getIdentifier(Partner.RES_DEFAULT_LAYOUT,
                            "xml", partner.getPackageName());
                    if (workspaceResId != 0) {
                        loader = new DefaultLayoutParser(getContext(), mOpenHelper.mAppWidgetHost,
                                mOpenHelper, partnerRes, workspaceResId);
                    }
                }
            }

            final boolean usingExternallyProvidedLayout = loader != null;
            //前面一直都在獲取這個loader
            if (loader == null) {
                loader = getDefaultLayoutParser();
            }

            // There might be some partially restored DB items, due to buggy restore logic in
            // previous versions of launcher.
            createEmptyDB();
            // Populate favorites table with initial favorites
            if ((mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader) <= 0)
                    && usingExternallyProvidedLayout) {
                // Unable to load external layout. Cleanup and load the internal layout.
                createEmptyDB();
                //開始往數據庫填充佈局解析出來的數據
                mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(),
                        getDefaultLayoutParser());
            }
            clearFlagEmptyDbCreated();
        }
    }

如果有數據庫的時候就不重新加載。所以如果想要讓Launcher重新刷界面,一個方法就是把數據庫刪掉。接下來看下loadFavorites解析數據:

@Thunk int loadFavorites(SQLiteDatabase db, AutoInstallsLayout loader) {
            ArrayList<Long> screenIds = new ArrayList<Long>();
            // TODO: Use multiple loaders with fall-back and transaction.
            int count = loader.loadLayout(db, screenIds);
            ...
            return count;
        }

重點是這個loadLayout,他在AutoInstallsLayout類中

public int loadLayout(SQLiteDatabase db, ArrayList<Long> screenIds) {
    mDb = db;
    try {
        return parseLayout(mLayoutId, screenIds);
    } catch (Exception e) {
        Log.w(TAG, "Got exception parsing layout.", e);
        return -1;
    }
}

	/**
     * Parses the layout and returns the number of elements added on the homescreen.
     */
    protected int parseLayout(int layoutId, ArrayList<Long> screenIds)
            throws XmlPullParserException, IOException {
        XmlResourceParser parser = mSourceRes.getXml(layoutId);
        beginDocument(parser, mRootTag);
        final int depth = parser.getDepth();
        int type;
        ArrayMap<String, TagParser> tagParserMap = getLayoutElementsMap();
        int count = 0;
        while (((type = parser.next()) != XmlPullParser.END_TAG ||
                parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
            if (type != XmlPullParser.START_TAG) {
                continue;
            }
            count += parseAndAddNode(parser, tagParserMap, screenIds);
        }
        return count;
    }

可以看到parseLayout方法是真正解析佈局的地方。那麼如果想要該佈局就可以從這個方法入手了。

3、Demo實例

​ 比如說默認佈局中有個應用被卸載了,那麼桌面上就空缺了一個應用的位置。那麼如何讓空缺後續的圖標自動排到前面呢?那就是從loadLayout方法入手:

/**
* Parses the layout and returns the number of elements added on the homescreen.
*/
    protected int parseLayout(int layoutId, ArrayList<Long> screenIds)
            throws XmlPullParserException, IOException {
        XmlResourceParser parser = mSourceRes.getXml(layoutId);
        beginDocument(parser, mRootTag);
        final int depth = parser.getDepth();
        int type;
        ArrayMap<String, TagParser> tagParserMap = getLayoutElementsMap();
        int count = 0;
        //循環遍歷每個位置
        while (((type = parser.next()) != XmlPullParser.END_TAG ||
                parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
            if (type != XmlPullParser.START_TAG) {
                continue;
            }
            count += parseAndAddNode(parser, tagParserMap, screenIds);
        }
        return count;
    }          

重點是parseAndAddNode這個方法,跟進去看下:

/**
     * Parses the current node and returns the number of elements added.
     */
    protected int parseAndAddNode(
        ...
        mValues.put(Favorites.CONTAINER, container);
        mValues.put(Favorites.SCREEN, screenId);
        mValues.put(Favorites.CELLX,
                convertToDistanceFromEnd(getAttributeValue(parser, ATTR_X), mColumnCount));
        mValues.put(Favorites.CELLY,
                convertToDistanceFromEnd(getAttributeValue(parser, ATTR_Y), mRowCount));
        ...
        if (newElementId >= 0) {
           	...
            return 1;
        }
        return 0;
    }

mValues是存放應用圖片位置信息的數據,待會兒要改的就是這個。newElementId >= 0的時候,表示的就是佈局中的該應用圖片會顯示出來。瞭解了這兩個,看下我的默認佈局是長什麼樣:
在這裏插入圖片描述
第一頁screen=-401,第二頁screen=1。我的思路是將這20個圖標從頭到尾排序,得到各自的序號。如下圖所示
在這裏插入圖片描述

用代碼實現的話就是,某個應用圖標位置location就是得到的序號(注意location是從XML解析出來的原始位置!):

int oriX = Integer.parseInt(getAttributeValue(parser, ATTR_X));//原始的x座標
int oriY = Integer.parseInt(getAttributeValue(parser, ATTR_Y));//原始的y座標
int location = 0;
if(screenId == -401){
    location = oriY*4+oriX-2;//第一頁的各自序號
}else if(screenId == 1){
    location = oriY*6+oriX+8;//第二頁的各自序號
}

回到上面分析的parseAndAddNode方法,在運算結束會返回0或者1。返回0的時候表示空缺了一個位置,用一個變量offset記錄該值。

在parseLayout方法中:
int num = parseAndAddNode(parser, tagParserMap, screenIds);
if((screenId == 1 || screenId == -401) && container == -100){
    if(num == 0) offset++;//該變量記錄的是上一次解析爲止的空缺數量
}

接着前面的location,location是根據佈局計算出來的原始序號。現在要得到的是佈局如果少了應用圖標的實際序號。也就是經過如下計算:

int newLocation = location - offset;//offset是計算該應用圖標位置之前就獲取的

比如說上面那張圖。location = 1的應用圖標空缺,那麼之後應用圖標newLocation = location-1

如果location = 1location =4圖標空缺的話。那麼從location = 2開始 newLocation = location-1,從location = 5開始 newLocation = location-2

計算好排序之後,將序號轉換成x,y,screen來表示put到mValues中。這就是重新填充空缺的佈局位置了。

if(newLocation<8){//如果小於8說明新的位置在第一頁,因爲第一頁就8個位置
    mValues.put(Favorites.SCREEN, -401);
    mValues.put(Favorites.CELLX, newLocation%4+2);
    mValues.put(Favorites.CELLY, newLocation/4);
}else{
    mValues.put(Favorites.SCREEN, 1);
    mValues.put(Favorites.CELLX, (newLocation-8)%6);
    mValues.put(Favorites.CELLY, (newLocation-8)/6);
}

下面貼出來的是剛纔分析的兩個方法的關鍵部分:

/**
 * Parses the layout and returns the number of elements added on the homescreen.
 */
protected int parseLayout(int layoutId, ArrayList<Long> screenIds)
        throws XmlPullParserException, IOException {
    ...
    int count = 0;

    while (((type = parser.next()) != XmlPullParser.END_TAG ||
            parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
        ...
        int num = parseAndAddNode(parser, tagParserMap, screenIds);
        if((screenId == 1 || screenId == -401) && container == -100){
            if(num == 0) offset++;
        }
        count += num;
    }
    return count;
}
/**
 * Parses the current node and returns the number of elements added.
 */
protected int parseAndAddNode(
        XmlResourceParser parser,
        HashMap<String, TagParser> tagParserMap,
        ArrayList<Long> screenIds)
                throws XmlPullParserException, IOException {

    ...

    mValues.put(Favorites.CONTAINER, container);
    mValues.put(Favorites.SCREEN, screenId);
    mValues.put(Favorites.CELLX, getAttributeValue(parser, ATTR_X));
    mValues.put(Favorites.CELLY, getAttributeValue(parser, ATTR_Y));
    if(container == -100){
        try{
            Log.i("Launcher_icon","CELLX ="+mValues.get(Favorites.CELLX)+",CELLY ="+mValues.get(Favorites.CELLY)+",offset ="+offset+",screenId ="+screenId);
            int oriX = Integer.parseInt(getAttributeValue(parser, ATTR_X));
            int oriY = Integer.parseInt(getAttributeValue(parser, ATTR_Y));
            int location = 0;
            if(screenId == -401){
                location = oriY*4+oriX-2;
            }else if(screenId == 1){
                location = oriY*6+oriX+8;
            }
            int newLocation = location - offset;
            if(newLocation<8){
                mValues.put(Favorites.SCREEN, -401);
                mValues.put(Favorites.CELLX, newLocation%4+2);
                mValues.put(Favorites.CELLY, newLocation/4);
            }else{
                mValues.put(Favorites.SCREEN, 1);
                mValues.put(Favorites.CELLX, (newLocation-8)%6);
                mValues.put(Favorites.CELLY, (newLocation-8)/6);
            }
            Log.i("Launcher_icon","NEW ICON: CELLX ="+mValues.get(Favorites.CELLX)+",CELLY ="+mValues.get(Favorites.CELLY));
        }catch (Exception e){
            e.printStackTrace();
        }
    }
	...
    long newElementId = tagParser.parseAndAdd(parser);
    if (newElementId >= 0) {
        // Keep track of the set of screens which need to be added to the db.
        if (!screenIds.contains(screenId) &&
                container == Favorites.CONTAINER_DESKTOP) {
            screenIds.add(screenId);
        }
        return 1;
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章