Launcher3--拖拽

    在Launcher3中,有三处长按拖拽处理:
    • 主屏幕(Workspace)上的图标和小部件
    • 文件夹中的图标
    • 抽屉中的图标和小部件
    这三种情况的拖拽处理是相似的的,我们只需知道其中一种,其他的也就不难理解了。在此我们以拖拽Workspace上的图标为例,分析整个拖拽的过程。

一、长按处理
    Launcher3上的拖拽处理都是通过长按开始的,然后经过一系列判断决定是否进行拖拽操作。Workspace上的长按操作在Launcher类中处理,从长按桌面开始,代码如下,
    public boolean onLongClick(View v) {
        if (!isDraggingEnabled()) return false;
        if (isWorkspaceLocked()) return false;
        if (mState != State.WORKSPACE) return false;

        // 长按空白处
        if (v instanceof Workspace) {
            if (!mWorkspace.isInOverviewMode()) {// 判断是否在缩略图模式下
                if (mWorkspace.enterOverviewMode()) {// 进入缩略图模式
                    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子类
            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);// 是否热键栏
        boolean allowLongPress = inHotseat || mWorkspace.allowLongPress();// 是否允许长按处理
        if (allowLongPress && !mDragController.isDragging()) {// 允许长按 && 没有进行拖拽
            if (itemUnderLongClick == null) {// 如果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 {
                    mWorkspace.enterOverviewMode();
                }
            } else {
                final boolean isAllAppsButton = inHotseat && isAllAppsButtonRank(
                        mHotseat.getOrderInHotseat(
                                longClickCellInfo.cellX,
                                longClickCellInfo.cellY));// 判断长按是否allapp按钮
                if (!(itemUnderLongClick instanceof Folder || isAllAppsButton)) {// 长按的不是allapp按钮也不在文件夹展开的布局中
                    // User long pressed on an item
                    mWorkspace.startDrag(longClickCellInfo);// 开始拖拽
                }
            }
        }
        return true;
    }
    该方法用来执行长按事件,整个长按操作分几种情况,不同情况的处理也是不一样的,流程图表示如下,

图1
    我们这里以长按快捷图标为例,最终执行到如下代码,
                   mWorkspace.startDrag(longClickCellInfo);// 开始拖拽

二、Workspace.startDrag
    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.setVisibility(INVISIBLE);// 拖拽对象在原来的位置设为不可见
        CellLayout layout = (CellLayout) child.getParent().getParent();// 拖拽对象所在的屏幕
        layout.prepareChildForDrag(child);

        beginDragShared(child, this);
    }
    先判断拖拽对象是否处于touch状态,如果是的就直接返回; 然后隐藏拖拽对象; 标记该位置为未占用,目的是让在拖拽挤压的过程中,可以让其他图标占据; 最后到 beginDragShared方法。
    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);

        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();
    }
    用流程表示,
 
图2
    最主要的就是创建拖拽视图了,通过DragController的startDrag方法来创建。

三、DragController.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;

        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对象(将该DragView添加到DragLayer上)
        dragView.show(mMotionDownX, mMotionDownY);
        // 根据当前的位置处理移动事件
        handleMoveEvent(mMotionDownX, mMotionDownY);
        return dragView;
    }
    隐藏软键盘; 调用各个监听对象实现的 onDragStart方法,这里就是在Workspace中的实现; 显示 DragView对象; 根据当前位置处理移动事件。
     
图3
    主要调用方法:
    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);
        // 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) {
        // 移动View,DragView继承至View,在方法move()中设置setTranslationX()、setTranslationY()即可移动view
        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.sqrt(Math.pow(mLastTouch[0] - x, 2) + Math.pow(mLastTouch[1] - y, 2));
        mLastTouch[0] = x;
        mLastTouch[1] = y;
        checkScrollState(x, y); // 对拖动时的翻页进行判断处理
    }
    方法中都有注释,还是比较容易理解的,其中 handleMoveEvent处理拖拽时的移动事件,是整个过程的一个很重要的方法,在下面的讲解中会详细介绍。

四、触摸事件的拦截和分发

