Android 4.0 Launcher2源码分析——桌面快捷图标的拖拽

本文来自http://blog.csdn.net/chenshaoyang0011 转载请申明文章出处!

通过上一篇文章Android4.0Launcher2源码分析(五)——Workspace的滑动中,已经了解了LauncherViewTree中各层所负责的工作,在DragLayer中就负责对快捷图标和AppWidget等组件的拖拽工作。桌面的滑动和图标的拖拽是两项独立的工作,正常情况下我们用手指滑动桌面会触发滑动操作,而当长按一个图标时,则会触发图标的拖拽操作,此时再滑动则会拖拽图标移动而桌面不会滑动。那么这里就分两大部分来探讨:1、拖拽操作的启动。2、拖拽。


一、拖拽操作的启动

那么首先进入Launcher.onCreate()中来探究下如何激活拖拽的状态。

  1. protected void onCreate(Bundle savedInstanceState) {  
  2.         ......  
  3.         setupViews();  
  4.         ......  
  5.     }  
protected void onCreate(Bundle savedInstanceState) {
        ......
        setupViews();
        ......
    }
接着进入setupViews();
  1. private void setupViews() {  
  2.         ......  
  3.         mWorkspace.setOnLongClickListener(this);  
  4.         ......  
  5.     }  
private void setupViews() {
        ......
        mWorkspace.setOnLongClickListener(this);
        ......
    }
从这里我们可以看到对Workspace设置了OnLongClickListener,而Launcher又实现了这个接口。接着进入Launcher.onLongClick()
  1. public boolean onLongClick(View v) {  
  2.         ......  
  3.         if (!(v instanceof CellLayout)) {  
  4.             v = (View) v.getParent().getParent();  
  5.         }  
  6.         resetAddInfo();  
  7.         CellLayout.CellInfo longClickCellInfo = (CellLayout.CellInfo) v.getTag();  
  8.         ......  
  9.         // The hotseat touch handling does not go through Workspace, and we always allow long press   
  10.         // on hotseat items.   
  11.         final View itemUnderLongClick = longClickCellInfo.cell;  
  12.         boolean allowLongPress = isHotseatLayout(v) || mWorkspace.allowLongPress();  
  13.         if (allowLongPress && !mDragController.isDragging()) {  
  14.             if (itemUnderLongClick == null) {  
  15.                 ......  
  16.             } else {  
  17.                 if (!(itemUnderLongClick instanceof Folder)) {  
  18.                     // User long pressed on an item   
  19.                     mWorkspace.startDrag(longClickCellInfo);  
  20.                 }  
  21.             }  
  22.         }  
  23.         return true;  
  24.     }  
public boolean onLongClick(View v) {
        ......
        if (!(v instanceof CellLayout)) {
            v = (View) v.getParent().getParent();
        }
        resetAddInfo();
        CellLayout.CellInfo longClickCellInfo = (CellLayout.CellInfo) v.getTag();
        ......
        // 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) {
                ......
            } else {
                if (!(itemUnderLongClick instanceof Folder)) {
                    // User long pressed on an item
                    mWorkspace.startDrag(longClickCellInfo);
                }
            }
        }
        return true;
    }
当用户在一个item上长按时,则itemUnderLongClick != null,再通过调用Workspace.startDrag()来激活item的拖拽。下面先通过时序图来看下拖拽状态激活所经历的过程:


图标拖拽功能的激活大概可以分为六步,下面就一步一步的探究下其中的实现:

Step1:Workspace.startDrag(CellLayout.CellInfo cellInfo)

  1. void startDrag(CellLayout.CellInfo cellInfo) {  
  2.         View child = cellInfo.cell;  
  3.         ......  
  4.         mDragInfo = cellInfo;  
  5.         //使图标从桌面上消失,给人一种被“拖到空中”的感觉   
  6.         child.setVisibility(GONE);  
  7.         ......  
  8.         final Canvas canvas = new Canvas();  
  9.   
  10.         // We need to add extra padding to the bitmap to make room for the glow effect   
  11.         final int bitmapPadding = HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS;  
  12.   
  13.         // The outline is used to visualize where the item will land if dropped   
  14.         //图标的轮廓,在桌面上的对应的位置绘制图标的轮廓,显示当手松开图标时它在桌面上的落点   
  15.         mDragOutline = createDragOutline(child, canvas, bitmapPadding);  
  16.         beginDragShared(child, this);  
  17.     }  
