Android-->Launcher拖拽事件詳解【androidICS4.0--Launcher系列二】

        AndroidICS4.0版本的launcher拖拽的流程,基本和2.3的相似。就是比2.3寫的封裝的接口多了一些,比如刪除類的寫法就多了個類。等等。4.0的改變有一些,但是不是特別大。這個月一直在改動Launcher的縮略圖的效果,4.0的縮略圖的功能沒有實現,還得從2.3的Launcher中摘出來。通過做這個縮略圖對Launcher的模塊有一點點了解,拿來分享一下Launcher拖拽的工作流程。微笑有圖有真相!吐舌頭

轉載請標明出處:http://blog.csdn.net/wdaming1986/article/details/7671318

                (1) 先來看看類之間的繼承關係

                           

                                                          圖(1)

                                                                                                                                               

               (2)再來看看Launcher拖拽流程的時序圖

              

                                                                       圖(2)

 

下面咱們分步來解析Launcher拖拽的詳細過程:

step 1 :先來看看Launcher.java這個類的onCreate()方法中的setupViews()方法中的一部分代碼:

 // Setup the workspace
        mWorkspace.setHapticFeedbackEnabled(false);
        mWorkspace.setOnLongClickListener(this);
        mWorkspace.setup(dragController);
        dragController.addDragListener(mWorkspace);

Workspace設置長按事件的監聽交給了Launcher.java這個類了。所以在主屏上長按事件會走到Launcher.java----->

onLongClick()這個方法中去;

step 2 :接着我們來看看Launcher.java中onLongClick()的代碼:

public boolean onLongClick(View v) {
         ··············
 // The hotseat touch handling does not go through Workspace, and we always allow long press
        // on hotseat items.
        final View itemUnderLongClick = longClickCellInfo.cell;
        boolean allowLongPress = isHotseatLayout(v) || mWorkspace.allowLongPress();
        if (allowLongPress && !mDragController.isDragging()) {
            if (itemUnderLongClick == null) {
                // User long pressed on empty space
                mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
                        HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
                startWallpaper();
            } else {
                if (!(itemUnderLongClick instanceof Folder)) {
                    // User long pressed on an item
                    mWorkspace.startDrag(longClickCellInfo);
                }
            }
        }
        return true;
    }

通過itemUnderLongClick == null 來判斷,在屏幕上觸發長按事件是否選中了shortcut或者widget。如果爲空,就啓動桌面的壁紙,else,就把拖拽事件往Workspace.java這個類傳遞。

 

Step 3 :通過mWorkspace.startDrag(longClickCellInfo),把長按事件傳遞給workspace來處理,具體來看代碼:

   void startDrag(CellLayout.CellInfo cellInfo) {
        View child = cellInfo.cell;

        // Make sure the drag was started by a long press as opposed to a long click.
        if (!child.isInTouchMode()) {
            return;
        }

        mDragInfo = cellInfo;
        //隱藏拖拽的child
        child.setVisibility(GONE);

        child.clearFocus();
        child.setPressed(false);

        final Canvas canvas = new Canvas();

        // We need to add extra padding to the bitmap to make room for the glow effect
        final int bitmapPadding = HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS;

        // The outline is used to visualize where the item will land if dropped
        mDragOutline = createDragOutline(child, canvas, bitmapPadding);
        beginDragShared(child, this);
    }

上面的代碼主要做的工作是:把正在拖拽的這個view隱藏掉,在主屏幕上繪製一個藍色的,大小和圖標相似的一個邊框,以表示能在主屏的這個位置放置。

 

Step 4 :接着調用beginDragShared(child, this)這個方法,代碼如下:

 public void beginDragShared(View child, DragSource source) {
    ··· ···
// Clear the pressed state if necessary
        if (child instanceof BubbleTextView) {
            BubbleTextView icon = (BubbleTextView) child;
            icon.clearPressedOrFocusedBackground();
        }

        mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
                DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect);
        b.recycle();
    }

這個方法做的工作是:開始進行拖拽,繪製正在拖拽的圖片,把拖拽的事件交給DragController來處理。

 

Step 5 :接着來看看mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(), DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect)這個方法,代碼如下:

 public void startDrag(Bitmap b, int dragLayerX, int dragLayerY,
            DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion) {
··· ···
 mDragObject.dragComplete = false;
        mDragObject.xOffset = mMotionDownX - (dragLayerX + dragRegionLeft);
        mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop);
        mDragObject.dragSource = source;
        mDragObject.dragInfo = dragInfo;
