Android學習歷程--Launcher拖拽流程

LR6666發表於2016-08-04

    

    Launcher之圖示拖拽事件學習——

    仔細想想我們每次對手機上的一個圖示(app)進行解除安裝、拖動換地兒、整理圖示、歸檔到資料夾等是否感覺是很簡單的操作呢?實則不然,其將通過一些列的判斷來進行效果的達到,今天通過程式碼的學習來一步一步的檢視,到底每一次都發生了什麼以及如何處理            
    每一次對於圖示的操作都是長按對不對?不信的話拿過你的手機嘗試一下?沒錯吧?  那麼我們就從長按的響應事件為入口進行程式碼剖析。
      一、長按響應
<pre name="code" class="java"><pre name="code" class="java">public boolean onLongClick(View v)



<span style="white-space:pre">	</span>public boolean onLongClick(View v) { 
<span style="white-space:pre">		</span>if (LauncherLog.DEBUG) {
<span style="white-space:pre">			</span>LauncherLog.d(TAG, "onLongClick: View = " + v + ", v.getTag() = " + v.getTag() + ", mState = " + mState);
<span style="white-space:pre">		</span>}


<span style="white-space:pre">		</span>if (!isDraggingEnabled()) {//允許拖拽與否
<span style="white-space:pre">			</span>LauncherLog.d(TAG, "onLongClick: isDraggingEnabled() = " + isDraggingEnabled());
<span style="white-space:pre">			</span>return false;
<span style="white-space:pre">		</span>}


<span style="white-space:pre">		</span>if (isWorkspaceLocked()) {//WorkSpace是否鎖定
<span style="white-space:pre">			</span>LauncherLog.d(TAG, "onLongClick: isWorkspaceLocked() mWorkspaceLoading " + mWorkspaceLoading
<span style="white-space:pre">					</span>+ ", mWaitingForResult = " + mWaitingForResult);
<span style="white-space:pre">			</span>return false;
<span style="white-space:pre">		</span>}


<span style="white-space:pre">		</span>if (mState != State.WORKSPACE){//是否處於WorkSpace狀態
<span style="white-space:pre">			</span>LauncherLog.d(TAG, "onLongClick: mState != State.WORKSPACE: = " + mState);
<span style="white-space:pre">			</span>return false;
<span style="white-space:pre">		</span>}


<span style="white-space:pre">		</span>if (v instanceof Workspace) {
<span style="white-space:pre">			</span>LauncherLog.d(TAG, "v instanceof Workspace");
<span style="white-space:pre">			</span>if (!mWorkspace.isInOverviewMode()) {// 判斷是否在縮圖模式下
<span style="white-space:pre">				</span>if (mWorkspace.enterOverviewMode()) {// 進入縮圖模式 
<span style="white-space:pre">					</span>mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
<span style="white-space:pre">							</span>HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
<span style="white-space:pre">					</span>return true;
<span style="white-space:pre">				</span>} else {
<span style="white-space:pre">					</span>return false;
<span style="white-space:pre">				</span>}
<span style="white-space:pre">			</span>} else {
<span style="white-space:pre">				</span>return false;
<span style="white-space:pre">			</span>}
<span style="white-space:pre">		</span>}


<span style="white-space:pre">		</span>CellLayout.CellInfo longClickCellInfo = null;
<span style="white-space:pre">		</span>View itemUnderLongClick = null;
<span style="white-space:pre">		</span>if (v.getTag() instanceof ItemInfo) {// ItemInfo子類
<span style="white-space:pre">			</span>ItemInfo info = (ItemInfo) v.getTag();
<span style="white-space:pre">			</span>longClickCellInfo = new CellLayout.CellInfo(v, info);
<span style="white-space:pre">			</span>;
<span style="white-space:pre">			</span>itemUnderLongClick = longClickCellInfo.cell;
<span style="white-space:pre">			</span>resetAddInfo();
<span style="white-space:pre">		</span>}


<span style="white-space:pre">		</span>// The hotseat touch handling does not go through Workspace, and we
<span style="white-space:pre">		</span>// always allow long press
<span style="white-space:pre">		</span>// on hotseat items.
<span style="white-space:pre">		</span>final boolean inHotseat = isHotseatLayout(v);// 是否熱鍵欄
<span style="white-space:pre">		</span>boolean allowLongPress = inHotseat || mWorkspace.allowLongPress();//是否允許長按處理 
<span style="white-space:pre">		</span>if (allowLongPress && !mDragController.isDragging()) {// 允許長按 && 沒有進行拖拽 
<span style="white-space:pre">			</span>if (itemUnderLongClick == null) {// 如果itemUnderLongClick為null,進行長按空白處一樣的處理  
<span style="white-space:pre">				</span>// User long pressed on empty space
<span style="white-space:pre">				</span>mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
<span style="white-space:pre">						</span>HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
<span style="white-space:pre">				</span>if (mWorkspace.isInOverviewMode()) {
<span style="white-space:pre">					</span>mWorkspace.startReordering(v);
<span style="white-space:pre">				</span>} else {
<span style="white-space:pre">					</span>mWorkspace.enterOverviewMode();
<span style="white-space:pre">				</span>}
<span style="white-space:pre">			</span>} else {
<span style="white-space:pre">				</span>final boolean isAllAppsButton = inHotseat && isAllAppsButtonRank(
<span style="white-space:pre">						</span>mHotseat.getOrderInHotseat(longClickCellInfo.cellX, longClickCellInfo.cellY));
<span style="white-space:pre">				</span>                                                                 // 判斷長按是否allapp按鈕
<span style="white-space:pre">				</span>if (!(itemUnderLongClick instanceof Folder || isAllAppsButton)) {// 長按的不是allapp按鈕也不在資料夾展開的佈局中  
<span style="white-space:pre">					</span>/// M: Call the appropriate callback for the IMtkWidget on
<span style="white-space:pre">					</span>/// the current page
<span style="white-space:pre">					</span>/// when long click and begin to drag IMtkWidget.
<span style="white-space:pre">					</span>mWorkspace.startDragAppWidget(mWorkspace.getCurrentPage());
<span style="white-space:pre">					</span>// User long pressed on an item
<span style="white-space:pre">					</span>mWorkspace.startDrag(longClickCellInfo);// 開始拖拽 
<span style="white-space:pre">				</span>}
<span style="white-space:pre">			</span>}
<span style="white-space:pre">		</span>}
<span style="white-space:pre">		</span>return true;
<span style="white-space:pre">	</span>}
    以上為對於長按事件的判斷最後可以從程式碼中得出結論待到一切條件成立:
           1、允許拖拽isDraggingEnabled()
           2、WorkSpace是否被鎖定isWorkspaceLocked()
           3、是否處於WorkSpace狀態mState != State.WORKSPACE
           4、當前長按的地方是否有Item如果沒有判斷是否處於縮圖模式如果在不處理假若不在進入縮圖模式
           5、獲取到CellInfo
           6、判斷是否為Hotseat,進而判斷是否允許長按處理然後如果itemUnderLongClick為null,進行長按空白處一樣的處理 ,即進入縮圖模式。
           7、判斷長按的不是allapp按鈕也不在資料夾展開的佈局中  
           8、拖拽widget或者item
