从Launcher3的视图结构看事件分发

我们从主Activity即Launcher.java的布局文件即launcher.xml看起

<!--LauncherRootView最终继承自FrameLayout-->
<com.android.launcher3.LauncherRootView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:launcher="http://schemas.android.com/apk/res-auto"
    android:id="@+id/launcher"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">
    <!--DragLayer最终继承自FrameLayout-->
    <com.android.launcher3.DragLayer
        android:id="@+id/drag_layer"
        android:clipChildren="false"
        android:clipToPadding="false"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <!--显示焦点的半透明背景-->
        <com.android.launcher3.FocusIndicatorView
            android:id="@+id/focus_indicator"
            android:layout_width="52dp"
            android:layout_height="52dp" />

        <!-- The workspace contains 5 screens of cells -->
        <!--装载应用图标的自定义视图,直接继承于PagedView,而
        PagedView继承自ViewGroup,包括所有屏,Launcher2时默认有
        五屏-->
        <!-- DO NOT CHANGE THE ID -->
        <com.android.launcher3.Workspace
            android:id="@+id/workspace"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            launcher:defaultScreen="@integer/config_workspaceDefaultScreen"
            launcher:pageIndicator="@+id/page_indicator">
        </com.android.launcher3.Workspace>

        <!-- DO NOT CHANGE THE ID -->
        <!--Workspace下方的四个固定图标(不随屏幕滑动)和一个抽屉菜单-->
        <include layout="@layout/hotseat"
            android:id="@+id/hotseat"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
        <!--长按桌面空白处显示的菜单-->
        <include layout="@layout/overview_panel"
            android:id="@+id/overview_panel"
            android:visibility="gone" />

        <!-- Keep these behind the workspace so that they are not visible when
             we go into AllApps -->
        <!--Workspace和Hotseat中间的几个原点,反馈滑动到了第几屏-->
        <include
            android:id="@+id/page_indicator"
            layout="@layout/page_indicator"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal" />
        <!--默认桌面上有google搜索框,但也是删除卸载应用信息的占位视图-->
        <include
            android:id="@+id/search_drop_target_bar"
            layout="@layout/search_drop_target_bar" />
        <!--装载小部件的视图,WidgetsContainerView直接继承自BaseContainerView
            最终继承自FrameLayout-->
        <include layout="@layout/widgets_view"
            android:id="@+id/widgets_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:visibility="invisible" />
        <!--显示所有应用的视图,AllAppsContainerView直接继承自BaseContainerView
            最终继承自FrameLayout-->
        <include layout="@layout/all_apps"
            android:id="@+id/apps_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:visibility="invisible" />
    </com.android.launcher3.DragLayer>

</com.android.launcher3.LauncherRootView>

由于LauncherRootView并没有重写事件处理的相关方法,我们从DragLayer开始,以从workspace上面拖动一个图标到新的位置为例,我们查看它关于事件分发与处理的函数重写

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    int action = ev.getAction();

    if (action == MotionEvent.ACTION_DOWN) {
        if (handleTouchDown(ev, true)) {
            return true;
        }
    } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
        if (mTouchCompleteListener != null) {
            mTouchCompleteListener.onTouchComplete();
        }
        mTouchCompleteListener = null;
    }
    clearAllResizeFrames();
    return mDragController.onInterceptTouchEvent(ev);
}
private boolean handleTouchDown(MotionEvent ev, boolean intercept) {
    Rect hitRect = new Rect();
    int x = (int) ev.getX();
    int y = (int) ev.getY();
    if (mBlockTouches) {
        return true;
    }

    for (AppWidgetResizeFrame child: mResizeFrames) {
        child.getHitRect(hitRect);
        if (hitRect.contains(x, y)) {
            if (child.beginResizeIfPointInRegion(x - child.getLeft(), y - child.getTop())) {
                mCurrentResizeFrame = child;
                mXDown = x;
                mYDown = y;
                requestDisallowInterceptTouchEvent(true);
                return true;
            }
        }
    }

    Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
    if (currentFolder != null && intercept) {
        if (currentFolder.isEditingName()) {
            if (!isEventOverFolderTextRegion(currentFolder, ev)) {
                currentFolder.dismissEditingName();
                return true;
            }
        }

        if (!isEventOverFolder(currentFolder, ev)) {
            if (isInAccessibleDrag()) {
                // Do not close the folder if in drag and drop.
                if (!isEventOverDropTargetBar(ev)) {
                    return true;
                }
            } else {
                mLauncher.closeFolder();
                return true;
            }
        }
    }
    return false;
}

