Android4.4-Launcher源碼分析系列之關鍵的類和接口之DragScroller、DragController

一、DragScroller

DragScroller是一個接口,顧名思義是控制滑動的接口,它定義了4個方法

public interface DragScroller {
  	/**
	 * 左滑
	 */
	void scrollLeft();
    
	/**
	 * 右滑
	 */
    void scrollRight();

    /**
     *進入滑動區域
     */
    boolean onEnterScrollArea(int x, int y, int direction);

    /**
     *退出滑動區域
     */
    boolean onExitScrollArea();
}
想具體分析它還是要結合WorkSpace和DragController,

二、DragController

DragController是一個類,不繼承任何父類,內部提供了一個接口DragListener

interface DragListener {

        void onDragStart(DragSource source, Object info, int dragAction);

        void onDragEnd();
    }

onDragStart————拖動圖標開始時執行的方法

onDragEnd———— 拖動圖標結束時執行的方法

DragController是處理拖動圖標的類,那麼我們自然得關注它的觸摸事件.拖動圖標的流程是這樣的


Launcher和WorkSpace後面會詳解,大家目前只要知道這個流程即可.Launcher的onLongClick事件會調用WorkSpace的startDrag方法,startDrag方法會調用beginDragShared方法, beginDragShared方法最終調用DragController的startDrag方法.好,我們先看下這個startDrag方法.

 /**
 	 * @param b            拖動對象的圖像,有可能會被縮放
	 * @param dragLayerX   拖動對象的圖像x座標
	 * @param dragLayerY   拖動對象的圖像y座標
	 * @param source       發起拖動的對象
	 * @param dragInfo     被拖動對象的數據
	 * @param dragAction   拖放的動作:移動或者複製
	 * @param dragRegion   拖動對象bitmap的區域  
	 */
      public void 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");
        }
         // 隱藏軟鍵盤
    	  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;

        final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left;
        final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top;

        mDragging = true;
        //創建DragObject對象
        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對象
        dragView.show(mMotionDownX, mMotionDownY);
        handleMoveEvent(mMotionDownX, mMotionDownY);
    }

事實上還有一個startDrag的重載方法,不過我不知道它是什麼時候執行的,這個重載方法最終還是進入到圖中的startDrag方法.
根據傳入的一些參數創建了一個DragView,DragView就是我們拖動圖標的時候顯示的圖片,爲了驗證一下,在這裏我做一個修改

Bitmap mbBitmap=BitmapFactory.decodeResource(mLauncher.getResources(), R.drawable.hand);
        //創建DragView對象
        final DragView dragView = mDragObject.dragView = new DragView(mLauncher, mbBitmap, registrationX,
                registrationY, 0, 0, b.getWidth(), b.getHeight(), initialDragViewScale);
我傳入了一張手指的Bitmap,其他參數不變,我們看下效果.



最後一行代碼調用了handleMoveEventd方法,它是處理移動圖標的方法

private void handleMoveEvent(int x, int y) {
        //跟隨手指移動dragview
    	mDragObject.dragView.move(x, y);
        
        final int[] coordinates = mCoordinatesTemp;
        // 根據手指所在屏幕座標獲取目前所在的拖放view的容器
        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.sqrt(Math.pow(mLastTouch[0] - x, 2) + Math.pow(mLastTouch[1] - y, 2));
        mLastTouch[0] = x;
        mLastTouch[1] = y;
        checkScrollState(x, y);
    }
根據傳入的座標會調用mDragObject.dragView.move方法移動dragview,然後獲取Dragview當前處於的容器DropTarget,然後調用checkTouchMove方法

 private void checkTouchMove(DropTarget dropTarget) {
     	if (dropTarget != null) {
        	// 如果還沒有設置dropTarget或者dropTarget已經發生了改變,則設置dropTarget,調用onDragEnter
        	if (mLastDropTarget != dropTarget) {
                if (mLastDropTarget != null) {
                    //退出這個DropTarget
                	mLastDropTarget.onDragExit(mDragObject);
                 }
                dropTarget.onDragEnter(mDragObject);
                	
        	}
            dropTarget.onDragOver(mDragObject);
        } else {
        	// 如果沒有dropTarget並且上次處理的mLastDropTarget不爲空則mLastDropTarget調用移出事件
        	if (mLastDropTarget != null) {
                mLastDropTarget.onDragExit(mDragObject);
            }
        }
        mLastDropTarget = dropTarget;
    }
如果傳入的容器DropTarget不爲空,那麼如果當前的容器DropTarget與上一次的DropTarget不同並且上一次的DropTarget不爲空,則退出上一次的DropTarget,進入當前的DropTarget.

如果傳入的容器DropTarget爲空,上一次的DropTarget不爲空,那麼上一次的DropTarget移除當前DragObject

最後把mLastDropTarge重新t賦值爲當前的dropTarget.

好了,checkTouchMove方法分析完了,回到handleMoveEventd方法往下看,會繼續調用checkScrollState方法.

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)) {
                    Toast.makeText(mLauncher, "向左邊滑動", 0).show();
                	dragLayer.onEnterScrollArea(forwardDirection);
                    mScrollRunnable.setDirection(forwardDirection);
                    mHandler.postDelayed(mScrollRunnable, delay);
                }
            }
        } 
        //如果從右往左拖動到滾動區域
        else if (x > mScrollView.getWidth() - mScrollZone) {
        	System.out.println("...................區域外");
        	if (mScrollState == SCROLL_OUTSIDE_ZONE) {
                mScrollState = SCROLL_WAITING_IN_ZONE;
                if (mDragScroller.onEnterScrollArea(x, y, backwardsDirection)) {
                	Toast.makeText(mLauncher, "向右邊滑動", 0).show();
                	dragLayer.onEnterScrollArea(backwardsDirection);
                    mScrollRunnable.setDirection(backwardsDirection);
                    mHandler.postDelayed(mScrollRunnable, delay);
                }
            }
        } 
        // 在滾動之前進入了不滾動區域,則取消滾動
        else {
            clearScrollRunnable();
        }
    }
ViewConfiguration.get(mLauncher).getScaledWindowTouchSlop()是獲取可以觸發移動事件的最短距離,賦值給slop.
mDistanceSinceScroll是上一次手指移動的距離,如果mDistanceSinceScroll小於slop,那麼delay取值RESCROLL_DELAY,默認是900,否則取SCROLL_DELAY,默認值爲500.

isRtl是判斷是否從右到左移動,is Right To Left的意思.然後根據isRtl的值取forwardDirection和backwardsDirection,這兩個是往前和往後的標誌.

如果x座標小於mScrollZone,說明是從左到右拖動圖標,那麼進行滑動到右邊屏幕的操作

如果x座標大於當前屏幕寬度和可滑動區域的差值,說明是從右到左拖動圖標,那麼進行滑動到左邊屏幕的操作

否則取消滾動.

checkTouchMove方法執行完後,會執行onTouchEvent方法,我覺得奇怪的是onTouchEvent的down事件無法觸發,不知道什麼原因.

move事件裏又調用了handleMoveEvent方法.

up事件裏就停止拖動,做一些恢復操作.

cancel事件裏清除handler消息隊列,取消拖動

public boolean onTouchEvent(MotionEvent ev) {
        if (!mDragging) {
            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:
        	 
        	// 確保我們在擡起點處理了事件
            handleMoveEvent(dragLayerX, dragLayerY);
            mHandler.removeCallbacks(mScrollRunnable);
            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:
            mHandler.removeCallbacks(mScrollRunnable);
            cancelDrag();
            break;
        }

        return true;
    }
DragController的分析就到此爲止了.



 

 

 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章