安卓系統中的桌面啟動器Launcher
阿新 • • 發佈:2019-01-10
安卓系統桌面啟動器 Launcher是安卓系統中的桌面啟動器,安卓系統的桌面UI統稱為Launcher。Launcher是安卓系統中的主要程式元件之一,安卓系統中如果沒有Launcher就無法啟動安卓桌面,Launcher出錯的時候,安卓系統會出現“程序
com.android.launcher 意外停止”的提示視窗。這時需要重新啟動桌面Launcher。
為Drag定義的一個介面。包含一個介面,兩個方法和兩個靜態常量。介面為DragListener(包含onDragStart(),onDragEnd()兩個函式),onDragStart()是在剛開始拖動的時候被呼叫,onDragEnd()是在拖動完成時被呼叫。在launcher中典型的應用是DeleteZone,在長按拖動item時呼叫onDragStart()顯示,在拖動結束的時候onDragEnd()隱藏。兩個函式包括startDrag()和setDragItemInfo().startDrag()用於在拖動是傳遞要拖動的item的資訊以及拖動的方式,setDragItemInfo()用於傳遞item的引數資訊(包括位置以及大小)。兩個 常量為DRAG_ACTION_MOVE,DRAG_ACTION_COPY來標識拖動的方式,DRAG_ACTION_MOVE為移動,表示在拖動的時候需要刪除原來的item,DRAG_ACTION_COPY為複製型的拖動,表示保留被拖動的item。
4.LauncherModel.java:輔助的檔案。裡面有許多封裝的對資料庫的操作。包含幾個執行緒,其中最主要的是ApplicationsLoader和DesktopItemsLoader。ApplicationsLoader在載入所有應用程式時使用,DesktopItemsLoader在載入workspace的時候使用。其他的函式就是對資料庫的封裝,比如在刪除,替換,新增程式的時候做更新資料庫和UI的工作。
5.Workspace.java: 抽象的桌面。由N個celllaout組成,從cellLayout更高一級的層面上對事件的處理。
6.LauncherProvider.java:launcher的資料庫,裡面儲存了桌面的item的資訊。在建立資料庫的時候會loadFavorites(db)方法,loadFavorites()會解析xml目錄下的default_workspace.xml檔案,把其中的內容讀出來寫到資料庫中,這樣就做到了桌面的預製。
7.CellLayout.java:組成workspace的view,繼承自viewgroup,既是一個dragSource,又是一個dropTarget,可以將它裡面的item拖出去,也可以容納拖動過來的item。在workspace_screen裡面定了一些它的view引數。
8.ItemInfo.java:對item的抽象,所有型別item的父類,item包含的屬性有id(標識item的id),cellX(在橫向位置上的位置,從0開始),cellY(在縱向位置上的位置,從0開始),spanX(在橫向位置上所佔的單位格),spanY(在縱向位置上所佔的單位格),screen(在workspace的第幾屏,從0開始),itemType(item的型別,有widget,search,application等),container(item所在的)。
9.UserFolder.java: 使用者建立的資料夾。可以將item拖進資料夾,單擊時開啟資料夾,長按資料夾上面標題處可以重新命名資料夾。
10.LiveFolder.java:系統自帶的資料夾。從系統中創建出的如聯絡人的資料夾等。
11.DeleteZone:刪除框。在平時是出於隱藏狀態,在將item長按拖動的時候會顯示出來,如果將item拖動到刪除框位置時會刪除item。DeleteZone實現了DropTarget和DragListener兩個介面。
12.LauncherSettings.java:字串的定義。資料庫項的字串定義,另外在這裡定義了container的型別,還有itemType的定義,除此還有一些特殊的widget(如search,clock的定義等)的型別定義。
補充Launcher工程中的類:
二、主要模組
1.介面模型:
Launcher的介面的rootview是DragLayer,它是一個FrameLayout,在它上面workspace(應該說是celllayout)佔了絕大部分的空間,celllayout的引數檔案是workspace_screen.xml。workspace既是一個DropTarget又是一個DragSource,可以從AllAppGridView中拖出應用程式放在它上面,也可以把它裡面的item拖走刪除或者拖到bottomabr裡面去。
(對於想修改launcher的同學,可以自定義DragLay.java,比如改為AbsoluteLayout等,再修改launcher.xml佈局檔案,就可以實現各種樣式的launcher幾面。)
2.Drop& Drag模型:
⒉1 DragSource:可以拖動的物件來源的容器,在launcher中主要有AllAppGridView,workspace等。
void onDropCompleted(View target,boolean success,int x,int y);
⒉2 DropTarget:可以放置被拖動的物件的容器。在launcher中有folder,workspace,bottombar等,一個View既可以是Dragsource也可以是DropTarget。主要包含以下幾個介面:
1) boolean acceptDrop(DragSource source,int x,int y,int xOffset,int yOffset,Object dragInfo);
acceptDrop 函式用來判斷dropTarget是否可以接受item放置在自己裡面。
2) void onDragEnter(DragSource source,int x,int y,int xOffset,int yOffset,Object dragInfo);
onDragEnter是item被拖動進入到一個dropTarget的時候的回撥。
3) void onDragOver(DragSource source,int x,int y,int xOffset,int yOffset,Object dragInfo);
onDragOver是item在上一次位置和這一次位置所處的dropTarget相同的時候的回撥。
4) void onDragExit(DragSource source,int x,int y,int xOffset,int yOffset,Object dragInfo);
onDragExit是item被拖出dropTarget時的回撥。
5) boolean onDrop(DragSource source,int x,int y,int xOffset,int yOffset,Object dragInfo);
onDrop是item被放置到dropTarget時的回撥。
函式的呼叫模式為:
DropTarget dropTarget = findDropTarget((int) x,(int) y,coordinates);
if (dropTarget != null) {
/**
* 當這一次的 target 跟上一次相同時,根據座標來移動item
*/
if (mLastDropTarget == dropTarget) {
dropTarget.onDragOver(mDragSource,coordinates[0],coordinates[1],
(int) mTouchOffsetX,(int) mTouchOffsetY,mDragInfo);
} else {
/**
* 當上一次的位置跟這一次不同而且上一次的位置不為空,說明item移 *動出了,將上次的 View 根據上次的座標重新排列,並根據當前座標重排*當前的*/
if (mLastDropTarget != null) {
mLastDropTarget.onDragExit(mDragSource,coordinates[0],coordinates[1],
(int) mTouchOffsetX,(int) mTouchOffsetY,mDragInfo);
}
dropTarget.onDragEnter(mDragSource,coordinates[0],coordinates[1],
(int) mTouchOffsetX,(int) mTouchOffsetY,mDragInfo);
}
} else {//如果這一次為 null,上一次不為 null,那麼把上一次座標位置的 cell 去掉
if (mLastDropTarget != null) {
mLastDropTarget.onDragExit(mDragSource,coordinates[0],coordinates[1],
(int) mTouchOffsetX,(int) mTouchOffsetY,mDragInfo);
}
}
//記錄上次的droptarget
mLastDropTarget = dropTarget;
3.Touch event總結:
由於launcher的事件比較多比較複雜,所以在事件處理的時候一般採用rootview先用onInterceptTouchEvent(MotionEvent)攔截所有的touch事件,經過判斷後分發給childview。
判斷的規則如下:
a.down事件首先會傳遞到onInterceptTouchEvent()方法
b.如果該ViewGroup的onInterceptTouchEvent()在接收到down事件處理完成之後return false,那麼後續的move,up等事件將繼續會先傳遞給該ViewGroup,之後才和down事件一樣傳遞給最終的目標view的onTouchEvent()處理。
c.如果該ViewGroup的onInterceptTouchEvent()在接收到down事件處理完成之後return true,那麼後續的move,up等事件將不再傳遞給onInterceptTouchEvent(),而是和down事件一樣傳遞給該ViewGroup的onTouchEvent()處理,注意,目標view將接收不到任何事件。
d.如果最終需要處理事件的view的onTouchEvent()返回了false,那麼該事件將被傳遞至其上一層次的view的onTouchEvent()處理。
e.如果最終需要處理事件的view 的onTouchEvent()返回了true,那麼後續事件將可以繼續傳遞給該view的onTouchEvent()處理。
三、幾種問題的解決方式
1.將所有的應用都排列在桌面上
將所有的應用都排列在桌面是通過首先建立一個三維的boolean型全域性陣列來記錄item的排列情況,第一維是屏數,第二維是縱向上的排列情況,第三維是橫向的排列情況,如果那個位置被item所佔用就標記為1,否則標記為0.在啟動時把全域性陣列初始化為0,然後在新增的時候把相應的位置置1.凡是涉及到workspace上item的變化,比如移動、新增、刪除操作時都需要維護陣列,保持陣列的正確性,因為在安裝新程式時依據陣列的狀態去判斷把item加到什麼位置。
2.動態增加螢幕
動態增加螢幕是通過worksapce .addchild(view)的方式實現。基本思路是:首先預先規定所允許的最大的螢幕數,然後在需要增加螢幕而且當前螢幕數沒有超過最大螢幕數的時候通過(CellLayout)mInflater.inflate(R.layout.workspace_screen,null)建立一個celllayout例項出來,然後通過addchild把它加入進去。在螢幕上的item被刪除時通過從最後一屏起判斷螢幕上是否有item,如果有的話保留,沒有的話則刪除最後一屏,以此類推。
3.預製桌面
a.新增普通的應用程式快捷方式:
在../res/xml下的default_workspace.xml檔案中加入預設要放置的普通的應用程式。加入的格式為:
<favorite
launcher:packageName="... " //應用的packageName
launcher:className="... " //應用啟動時的第一個activity
launcher:screen="..." //放置在第幾屏(放在workspace的時候需要,從0開始,0為第一屏,1為第二屏,以此類推...)
launcher:x="..." //放置x方向的位置(在列中的位置)
launcher:y="..." /> //放置y方向的位置(在行中的位置)
packageName和className可以通過點選程式,然後在打印出的log中找到comp={...},例如如下資訊:
comp={com.estrongs.android.taskmanager/com.estrongs.android.taskmanager.TaskManager}。其中com.estrongs.android.taskmanager為packageName,com.estrongs.android.taskmanager.TaskManager為className。
workspace的佈局如下:
b.新增widget:
在../package/apps/VLauncher/res/xml下的default_workspace.xml檔案中加入預設要放置的普通的應用程式。加入的格式為:
<widget
launcher:packageName="..." //widget的packageName
launcher:className=" ..." //實現 widget的 receiver 類的名稱.
launcher:container="..." //放置的位置(只能為desktop)
launcher:screen="..." //放置在第幾屏上
launcher:x="..." //放置的x位置
launcher:y="..." //放置的y位置
launcher:spanx="..." //在x方向上所佔格數
launcher:spany="..."/> //在y方向上所佔格數
例如,要在第3屏的第一行第二列放置開始放置一個x方向上佔兩個單位格,y方向上佔兩個單位格的時鐘,可以加入以下程式碼:
<appwidget
launcher:packageName="com.android.alarmclock" launcher:className="com.android.alarmclock.AnalogAppWidgetProvider"
launcher:container="desktop"
launcher:screen="2"
launcher:x="1"
launcher:y="0"
launcher:spanx="2"
launcher:spany="2"/>
4.改變主介面的排列方式
要修改桌面的排列方式,如下,先根據橫豎屏設定修改workspace_screen.xml裡shortAxisCells和longAxisCells的引數,然後在Launcher.java中修改NUMBER_CELLS_X和NUMBER_CELLS_Y的值,在2.3版本中剛開始往資料庫中新增item的時候會去判斷,如果不修改NUMBER_CELLS_X和NUMBER_CELLS_Y的話會導致一部分的item顯示不出來,導致預製apk的失敗。
5.增加worksapce上的屏數
要增加屏數,首先在根據橫豎屏在launcher.xml中的<com.android.launcher.Workspace 中刪除或增加 <include android:id="@+id/cellN" layout="@layout/workspace_screen" />;,然後在Launcher.java中修改SCREEN_COUNT的值即可。
四、xml檔案
1.workspace_screen.xml
launcher:cellWidth="95dip" cell(即item)的寬
launcher:cellHeight="93dip" cell(即item)的寬
launcher:longAxisStartPadding="25dip"
較長(螢幕的寬和高中較大的那一方向,根據橫豎屏方向有所不同)方向上距離起點的畫素數
launcher:longAxisEndPadding="55dip"
較長(螢幕的寬和高中較大的那一方向,根據橫豎屏方向有所不同)方向上距離終點的畫素數
launcher:shortAxisStartPadding="20dip"
較短(螢幕的寬和高中較大的那一方向,根據橫豎屏方向有所不同)方向上距離起點的畫素數
launcher:shortAxisEndPadding="120dip"
較短(螢幕的寬和高中較大的那一方向,根據橫豎屏方向有所不同)方向上距離起點的畫素數
launcher:shortAxisCells="3"
較短的方向上可以容納的cell的數量
launcher:longAxisCells="5"
較長的方向上可以容納的cell的數量
shortAxisCells和longAxisCells決定一個workspace(即CellLayout)上可以容納的item的個數為shortAxisCells*longAxisCells.
2. application_boxed.xml
所有應用程式和系統資料夾中item的定義。
3.application.xml
安卓系統Launcher的開發
一、主要檔案和類 1.Launcher.java:launcher中主要的activity。 2.DragLayer.java:launcher layout的rootview。DragLayer實際上也是一個抽象的介面,用來處理拖動和對事件進行初步處理然後按情況分發下去,角色是一個controller。它首先用onInterceptTouchEvent(MotionEvent)來攔截所有的touch事件,如果是長按item拖動的話不把事件傳下去,直接交由onTouchEvent()處理,這樣就可以實現item的移動了,如果不是拖動item的話就把事件傳到目標view,交有目標view的事件處理函式做相應處理。如過有要對事件的特殊需求的話可以修改onInterceptTouchEvent(MotionEvent)來實現所需要的功能。 3. DragController.java:(0,0) | (1,0) | (2,0) | (3,0) | (4,0) |
(0,1) | (1,1) | (2,1) | (3,1) | (4,1) |
(0,2) | (1,2) | (2,2) | (3,2) | (4,2) |
Workspace的item的layout定義。[1]