二、拖拽
        void startDrag(CellLayout.CellInfo cellInfo)
    void startDrag(CellLayout.CellInfo cellInfo) {
        View child = cellInfo.cell;
        if (LauncherLog.DEBUG_DRAG) {
            LauncherLog.d(TAG, "startDrag cellInfo = " + cellInfo + ",child = " + child);
        }

        /// M: [ALPS01263567] Abnormal case, if user long press on all apps button and then
        /// long press on other shortcuts in hotseat, the dragInfo will be
        /// null, exception will happen, so need return directly.
        if (child != null && child.getTag() == null) {
            LauncherLog.d(TAG, "Abnormal start drag: cellInfo = " + cellInfo + ",child = " + child);
            return;
        }

        // Make sure the drag was started by a long press as opposed to a long click.
        if (!child.isInTouchMode()) {//判斷是否在觸碰模式下
            if (LauncherLog.DEBUG) {
                LauncherLog.i(TAG, "The child " + child + " is not in touch mode.");
            }
            return;
        }

        mDragInfo = cellInfo;// 更新單元資訊  
        child.setVisibility(INVISIBLE);// 拖拽物件在原來的位置設為不可見可以讓其他item進行佔用該位置  
        CellLayout layout = (CellLayout) child.getParent().getParent();// 拖拽物件所在的螢幕  
        layout.prepareChildForDrag(child);

        beginDragShared(child, this);
    }

    public void beginDragShared(View child, DragSource source) {
        child.clearFocus();
        child.setPressed(false);
        
        // The outline is used to visualize where the item will land if dropped
        // 建立拖拽物件投射輪廓 
        mDragOutline = createDragOutline(child, DRAG_BITMAP_PADDING);

        mLauncher.onDragStarted(child);
        // The drag bitmap follows the touch point around on the screen
        AtomicInteger padding = new AtomicInteger(DRAG_BITMAP_PADDING);
        final Bitmap b = createDragBitmap(child, padding);// 建立拖拽影像

        final int bmpWidth = b.getWidth();
        final int bmpHeight = b.getHeight();

        float scale = mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY);
        int dragLayerX = Math.round(mTempXY[0] - (bmpWidth - scale * child.getWidth()) / 2);
        int dragLayerY = Math.round(mTempXY[1] - (bmpHeight - scale * bmpHeight) / 2
                        - padding.get() / 2);
        if (LauncherLog.DEBUG_DRAG) {
            LauncherLog.d(TAG, "beginDragShared: child = " + child + ", source = " + source
                    + ", dragLayerX = " + dragLayerX + ", dragLayerY = " + dragLayerY);
        }

        LauncherAppState app = LauncherAppState.getInstance();
        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
        Point dragVisualizeOffset = null;
        Rect dragRect = null;
        if (child instanceof BubbleTextView) {
            int iconSize = grid.iconSizePx;
            int top = child.getPaddingTop();
            int left = (bmpWidth - iconSize) / 2;
            int right = left + iconSize;
            int bottom = top + iconSize;
            dragLayerY += top;
            // Note: The drag region is used to calculate drag layer offsets, but the
            // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
            dragVisualizeOffset = new Point(-padding.get() / 2, padding.get() / 2);
            dragRect = new Rect(left, top, right, bottom);
        } else if (child instanceof FolderIcon) {
            int previewSize = grid.folderIconSizePx;
            dragRect = new Rect(0, child.getPaddingTop(), child.getWidth(), previewSize);
        }

        // Clear the pressed state if necessary
        if (child instanceof BubbleTextView) {
            BubbleTextView icon = (BubbleTextView) child;
            icon.clearPressedBackground();
        }

        if (child.getTag() == null || !(child.getTag() instanceof ItemInfo)) {
            String msg = "Drag started with a view that has no tag set. This "
                    + "will cause a crash (issue 11627249) down the line. "
                    + "View: " + child + "  tag: " + child.getTag();
            throw new IllegalStateException(msg);
        }
        // 建立拖拽檢視 
        DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
                DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale);
        dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());

        if (child.getParent() instanceof ShortcutAndWidgetContainer) {
            mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
        }

        b.recycle();
    }
