Android13-Launcer3_桌面长按事件分析

437 阅读6分钟

LauncherDragController,继承了DragController

  • DragListener集合,回调对应的onDragStart()、onDragEnd()方法,Workspace,DropTargetBar和它的两个子View,DeleteDropTarget、SecondaryDropTarget,Folder、PopupContainerWithArrow、应用列表都会被添加到这个集合
  • 构建LauncherDragView,添加到DragLayer,用来显示应用图标,并调用它移动方法进行移动
  • 构建PopupContainerWithArrow控件,用来弹窗显示应用信息

LauncherDragView,继承DragView
长按显示的图标控件和进行移动

添加控件到页面的时候,会设置长按事件
WorkspaceLayoutManager

    default void addInScreen(View child, int container, int screenId, int x, int y,
            int spanX, int spanY) {
        ...
        //添加到页面
        if (!layout.addViewToCellLayout(child, -1, childId, lp, markCellsAsOccupied)) {
            // TODO: This branch occurs when the workspace is adding views
            // outside of the defined grid
            // maybe we should be deleting these items from the LauncherModel?
            Log.e(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout");
        }

        child.setHapticFeedbackEnabled(false);
        //这里设置了长按事件
        child.setOnLongClickListener(getWorkspaceChildOnLongClickListener());
        if (child instanceof DropTarget) {
            //添加到LauncherDragController的mDropTargets集合
            onAddDropTarget((DropTarget) child);
        }
    }
    
    default View.OnLongClickListener getWorkspaceChildOnLongClickListener() {
        return ItemLongClickListener.INSTANCE_WORKSPACE;
    }

ItemLongClickListener

    //静态方法
    private static boolean onWorkspaceItemLongClick(View v) {
        if (v instanceof LauncherAppWidgetHostView) {
            TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "Widgets.onLongClick");
        } else {
            TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onWorkspaceItemLongClick");
        }
        Launcher launcher = Launcher.getLauncher(v.getContext());
        if (!canStartDrag(launcher)) return false;
        if (!launcher.isInState(NORMAL) && !launcher.isInState(OVERVIEW)) return false;
        if (!(v.getTag() instanceof ItemInfo)) return false;

        launcher.setWaitingForResult(null);
        beginDrag(v, launcher, (ItemInfo) v.getTag(), launcher.getDefaultWorkspaceDragOptions());
        return true;
    }

    public static void beginDrag(View v, Launcher launcher, ItemInfo info,
            DragOptions dragOptions) {
        //大于0,表示是文件夹的数据
        if (info.container >= 0) {
            //通过DragLayer遍历子View获取Folder控件,Folder继承自AbstractFloatingView
            Folder folder = Folder.getOpen(launcher);
            if (folder != null) {
                if (!folder.getIconsInReadingOrder().contains(v)) {
                    folder.close(true);
                } else {
                    folder.startDrag(v, dragOptions);
                    return;
                }
            }
        }

        CellLayout.CellInfo longClickCellInfo = new CellLayout.CellInfo(v, info);
        //调用Workspace的startDrag()方法
        launcher.getWorkspace().startDrag(longClickCellInfo, dragOptions);
    }

Workspace

    public void startDrag(CellLayout.CellInfo cellInfo, DragOptions options) {
        View child = cellInfo.cell;

        mDragInfo = cellInfo;
        //控件设置不可见
        child.setVisibility(INVISIBLE);

        //DragOptions默认为false
        if (options.isAccessibleDrag) {
            mDragController.addDragListener(
                    new AccessibleDragListenerAdapter(this, WorkspaceAccessibilityHelper::new) {
                        @Override
                        protected void enableAccessibleDrag(boolean enable) {
                            super.enableAccessibleDrag(enable);
                            setEnableForLayout(mLauncher.getHotseat(), enable);
                        }
                    });
        }

        beginDragShared(child, this, options);
    }