void startDrag(CellLayout.CellInfo cellInfo) {
        View child = cellInfo.cell;
        ......
        mDragInfo = cellInfo;
        //使图标从桌面上消失,给人一种被“拖到空中”的感觉
        child.setVisibility(GONE);
        ......
        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);
    }
在这个方法中,主要的工作就是让图标从桌面上消失,并且显示一个图标的外部轮廓,以表明它将要放置的位置,其显示的效果如下:


显示图标的轮廓可以从视觉上给用户更加好的体验。接着,进入beginDragShared()

Step2:Workspace.beginDragShared(View child,DragSource source)

  1. public void beginDragShared(View child, DragSource source) {  
  2.         ......  
  3.         // The drag bitmap follows the touch point around on the screen   
  4.         final Bitmap b = createDragBitmap(child, new Canvas(), bitmapPadding);  
  5.         final int bmpWidth = b.getWidth();  
  6.   
  7.         //我们将在DragLayer中绘制“拖拽后”的图标,通过DragLayer.getLoactionInDragLayer()   
  8.         //获取在DragLayer中的坐标,并存放在mTempXY中。   
  9.         mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY);  
  10.         final int dragLayerX = (int) mTempXY[0] + (child.getWidth() - bmpWidth) / 2;  
  11.         int dragLayerY = mTempXY[1] - bitmapPadding / 2;  
  12.   
  13.         Point dragVisualizeOffset = null;  
  14.         Rect dragRect = null;  
  15.           
  16.         //无论child是BubbleTextView或者PagedViewIncon或者FolderIcon的实例   
  17.         //定位图标的位置与大小   
  18.         if (child instanceof BubbleTextView || child instanceof PagedViewIcon) {  
  19.             int iconSize = r.getDimensionPixelSize(R.dimen.app_icon_size);  
  20.             int iconPaddingTop = r.getDimensionPixelSize(R.dimen.app_icon_padding_top);  
  21.             int top = child.getPaddingTop();  
  22.             int left = (bmpWidth - iconSize) / 2;  
  23.             int right = left + iconSize;  
  24.             int bottom = top + iconSize;  
  25.             dragLayerY += top;  
  26.             // Note: The drag region is used to calculate drag layer offsets, but the   
  27.             // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.   
  28.             dragVisualizeOffset = new Point(-bitmapPadding / 2, iconPaddingTop - bitmapPadding / 2);  
  29.             dragRect = new Rect(left, top, right, bottom);  
  30.         } else if (child instanceof FolderIcon) {  
  31.             int previewSize = r.getDimensionPixelSize(R.dimen.folder_preview_size);  
  32.             dragRect = new Rect(00, child.getWidth(), previewSize);  
  33.         }  
  34.         ......  
  35.         mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),  
  36.                 DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect);  
  37.         b.recycle();  
  38.     }  
public void beginDragShared(View child, DragSource source) {
        ......
        // The drag bitmap follows the touch point around on the screen
        final Bitmap b = createDragBitmap(child, new Canvas(), bitmapPadding);
        final int bmpWidth = b.getWidth();

        //我们将在DragLayer中绘制“拖拽后”的图标,通过DragLayer.getLoactionInDragLayer()
        //获取在DragLayer中的坐标,并存放在mTempXY中。
        mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY);
        final int dragLayerX = (int) mTempXY[0] + (child.getWidth() - bmpWidth) / 2;
        int dragLayerY = mTempXY[1] - bitmapPadding / 2;

        Point dragVisualizeOffset = null;
        Rect dragRect = null;
        
        //无论child是BubbleTextView或者PagedViewIncon或者FolderIcon的实例
        //定位图标的位置与大小
        if (child instanceof BubbleTextView || child instanceof PagedViewIcon) {
            int iconSize = r.getDimensionPixelSize(R.dimen.app_icon_size);
            int iconPaddingTop = r.getDimensionPixelSize(R.dimen.app_icon_padding_top);
            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(-bitmapPadding / 2, iconPaddingTop - bitmapPadding / 2);
            dragRect = new Rect(left, top, right, bottom);
        } else if (child instanceof FolderIcon) {
            int previewSize = r.getDimensionPixelSize(R.dimen.folder_preview_size);
            dragRect = new Rect(0, 0, child.getWidth(), previewSize);
        }
        ......
        mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
                DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect);
        b.recycle();
    }
