本文記錄下以前修改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;
}