普通的应用图标,这里不考虑小部件和文件夹的问题,handleTouchDown(ev, true)方法返回false最后执行了DragController的onInterceptTouchEvent(MotionEvent ev)方法,同时它没有对Move做任何处理,直接交给DragController

public boolean onInterceptTouchEvent(MotionEvent ev) {
    @SuppressWarnings("all") // suppress dead code warning
    final boolean debug = false;
    if (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_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.supportsDrop(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;
}

将不同的拖拽源事件统一传入DragController进行处理,这里返回false,未拦截,进入Workspace,Move过程未处理同样交给Workspace

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {  
    switch (ev.getAction() & MotionEvent.ACTION_MASK) {
    case MotionEvent.ACTION_DOWN:
        Trace.traceBegin(Trace.TRACE_TAG_INPUT, "Workspace.ACTION_DOWN");
        mXDown = ev.getX();
        mYDown = ev.getY();
        mTouchDownTime = System.currentTimeMillis();
        Trace.traceEnd(Trace.TRACE_TAG_INPUT);
        break;
    case MotionEvent.ACTION_POINTER_UP:
    case MotionEvent.ACTION_UP:
        Trace.traceBegin(Trace.TRACE_TAG_INPUT, "Workspace.ACTION_UP");
        if (mTouchState == TOUCH_STATE_REST) {
            final CellLayout currentPage = (CellLayout) getChildAt(mCurrentPage);
            if (currentPage != null) {
                onWallpaperTap(ev);
            }
        }
        Trace.traceEnd(Trace.TRACE_TAG_INPUT);
    }
    return super.onInterceptTouchEvent(ev);
}

Workspace可看作一个自定义的ViewGroup,因此可重写它的事件拦截方法,这里它执行了父类(PagedView)的方法super.onInterceptTouchEvent(ev),Move过程未处理,交给父类

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    acquireVelocityTrackerAndAddMovement(ev);
    if (getChildCount() <= 0) {
        LauncherLog.d(TAG, "There are no pages to swipe, page count = " + getChildCount());
        return super.onInterceptTouchEvent(ev);
    }

    final int action = ev.getAction();
    if ((action == MotionEvent.ACTION_MOVE) &&
            (mTouchState == TOUCH_STATE_SCROLLING)) {
        LauncherLog.d(TAG, "onInterceptTouchEvent: touch move during scrolling.");
        return true;
    }

    switch (action & MotionEvent.ACTION_MASK) {
        case MotionEvent.ACTION_MOVE: {
            if (mActivePointerId != INVALID_POINTER) {
                determineScrollingStart(ev);

                final int pointerIndex = ev.findPointerIndex(mActivePointerId);

                if (pointerIndex == -1) { return true; }

                final float x = ev.getX(pointerIndex);
                final float deltaX = mLastX + mLastMotionXRemainder - x;
                mLastX = x;

                if (mTouchState == TOUCH_STATE_SCROLLING) {
                    mTotalMotionX += Math.abs(deltaX);

                    if (Math.abs(deltaX) >= 1.0f) {
                        mTouchX += deltaX;
                        mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
                        scrollBy((int) deltaX, 0);
                        mLastMotionX = x;
                        mLastMotionXRemainder = deltaX - (int) deltaX;
                    } else {
                        awakenScrollBars();
                    }
                }
            }
            break;
        }

        case MotionEvent.ACTION_DOWN: {
            final float x = ev.getX();
            final float y = ev.getY();
            // Remember location of down touch
            mDownMotionX = x;
            mDownMotionY = y;
            mDownScrollX = getScrollX();
            mLastMotionX = x;
            /// M: [Performance] Scroll page in first performTraversal to improve response time.
            mLastX = mLastMotionX;
            mLastMotionY = y;
            float[] p = mapPointFromViewToParent(this, x, y);
            mParentDownMotionX = p[0];
            mParentDownMotionY = p[1];
            mLastMotionXRemainder = 0;
            mTotalMotionX = 0;
            mActivePointerId = ev.getPointerId(0);

            final int xDist = Math.abs(mScroller.getFinalX() - mScroller.getCurrX());
            final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop / 3);

            if (finishedScrolling) {
                mTouchState = TOUCH_STATE_REST;
                if (!mScroller.isFinished() && !mFreeScroll) {
                    setCurrentPage(getNextPage());
                    pageEndMoving();
                }
            } else {
                if (isTouchPointInViewportWithBuffer((int) mDownMotionX, (int) mDownMotionY)) {
                    mTouchState = TOUCH_STATE_SCROLLING;
                } else {
                    mTouchState = TOUCH_STATE_REST;
                }
            }
            break;
        }

        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            resetTouchState();
            break;

        case MotionEvent.ACTION_POINTER_UP:
            onSecondaryPointerUp(ev);
            releaseVelocityTracker();
            break;
    }
    return mTouchState != TOUCH_STATE_REST;
}