Workspace.beginSharedDrag()中主要所做的工作就是计算拖拽目标位于DragLayer中的坐标和尺寸大小,接着又调用DragController.startDrag()

Step3:DragController.startDrag(Bitmap b ,int dragLayerX, int dragLayerY,DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion)

  1. /** 
  2.  * Starts a drag. 
  3.  * 
  4.  * @param b The bitmap to display as the drag image.  It will be re-scaled to the 
  5.  *          enlarged size. 
  6.  * @param dragLayerX The x position in the DragLayer of the left-top of the bitmap. 
  7.  * @param dragLayerY The y position in the DragLayer of the left-top of the bitmap. 
  8.  * @param source An object representing where the drag originated 
  9.  * @param dragInfo The data associated with the object that is being dragged 
  10.  * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or 
  11.  *        {@link #DRAG_ACTION_COPY} 
  12.  * @param dragRegion Coordinates within the bitmap b for the position of item being dragged. 
  13.  *          Makes dragging feel more precise, e.g. you can clip out a transparent border 
  14.  */  
  15. public void startDrag(Bitmap b, int dragLayerX, int dragLayerY,  
  16.         DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion) {  
  17.     ......  
  18.     for (DragListener listener : mListeners) {  
  19.         listener.onDragStart(source, dragInfo, dragAction);  
  20.     }  
  21.   
  22.     final int registrationX = mMotionDownX - dragLayerX;  
  23.     final int registrationY = mMotionDownY - dragLayerY;  
  24.   
  25.     final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left;  
  26.     final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top;  
  27.   
  28.     //设置mDragging=true,表示拖拽已经开始   
  29.     //在DragLayer的onInterceptTouchEvent()中根据这个值判断是否拦截MotionEvent   
  30.     mDragging = true;  
  31.   
  32.     //实例化DragObject,表示拖拽的对象   
  33.     //封装了拖拽对象的信息   
  34.     mDragObject = new DropTarget.DragObject();  
  35.   
  36.     mDragObject.dragComplete = false;  
  37.     mDragObject.xOffset = mMotionDownX - (dragLayerX + dragRegionLeft);  
  38.     mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop);  
  39.     mDragObject.dragSource = source;  
  40.     mDragObject.dragInfo = dragInfo;  
  41.     ......  
  42.     final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX,  
  43.             registrationY, 00, b.getWidth(), b.getHeight());  
  44.     ......  
  45.     //将拖拽的图标显示在DragLayer中   
  46.     dragView.show(mMotionDownX, mMotionDownY);  
  47.     handleMoveEvent(mMotionDownX, mMotionDownY);  
  48. }  
    /**
     * Starts a drag.
     *
     * @param b The bitmap to display as the drag image.  It will be re-scaled to the
     *          enlarged size.
     * @param dragLayerX The x position in the DragLayer of the left-top of the bitmap.
     * @param dragLayerY The y position in the DragLayer of the left-top of the bitmap.
     * @param source An object representing where the drag originated
     * @param dragInfo The data associated with the object that is being dragged
     * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or
     *        {@link #DRAG_ACTION_COPY}
     * @param dragRegion Coordinates within the bitmap b for the position of item being dragged.
     *          Makes dragging feel more precise, e.g. you can clip out a transparent border
     */
    public void startDrag(Bitmap b, int dragLayerX, int dragLayerY,
            DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion) {
        ......
        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,表示拖拽已经开始
        //在DragLayer的onInterceptTouchEvent()中根据这个值判断是否拦截MotionEvent
        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;
        ......
        final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX,
                registrationY, 0, 0, b.getWidth(), b.getHeight());
        ......
        //将拖拽的图标显示在DragLayer中
        dragView.show(mMotionDownX, mMotionDownY);
        handleMoveEvent(mMotionDownX, mMotionDownY);
    }

