Launcher拖拽框架

Launcher拖拽框架

桌面應(yīng)用 icon 的拖拽框架

前置文章

  1. Launcher的啟動(dòng)過(guò)程
  2. Launcher界面結(jié)構(gòu)

前言

在手機(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频轿。拖拽演示如下圖:

GIF_20170519_152557.gif

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)被放大黎烈。

GIF_20150102_141124.gif

在(代碼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ù),詳情如下:

  1. 第一參數(shù)蛉加,也就是 DragView 的 icon蚜枢,

  2. 第二、第三個(gè)參數(shù) dragLayerX, dragLayerY 是 int 類型针饥,從 relativeTouchPos 化身而來(lái)厂抽,也就是x, y丁眼。

  3. 第六個(gè)參數(shù) dragAction 是 DragController.DRAG_ACTION_MOVE筷凤。

  4. 第七個(gè)參數(shù) dragVisualizeOffset 用于描繪 DragView 拖動(dòng)時(shí)在位置上的類似投影的視圖所使用的偏移參數(shù)。

  5. 第八個(gè)參數(shù) dragRect 記錄了 icon 到 BubbleTextView 邊框的距離苞七,用于后面給 View 計(jì)算位置藐守。

  6. 第九個(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è)位置為例

launcher_drag.jpg

拖拽時(shí)序圖

timeline.jpg

總結(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ù)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末哼丈,一起剝皮案震驚了整個(gè)濱河市启妹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌醉旦,老刑警劉巖饶米,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異车胡,居然都是意外死亡檬输,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門匈棘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)丧慈,“玉大人,你說(shuō)我怎么就攤上這事主卫√幽” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵队秩,是天一觀的道長(zhǎng)笑旺。 經(jīng)常有香客問(wèn)我昼浦,道長(zhǎng)馍资,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮鸟蟹,結(jié)果婚禮上乌妙,老公的妹妹穿的比我還像新娘。我一直安慰自己建钥,他們只是感情好藤韵,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著熊经,像睡著了一般泽艘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上镐依,一...
    開(kāi)封第一講書人閱讀 49,079評(píng)論 1 285
  • 那天匹涮,我揣著相機(jī)與錄音,去河邊找鬼槐壳。 笑死然低,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的务唐。 我是一名探鬼主播雳攘,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼枫笛!你這毒婦竟也來(lái)了吨灭?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤刑巧,失蹤者是張志新(化名)和其女友劉穎沃于,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體海诲,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡繁莹,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了特幔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片咨演。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蚯斯,靈堂內(nèi)的尸體忽然破棺而出薄风,到底是詐尸還是另有隱情,我是刑警寧澤拍嵌,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布遭赂,位于F島的核電站,受9級(jí)特大地震影響横辆,放射性物質(zhì)發(fā)生泄漏撇他。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望困肩。 院中可真熱鬧划纽,春花似錦、人聲如沸锌畸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)潭枣。三九已至比默,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間盆犁,已是汗流浹背退敦。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蚣抗,地道東北人侈百。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像翰铡,于是被迫代替她去往敵國(guó)和親钝域。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容