本文记录下以前修改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 = 1
,location =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;
}