代码中显示通过一个for语句调用了DragListener.onDragStart()方法,通知它们已经开始拖拽了,其中由于Workspace实现了DragListener并且添加到了mListeners中。所以Workspace.onDragStart()被调用。然后又封装了一个DragObject对象,封装DragSourceDragInfoDragView等信息。接着,将调用DragView.show()DragView显示在DragLayer中。


Step4:DragView.show(int touchX,int touchY)

  1. /** 
  2.      * Create a window containing this view and show it. 
  3.      * 
  4.      * @param touchX the x coordinate the user touched in DragLayer coordinates 
  5.      * @param touchY the y coordinate the user touched in DragLayer coordinates 
  6.      */  
  7.     public void show(int touchX, int touchY) {  
  8.         //将DragView添加到DragLayer中   
  9.         mDragLayer.addView(this);  
  10.           
  11.         //设置位置、尺寸等信息   
  12.         DragLayer.LayoutParams lp = new DragLayer.LayoutParams(00);  
  13.         lp.width = mBitmap.getWidth();  
  14.         lp.height = mBitmap.getHeight();  
  15.         lp.x = touchX - mRegistrationX;  
  16.         lp.y = touchY - mRegistrationY;  
  17.         lp.customPosition = true;  
  18.         setLayoutParams(lp);  
  19.         mLayoutParams = lp;  
  20.         mAnim.start();  
  21.     }  
/**
     * Create a window containing this view and show it.
     *
     * @param touchX the x coordinate the user touched in DragLayer coordinates
     * @param touchY the y coordinate the user touched in DragLayer coordinates
     */
    public void show(int touchX, int touchY) {
        //将DragView添加到DragLayer中
        mDragLayer.addView(this);
        
        //设置位置、尺寸等信息
        DragLayer.LayoutParams lp = new DragLayer.LayoutParams(0, 0);
        lp.width = mBitmap.getWidth();
        lp.height = mBitmap.getHeight();
        lp.x = touchX - mRegistrationX;
        lp.y = touchY - mRegistrationY;
        lp.customPosition = true;
        setLayoutParams(lp);
        mLayoutParams = lp;
        mAnim.start();
    }

其中的内容很简单易懂,就是在将DragView添加到了DragLayer中,并且在合适的位置显示了出来。接着应该调用在DragController.startDrag()中调用handleMoveEvent(),这个将在后文将拖拽过程分析时在看。到这一步,拖拽操作的启动过程就完成了。接着就可以拖拽图标了。


二、拖拽

通过了前面文章的分析,已经知道了拖拽过程的实现在DragLayer中,当进行图标的拖拽时,DragLayer.onInterceptTouchEvent()就会对MotionEvent进行拦截。并且

在自身的onTouchEvent()方法中进行操作,从而实现图标的移动。由于onInterceptTouchEvent()拦截了MotionEvent,因此WorkspaceUI控件不会接收到事件,从而不会产生

干扰。那么首先进入DragLayer.onInterceptTouchEvent()


  1. public boolean onInterceptTouchEvent(MotionEvent ev) {  
  2.         ......  
  3.         return mDragController.onInterceptTouchEvent(ev);  
  4.     }  
public boolean onInterceptTouchEvent(MotionEvent ev) {
        ......
        return mDragController.onInterceptTouchEvent(ev);
    }