1、開始拖拽
2、判斷是否在TouchMode模式下並判斷是否
3、更新單元資訊,設定拖拽物件不可見然使之可以進行被其他item佔用
4、beginDragShared
5、按照一定成的對所拖拽的物件進行投影繪製
6、OnDragStarted->建立拖拽物件->建立拖拽檢視(重難點)
7、BitMap回收(注  Bitmap:
點陣圖是位的陣列,它制訂了畫素矩陣中各畫素的顏色。
亦稱為點陣影像或繪製影像,是由稱作畫素(圖片元素)的單個點組成的。這些點可以進行不同的排列和染色以構成圖樣。當放大點陣圖時,可以看見賴以構成整個影像的無數單個方塊。擴大點陣圖尺寸的效果是增多單個畫素,從而使線條和形狀顯得參差不齊。然而,如果從稍遠的位置觀看它,點陣圖影像的顏色和形狀又顯得是連續的。在體檢時,工作人員會給你一個本子,在這個本子上有一些影像,而影像都是由一個個的點組成的,這和點陣圖影像其實是差不多的。由於每一個畫素都是單獨染色的,您可以通過以每次一個畫素的頻率操作選擇區域而產生近似相片的逼真效果,諸如加深陰影和加重顏色。縮小點陣圖尺寸也會使原圖變形,因為此舉是通過減少畫素來使整個影像變小的。同樣,由於點陣圖影像是以排列的畫素集合體形式建立的,所以不能單獨操作(如移動)區域性點陣圖。
點陣圖檔案(Bitmap),副檔名可以是.bmp或者.dib。點陣圖是Windows標準格式圖形檔案,它將影像定義為由點(畫素)組成,每個點可以由多種色彩表示,包括2、4、8、16、24和32位色彩。例如,一幅1024×768解析度的32位真彩圖片,其所佔儲存位元組數為:1024×768×32/(8*1024)=3072KB
8、結束 over

     拖拽檢視的建立
   public DragView startDrag()

    public DragView startDrag(Bitmap b, int dragLayerX, int dragLayerY,
            DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion,
            float initialDragViewScale) {
        if (PROFILE_DRAWING_DURING_DRAG) {
            android.os.Debug.startMethodTracing("Launcher");
        }

        // Hide soft keyboard, if visible
        // 隱藏軟體盤
        if (mInputMethodManager == null) {
            mInputMethodManager = (InputMethodManager)
                    mLauncher.getSystemService(Context.INPUT_METHOD_SERVICE);
        }
        mInputMethodManager.hideSoftInputFromWindow(mWindowToken, 0);
       // 呼叫各個監聽物件
        for (DragListener listener : mListeners) {
            listener.onDragStart(source, dragInfo, dragAction);
        }

        final int registrationX = mMotionDownX - dragLayerX;
        final int registrationY = mMotionDownY - dragLayerY;
        
        if (LauncherLog.DEBUG_DRAG) {
            LauncherLog.d(TAG, "startDrag: dragLayerX = " + dragLayerX + ", dragLayerY = " + dragLayerY
                    + ", dragInfo = " + dragInfo + ", registrationX = " + registrationX
                    + ", registrationY = " + registrationY + ", dragRegion = " + dragRegion);
        }

        final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left;
        final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top;
        // 記錄當前的狀態 
        mDragging = true;

        mDragObject = new DropTarget.DragObject();

        mDragObject.dragComplete = false;
        mDragObject.xOffset = mMotionDownX - (dragLayerX + dragRegionLeft);
        mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop);
        mDragObject.dragSource = source;
        mDragObject.dragInfo = dragInfo;
        // 建立DragView物件  
        final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX,
                registrationY, 0, 0, b.getWidth(), b.getHeight(), initialDragViewScale);

        if (dragOffset != null) {
            dragView.setDragVisualizeOffset(new Point(dragOffset));
        }
        if (dragRegion != null) {
            dragView.setDragRegion(new Rect(dragRegion));
        }
         // 觸控反饋
        mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
        dragView.show(mMotionDownX, mMotionDownY);
        handleMoveEvent(mMotionDownX, mMotionDownY);
        return dragView;
    }