beginDragShared()

    /**
     * Core functionality for beginning a drag operation for an item that will be dropped within
     * the workspace
     */
    public DragView beginDragShared(View child, DraggableView draggableView, DragSource source,
            ItemInfo dragObject, DragPreviewProvider previewProvider, DragOptions dragOptions) {

        float iconScale = 1f;
        if (child instanceof BubbleTextView) {
            Drawable icon = ((BubbleTextView) child).getIcon();
            //BubbleTextView的icon是FastBitmapDrawable
            if (icon instanceof FastBitmapDrawable) {
                iconScale = ((FastBitmapDrawable) icon).getAnimatedScale();
            }
        }

        // Clear the pressed state if necessary
        child.clearFocus();
        child.setPressed(false);
        if (child instanceof BubbleTextView) {
            BubbleTextView icon = (BubbleTextView) child;
            icon.clearPressedBackground();
        }

        //draggableView传进来是空,BubbleTextView实现了DraggableView接口
        if (draggableView == null && child instanceof DraggableView) {
            draggableView = (DraggableView) child;
        }

        //如果child不是LauncherAppWidgetHostView控件,则contentView为null
        final View contentView = previewProvider.getContentView();
        final float scale;
        // The draggable drawable follows the touch point around on the screen
        final Drawable drawable;
        if (contentView == null) {
            drawable = previewProvider.createDrawable();
            scale = previewProvider.getScaleAndPosition(drawable, mTempXY);
        } else {
            drawable = null;
            scale = previewProvider.getScaleAndPosition(contentView, mTempXY);
        }

        int halfPadding = previewProvider.previewPadding / 2;
        int dragLayerX = mTempXY[0];
        int dragLayerY = mTempXY[1];

        Point dragVisualizeOffset = null;
        Rect dragRect = new Rect();

        if (draggableView != null) {
            ////如果是BuddleTextView,则把图标的大小赋值给dragRect矩形
            draggableView.getSourceVisualDragBounds(dragRect);
            dragLayerY += dragRect.top;
            dragVisualizeOffset = new Point(- halfPadding, halfPadding);
        }

        if (child.getParent() instanceof ShortcutAndWidgetContainer) {
            mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
        }

        if (child instanceof BubbleTextView) {
            BubbleTextView btv = (BubbleTextView) child;
            if (!dragOptions.isAccessibleDrag) {
                //startLongPressAction()方法构建PopupContainerWithArrow,也就是显示应用信息的弹窗
                dragOptions.preDragCondition = btv.startLongPressAction();
            }
            if (btv.isDisplaySearchResult()) {
                dragOptions.preDragEndScale = (float) mAllAppsIconSize / btv.getIconSize();
            }
        }

        final DragView dv;
        if (contentView instanceof View) {
            if (contentView instanceof LauncherAppWidgetHostView) {
                mDragController.addDragListener(new AppWidgetHostViewDragListener(mLauncher));
            }
            dv = mDragController.startDrag(
                    contentView,
                    draggableView,
                    dragLayerX,
                    dragLayerY,
                    source,
                    dragObject,
                    dragVisualizeOffset,
                    dragRect,
                    scale * iconScale,
                    scale,
                    dragOptions);
        } else {
            //调用LauncherDragController的startDrag()方法
            dv = mDragController.startDrag(
                    drawable,
                    draggableView,
                    dragLayerX,
                    dragLayerY,
                    source,
                    dragObject,
                    dragVisualizeOffset,
                    dragRect,
                    scale * iconScale,
                    scale,
                    dragOptions);
        }
        return dv;
    }

如果是BubbleTextView控件,通过调用BubbleTextView.startLongPressAction()方法构建PopupContainerWithArrow控件,也就是用来显示应用信息的弹窗

调用LauncherDragController的startDrag()方法