代码中省略了与其他功能的不部分代码,最后调用了DragController.onInterceptTouchEvent() ,并取其返回值作为自身方法的返回值。进入DragController.onInterceptTouchEvent()。
  1. /** 
  2.      * Call this from a drag source view. 
  3.      */  
  4.     public boolean onInterceptTouchEvent(MotionEvent ev) {  
  5.         ......  
  6.         final int action = ev.getAction();  
  7.   
  8.         final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());  
  9.         final int dragLayerX = dragLayerPos[0];  
  10.         final int dragLayerY = dragLayerPos[1];  
  11.   
  12.         switch (action) {  
  13.             case MotionEvent.ACTION_MOVE:  
  14.                 break;  
  15.             case MotionEvent.ACTION_DOWN:  
  16.                 // Remember location of down touch   
  17.                 mMotionDownX = dragLayerX;  
  18.                 mMotionDownY = dragLayerY;  
  19.                 mLastDropTarget = null;  
  20.                 break;  
  21.             case MotionEvent.ACTION_UP:  
  22.                 if (mDragging) {  
  23.                     drop(dragLayerX, dragLayerY);  
  24.                 }  
  25.                 endDrag();  
  26.                 break;  
  27.             case MotionEvent.ACTION_CANCEL:  
  28.                 cancelDrag();  
  29.                 break;  
  30.         }  
  31.   
  32.         return mDragging;  
  33.     }  
/**
     * Call this from a drag source view.
     */
    public boolean onInterceptTouchEvent(MotionEvent 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_MOVE:
                break;
            case MotionEvent.ACTION_DOWN:
                // Remember location of down touch
                mMotionDownX = dragLayerX;
                mMotionDownY = dragLayerY;
                mLastDropTarget = null;
                break;
            case MotionEvent.ACTION_UP:
                if (mDragging) {
                    drop(dragLayerX, dragLayerY);
                }
                endDrag();
                break;
            case MotionEvent.ACTION_CANCEL:
                cancelDrag();
                break;
        }

        return mDragging;
    }

这里我们关心的是它的返回值。可以看到方法将mDragging作为返回值。当触发了拖拽状态,在的DragController.startDrag()中将mDragging的值改为true。所以这里也将返回trueDragLayer将拦截MotionEvent,并传给自身的onTouchEvent()方法,在onTouchEvent()中对图标进行移动,刷新界面。


  1. /** 
  2.  * Call this from a drag source view. 
  3.  */  
  4. public boolean onTouchEvent(MotionEvent ev) {  
  5.     ......  
  6.     final int action = ev.getAction();  
  7.     final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());  
  8.     final int dragLayerX = dragLayerPos[0];  
  9.     final int dragLayerY = dragLayerPos[1];  
  10.   
  11.     switch (action) {  
  12.     case MotionEvent.ACTION_DOWN:  
  13.         // Remember where the motion event started   
  14.         mMotionDownX = dragLayerX;  
  15.         mMotionDownY = dragLayerY;  
  16.   
  17.         //判断当前的触点是否处于屏幕边缘的ScrollZone,当处于这个区域时   
  18.         //状态mScrollState将转变为SCROLL,并且在一定时间的停留之后,屏幕滑动到另一屏。   
  19.         if ((dragLayerX < mScrollZone) || (dragLayerX > mScrollView.getWidth() - mScrollZone)) {  
  20.             mScrollState = SCROLL_WAITING_IN_ZONE;  
  21.             mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);  
  22.         } else {  
  23.             mScrollState = SCROLL_OUTSIDE_ZONE;  
  24.         }  
  25.         break;  
  26.     case MotionEvent.ACTION_MOVE:  
  27.         //调用handleMoveEvent()处理图标移动   
  28.         handleMoveEvent(dragLayerX, dragLayerY);  
  29.         break;  
  30.     case MotionEvent.ACTION_UP:  
  31.         // Ensure that we've processed a move event at the current pointer location.   
  32.         handleMoveEvent(dragLayerX, dragLayerY);  
  33.   
  34.         mHandler.removeCallbacks(mScrollRunnable);  
  35.         if (mDragging) {  
  36.           <SPAN style="WHITE-SPACE: pre">   </SPAN>//根据目前相对DragLayer的坐标,将图标“降落”到指定的DropTarget上。   
  37.             drop(dragLayerX, dragLayerY);  
  38.         }  
  39.         endDrag();  
  40.         break;  
  41.     case MotionEvent.ACTION_CANCEL:  
  42.         cancelDrag();  
  43.         break;  
  44.     }  
  45.     return true;  
  46. }  
    /**
     * Call this from a drag source view.
     */
    public boolean onTouchEvent(MotionEvent 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;

            //判断当前的触点是否处于屏幕边缘的ScrollZone,当处于这个区域时
            //状态mScrollState将转变为SCROLL,并且在一定时间的停留之后,屏幕滑动到另一屏。
            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()处理图标移动
            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) {
              	//根据目前相对DragLayer的坐标,将图标“降落”到指定的DropTarget上。
                drop(dragLayerX, dragLayerY);
            }
            endDrag();
            break;
        case MotionEvent.ACTION_CANCEL:
            cancelDrag();
            break;
        }
        return true;
    }