public void onDragStart(final DragSource source, Object info, int dragAction) {  
    Log.d(TAG, "onDragStart...");  
    mIsDragOccuring = true;  
    updateChildrenLayersEnabled(false);  
    mLauncher.lockScreenOrientation();// 鎖定螢幕  
    mLauncher.onInteractionBegin();  
    setChildrenBackgroundAlphaMultipliers(1f);  
    // Prevent any Un/InstallShortcutReceivers from updating the db while we are dragging  
    // 正在拖拽的時候,防止解除安裝或安裝導致快捷圖示變化更新資料庫的操作  
    InstallShortcutReceiver.enableInstallQueue();  
    UninstallShortcutReceiver.enableUninstallQueue();  
    post(new Runnable() {  
        @Override  
        public void run() {  
            if (mIsDragOccuring) {  
                mDeferRemoveExtraEmptyScreen = false;  
                addExtraEmptyScreenOnDrag();// 新增額外的空白頁  
            }  
        }  
    });  
}  

// 顯示DragView物件(將該DragView新增到DragLayer上) 
    public void show(int touchX, int touchY) {
        mDragLayer.addView(this);

        // Start the pick-up animation
        DragLayer.LayoutParams lp = new DragLayer.LayoutParams(0, 0);
        lp.width = mBitmap.getWidth();
        lp.height = mBitmap.getHeight();
        lp.customPosition = true;
        setLayoutParams(lp);
        // 設定顯示位置
        setTranslationX(touchX - mRegistrationX);
        setTranslationY(touchY - mRegistrationY);
        if (LauncherLog.DEBUG) {
            LauncherLog.d(TAG, "show DragView: x = " + lp.x + ", y = " + lp.y + ", width = " + lp.width
                    + ", height = " + lp.height + ", this = " + this);
        }

        // Post the animation to skip other expensive work happening on the first frame
        //動畫播放   
        post(new Runnable() {
                public void run() {
                    mAnim.start();
                }
            });
    }

    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];

        if (LauncherLog.DEBUG_DRAG) {
            LauncherLog.d(TAG, "handleMoveEvent: x = " + x + ", y = " + y + ", dragView = "
                    + mDragObject.dragView + ", dragX = " + mDragObject.x + ", dragY = " + mDragObject.y);
        }

        checkTouchMove(dropTarget);// 檢查拖動時的狀態

        // Check if we are hovering over the scroll areas
        mDistanceSinceScroll +=
            Math.sqrt(Math.pow(mLastTouch[0] - x, 2) + Math.pow(mLastTouch[1] - y, 2));
        mLastTouch[0] = x;
        mLastTouch[1] = y;
        checkScrollState(x, y);// 對拖動時的翻頁進行判斷處理  
    }



