Launcher拖拽框架
桌面應(yīng)用 icon 的拖拽框架
前置文章
前言
在手機(jī)桌面暴匠,我們經(jīng)常會(huì)把一個(gè)應(yīng)用的圖標(biāo)從菜單里面,拉到桌面鲤看。或者把一個(gè)應(yīng)用的圖標(biāo)移到自己更加喜歡的位置。這個(gè)過(guò)程,我們叫它拖拽存筏。拖拽能夠讓用戶方便的把應(yīng)用放到用戶可記得易操作的位置,從而能夠讓用戶快捷的打開(kāi)高頻使用的應(yīng)用味榛。同時(shí)椭坚,拖拽也可以讓用戶能夠布置自己的桌面,能夠把應(yīng)用進(jìn)行分類的存放搏色。因此善茎,Launcher拖拽讓用戶可自定義桌面。本文所使用代碼和任何展示均是使用Launcher3频轿。拖拽演示如下圖:
Launcher 拖拽框架
在Launcher的拖拽過(guò)程中垂涯,代碼出現(xiàn)的對(duì)應(yīng)的關(guān)鍵字便是Drag和Drop,Drag表示用戶拖拽開(kāi)始航邢,Drop表示用戶拖拽完成耕赘。
拖拽開(kāi)始
在 Launcher 中觸發(fā)拖拽,是通過(guò)長(zhǎng)按應(yīng)用的圖標(biāo)的方式膳殷,拖拽開(kāi)始后操骡,用戶便可把應(yīng)用的圖標(biāo)拖到喜歡的位置。我們以從 Workspace 長(zhǎng)按應(yīng)用圖標(biāo)為例(即上圖中的拖拽演示),看看觸發(fā)拖拽過(guò)程册招。如果讀者對(duì) workspace 的概念不是很清楚岔激,可以先閱讀文章《 Launcher界面結(jié)構(gòu) 》。
下面我們就從長(zhǎng)按的回調(diào)方法開(kāi)始
public boolean onLongClick(View v) {
......
CellLayout.CellInfo longClickCellInfo = null;
if (v.getTag() instanceof ItemInfo) {
ItemInfo info = (ItemInfo) v.getTag();
longClickCellInfo = new CellLayout.CellInfo(v, info);
itemUnderLongClick = longClickCellInfo.cell;
resetAddInfo();
}
......
if (!(itemUnderLongClick instanceof Folder || isAllAppsButton)) {
// User long pressed on an item
mWorkspace.startDrag(longClickCellInfo);
}
}
}
return true;
}
(代碼1)
這個(gè)方法定義在文件packages/apps/Launcher3/src/com/android/launcher3/Launcher.java中是掰。
長(zhǎng)按事件傳遞過(guò)來(lái)的 Vie w便是我們長(zhǎng)按的應(yīng)用圖標(biāo)鹦倚,在 Launcher 中,v的實(shí)質(zhì)便是 BubbleTextView冀惭,文章中《 Launcher界面結(jié)構(gòu) 》有相關(guān)描述。在 onLongClick() 中掀鹅,首先是從 v 中獲取到 tag散休,tag 實(shí)質(zhì)是一個(gè) ItemInfo,當(dāng)然乐尊,如果我們從 workspace 中拖拽一個(gè)圖標(biāo)戚丸,這個(gè) tag 的真面目是 ItemInfo 的子類 ShortcutInfo。ShortcutInfo 或者說(shuō) ItemInfo 有什么作用呢扔嵌?在 Launcher 中我們看到的每一個(gè)應(yīng)用的圖標(biāo)限府,代碼中就是抽象成BubbleTextView,一個(gè) BubbleTextView 就是一個(gè) item痢缎,每個(gè) item 都會(huì)擁有自己的 ItemInfo 實(shí)例胁勺,這個(gè) ItemInfo 實(shí)例記錄了每個(gè) item 的信息,如這個(gè) item 在 Workspace 中的具體位置独旷, item 的標(biāo)題和描述署穗,item 的擁有者等等。還有一點(diǎn)很重要的嵌洼,記錄了 item 的單擊觸發(fā)的意圖案疲,也即抽象成 Android 中的 Intent 實(shí)例。也就是說(shuō)麻养,當(dāng)我們點(diǎn)擊一個(gè)應(yīng)用的圖標(biāo)后褐啡,打開(kāi)哪個(gè)應(yīng)用,便由 ItemInfo 中的 Intent 變量來(lái)決定鳖昌。
在onLongClick()中备畦,把 v 即 BubbleTextView 封裝到 CellLayout.CellInfo 的實(shí)例 longClickCellInfo 中,longClickCellInfo.cell 即是 BubbleTextView遗遵。然后 longClickCellInfo 作為參數(shù)調(diào)用 startDrag() 方法萍恕。
public void startDrag(CellLayout.CellInfo cellInfo) {
startDrag(cellInfo, false);
}
@Override
public void startDrag(CellLayout.CellInfo cellInfo, boolean accessible) {
View child = cellInfo.cell;
......
child.setVisibility(INVISIBLE);
CellLayout layout = (CellLayout) child.getParent().getParent();
layout.prepareChildForDrag(child);
beginDragShared(child, this, accessible);
}
(代碼2)這個(gè)兩個(gè)方法定義在文件packages/apps/Launcher3/src/com/android/launcher3/Workspace.java中。
上述方法中 cellInfo.cell 即 BubbleTextView 實(shí)例賦值給 child车要,即我們點(diǎn)擊的應(yīng)用圖標(biāo)允粤,參數(shù) accessible 是輔助功能所用,本文不贅述這個(gè)。然后把 child 的可見(jiàn)性設(shè)置成不可見(jiàn)类垫,layout.prepareChildForDrag(child) 把child的位置信息記錄下來(lái)司光,以防 child 還要回到這個(gè)位置。接著調(diào)用 beginDragShared()悉患,這里注意第二個(gè)參數(shù) this残家,實(shí)際類型是 DragSource,DragSource 的接口定義如下
public interface DragSource {
boolean supportsFlingToDelete();
boolean supportsAppInfoDropTarget();
boolean supportsDeleteDropTarget();
float getIntrinsicIconScaleFactor();
void onFlingToDeleteCompleted();
void onDropCompleted(View target, DragObject d, boolean isFlingToDelete, boolean success);
}
(代碼3)這個(gè)接口定義在文件packages/apps/Launcher3/src/com/android/launcher3/DragSource.java中售躁。
DragSource 表示本次拖拽的應(yīng)用圖標(biāo)來(lái)自哪里坞淮,在 beginDragShared() 中傳入的參數(shù)是 this,也即本次拖拽來(lái)自 Workspace陪捷。DragSource 定義了一些方法回窘,目的兩個(gè),第一市袖,告訴拖拽框架啡直,來(lái)自我這個(gè) DragSource 拖拽的 item 所能支持的某些功能或者某些屬性。如苍碟,是否允許拖拽刪除酒觅;第二,讓拖拽框架告訴我這個(gè) DragSource 拖拽的一些“情報(bào)”微峰,如拖拽結(jié)束舷丹。
注意:這里論述到 DragSource,這里拋出拖拽框架中兩個(gè)核心問(wèn)題:
第一:拖拽的應(yīng)用圖標(biāo)來(lái)自哪里蜓肆,對(duì)拖拽框架而言掂榔,你從哪里來(lái)? 第二:拖拽的應(yīng)用圖標(biāo)放到哪里症杏,對(duì)拖拽框架而言装获,你到哪里去?(后文會(huì)現(xiàn)身)
在拖拽框架中厉颤,也就圍繞著兩個(gè)核心問(wèn)題進(jìn)行應(yīng)用圖標(biāo)的拖拽穴豫。
回到beginDragShared()這個(gè)方法
public void beginDragShared(View child, DragSource source, boolean accessible) {
beginDragShared(child, new Point(), source, accessible);
}
public void beginDragShared(View child, Point relativeTouchPos, DragSource source,
boolean accessible) {
......
final Bitmap b = createDragBitmap(child, padding);
......
DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale, accessible);
dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
}
(代碼4)這個(gè)兩個(gè)方法定義在文件packages/apps/Launcher3/src/com/android/launcher3/Workspace.java中。
在第二個(gè)beginDragShared()方法中多了一個(gè)參數(shù)逼友,Point 類型的 relativeTouchPos精肃。我們先看 mDragController.startDrag() 返回一個(gè) DragView,DragView 是 View 的子類帜乞。如下圖司抱,當(dāng)我們按下應(yīng)用的圖標(biāo)后,就好像圖標(biāo)被放大黎烈。
在(代碼2)中习柠,我們知道匀谣,代碼 child.setVisibility(INVISIBLE) 把長(zhǎng)按的應(yīng)用 icon 設(shè)置成不可見(jiàn),那么上圖中這個(gè)被略放大的 icon 和被長(zhǎng)按的 icon 長(zhǎng)得一樣的到底又是什么東西资溃。它就是上面代碼中的 DragView武翎。換句話說(shuō),我們拖拽時(shí)拖動(dòng)的 icon 并不是原來(lái)的 icon溶锭,而是一個(gè)原 icon 的“親兄弟” DragView宝恶。
回到(代碼4),beginDragShared()中趴捅,當(dāng)我們手指觸摸手機(jī)屏幕的地方和 DragView 形成的的地方有偏移量垫毙,此時(shí)可以通過(guò)參數(shù) relativeTouchPos 把 DragView 移到手指觸摸的地方。當(dāng)然拱绑,在我們的圖片中露久,長(zhǎng)按的地方,DragView 形成的地方也就是我們手指觸摸的地方欺栗,因此,本文中的代碼傳入的是一個(gè)沒(méi)有使用價(jià)值的 Point 實(shí)例(x, y = 0)(其實(shí)設(shè)置 y 的屬性是不會(huì)生效的征峦,在startDrag()方法中不考慮 y 屬性)迟几。再回到代碼中,首先調(diào)用了 createDragBitmap() 方法栏笆,把 child 中的 icon 取出來(lái)类腮,作為 startDrag() 中的第一個(gè)參數(shù),詳情如下:
第一參數(shù)蛉加,也就是 DragView 的 icon蚜枢,
第二、第三個(gè)參數(shù) dragLayerX, dragLayerY 是 int 類型针饥,從 relativeTouchPos 化身而來(lái)厂抽,也就是x, y丁眼。
第六個(gè)參數(shù) dragAction 是 DragController.DRAG_ACTION_MOVE筷凤。
第七個(gè)參數(shù) dragVisualizeOffset 用于描繪 DragView 拖動(dòng)時(shí)在位置上的類似投影的視圖所使用的偏移參數(shù)。
第八個(gè)參數(shù) dragRect 記錄了 icon 到 BubbleTextView 邊框的距離苞七,用于后面給 View 計(jì)算位置藐守。
第九個(gè)參數(shù) scale 是對(duì) DragView 的縮放。
在這個(gè)方法中的參數(shù)也是夠多的蹂风,這里調(diào)用 startDrag() 的是 mDragController卢厂,mDragController 是 DragController 的實(shí)例,DragController 是整個(gè)拖拽框架中的控制中心惠啄,在拖拽中兩個(gè)核心問(wèn)題你從哪里來(lái)慎恒,你到哪里去的指揮者任内。DragController 在 Launcher 啟動(dòng) Laucher activity 的時(shí)候,在 onCreate() 方法中實(shí)例化巧号。從長(zhǎng)按應(yīng)用 icon族奢,到調(diào)用 startDrag() 方法時(shí),表示拖拽真正進(jìn)入到拖拽框架丹鸿,拖拽也就從 startDrag() 真正開(kāi)始越走。
在(代碼4)中,我們知道真正拖拽移動(dòng)的是一個(gè) DragView靠欢,因此廊敌,這個(gè) DragView 在拖拽框架中充當(dāng)一個(gè)事件執(zhí)行的代表,它不是真正的應(yīng)用 icon门怪,也沒(méi)有任何可利用的數(shù)據(jù)價(jià)值骡澈,它是一個(gè)應(yīng)用 icon(BubbleTextView) 的“代理者”,在拖拽框架中你到哪里去的執(zhí)行代表掷空。
繼續(xù)往下跟蹤代碼
public DragView startDrag(Bitmap b, int dragLayerX, int dragLayerY,
DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion,
float initialDragViewScale, boolean accessible) {
......
for (DragListener listener : mListeners) {
listener.onDragStart(source, dragInfo, dragAction);
}
......
mDragObject = new DropTarget.DragObject();
final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX,
registrationY, 0, 0, b.getWidth(), b.getHeight(), initialDragViewScale);
......
mDragObject.dragSource = source;
mDragObject.dragInfo = dragInfo;
......
dragView.show(mMotionDownX, mMotionDownY);
handleMoveEvent(mMotionDownX, mMotionDownY);
return dragView;
}
(代碼5)這個(gè)方法定義在文件packages/apps/Launcher3/src/com/android/launcher3/DragController.java中肋殴。
首先循環(huán)遍歷 mListeners 把 DragListener 取出來(lái),DragListener 顧名思義坦弟,拖拽監(jiān)聽(tīng)者护锤。拖拽開(kāi)始時(shí),通過(guò)回調(diào) onDragStart() 通知監(jiān)聽(tīng)者拖拽已經(jīng)開(kāi)始酿傍,在 Launcher3 中烙懦,注冊(cè)拖拽監(jiān)聽(tīng)者的有 Folder.java 、Launcher activity 、SearchDropTarget.java、 InfoDropTarget.java聚假、 DeleteDropTarget.java分蓖、 UninstallDropTarget.java 和 WidgetHostViewLoader.java 等。對(duì)這些不熟悉的讀者,可以先閱讀文章 《 Launcher界面結(jié)構(gòu) 》。
隨后 new 一個(gè) DropTarget.DragObject,DropTarget.DragObject 是整個(gè)拖拽框架中最有“實(shí)權(quán)”的實(shí)例對(duì)象拾因,它包含了拖拽的視圖代表 DragView,包含被拖拽的應(yīng)用 icon(BubbleTextView) 數(shù)據(jù) ItemInfo旷余,包含拖拽的源頭 DragSource绢记。在整個(gè)拖拽過(guò)程中,接受控制中心 DragController 的執(zhí)行指令正卧,是指令的首要執(zhí)行者蠢熄,DragObject 會(huì)伴隨整個(gè)拖拽過(guò)程,對(duì) DragView炉旷、icon(BubbleTextView)签孔、DragSource 有絕對(duì)“控制權(quán)”叉讥,如控制 DragView 的顯示、消失和位置等饥追。
繼續(xù)往下便是 DragView 的初始化图仓,傳入的參數(shù)基本都是上文中有說(shuō)明的變量或類型,本文不再贅述 DragView 初始化的詳細(xì)過(guò)程但绕。隨后調(diào)用 dragView.show(mMotionDownX, mMotionDownY) 把 DragView 顯示到屏幕上救崔,這里應(yīng)該改為 mDragObject.dragView.show(),這樣更能充分體現(xiàn) DragObject 的“實(shí)權(quán)作用”捏顺。顯示 DragView 之后六孵,直接調(diào)用 handleMoveEvent() 方法,這個(gè)方法很重要幅骄,它是整個(gè)拖拽過(guò)程實(shí)現(xiàn)的基本條件劫窒,因此,這個(gè)方法在拖拽過(guò)程中會(huì)被調(diào)用非常多次數(shù)拆座,也就是處理我們拖拽的時(shí)候的手指移動(dòng)事件主巍。
到此,拖拽的開(kāi)始和準(zhǔn)別工作到這里已經(jīng)徹底完成挪凑,下面將進(jìn)入真正的拖拽過(guò)程孕索。
拖拽過(guò)程
在上一章節(jié)中,我們知道岖赋,我們拖拽的視圖是一個(gè) DragView,而不是應(yīng)用 icon(BubbleTextView) 的本身瓮孙,為什么唐断?為什么不直接拖拽 icon,而是找一個(gè)化身 DragView杭抠?我們知道脸甘,一個(gè)子 View 是不能越界到它的父控件的外面,因此偏灿,icon(BubbleTextView) 它的父控件的范圍可能就好小丹诀,但我們的 icon(BubbleTextView) 是要能拖到界面上得任何一個(gè)位置的。所以翁垂,Launcher 有一個(gè)覆蓋整個(gè) Laucher 界面的 DragLayer铆遭,而 DragView 便是依附在 DragLayer 作為它的直接子 View,便能在整個(gè) Launcher 界面上移動(dòng)沿猜。
因?yàn)?DragLayer 在拖拽框架中控制拖拽范圍的枚荣,因此,實(shí)質(zhì)我們手指在手機(jī)屏幕上的移動(dòng)范圍便是 DragLayer啼肩,換句話說(shuō)橄妆,我們手指移到哪里衙伶,需要受 DragLayer 的限制,也就因此害碾,我們手指的觸摸事件應(yīng)由 DragLayer 來(lái)接收矢劲,并傳達(dá)給拖拽的控制中心 DragControlller, 即 handleMoveEvent()慌随。
public boolean onTouchEvent(MotionEvent ev) {
......
return mDragController.onTouchEvent(ev);
}
(代碼6)這個(gè)方法定義在文件packages/apps/Launcher3/src/com/android/launcher3/DragLayer.java中芬沉。
public boolean onTouchEvent(MotionEvent ev) {
......
case MotionEvent.ACTION_MOVE:
handleMoveEvent(dragLayerX, dragLayerY);
break;
......
return true;
}
(代碼7)這個(gè)方法定義在文件packages/apps/Launcher3/src/com/android/launcher3/DragController.java中。
上面的代碼是觸摸事件的接收和傳遞儒陨,最終傳遞到 DragControlller 的 handleMoveEvent() 方法花嘶。如下:
private void handleMoveEvent(int x, int y) {
mDragObject.dragView.move(x, y);
// Drop on someone?
final int[] coordinates = mCoordinatesTemp;
DropTarget dropTarget = findDropTarget(x, y, coordinates);
mDragObject.x = coordinates[0];
mDragObject.y = coordinates[1];
checkTouchMove(dropTarget);
// Check if we are hovering over the scroll areas
mDistanceSinceScroll += Math.hypot(mLastTouch[0] - x, mLastTouch[1] - y);
mLastTouch[0] = x;
mLastTouch[1] = y;
checkScrollState(x, y);
}
(代碼8)這個(gè)方法定義在文件packages/apps/Launcher3/src/com/android/launcher3/DragController.java中。
handleMoveEvent() 的比較簡(jiǎn)潔蹦漠,沒(méi)有過(guò)多的計(jì)算等代碼椭员,不需要做任何的代碼“省略”。首先是通過(guò) DragObject 控制移動(dòng) DragView 到手指的位置笛园。接著隘击,注意,請(qǐng)注意研铆,調(diào)用了 findDropTarget(x, y, coordinates) 方法埋同,傳入了 int 類型的 x, y 和 int[] 類型的 coordinates, 返回一個(gè) DropTarget 的實(shí)例 dropTarget棵红,drop 放下凶赁, target 目標(biāo),放下目標(biāo)逆甜,即 DragView 放下的目標(biāo)虱肄,即 icon(BubbleTextView) 移到的新的位置。因此交煞,DropTarget 是拖拽框架中兩個(gè)核心問(wèn)題中的第二個(gè)咏窿,你到哪里去的真實(shí)身份現(xiàn)身,它就是 DropTarget素征。我們進(jìn)入 findDropTarget() 這個(gè)方法瞧瞧
private DropTarget findDropTarget(int x, int y, int[] dropCoordinates) {
final Rect r = mRectTemp;
final ArrayList<DropTarget> dropTargets = mDropTargets;
final int count = dropTargets.size();
for (int i=count-1; i>=0; i--) {
DropTarget target = dropTargets.get(i);
if (!target.isDropEnabled())
continue;
//獲取target 在 draglayer 中的位置
target.getHitRectRelativeToDragLayer(r);
mDragObject.x = x;
mDragObject.y = y;
if (r.contains(x, y)) {
dropCoordinates[0] = x;
dropCoordinates[1] = y;
return target;
}
}
}
(代碼9)這個(gè)方法定義在文件packages/apps/Launcher3/src/com/android/launcher3/DragController.java中集嵌。
在這里,有一個(gè) mDropTargets 的全局變量御毅,保存了所有的 Target根欧,這些 Target 在 Laucher 啟動(dòng)的時(shí)候就通過(guò)方法 addDropTarget(DropTarget target) 添加到 DrageController 中來(lái)。包括 Workspace端蛆、Folder咽块、InfoDropTarget 等等。在這么多 Target 中欺税,如何判斷當(dāng)前是移到哪個(gè) Target 呢侈沪?通過(guò) target 的實(shí)例調(diào)用 getHitRectRelativeToDragLayer(r) 方法揭璃,獲取到 target 所處的位置 r,通過(guò) r.contains(x, y) 判斷亭罪, x, y 是否包含在 target 內(nèi)瘦馍,如果是,則手指移到了該 target应役,同時(shí)把 x情组,y 保存到 target 實(shí)例中,返回該 target 對(duì)象箩祥。
回到(代碼8)handleMoveEvent() 方法院崇,獲取到當(dāng)前手指移到得 targe 實(shí)例后,作為參數(shù)傳遞給 checkTouchMove(dropTarget)袍祖,如下:
private void checkTouchMove(DropTarget dropTarget) {
if (dropTarget != null) {
if (mLastDropTarget != dropTarget) {
if (mLastDropTarget != null) {
mLastDropTarget.onDragExit(mDragObject);
}
dropTarget.onDragEnter(mDragObject);
}
dropTarget.onDragOver(mDragObject);
} else {
if (mLastDropTarget != null) {
mLastDropTarget.onDragExit(mDragObject);
}
}
mLastDropTarget = dropTarget;
}
(代碼10)這個(gè)方法定義在文件packages/apps/Launcher3/src/com/android/launcher3/DragController.java中底瓣。
如果前后兩次的 target 實(shí)例不一致,說(shuō)明 target 發(fā)生變化蕉陋,通過(guò)調(diào)用 onDragExit() 方法通知上一次的 target 拖拽已經(jīng)移走捐凭,然后通過(guò) onDragEnter() 方法通知當(dāng)前 target,拖拽已經(jīng)移動(dòng)進(jìn)來(lái)凳鬓。同時(shí)茁肠,通過(guò) onDragOver() 通知 target 拖拽已經(jīng)移到你的上面,準(zhǔn)確的說(shuō)缩举,是 DragView 移到了 target 的上面垦梆。在這里,每個(gè)方法的參數(shù)都是 mDragObject 實(shí)例仅孩,在上文中我們知道托猩,mDragObject 在拖拽中是最有“實(shí)權(quán)”的“人物”,擁有視圖的化身 DragView杠氢,保存有 icon(BubbleTextView)的數(shù)據(jù)站刑,持有拖拽的來(lái)源 DragSource另伍,同時(shí)鼻百,mDragObject 到達(dá)了 target,也可以操作 target 對(duì)象實(shí)例本身摆尝,因此温艇,在 target 可以發(fā)生一切事情。
拖拽結(jié)束
當(dāng)我們的手指離開(kāi)屏幕堕汞,標(biāo)志著一次拖拽結(jié)束勺爱,代碼如下:
public boolean onTouchEvent(MotionEvent ev) {
......
case MotionEvent.ACTION_UP:
// Ensure that we've processed a move event at the current pointer location.
handleMoveEvent(dragLayerX, dragLayerY);
mHandler.removeCallbacks(mScrollRunnable);
if (mDragging) {
// 是否 甩 到移除
PointF vec = isFlingingToDelete(mDragObject.dragSource);
if (!DeleteDropTarget.supportsDrop(mDragObject.dragInfo)) {
vec = null;
}
if (vec != null) {
// 執(zhí)行 移除
dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec);
} else {
drop(dragLayerX, dragLayerY);
}
}
endDrag();
break;
......
}
(代碼11)這個(gè)方法定義在文件packages/apps/Launcher3/src/com/android/launcher3/DragController.java中。
再次調(diào)用 handleMoveEvent() 把 DragView 精確到手指離開(kāi)的位置讯检,首先是判斷是否有甩到移除的手勢(shì)琐鲁,如果是卫旱,移除相關(guān)數(shù)據(jù)和操作,如果不是围段,執(zhí)行 drop() 方法顾翼。我們先來(lái)看 endDrag() 方法,在回頭看這個(gè) drop() 方法奈泪。
private void endDrag() {
if (mDragging) {
mDragging = false;
......
if (!isDeferred) {
mDragObject.dragView.remove();
}
mDragObject.dragView = null;
}
// Only end the drag if we are not deferred
if (!isDeferred) {
for (DragListener listener : new ArrayList<>(mListeners)) {
listener.onDragEnd();
}
}
}
}
(代碼12)這個(gè)方法定義在文件packages/apps/Launcher3/src/com/android/launcher3/DragController.java中适贸。
如果不是 Deferred,立即執(zhí)行把 DragView 從 DragLayer 中移除涝桅,然后通知監(jiān)聽(tīng)者 onDragEnd()拜姿,拖拽結(jié)束。好冯遂,這里沒(méi)有什么特別的蕊肥,執(zhí)行了一些結(jié)束的動(dòng)作,我們回到前面的 drop() 方法
private void drop(float x, float y) {
final int[] coordinates = mCoordinatesTemp;
final DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates);
mDragObject.x = coordinates[0];
mDragObject.y = coordinates[1];
boolean accepted = false;
if (dropTarget != null) {
mDragObject.dragComplete = true;
dropTarget.onDragExit(mDragObject);
if (dropTarget.acceptDrop(mDragObject)) {
dropTarget.onDrop(mDragObject);
accepted = true;
}
}
mDragObject.dragSource.onDropCompleted((View) dropTarget, mDragObject, false, accepted);
}
(代碼13)這個(gè)方法定義在文件packages/apps/Launcher3/src/com/android/launcher3/DragController.java中债蜜。
再次獲取手指離開(kāi)屏幕時(shí)的 DropTarget晴埂,把 DragObject 的標(biāo)志為 dragComplete 置為 true,通過(guò) onDragExit() 方法通知 DragTarget 拖拽退出寻定。到這里 DragTarget 還沒(méi)有做拖拽退出的事情儒洛,應(yīng)用 icon 還不可以就此“安家”。而是先調(diào)用 target 的 acceptDrop() 方法征求 target 是否可以接受本次拖拽狼速,也就是是否可以接受該應(yīng)用 icon(BubbleTextView)琅锻。target 如果可以接受該拖拽,接著調(diào)用 target 的 onDrop() 方法向胡,表示本次拖拽的真正結(jié)束恼蓬。以本次演習(xí)的例子來(lái)看,我們是要給 icon 尋找一個(gè)新的家僵芹,那么當(dāng) Workspace 接受這個(gè) icon的時(shí)候处硬,調(diào)用 animateViewIntoPosition() 為應(yīng)用 icon 重新按一個(gè)“家”。當(dāng)然拇派,icon(BubbleTextView)還是原來(lái)的 icon荷辕,但是,有時(shí)候我們是從 Workspace 的外部拖拽一個(gè) icon 到 Workspace 的件豌,這時(shí)疮方,在 onDrop() 方法中,會(huì)走 onDropExternal() 這個(gè)分支從而會(huì)形成一個(gè)新的 icon(BubbleTextView)茧彤。最后骡显,調(diào)用 onDropCompleted() 通知拖拽的起源地,拖拽完成,并傳送拖拽結(jié)果惫谤。
拖拽控制框架圖
以從 Workspace 拖拽一個(gè) icon(BubbleTextView)到另外一個(gè)位置為例
拖拽時(shí)序圖
總結(jié)
在 Launcher 的拖拽框架中壁顶,由 DragController 擔(dān)當(dāng)指揮中心,用 DragSource 抽象拖拽的來(lái)源溜歪,用 DragView 描繪拖拽視圖博助,設(shè)置 DragListener 通知拖拽的開(kāi)始和結(jié)束,整個(gè)拖拽過(guò)程中痹愚,由 DragObject 執(zhí)行拖拽事務(wù)富岳,與 DragSource 相對(duì)的 DropTarget 描述拖拽的目的地,DragSource拯腮、 DropTarget 代表“你從哪里來(lái)窖式,你到哪里去”,是拖拽框架中核心問(wèn)題的抽象动壤。當(dāng)長(zhǎng)按應(yīng)用 icon 觸發(fā)后萝喘,DragLayer 把觸摸事件攔截,再傳遞給控制中心 DragControlller 處理琼懊。拖拽結(jié)束后阁簸,在 DropTarget 的 onDrop() 方法中處理拖拽的結(jié)束的事務(wù)。