onTouchEvent()中处理的事件涉及到不同状态之间的转换,以及每种状态之下对相应的MotionEvent的对策。这里同样,从简单的情况入手:图标拖拽起来后,移动一段距离,在屏幕的另一个位置放下。

首先,当拖拽起图标时,拖拽图标的状态被启动,这就是第一部分所探讨的内容。

然后,移动拖拽的图标。此时触发了MotionEvent.ACTION_MOVE事件,紧接着调用handleMoveEvent()来处理移动。进入handleMoveEvent()来看看图标移动是怎么实现的。


  1. private void handleMoveEvent(int x, int y) {  
  2.         //更新在DragLayer中的位置   
  3.         mDragObject.dragView.move(x, y);  
  4.   
  5.         // Drop on someone?   
  6.         final int[] coordinates = mCoordinatesTemp;  
  7.           
  8.         //根据当前的位置寻找DropTarget对象来放置图标   
  9.         DropTarget dropTarget = findDropTarget(x, y, coordinates);  
  10.         mDragObject.x = coordinates[0];  
  11.         mDragObject.y = coordinates[1];  
  12.         if (dropTarget != null) {  
  13.             DropTarget delegate = dropTarget.getDropTargetDelegate(mDragObject);  
  14.             if (delegate != null) {  
  15.                 dropTarget = delegate;  
  16.             }  
  17.   
  18.             if (mLastDropTarget != dropTarget) {  
  19.                 if (mLastDropTarget != null) {  
  20.                     //从最后一次记录的DropTarget中退出   
  21.                     mLastDropTarget.onDragExit(mDragObject);  
  22.                 }  
  23.                 //进入到当前寻找到的DropTarget   
  24.                 dropTarget.onDragEnter(mDragObject);  
  25.             }  
  26.             dropTarget.onDragOver(mDragObject);  
  27.         } else {  
  28.             if (mLastDropTarget != null) {  
  29.                 mLastDropTarget.onDragExit(mDragObject);  
  30.             }  
  31.         }  
  32.         mLastDropTarget = dropTarget;  
  33.   
  34.         // Scroll, maybe, but not if we're in the delete region.   
  35.         boolean inDeleteRegion = false;  
  36.         if (mDeleteRegion != null) {  
  37.             inDeleteRegion = mDeleteRegion.contains(x, y);  
  38.         }  
  39.   
  40.         // After a scroll, the touch point will still be in the scroll region.   
  41.         // Rather than scrolling immediately, require a bit of twiddling to scroll again   
  42.         final int slop = ViewConfiguration.get(mLauncher).getScaledWindowTouchSlop();  
  43.         mDistanceSinceScroll +=  
  44.             Math.sqrt(Math.pow(mLastTouch[0] - x, 2) + Math.pow(mLastTouch[1] - y, 2));  
  45.         mLastTouch[0] = x;  
  46.         mLastTouch[1] = y;  
  47.   
  48.         //判断当前拖拽的图标是否处于ScrollZone即滑动区域。   
  49.         //并且根据在哪个一个ScrollZone来处理屏幕滑动的方向。   
  50.         if (!inDeleteRegion && x < mScrollZone) {  
  51.             if (mScrollState == SCROLL_OUTSIDE_ZONE && mDistanceSinceScroll > slop) {  
  52.                 mScrollState = SCROLL_WAITING_IN_ZONE;  
  53.                 if (mDragScroller.onEnterScrollArea(x, y, SCROLL_LEFT)) {  
  54.                     mScrollRunnable.setDirection(SCROLL_LEFT);  
  55.                     mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);  
  56.                 }  
  57.             }  
  58.         } else if (!inDeleteRegion && x > mScrollView.getWidth() - mScrollZone) {  
  59.             if (mScrollState == SCROLL_OUTSIDE_ZONE && mDistanceSinceScroll > slop) {  
  60.                 mScrollState = SCROLL_WAITING_IN_ZONE;  
  61.                 if (mDragScroller.onEnterScrollArea(x, y, SCROLL_RIGHT)) {  
  62.                     mScrollRunnable.setDirection(SCROLL_RIGHT);  
  63.                     mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);  
  64.                 }  
  65.             }  
  66.         } else {  
  67.             if (mScrollState == SCROLL_WAITING_IN_ZONE) {  
  68.                 mScrollState = SCROLL_OUTSIDE_ZONE;  
  69.                 mScrollRunnable.setDirection(SCROLL_RIGHT);  
  70.                 mHandler.removeCallbacks(mScrollRunnable);  
  71.                 mDragScroller.onExitScrollArea();  
  72.             }  
  73.         }  
  74.     }  