1、如果軟鍵盤可見那麼隱藏
2、呼叫監聽事件
3、根據位置座標建立拖拽物件
4、反饋觸控
5、顯示DragView物件dragView.show()
6、根據當前位置處理移動事件(handleMoveEvent())

三、攔截之後的消費
  public boolean onInterceptTouchEvent(MotionEvent ev)
若上述方法返回值為true則表示有觸控事件將該訊息傳遞給onTouchEvent       
onTouchEvent ()
用以處理觸控事件。
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        @SuppressWarnings("all") // suppress dead code warning
        final boolean debug = false;
        if (debug) {
            Log.d(Launcher.TAG, "DragController.onInterceptTouchEvent " + ev + " mDragging="
                    + mDragging);
        }

        // Update the velocity tracker
        acquireVelocityTrackerAndAddMovement(ev);

        final int action = ev.getAction();
        final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
        final int dragLayerX = dragLayerPos[0];
        final int dragLayerY = dragLayerPos[1];
        if (LauncherLog.DEBUG_MOTION) {
            LauncherLog.d(TAG, "onInterceptTouchEvent: action = " + action + ", mDragging = " + mDragging
                    + ", dragLayerX = " + dragLayerX + ", dragLayerY = " + dragLayerY);
        }

        switch (action) {
            case MotionEvent.ACTION_MOVE:
                break;
            case MotionEvent.ACTION_DOWN:
                // Remember location of down touch
                mMotionDownX = dragLayerX;
                mMotionDownY = dragLayerY;
                mLastDropTarget = null;
                break;
            case MotionEvent.ACTION_UP:
                mLastTouchUpTime = System.currentTimeMillis();
                if (mDragging) {
                    PointF vec = isFlingingToDelete(mDragObject.dragSource);
                    if (!DeleteDropTarget.willAcceptDrop(mDragObject.dragInfo)) {
                        vec = null;
                    }
                    if (vec != null) {
                        dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec);
                    } else {
                        drop(dragLayerX, dragLayerY);
                    }
                }
                endDrag();
                break;
            case MotionEvent.ACTION_CANCEL:
                cancelDrag();
                break;
        }

        return mDragging;
    }
    public boolean onTouchEvent(MotionEvent ev) {
        if (!mDragging || mIsAccessibleDrag) {
            return false;
        }

        // Update the velocity tracker
        acquireVelocityTrackerAndAddMovement(ev);

        final int action = ev.getAction();
        final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
        final int dragLayerX = dragLayerPos[0];
        final int dragLayerY = dragLayerPos[1];

        switch (action) {
        case MotionEvent.ACTION_DOWN:
            // Remember where the motion event started
            mMotionDownX = dragLayerX;
            mMotionDownY = dragLayerY;

            if ((dragLayerX < mScrollZone) || (dragLayerX > mScrollView.getWidth() - mScrollZone)) {
                mScrollState = SCROLL_WAITING_IN_ZONE;
                mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
            } else {
                mScrollState = SCROLL_OUTSIDE_ZONE;
            }
            handleMoveEvent(dragLayerX, dragLayerY);
            break;
        case MotionEvent.ACTION_MOVE:
            handleMoveEvent(dragLayerX, dragLayerY);
            break;
        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) {// 拖動到垃圾箱中進行刪除
                    dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec);
                } else {
                    drop(dragLayerX, dragLayerY);
                }
            }
         // 拖放結束
            endDrag();
            break;
        case MotionEvent.ACTION_CANCEL:
            mHandler.removeCallbacks(mScrollRunnable);
            cancelDrag();
            break;
        }

        return true;
    }

