读书笔记之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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章