private void handleMoveEvent(int x, int y) {
        //更新在DragLayer中的位置
        mDragObject.dragView.move(x, y);

        // Drop on someone?
        final int[] coordinates = mCoordinatesTemp;
        
        //根据当前的位置寻找DropTarget对象来放置图标
        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) {
                    //从最后一次记录的DropTarget中退出
                    mLastDropTarget.onDragExit(mDragObject);
                }
                //进入到当前寻找到的DropTarget
                dropTarget.onDragEnter(mDragObject);
            }
            dropTarget.onDragOver(mDragObject);
        } else {
            if (mLastDropTarget != null) {
                mLastDropTarget.onDragExit(mDragObject);
            }
        }
        mLastDropTarget = dropTarget;

        // Scroll, maybe, but not if we're in the delete region.
        boolean inDeleteRegion = false;
        if (mDeleteRegion != null) {
            inDeleteRegion = mDeleteRegion.contains(x, y);
        }

        // After a scroll, the touch point will still be in the scroll region.
        // Rather than scrolling immediately, require a bit of twiddling to scroll again
        final int slop = ViewConfiguration.get(mLauncher).getScaledWindowTouchSlop();
        mDistanceSinceScroll +=
            Math.sqrt(Math.pow(mLastTouch[0] - x, 2) + Math.pow(mLastTouch[1] - y, 2));
        mLastTouch[0] = x;
        mLastTouch[1] = y;

        //判断当前拖拽的图标是否处于ScrollZone即滑动区域。
        //并且根据在哪个一个ScrollZone来处理屏幕滑动的方向。
        if (!inDeleteRegion && x < mScrollZone) {
            if (mScrollState == SCROLL_OUTSIDE_ZONE && mDistanceSinceScroll > slop) {
                mScrollState = SCROLL_WAITING_IN_ZONE;
                if (mDragScroller.onEnterScrollArea(x, y, SCROLL_LEFT)) {
                    mScrollRunnable.setDirection(SCROLL_LEFT);
                    mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
                }
            }
        } else if (!inDeleteRegion && x > mScrollView.getWidth() - mScrollZone) {
            if (mScrollState == SCROLL_OUTSIDE_ZONE && mDistanceSinceScroll > slop) {
                mScrollState = SCROLL_WAITING_IN_ZONE;
                if (mDragScroller.onEnterScrollArea(x, y, SCROLL_RIGHT)) {
                    mScrollRunnable.setDirection(SCROLL_RIGHT);
                    mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
                }
            }
        } else {
            if (mScrollState == SCROLL_WAITING_IN_ZONE) {
                mScrollState = SCROLL_OUTSIDE_ZONE;
                mScrollRunnable.setDirection(SCROLL_RIGHT);
                mHandler.removeCallbacks(mScrollRunnable);
                mDragScroller.onExitScrollArea();
            }
        }
    }