四、處理移動事件
    private void handleMoveEvent(int x, int y) 
其是處理拖拽的主要方法根據被拖拽的物件的釋放位置進行判斷。
    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];

        if (LauncherLog.DEBUG_DRAG) {
            LauncherLog.d(TAG, "handleMoveEvent: x = " + x + ", y = " + y + ", dragView = "
                    + mDragObject.dragView + ", dragX = " + mDragObject.x + ", dragY = " + mDragObject.y);
        }

        checkTouchMove(dropTarget);// 檢查拖動時的狀態

        // Check if we are hovering over the scroll areas
        mDistanceSinceScroll +=
            Math.sqrt(Math.pow(mLastTouch[0] - x, 2) + Math.pow(mLastTouch[1] - y, 2));
        mLastTouch[0] = x;
        mLastTouch[1] = y;
        checkScrollState(x, y);// 對拖動時的翻頁進行判斷處理  
    }

    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;// 更新前一次記錄的拖拽目的物件為當前拖拽目的物件 
    }

    private void checkScrollState(int x, int y) {// 對拖動時的翻頁進行判斷處理 
        final int slop = ViewConfiguration.get(mLauncher).getScaledWindowTouchSlop();
        final int delay = mDistanceSinceScroll < slop ? RESCROLL_DELAY : SCROLL_DELAY;
        final DragLayer dragLayer = mLauncher.getDragLayer();
        final boolean isRtl = (dragLayer.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
        final int forwardDirection = isRtl ? SCROLL_RIGHT : SCROLL_LEFT;
        final int backwardsDirection = isRtl ? SCROLL_LEFT : SCROLL_RIGHT;

        if (x < mScrollZone) {
            if (mScrollState == SCROLL_OUTSIDE_ZONE) {
                mScrollState = SCROLL_WAITING_IN_ZONE;
                if (mDragScroller.onEnterScrollArea(x, y, forwardDirection)) {
                    dragLayer.onEnterScrollArea(forwardDirection);
                    mScrollRunnable.setDirection(forwardDirection);
                    mHandler.postDelayed(mScrollRunnable, delay);
                }
            }
        } else if (x > mScrollView.getWidth() - mScrollZone) {
            if (mScrollState == SCROLL_OUTSIDE_ZONE) {
                mScrollState = SCROLL_WAITING_IN_ZONE;
                if (mDragScroller.onEnterScrollArea(x, y, backwardsDirection)) {
                    dragLayer.onEnterScrollArea(backwardsDirection);
                    mScrollRunnable.setDirection(backwardsDirection);
                    mHandler.postDelayed(mScrollRunnable, delay);
                }
            }
        } else {
            clearScrollRunnable();
        }
    }

五、拖拽結束操作
    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.getHitRectRelativeToDragLayer(r);// 計算當前拖拽目的物件的有效觸發範圍 

            mDragObject.x = x;// 更新被拖拽物的位置資訊 
            mDragObject.y = y;
            if (r.contains(x, y)) {// 指定位置是否位於有效出發範圍內  

                dropCoordinates[0] = x;
                dropCoordinates[1] = y;
                mLauncher.getDragLayer().mapCoordInSelfToDescendent((View) target, dropCoordinates);

                return target;
            }
        }
        return null;
    }

    // 拖拽物件被放置到目標位置
    public void onDrop(final DragObject d) {
        mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView,
                mDragViewVisualCenter);// 計算拖動View的視覺中心

        CellLayout dropTargetLayout = mDropToLayout;

        // We want the point to be mapped to the dragTarget.
        // 判斷當前是否在Hotseat上,求出相對於dropTargetLayout的視覺中心座標
        if (dropTargetLayout != null) {
            if (mLauncher.isHotseatLayout(dropTargetLayout)) {
                mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
            } else {
                mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
            }
        }
        if (LauncherLog.DEBUG_DRAG) {
            LauncherLog.d(TAG, "onDrop 1: drag view = " + d.dragView + ", dragInfo = " + d.dragInfo
                    + ", dragSource  = " + d.dragSource + ", dropTargetLayout = " + dropTargetLayout
                    + ", mDragInfo = " + mDragInfo + ", mInScrollArea = " + mInScrollArea
                    + ", this = " + this);
        }

        int snapScreen = -1;
        boolean resizeOnDrop = false;
        // 如果DragObject-dragSource!= Worspace,轉而呼叫onDropExternal(),否則繼續處理onDrop()的內容
        if (d.dragSource != this) {
            final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0],
                    (int) mDragViewVisualCenter[1] };
            onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d);
        } else if (mDragInfo != null) {
            final View cell = mDragInfo.cell;

            Runnable resizeRunnable = null;
            if (dropTargetLayout != null && !d.cancelled) {
                // Move internally
                boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout);
                boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
                long container = hasMovedIntoHotseat ?
                        LauncherSettings.Favorites.CONTAINER_HOTSEAT :
                        LauncherSettings.Favorites.CONTAINER_DESKTOP;
                long screenId = (mTargetCell[0] < 0) ?
                        mDragInfo.screenId : getIdForScreen(dropTargetLayout);
                int spanX = mDragInfo != null ? mDragInfo.spanX : 1;
                int spanY = mDragInfo != null ? mDragInfo.spanY : 1;
                // First we find the cell nearest to point at which the item is
                // dropped, without any consideration to whether there is an item there.

                mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int)
                        mDragViewVisualCenter[1], spanX, spanY, dropTargetLayout, mTargetCell);
                float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
                        mDragViewVisualCenter[1], mTargetCell);
                if (LauncherLog.DEBUG_DRAG) {
                    LauncherLog.d(TAG, "onDrop 2: cell = " + cell + ", screenId = " + screenId
                            + ", mInScrollArea = " + mInScrollArea + ", mTargetCell = " + mTargetCell
                            + ", this = " + this);
                }

                // If the item being dropped is a shortcut and the nearest drop
                // cell also contains a shortcut, then create a folder with the two shortcuts.
             // 如果拖拽的物件是一個快捷圖示並且最近的位置上也是一個快捷圖示,就建立一個資料夾來防止這兩個圖示  
                if (!mInScrollArea && createUserFolderIfNecessary(cell, container,
                        dropTargetLayout, mTargetCell, distance, false, d.dragView, null)) {
                    return;
                }

                if (addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
                        distance, d, false)) {
                    return;
                }
                // 新增到已存在的資料夾上 
                // Aside from the special case where we're dropping a shortcut onto a shortcut,
                // we need to find the nearest cell location that is vacant
                ItemInfo item = (ItemInfo) d.dragInfo;
                int minSpanX = item.spanX;
                int minSpanY = item.spanY;
                if (item.minSpanX > 0 && item.minSpanY > 0) {
                    minSpanX = item.minSpanX;
                    minSpanY = item.minSpanY;
                }
    //如果不滿足資料夾的條件,則呼叫CellLayout-performReorder方法,這個方法就是處理拖動圖示時,如果當前落點被佔據時,擠開當前圖示的效果
                int[] resultSpan = new int[2];
                mTargetCell = dropTargetLayout.performReorder((int) mDragViewVisualCenter[0],
                        (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, cell,
                        mTargetCell, resultSpan, CellLayout.MODE_ON_DROP);

                /// M: We think a cell has been found only if the target cell
                /// and the span are both valid.
                boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0 && resultSpan[0] > 0
                        && resultSpan[1] > 0;
                if (LauncherLog.DEBUG) {
                    LauncherLog.d(TAG, "onDrop 3: foundCell = " + foundCell + "mTargetCell = ("
                            + mTargetCell[0] + ", " + mTargetCell[1] + "), resultSpan = ("
                            + resultSpan[0] + "," + resultSpan[1] + "), item.span = (" + item.spanX
                            + ", " + item.spanY + ") ,item.minSpan = (" + item.minSpanX + ", "
                            + item.minSpanY + "),minSpan = (" + minSpanX + "," + minSpanY + ").");
                }

                // if the widget resizes on drop
                if (foundCell && (cell instanceof AppWidgetHostView) &&
                        (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY)) {
                    resizeOnDrop = true;
                    item.spanX = resultSpan[0];
                    item.spanY = resultSpan[1];
                    AppWidgetHostView awhv = (AppWidgetHostView) cell;
                    AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, resultSpan[0],
                            resultSpan[1]);// AppWidget可能在拖動時發生縮小,因此會呼叫
                                           //AppWidgetResizeFrame-updateWidgetSizeRanges方法 
                }
                // 拖動時可能落點在別的頁面,所以還會有頁面滑動的效果
                if (getScreenIdForPageIndex(mCurrentPage) != screenId && !hasMovedIntoHotseat) {
                    snapScreen = getPageIndexForScreenId(screenId);
                    snapToPage(snapScreen);
                }
                // 如果滿足則更新位置,儲存新的位置資訊到資料庫中,播放動畫效果,否則彈回原來位置 
                if (foundCell) {
                    final ItemInfo info = (ItemInfo) cell.getTag();
                    if (hasMovedLayouts) {
                        // Reparent the view
                        CellLayout parentCell = getParentCellLayoutForView(cell);
                        if (parentCell != null) {
                            parentCell.removeView(cell);
                        } else if (LauncherAppState.isDogfoodBuild()) {
                            throw new NullPointerException("mDragInfo.cell has null parent");
                        }
                        addInScreen(cell, container, screenId, mTargetCell[0], mTargetCell[1],
                                info.spanX, info.spanY);// 更新資料庫
                    }

                    // update the item's position after drop
                    CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
                    lp.cellX = lp.tmpCellX = mTargetCell[0];
                    lp.cellY = lp.tmpCellY = mTargetCell[1];
                    lp.cellHSpan = item.spanX;
                    lp.cellVSpan = item.spanY;
                    lp.isLockedToGrid = true;

                    if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
                            cell instanceof LauncherAppWidgetHostView) {
                        final CellLayout cellLayout = dropTargetLayout;
                        // We post this call so that the widget has a chance to be placed
                        // in its final location

                        final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell;
                        AppWidgetProviderInfo pinfo = hostView.getAppWidgetInfo();
                        if (pinfo != null &&
                                pinfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE) {
                            final Runnable addResizeFrame = new Runnable() {
                                public void run() {
                                    DragLayer dragLayer = mLauncher.getDragLayer();
                                    dragLayer.addResizeFrame(info, hostView, cellLayout);
                                }
                            };
                            resizeRunnable = (new Runnable() {
                                public void run() {
                                    if (!isPageMoving()) {
                                        addResizeFrame.run();
                                    } else {
                                        mDelayedResizeRunnable = addResizeFrame;
                                    }
                                }
                            });
                        }
                    }

                    LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, lp.cellX,
                            lp.cellY, item.spanX, item.spanY);
                } else {
                    // If we can't find a drop location, we return the item to its original position
                    CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
                    mTargetCell[0] = lp.cellX;
                    mTargetCell[1] = lp.cellY;
                    CellLayout layout = (CellLayout) cell.getParent().getParent();
                    layout.markCellsAsOccupiedForView(cell);
                }
            }

            final CellLayout parent = (CellLayout) cell.getParent().getParent();
            final Runnable finalResizeRunnable = resizeRunnable;
            // Prepare it to be animated into its new position
            // This must be called after the view has been re-parented
            final Runnable onCompleteRunnable = new Runnable() {
                @Override
                public void run() {
                    mAnimatingViewIntoPlace = false;
                    updateChildrenLayersEnabled(false);
                    if (finalResizeRunnable != null) {
                        finalResizeRunnable.run();
                    }
                }
            };
            mAnimatingViewIntoPlace = true;
            if (d.dragView.hasDrawn()) {
                final ItemInfo info = (ItemInfo) cell.getTag();
                if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET) {
                    int animationType = resizeOnDrop ? ANIMATE_INTO_POSITION_AND_RESIZE :
                            ANIMATE_INTO_POSITION_AND_DISAPPEAR;
                    animateWidgetDrop(info, parent, d.dragView,
                            onCompleteRunnable, animationType, cell, false);
                } else {
                    int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION;
                    mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration,
                            onCompleteRunnable, this);
                }
            } else {
                d.deferDragViewCleanupPostAnimation = false;
                cell.setVisibility(VISIBLE);
            }
            parent.onDropChild(cell);

            /// M: Call the appropriate callback when don't drop the IMtkWidget.
            if (mTargetCell[0] == -1 && mTargetCell[1] == -1) {
                stopDragAppWidget(getPageIndexForScreenId(mDragInfo.screenId));
            }
        }
    }

拖拽結束  程式碼全程標有註釋  大概如此  我不會告訴你這篇部落格我寫了差不多一整天 好心累  熟能生巧 繼續加油


相關文章