Android Launcher3 基本功能分析
1、介面的佈局,從上往下分別為:
DeleteDropTarget(應用解除安裝區域,它是一個DropTarget)
Workspace(頁面容器,一個頁面是一個CellLayout)
PageIndicator(指示器,指示workspace當前位於第幾個頁面)
Hotseat(底部圖示區域)
2、Launcher桌面圖示的載入:
LauncherApplication:onCreate()建立IconCache和LauncherModel,併為LauncherModel設定廣播過濾條件,如應用安裝廣播、配置切換廣播等 ;
Launcher:onCreate()建立DragController、LauncherAppWidgetHost,用setupViews()初始化各個view ;
LauncherModel:startLoader() 開啟LoaderTask執行緒,用於載入桌面圖示 ;
LauncherModel:loadAndBindWorkspace()分為loadWorkspace和bindWorkspace兩個過程,如果是剛刷完機或恢復出廠設定,loadWorkspace會解析相應的xml檔案,從中讀取workspace和hotseat的圖示配置,插入到新建立的launcher.db資料庫中,並形成相應的資料結構:sBgWorkspaceItems、sBgAppWidgets、sBgFolders。如果已經存在了launcher.db檔案,則直接查詢launcher.db,載入相應的資料結構。而bindWorkspace,顧名思義,是進行桌面圖示的繫結,也就是根據loadWorkspace生成的資料結構來建立並顯示圖示的過程,它會依次呼叫Launcher的bindItems()、bindFolders、bindAppWidgets,先顯示當前頁面,再顯示其他頁面。
3、資料結構(data)和檢視(view)的對應關係:
桌面應用:shortcutInfo 對應BubbleTextView
所有應用:AppInfo 對應BubbleTextView
桌面小部件:LauncherAppWidgetInfo對應LauncherAppWidgetHostView
資料夾:FolderInfo對應FoldeIcon
4、圖示的拖拽:
談拖拽,首先要談2個概念,DragSource和DropTarget。DragSource是拖拽源所在的容器,DropTarget則為拖拽目的地所處的容器。
DragSource:Workspace、Folder 和 AppcustomizedPagedView。注意,Hotseat邏輯上應當也屬於DragSource,只是我們的程式碼中把它歸併Workspace中去處理了。
DropTarget:Workspace、Folder、DeleteDropTarget。Workspace中圖示的拖拽,是從Launcher的onLongClick開始的,Folder則從它自己的onLongClick開始。
以Workspace中的圖示拖拽為例,流程如下:
Launcher:onLongClick->
Workspace:startDrag->Workspace:createDragOutline ->
Workspace:beginDragShared->
DragController:startDrag->DragController:handleMoveEvent
上面是圖示長按,觸發的一系列函式呼叫,如果此時,再滑動呢,根據下面的函式
DragController的onTouchEvent中
case MotionEvent.ACTION_MOVE:
handleMoveEvent(dragLayerX, dragLayerY);
break;
可知,滑動時會不斷觸發handleMoveEvent,這樣就能看到圖示跟著手移動了。
那麼跟手移動的虛圖示是在哪裡繪製的呢,前面說到了DragSource和DropTarget,在你拖動圖示到放手的過程中,會依次呼叫DragController.DragListener的onDragStart,
DropTarget的 onDragEnter、onDragOver、onDragExit、onDrop,
DragSource的onDropCompleted,
最後會呼叫DragController.DragListener的onDragEnd.
應用虛圖示的繪製是放在Workspace的onDragOver中:
if (!nearestDropOccupied&&!isWidgetOverView && (mAuroraSwapTag || !mFromDesktopTag)) {
mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
(int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1],
mTargetCell[0], mTargetCell[1], item.spanX, item.spanY, false,
d.dragView.getDragVisualizeOffset(), d.dragView.getDragRegion());
}
滑動時,Workspace的onDragOver會跟著手不斷被執行,根據計算出來的最近的位置,來放置mDragOutline。被拖拽圖示的隱藏和顯示分別在startDrag和onDropCompleted中處理的。比較特殊的是,當滑動到Workspace的邊界的時候,邊界位置會顯示條狀的藍色豎線,然後決定頁面是否跳轉,程式碼如下,這也是在DragController的handleMoveEvent中
if (x < mScrollZone && y < mScrollZoneY) {
if (mScrollState == SCROLL_OUTSIDE_ZONE) {
mScrollState = SCROLL_WAITING_IN_ZONE;
if (mDragScroller.onEnterScrollArea(x, y, SCROLL_LEFT)) {
mLauncher.getDragLayer().onEnterScrollArea(SCROLL_LEFT);
mScrollRunnable.setDirection(SCROLL_LEFT);
mHandler.postDelayed(mScrollRunnable, delay);
}
}
} else if (x > mScrollView.getWidth() - mScrollZone && y < mScrollZoneY) {
if (mScrollState == SCROLL_OUTSIDE_ZONE) {
mScrollState = SCROLL_WAITING_IN_ZONE;
if (mDragScroller.onEnterScrollArea(x, y, SCROLL_RIGHT)) {
mLauncher.getDragLayer().onEnterScrollArea(SCROLL_RIGHT);
mScrollRunnable.setDirection(SCROLL_RIGHT);
mHandler.postDelayed(mScrollRunnable, delay);
}
}
} else {
clearScrollRunnable();
}
}
如果不是長按事件,只是滑動頁面,則DragLayer的OnInterceptTouchEvent會返回false,把事件傳遞給Workspace去處理。
5、圖示推擠:
在Workspace.java的onDragOver方法裡邊處理的
if (!nearestDropOccupied) { //有空位,不需要推擠圖示,直接放置
mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
(int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1],
mTargetCell[0], mTargetCell[1], item.spanX, item.spanY, false,
d.dragView.getDragVisualizeOffset(), d.dragView.getDragRegion());
} else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER)
&& !mReorderAlarm.alarmPending() && (mLastReorderX != reorderX ||
mLastReorderY != reorderY)) {
int[] resultSpan = new int[2];
mDragTargetLayout.performReorder((int) mDragViewVisualCenter[0],
(int) mDragViewVisualCenter[1], minSpanX, minSpanY, item.spanX, item.spanY,
child, mTargetCell, resultSpan, CellLayout.MODE_SHOW_REORDER_HINT);
// Otherwise, if we aren't adding to or creating a folder and there's no pending
// reorder, then we schedule a reorder
ReorderAlarmListener listener = new ReorderAlarmListener(mDragViewVisualCenter,
minSpanX, minSpanY, item.spanX, item.spanY, d.dragView, child);
mReorderAlarm.setOnAlarmListener(listener);
mReorderAlarm.setAlarm(REORDER_TIMEOUT);
}
如果當前將要放置的位置已經被佔據了,則除了計算出需要移動的圖示外,還設定了一個延時器ReorderAlarm,這個延時器本質上是一個通過Handler.postDelayed的Runnable。設定延時的目的,是為了給建立資料夾提供緩衝時間,如果在這個延時時間內,被拖拽的圖示與將要放至的位置上的圖示的中心距離足夠近,就會觸發新資料夾的建立,此時會取消ReorderAlarm。如果延時的時間到了,而距離沒有達到,則會直接觸發ReorderAlarm,播放圖示挪動的動畫。
6、總結:
LauncherModel.java是整個Launcher中最重要的一個類,它儲存有整個Launcher所需的全部資料結構,可以理解為Model層。Launcher.java則負責UI的顯示,所有圖示的建立和顯示都需要放到它裡邊去處理,可以理解為View層。DragController則實現了對拖拽事件的攔截和對滑動事件的分發,可以理解為Control層。這是基本的架構,其他的業務需求是在此基礎上,進行搭建的。