LauncherDragController,继承自DragController

    @Override
    protected DragView startDrag(
            @Nullable Drawable drawable,
            @Nullable View view,
            DraggableView originalView,
            int dragLayerX,
            int dragLayerY,
            DragSource source,
            ItemInfo dragInfo,
            Point dragOffset,
            Rect dragRegion,
            float initialDragViewScale,
            float dragViewScaleOnDrop,
            DragOptions options) {
        if (TestProtocol.sDebugTracing) {
            Log.d(TestProtocol.NO_DROP_TARGET, "5");
        }
        if (PROFILE_DRAWING_DURING_DRAG) {
            android.os.Debug.startMethodTracing("Launcher");
        }

        mActivity.hideKeyboard();
        //通过DragLayer遍历找到AbstractFloatingView控件,并关闭
        //Folder、PopupContainerWithArrow都继承了AbstractFloatingView
        AbstractFloatingView.closeOpenViews(mActivity, false, TYPE_DISCOVERY_BOUNCE);

        mOptions = options;
        //默认为空
        if (mOptions.simulatedDndStartPoint != null) {
            mLastTouch.x = mMotionDown.x = mOptions.simulatedDndStartPoint.x;
            mLastTouch.y = mMotionDown.y = mOptions.simulatedDndStartPoint.y;
        }

        final int registrationX = mMotionDown.x - dragLayerX;
        final int registrationY = mMotionDown.y - dragLayerY;

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

        mLastDropTarget = null;

        //构建DragObject对象
        mDragObject = new DropTarget.DragObject(mActivity.getApplicationContext());
        mDragObject.originalView = originalView;

        //BuddleTextVie这里不为空,由PopupContainerWithArrow的createPreDragCondition()创建
        //如果是PopupContainerWithArrow中的,那么mIsInPreDrag 为true
        mIsInPreDrag = mOptions.preDragCondition != null
                && !mOptions.preDragCondition.shouldStartDrag(0);

        final Resources res = mActivity.getResources();
        final float scaleDps = mIsInPreDrag
                ? res.getDimensionPixelSize(R.dimen.pre_drag_view_scale) : 0f;
        //构建LauncherDragView对象
        //构造的同时,创建一个ImageView,并添加到自身,并配置缩放动画
        final DragView dragView = mDragObject.dragView = drawable != null
                ? new LauncherDragView(
                mActivity,
                drawable,
                registrationX,
                registrationY,
                initialDragViewScale,
                dragViewScaleOnDrop,
                scaleDps)
                : new LauncherDragView(
                        mActivity,
                        view,
                        view.getMeasuredWidth(),
                        view.getMeasuredHeight(),
                        registrationX,
                        registrationY,
                        initialDragViewScale,
                        dragViewScaleOnDrop,
                        scaleDps);
        dragView.setItemInfo(dragInfo);
        mDragObject.dragComplete = false;

        mDragObject.xOffset = mMotionDown.x - (dragLayerX + dragRegionLeft);
        mDragObject.yOffset = mMotionDown.y - (dragLayerY + dragRegionTop);

        mDragDriver = DragDriver.create(this, mOptions, mFlingToDeleteHelper::recordMotionEvent);
        if (!mOptions.isAccessibleDrag) {
            mDragObject.stateAnnouncer = DragViewStateAnnouncer.createFor(dragView);
        }

        mDragObject.dragSource = source;
        mDragObject.dragInfo = dragInfo;
        mDragObject.originalDragInfo = mDragObject.dragInfo.makeShallowCopy();

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

        mActivity.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
        //把LauncherDragView添加到DragLayer指定位置,并执行动画
        dragView.show(mLastTouch.x, mLastTouch.y);
        mDistanceSinceScroll = 0;

        if (!mIsInPreDrag) {
            callOnDragStart();
        } else if (mOptions.preDragCondition != null) {
            mOptions.preDragCondition.onPreDragStart(mDragObject);
        }

        handleMoveEvent(mLastTouch.x, mLastTouch.y);

        if (!mActivity.isTouchInProgress() && options.simulatedDndStartPoint == null) {
            // If it is an internal drag and the touch is already complete, cancel immediately
            MAIN_EXECUTOR.submit(this::cancelDrag);
        }
        return dragView;
    }

LauncherDragController的startDrag()方法

  • 构建DragObject对象
  • 构建LauncherDragView对象,构造的同时,创建一个ImageView,并添加到自身,并配置缩放动画
  • 调用LauncherDragView的show()进行显示,会把LauncherDragView添加到DragLayer控件中,并执行动画
  • 调用handleMoveEvent()方法

LauncherDragView,继承DragView

    public void show(int touchX, int touchY) {
        //Launcher.xml中的DragLayer控件添加LauncherDragView
        mDragLayer.addView(this);

        // Start the pick-up animation
        BaseDragLayer.LayoutParams lp = new BaseDragLayer.LayoutParams(mWidth, mHeight);
        lp.customPosition = true;
        setLayoutParams(lp);

        //mContent 是ImageView
        if (mContent != null) {
            // At the drag start, the source view visibility is set to invisible.
            mContent.setVisibility(VISIBLE);
        }

        //移动到触摸位置
        move(touchX, touchY);
        // Post the animation to skip other expensive work happening on the first frame
        //触发动画执行
        post(mAnim::start);
    }
    
    public void move(int touchX, int touchY) {
        if (touchX > 0 && touchY > 0 && mLastTouchX > 0 && mLastTouchY > 0
                && mScaledMaskPath != null) {
            mTranslateX.animateToPos(mLastTouchX - touchX);
            mTranslateY.animateToPos(mLastTouchY - touchY);
        }
        mLastTouchX = touchX;
        mLastTouchY = touchY;
        applyTranslation();
    }

