標籤:
在Android4.0源碼內建的Launcher中,拖拽是由DragController進行控制的。
1、基本流程:
- 相應的View在檢測到使用者操作後進行判斷,若可以觸發拖拽,則設定自身的相應狀態,然後將待拖拽對象的Bitmap對象、當前位置、拖拽源、待拖拽對象等資訊傳給DragController的startDrag方法啟動拖拽。
- 接下來,DragLayer的onInterceptTouchEvent攔截觸屏事件,將其轉到DragController的onTouchEvent方法。
- 在DragController的onTouchEvent中調用DragController的handleMoveEvent進行對象的移動,並通知相應拖拽目的對象狀態的改變。
- 最後在DragController的onTouchEvent中檢測到抬起事件時調用drop方法釋放待拖拽對象,調用endDrag方法結束拖拽。
2、拖拽開始
在Launcher中,拖拽有兩種方式觸發:
- 在Workspace上進行拖拽;
- 從AppsCustomizePagedView中選擇一個應用或Widget放置到Workspace上。
2.1 在Workspace上進行拖拽1) 長按某個表徵圖或Widget
在這種情況下,會調用Launcher的onLongClick方法,進而對於非檔案夾調用Workspace的startDrag方法來隱藏相應視圖並繪製表徵圖邊界(在Workspace上顯示的用於標識當前拖拽表徵圖所處位置的藍色邊界圖形),最終轉到Workspace.beginDragShared方法。
2) 在開啟的檔案夾中長按某個表徵圖
在Launcher中,對於檔案夾中元素的長按,是在Folder的onLongClick裡處理的,故長按某個表徵圖或Widget中,對於檔案夾,則直接返回。
若允許拖拽,則調用Workspace的beginDragShared進行處理。
3) Workspace的beginDragShared方法
從上文中可以發現,只要是在Workspace上啟動物體的拖拽,最終都會走到Workspace.beginDragShared方法裡。在這個方法中,首先會通過createDragBitmap繪製用於拖拽的圖形(包括在原圖外層繪製一圈紅色邊界),然後計算位置與邊界,並將其傳給DragController管理。
2.2 從AppsCustomizePagedView中選擇一個應用或Widget放置到Workspace上
AppsCustomizePagedView繼承自PagedViewWithDraggableItems,即我們平時所說的應用程式抽屜。當長按應用表徵圖或widget時,AppsCustomizePagedView會隱藏,顯示Workspace的縮小狀態,即SPRING_LOADED。
在源碼中,該狀態轉換有三個入口,均在PagedViewWithDraggableItems中給出,即onInterceptTouchEvent、onTouchEvent與onLongClick。最終都轉到AppsCustomizePagedView的beginDragging方法。但筆者試了多次,發現只有onLongClick被調用。
1) AppsCustomizePagedView的beginDragging
beginDragging類似一個代理方法,首先進行Launcher狀態的轉換,然後會根據被拖拽物的不同,調用不同的拖拽方法。
2) 應用程式的拖拽beginDraggingApplication
對於應用程式來說,從抽屜拖拽到案頭,介面的隱藏在beginDragging中都已經處理好了,AppsCustomizePagedView不需要儲存任何有關被拖拽應用的資訊(就算取消拖拽,也只需要重新顯示AppsCustomizePagedView就行了,不像Folder那樣還需要恢複捷徑)。因此,只需要通知Workspace繪製表徵圖邊界,然後啟動拖拽即可。
3) Widget/捷徑的拖拽beginDraggingWidget
對於Widget列表中的元素,由於有可能為捷徑,因此還需要進行判斷。對不同類型的拖拽物,用不同的方式繪製圖形及表徵圖邊界。
3、DragController拖拽控制流程程3.1 startDrag()開始拖拽
從上文可以發現,無論是以何種方式進入拖拽,最終都是調用DragController的startDrag方法進行處理。
在DragController中,startDrag是個多態方法,但最終,都走到了以下這個實現中。
1 public void startDrag(Bitmap b, int dragLayerX, int dragLayerY, DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion)
startDrag的邏輯比較清晰,主要是通知相應的監聽器拖拽開始,然後建立拖拽對象及其視圖,將其移動到當前觸摸到位置。
3.2 onInterceptTouchEvent()
DragLayer繼承自ViewGroup,其onInterceptTouchEvent方法若返回true,說明需要攔截觸屏事件,則後續的一系列事件將傳遞給自身的onTouchEvent方法,而不再向其子控制項傳遞。
DragController的onInterceptTouchEvent由DragLayer的onInterceptTouchEvent調用,用於攔截觸屏事件的處理。當使用者點擊螢幕時,觸發ACTION_DOWN事件,記錄當前觸摸位置。當抬起時,觸發ACTION_UP事件,結束拖拽。若抬起時處於拖拽中,在當前位置釋放被拖拽物(在筆者測試過程中未檢測到其調用)。最後,返回是否處於拖拽狀態。
因此,若此時處於拖拽中,後續的觸屏事件將只傳遞到DragLayer的onTouchEvent。
onTouchEvent()
onTouchEvent由於處理觸屏事件,若返回true,則表示消費掉該事件,事件不再向父控制項的onTouchEvent傳遞。
DragController的onTouchEvent由DragLayer的onTouchEvent調用,用於處理被拖拽物的移動。
當startDrag執行完畢,DragController設定拖拽狀態為true,這樣,觸屏事件將最終轉到onTouchEvent中,在此處調用handleMoveEvent進行物體的移動。其基本流程如下。
3.3 handleMoveEvent()進行移動
handleMoveEvent是拖拽的主要方法,當使用者觸發拖拽後,DragController將通過該方法移動被拖拽物視圖,並通知各個釋放目的對象相應狀態的改變。若進入滑屏地區且允許滑屏,執行相應的滑屏操作。如所示。
3.4 drop()釋放被拖拽物到當前位置
當使用者將被拖拽物移動到相應位置後,可以將手指從螢幕上移開。此時,將在onInterceptTouchEvent(未試出)與onTouchEvent中調用drop方法釋放被拖拽物。
其主要功能,就是尋找拖拽目的對象(DropTarget),若找到且接受釋放,通知該對象被拖拽物的放入。最後,通知拖拽源(被拖拽物最初所在的容器)拖拽結果。
findDropTarget()尋找當前位置對應的拖拽目標對象
在handleMoveEvent與drop中,均使用了findDropTarget來尋找當前位置對應的拖拽目的對象,其基本原理就是遍曆所有登入的拖拽目的對象,若其支援放入且當前位置位於該對象的觸發地區內,則匹配成功返回該對象。
3.5 DragController拖拽控制流程程總結
Workspace.beginDragShared()/AppsCustomizePagedView.beginDragging() -> startDrag() -> onInterceptTouchEvent() -> onTouchEvent() -> handleMoveEvent() -> drop() -> findDropTarget()
總的來說,DragController拖拽控制就是:
- 進入狀態:使用startDrag進入拖拽狀態;
- 響應動作:使用onInterceptTouchEvent與onTouchEvent響應使用者的觸屏動作;
- 處理移動:使用handleMoveEvent處理被拖拽物的移動;
- 釋放移動:使用drop將被拖拽物釋放到相應位置。
http://johnsonxu.iteye.com/blog/1933655
Android4.0 Launcher拖拽原理分析1