未拦截,交给它的子view即CellLayout的方法onInterceptTouchEvent(MotionEvent ev)

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    final int action = ev.getAction();
    
    if (mUseTouchHelper ||
            (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev))) {
        return true;
    }
    return false;
}

未拦截,继续交给它的子view即BubbleTextView,它继承自TextView,用来显示应用的图标与名称,
到这里就已经是最小的子View了,执行它的onTouchEvent(MotionEvent event)方法

@Override
public boolean onTouchEvent(MotionEvent event) {
    boolean result = super.onTouchEvent(event);
    if (mStylusEventHelper.checkAndPerformStylusEvent(event)) {
        mLongPressHelper.cancelLongPress();
        result = true;
    }

    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            if (!mDeferShadowGenerationOnTouch && mPressedBackground == null) {
                mPressedBackground = mOutlineHelper.createMediumDropShadow(this);
            }

            if (!mStylusEventHelper.inStylusButtonPressed()) {
                mLongPressHelper.postCheckForLongPress();
            }
            break;
        case MotionEvent.ACTION_CANCEL:
        case MotionEvent.ACTION_UP:
            if (!isPressed()) {
                mPressedBackground = null;
            }

            mLongPressHelper.cancelLongPress();
            break;
        case MotionEvent.ACTION_MOVE:
            if (!Utilities.pointInView(this, event.getX(), event.getY(), mSlop)) {
                mLongPressHelper.cancelLongPress();
            }
            break;
    }
    return result;
}

Down事件处理完成后层层交付直到Activity即Launcher,如果是长按会响应onLongClick(View v)