onInterceptTouchEvent

    DragLayer继承自ViewGroup,其onInterceptTouchEvent方法若返回true,说明需要拦截触屏事件,则后续的一系列事件将传递给自身的onTouchEvent方法,而不再向其子控件传递。DragControlleronInterceptTouchEventDragLayeronInterceptTouchEvent调用,用于拦截触屏事件的处理。当用户点击屏幕时,触发ACTION_DOWN事件,记录当前触摸位置。当抬起时,触发ACTION_UP事件,结束拖拽。若抬起时处于拖拽中,在当前位置释放被拖拽物。因此,若此时处于拖拽中,后续的触屏事件将只传递到DragLayer的onTouchEvent。

onTouchEvent

    onTouchEvent处理触屏事件,若返回true,则表示消费掉该事件,事件不再向父控件的onTouchEvent传递。DragControlleronTouchEventDragLayeronTouchEvent调用,用于处理被拖拽物的移动。startDrag执行完毕,DragController设置拖拽状态为true,这样,触屏事件将最终转到onTouchEvent中,在此处调用handleMoveEvent进行物体的移动。

    DragLayer是Launcher上所有布局的父容器,主要进行事件的拦截和分发,但是具体工作都是交由DragController来处理的,我们直接看DragController的onInterceptTouchEvent的拦截处理。

    /**
     * Call this from a drag source view.
     */
    //DragLayer是Launcher所有布局的父容器,它的onInterceptTouchEvent()已交由DragController.onInterceptTouchEvent()来处理
    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];

        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;
    }
    是否拦截,主要看返回值,这里返回 mDragging,在 startDrag的时候设为true,所以DragLayer对touch事件进行了拦截,在 DragController的 onTouchEvent中进行处理,
    /**
     * Call this from a drag source view.
     */
    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:
            // 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.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;
    }


图4

五、handleMoveEvent
    handleMoveEvent是拖拽的主要方法,当用户触发拖拽后,DragController将通过该方法移动被拖拽物视图,并通知各个释放目的对象相应状态的改变。若进入滑屏区域且允许滑屏,执行相应的滑屏操作。
    // 根据当前的位置处理移动事件
    private void handleMoveEvent(int x, int y) {
        // 移动View,DragView继承至View,在方法move()中设置setTranslationX()、setTranslationY()即可移动view
        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.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();
        }
    }

图5

六、结束拖拽操作

    当用户将被拖拽物移动到相应位置后,可以将手指从屏幕上移开。此时,将在onInterceptTouchEvent与onTouchEvent中调用drop方法释放被拖拽物。其主要功能,就是查找拖拽目的对象(DropTarget),若找到且接受释放,通知该对象被拖拽物的放入。最后,通知拖拽源(被拖拽物最初所在的容器)拖拽结果。

        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.willAcceptDrop(mDragObject.dragInfo)) {
                    vec = null;
                }
                if (vec != null) {
                    // 拖动到垃圾箱中进行删除
                    dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec);
                } else {
                    drop(dragLayerX, dragLayerY);
                }
            }
            // 拖放结束
            endDrag();
            break;
    先判断是否在删除区域,如果在并且可以删除就将该图标删除,否则进行drop处理,及释放拖拽物到当前位置,
    // 释放被拖拽物到当前位置
    private void drop(float x, float y) {
        final int[] coordinates = mCoordinatesTemp;
        // x,y所在区域是否有合适的目标
        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);
    }
 
图6
    在 handleMoveEvent drop 中,均使用了 findDropTarget 来查找当前位置对应的拖拽目的对象,其基本原理就是遍历所有已注册的拖拽目的对象,若其支持放入且当前位置位于该对象的触发区域内,则匹配成功返回该对象。
    // 取得当前的具体的DropTarget对象
    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;
    }
DropTarget.onDrop
    这个方法最终将拖拽对象放置到目标位置,Workspace实现该方法,
    // 拖拽对象被放置到目标位置
    public void onDrop(final DragObject d) {
        mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView,
                mDragViewVisualCenter);// 计算拖动View的视觉中心

        CellLayout dropTargetLayout = mDropToLayout;// Drop的Celllayout对象

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

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

                boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;

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

    这样整个拖拽过程就结束了。

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值