mVibrator.vibrate(VIBRATE_DURATION);

        final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX,
                registrationY, 0, 0, b.getWidth(), b.getHeight());

        if (dragOffset != null) {
            dragView.setDragVisualizeOffset(new Point(dragOffset));
        }
        if (dragRegion != null) {
            dragView.setDragRegion(new Rect(dragRegion));
        }

        dragView.show(mMotionDownX, mMotionDownY);
        handleMoveEvent(mMotionDownX, mMotionDownY);
    }

這個方法的作用是:計算要拖拽的view的大小,顯示在workspace上,dragView.show(mMotionDownX, mMotionDownY);這個show()會根據手指的移動而移動的。然後在通過handleMoveEvent()方法來分發拖拽的目標到底在哪個目標上。DropTarget一共有3個:workspace,ButtonDropTarget(刪除類),Folder;他們分別實現了DropTarget這個接口。

下面來看看這個接口有一下幾個方法:

boolean isDropEnabled();
void onDrop(DragObject dragObject);

    void onDragEnter(DragObject dragObject);

    void onDragOver(DragObject dragObject);

    void onDragExit(DragObject dragObject);
DropTarget getDropTargetDelegate(DragObject dragObject);
boolean acceptDrop(DragObject dragObject);

    // These methods are implemented in Views
    void getHitRect(Rect outRect);
    void getLocationInDragLayer(int[] loc);
    int getLeft();
    int getTop();

這些方法不是每個類繼承了DropTarget的接口,都要把每個方法都實現,這要看具體的需要來定。

 

另外這個接口中有個內部類-----DragObject:如下

class DragObject {
        public int x = -1;
        public int y = -1;

        /** X offset from the upper-left corner of the cell to where we touched.  */
        public int xOffset = -1;

        /** Y offset from the upper-left corner of the cell to where we touched.  */
        public int yOffset = -1;

        /** This indicates whether a drag is in final stages, either drop or cancel. It
         * differentiates onDragExit, since this is called when the drag is ending, above
         * the current drag target, or when the drag moves off the current drag object.
         */
        public boolean dragComplete = false;

        /** The view that moves around while you drag.  */
        public DragView dragView = null;

        /** The data associated with the object being dragged */
        public Object dragInfo = null;

        /** Where the drag originated */
        public DragSource dragSource = null;

        /** Post drag animation runnable */
        public Runnable postAnimationRunnable = null;

        /** Indicates that the drag operation was cancelled */
        public boolean cancelled = false;

        public DragObject() {
        }
    }

這個類的作用是存儲一些座標,拖拽點距離整個view左上角x軸上的距離,y軸上的距離,還有一些拖拽的信息都保存在這個類中,還有動畫線程類等等。在拖拽過程中這些信息都是會用到的。

 

Step 6 :接着來看看handleMoveEvent()這個類,這個類頻繁被調用,因爲在DragLayer.java這個類中onTouchEvent()方法,最後調用的是 mDragController.onTouchEvent(ev)這個方法,長按後,移動的事件就傳遞到了DragController中的onTouchEvent()方法中,先來看看mDragController.onTouchEvent(ev)這個方法,代碼如下:

/**
     * Call this from a drag source view.
     */
    public boolean onTouchEvent(MotionEvent ev) {
        if (!mDragging) {
            return false;
        }

        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;
            }
            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) {
                drop(dragLayerX, dragLayerY);
            }
            endDrag();
            break;
        case MotionEvent.ACTION_CANCEL:
            cancelDrag();
            break;
        }

        return true;
    }

在這個方法中清楚的可以看見handleMoveEvent()這個方法會在move,up的時候頻繁地調用。

現在再來看看這個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];
        if (dropTarget != null) {
            DropTarget delegate = dropTarget.getDropTargetDelegate(mDragObject);
            if (delegate != null) {
                dropTarget = delegate;
            }

            if (mLastDropTarget != dropTarget) {
                if (mLastDropTarget != null) {
                    mLastDropTarget.onDragExit(mDragObject);
                }
                dropTarget.onDragEnter(mDragObject);
            }
            dropTarget.onDragOver(mDragObject);
        } else {
            if (mLastDropTarget != null) {
                mLastDropTarget.onDragExit(mDragObject);
            }
        }
        mLastDropTarget = dropTarget;

··· ···
}

這個方法的作用:通過findDropTarget(x, y, coordinates),來判斷在哪個拖拽目標裏面,然後通過下面的if判斷來執行不同的onDragOver,onDragExit等的方法。這樣就在相應的類中去做處理,以後的事情就明朗了。這就是Launcher的拖拽事件的分發與處理,用到了MVC的思想,代碼閱讀起來還是比較順利的。有圖有真相。

歡迎大家留言討論相關問題。

 

 

 

 

發佈了88 篇原創文章 · 獲贊 25 · 訪問量 124萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章