回到handleMoveEvent()方法

    protected void handleMoveEvent(int x, int y) {
        //LauncherDragView进行移动
        mDragObject.dragView.move(x, y);

        // Drop on someone?
        final int[] coordinates = mCoordinatesTemp;
        //根据x/y坐标找到要移动到的目标控件
        //Workspace、Folder、DeleteDropTarget、SecondaryDropTarget都实现了DropTarget接口
        DropTarget dropTarget = findDropTarget(x, y, coordinates);
        mDragObject.x = coordinates[0];
        mDragObject.y = coordinates[1];
        //回调dropTarget相应接口,进入、退出等
        checkTouchMove(dropTarget);

        // Check if we are hovering over the scroll areas
        mDistanceSinceScroll += Math.hypot(mLastTouch.x - x, mLastTouch.y - y);
        mLastTouch.set(x, y);

        int distanceDragged = mDistanceSinceScroll;
        if (ATLEAST_Q && mLastTouchClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS) {
            distanceDragged /= DEEP_PRESS_DISTANCE_FACTOR;
        }
        if (mIsInPreDrag && mOptions.preDragCondition != null
                && mOptions.preDragCondition.shouldStartDrag(distanceDragged)) {
            callOnDragStart();
        }
    }

    private void checkTouchMove(DropTarget dropTarget) {
        if (dropTarget != null) {
            if (mLastDropTarget != dropTarget) {
                if (mLastDropTarget != null) {
                    //退出上一个DropTarget
                    mLastDropTarget.onDragExit(mDragObject);
                }
                //进入目标DropTarget
                dropTarget.onDragEnter(mDragObject);
            }
            //还在DropTarget内移动
            dropTarget.onDragOver(mDragObject);
        } else {
            if (mLastDropTarget != null) {
                mLastDropTarget.onDragExit(mDragObject);
            }
        }
        mLastDropTarget = dropTarget;
    }

    private DropTarget findDropTarget(int x, int y, int[] dropCoordinates) {
        mDragObject.x = x;
        mDragObject.y = y;

        final Rect r = mRectTemp;
        //Workspace页面在添加子View的时候,发现实现了DropTarget接口,会添加到mDropTargets集合
        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);
            if (r.contains(x, y)) {
                dropCoordinates[0] = x;
                dropCoordinates[1] = y;
                mActivity.getDragLayer().mapCoordInSelfToDescendant((View) target, dropCoordinates);
                return target;
            }
        }
        // Pass all unhandled drag to workspace. Workspace finds the correct
        // cell layout to drop to in the existing drag/drop logic.
        dropCoordinates[0] = x;
        dropCoordinates[1] = y;
        //上面如果没有,直接返回的是Workspace
        return getDefaultDropTarget(dropCoordinates);
    }
    
    
    //LauncherDragController
    @Override
    protected DropTarget getDefaultDropTarget(int[] dropCoordinates) {
        mActivity.getDragLayer().mapCoordInSelfToDescendant(mActivity.getWorkspace(),
                dropCoordinates);
        return mActivity.getWorkspace();
    }
    protected void callOnDragStart() {
        if (TestProtocol.sDebugTracing) {
            Log.d(TestProtocol.NO_DROP_TARGET, "6");
        }
        if (mOptions.preDragCondition != null) {
            mOptions.preDragCondition.onPreDragEnd(mDragObject, true /* dragStarted*/);
        }
        mIsInPreDrag = false;
        if (mOptions.preDragEndScale != 0) {
            mDragObject.dragView
                    .animate()
                    .scaleX(mOptions.preDragEndScale)
                    .scaleY(mOptions.preDragEndScale)
                    .setInterpolator(Interpolators.EMPHASIZED)
                    .setDuration(DRAG_VIEW_SCALE_DURATION_MS)
                    .start();
        }
        //执行LauncherDragView的onDragStart()回调方法
        mDragObject.dragView.onDragStart();
        //mListeners集合有Workspace、DropTargetBar和它的两个子View,DeleteDropTarget、SecondaryDropTarget
        //Folder、PopupContainerWithArrow、应用列表
        for (DragListener listener : new ArrayList<>(mListeners)) {
            listener.onDragStart(mDragObject, mOptions);
        }
    }

handleMoveEvent()方法逻辑

  • LauncherDragView进行移动,根据坐标找到对应DropTarget实现类,并根据情况调用对应的onDragEnter()、onDragExit()等
  • 回调用LauncherDragView的onDragStart()方法,回调DragListener集合的onDragStart()方法,Workspace、DeleteDropTarget等都实现了DragListener 接口,并被添加到这个集合

归纳

长按事件会调用到Workspace的startDrag()方法,如果是应用图标的长按事件,则会创建PopupContainerWithArrow控件,用来负责弹窗显示应用信息,接着调用LauncherDragController的startDrag()方法

在startDarg()方法中,构建DragObject、LauncherDragView对象,LauncherDragView在构造方法中创建一个ImageView,并添加到自身,并配置动画,接着LauncherDragView的show()方法,把LauncherDragView添加到DragLayer控件中,并执行动画

接着调用handleMoveEvent()方法,会调用到LauncherDragView的move()进行移动,根据坐标找到对应DropTarget实现类,并根据情况调用对应的onDragEnter()、onDragExit()等,DropTarget也就是图标移动到的目标对象

接着回调DragListener集合的onDragStart()方法,Workspace、DeleteDropTarget等都实现了DragListener 接口,并被添加到这个集合,DragListener 接口提供了onDragStart()、onDragEnd()方法