handleMoveEvent()主要处理拖拽过程中需要处理的事务。包括:1、在更新图标在屏幕中的位置,并刷新UI2、判断图标当前所处的位置。包括SCROLL_OUTSIDE_ZONESCROLL_WAITING_IN_ZONE,对处于SCROLL_WAITING_IN_ZONE位置时,需要根据具体的位置,向前或向后切换显示的屏幕。再回到上面假设的情况中。则此时只是简单的刷新了位置信息,并重新绘制图标。


最后,当松开拖拽的对象时,触发了MotionEvent.ACTION_UP事件。则进入下面一段代码:

  1. <SPAN style="WHITE-SPACE: pre"> </SPAN>    // Ensure that we've processed a move event at the current pointer location.   
  2.             handleMoveEvent(dragLayerX, dragLayerY);  
  3.   
  4.             mHandler.removeCallbacks(mScrollRunnable);  
  5.             if (mDragging) {  
  6.                 drop(dragLayerX, dragLayerY);  
  7.             }  
  8.             endDrag();  
	    // 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();

先调用handleMoveEvent()确保已经完成了位置移动的操作。接下来调用mHandler.removeCallbacks(mScrollRunnalbe)取消可能存放在消息队列中的滑动任务。接着调用drop(dragLayerX,dragLayerY)将拖拽的对象放置合适的DropTarget对象中(如WorkspaceFolder)。


  1. private void drop(float x, float y) {  
  2.         ......  
  3.         //根据当前的坐标查找适合的DropTarget对象   
  4.         final DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates);  
  5.   
  6.         ......  
  7.         boolean accepted = false;  
  8.         if (dropTarget != null) {  
  9.             mDragObject.dragComplete = true;  
  10.             dropTarget.onDragExit(mDragObject);  
  11.             if (dropTarget.acceptDrop(mDragObject)) {  
  12.                 //将拖拽的对象放置到指定的DropTarget对象中。   
  13.                 dropTarget.onDrop(mDragObject);  
  14.                 accepted = true;  
  15.             }  
  16.         }  
  17.         mDragObject.dragSource.onDropCompleted((View) dropTarget, mDragObject, accepted);  
  18.     }  
private void drop(float x, float y) {
        ......
        //根据当前的坐标查找适合的DropTarget对象
        final DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates);

        ......
        boolean accepted = false;
        if (dropTarget != null) {
            mDragObject.dragComplete = true;
            dropTarget.onDragExit(mDragObject);
            if (dropTarget.acceptDrop(mDragObject)) {
                //将拖拽的对象放置到指定的DropTarget对象中。
                dropTarget.onDrop(mDragObject);
                accepted = true;
            }
        }
        mDragObject.dragSource.onDropCompleted((View) dropTarget, mDragObject, accepted);
    }

最后调用endDrag()结束拖拽过程。

  1. private void endDrag() {  
  2.         if (mDragging) {  
  3.             //拖拽结束   
  4.             mDragging = false;  
  5.             for (DragListener listener : mListeners) {  
  6.                 //调用回调方法,通知拖拽结束。   
  7.                 listener.onDragEnd();  
  8.             }  
  9.             if (mDragObject.dragView != null) {  
  10.                 //不需要DragView了,将其删除   
  11.                 mDragObject.dragView.remove();  
  12.                 mDragObject.dragView = null;  
  13.             }  
  14.         }  
  15.     }  
private void endDrag() {
        if (mDragging) {
            //拖拽结束
            mDragging = false;
            for (DragListener listener : mListeners) {
                //调用回调方法,通知拖拽结束。
                listener.onDragEnd();
            }
            if (mDragObject.dragView != null) {
                //不需要DragView了,将其删除
                mDragObject.dragView.remove();
                mDragObject.dragView = null;
            }
        }
    }
至此,拖拽的图标的过程就结束了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值