public boolean onLongClick(View v) {
    if (!isDraggingEnabled()) return false;
    if (isWorkspaceLocked()) return false;
    if (mState != State.WORKSPACE) return false;

    if (v == mAllAppsButton) {
        onLongClickAllAppsButton(v);
        return true;
    }
                
    if (v instanceof Workspace) {
        if (!mWorkspace.isInOverviewMode()) {
            if (!mWorkspace.isTouchActive()) {
                showOverviewMode(true);
                mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
                        HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

    CellLayout.CellInfo longClickCellInfo = null;
    View itemUnderLongClick = null;
    if (v.getTag() instanceof ItemInfo) {
        ItemInfo info = (ItemInfo) v.getTag();
        longClickCellInfo = new CellLayout.CellInfo(v, info);
        itemUnderLongClick = longClickCellInfo.cell;
        resetAddInfo();
    }

    // The hotseat touch handling does not go through Workspace, and we always allow long press
    // on hotseat items.
    final boolean inHotseat = isHotseatLayout(v);
    if (!mDragController.isDragging()) {
        if (itemUnderLongClick == null) {
            // User long pressed on empty space
            mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
                    HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
            if (mWorkspace.isInOverviewMode()) {
                mWorkspace.startReordering(v);
            } else {
                showOverviewMode(true);
            }
        } else {
            final boolean isAllAppsButton = inHotseat && isAllAppsButtonRank(
                    mHotseat.getOrderInHotseat(
                            longClickCellInfo.cellX,
                            longClickCellInfo.cellY));
            if (!(itemUnderLongClick instanceof Folder || isAllAppsButton)) {
                // User long pressed on an item
                mWorkspace.startDrag(longClickCellInfo);
            }
        }
    }
    return true;
}

执行mWorkspace.startDrag(longClickCellInfo)

public void startDrag(CellLayout.CellInfo cellInfo) {
    startDrag(cellInfo, false);
}

@Override
public void startDrag(CellLayout.CellInfo cellInfo, boolean accessible) {
    View child = cellInfo.cell;

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

    ///M : ALPS02375941
    boolean isValid = (child.getParent().getParent()) instanceof CellLayout;
    if (!isValid) {
        return ;
    }

    mDragInfo = cellInfo;
    child.setVisibility(INVISIBLE);
    CellLayout layout = (CellLayout) child.getParent().getParent();
    layout.prepareChildForDrag(child);

    beginDragShared(child, this, accessible);
}

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) {
    //清除拖动对象的焦点
    child.clearFocus();
    child.setPressed(false);

    //创建这个将要被拖动的图标的轮廓
    mDragOutline = createDragOutline(child, DRAG_BITMAP_PADDING);

    mLauncher.onDragStarted(child);
    // 让这个图标的轮廓跟随图标的拖动而移动
    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);

    DeviceProfile grid = mLauncher.getDeviceProfile();
    Point dragVisualizeOffset = null;
    Rect dragRect = null;
    if (child instanceof BubbleTextView) {
        BubbleTextView icon = (BubbleTextView) child;
        int iconSize = grid.iconSizePx;
        int top = child.getPaddingTop();
        int left = (bmpWidth - iconSize) / 2;
        int right = left + iconSize;
        int bottom = top + iconSize;
        if (icon.isLayoutHorizontal()) {
            if (icon.getIcon().getBounds().contains(relativeTouchPos.x, relativeTouchPos.y)) {
                dragLayerX = Math.round(mTempXY[0]);
            } else {
                dragLayerX = Math.round(mTempXY[0] + relativeTouchPos.x - (bmpWidth / 2));
            }
        }
        dragLayerY += top;

        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;
        dragVisualizeOffset = new Point(-padding.get() / 2,
                padding.get() / 2 - child.getPaddingTop());
        dragRect = new Rect(0, child.getPaddingTop(), child.getWidth(), previewSize);
    }

    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);
    }

    if (child.getParent() instanceof ShortcutAndWidgetContainer) {
        mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
    }
    //创建拖动的视图
    DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
            DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale, accessible);
    dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());

    b.recycle();
}

最后执行了DragController.startDrag()

public void startDrag(View v, Bitmap bmp, DragSource source, Object dragInfo,
        Rect viewImageBounds, int dragAction, float initialDragViewScale) {
    int[] loc = mCoordinatesTemp;
    mLauncher.getDragLayer().getLocationInDragLayer(v, loc);
    int dragLayerX = loc[0] + viewImageBounds.left
            + (int) ((initialDragViewScale * bmp.getWidth() - bmp.getWidth()) / 2);
    int dragLayerY = loc[1] + viewImageBounds.top
            + (int) ((initialDragViewScale * bmp.getHeight() - bmp.getHeight()) / 2);

    startDrag(bmp, dragLayerX, dragLayerY, source, dragInfo, dragAction, null,
            null, initialDragViewScale, false);

    if (dragAction == DRAG_ACTION_MOVE) {
        v.setVisibility(View.GONE);
    }
}
public DragView startDrag(Bitmap b, int dragLayerX, int dragLayerY,
            DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion,
            float initialDragViewScale, boolean accessible) {
    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;

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

    mDragging = true;
    mIsAccessibleDrag = accessible;

    mDragObject = new DropTarget.DragObject();

    mDragObject.dragComplete = false;
    if (mIsAccessibleDrag) {
        // For an accessible drag, we assume the view is being dragged from the center.
        mDragObject.xOffset = b.getWidth() / 2;
        mDragObject.yOffset = b.getHeight() / 2;
        mDragObject.accessibleDrag = true;
    } else {
        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(), initialDragViewScale);

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

    mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
    //将我们前面创建的拖动视图显示在DrayLayer上,这个show方法将启用一个拾取动画
    dragView.show(mMotionDownX, mMotionDownY);  
    handleMoveEvent(mMotionDownX, mMotionDownY);
    return dragView;
}
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);
}
//遍历找出拖拽目的
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;
}
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;
}

移动前后dropTarget一致,故执行onDragOver(mDragObject),我们是在Workspace上进行的操作,而Workspace也的确实现了DropTarget接口,所以我们接下来进入Workspace的onDragOver()方法。执行完onDragOver()后,Launcher的LongClick就彻底执行完毕了,接下来事件交给DragLayer的onTouchEvent()方法进行处理

@Override
public boolean onTouchEvent(MotionEvent ev) {
    boolean handled = false;
    int action = ev.getAction();

    int x = (int) ev.getX();
    int y = (int) ev.getY();

    if (action == MotionEvent.ACTION_UP) {
        LauncherLog.d(TAG, "[PerfTest --> drag widget] start process");
    }

    if (mBlockTouches) {
        return true;
    }

    if (action == MotionEvent.ACTION_DOWN) {
        if (handleTouchDown(ev, false)) {
            return true;
        }
    } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
        if (mTouchCompleteListener != null) {
            mTouchCompleteListener.onTouchComplete();
        }
        mTouchCompleteListener = null;
    }

    if (mCurrentResizeFrame != null) {
        handled = true;
        switch (action) {
            case MotionEvent.ACTION_MOVE:
                mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown);
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown);
                mCurrentResizeFrame.onTouchUp();
                mCurrentResizeFrame = null;
        }
    }
    if (handled) return true;
    return mDragController.onTouchEvent(ev);
}

进入DragController的onTouchEvent()方法

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 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);
}

这里又执行了handleMoveEvent()方法,同上进入Workspace的onDragOver(),整个拖动的
过程不断重复上述同程,如果从一个位置(DropTarget)移出则执行Workspace的onDragExit()方法如果成功移到一个新的位置,最后的UP事件执行Workspace的acceptDrop(DragObject d)、onDrop(DragObject d)、onDropCompleted(final View target, final DragObject d, final boolean isFlingToDelete, final boolean success)然后执行了DragController的endDrag()

private void endDrag() {      
    if (mDragging) {
        mDragging = false;
        mIsAccessibleDrag = false;
        clearScrollRunnable();
        boolean isDeferred = false;
        if (mDragObject.dragView != null) {
            isDeferred = mDragObject.deferDragViewCleanupPostAnimation;
            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();
            }
        }
    }

    releaseVelocityTracker();
}

Workspace实现了DragController.DragListener接口,所以最后是Workspace的onDragEnd()方法

最后补充说明一下DropTarget这个接口

/**
 * Interface defining an object that can receive a drag.
 * 定义一个可以接收拖动对象的接口,比如Workspace、Folder、ButtonDropTarget,它们都实现了此接口
 */
public interface DropTarget {
    void onDrop( DragObject dragObject );  //当dragview被放下时调用
    void onDragEnter( DragObject dragObject );  //进入一个dropTarget时调用
    void onDragOver( DragObject dragObject );  //dropTarget不变时调用
    void onDragExit( DragObject dragObject );  //退出一个droptarget时调用
    void onFlingToDeletee(DragObject dragObject, PointF vec);  //删除dragview
    boolean acceptDrop( DragObject dragObject );  //判断是否可以放入
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容