Launcher的长按---移动---松开

本文详细介绍了Launcher应用中的长按—移动—松开操作,涉及滑动监听、DragController、DropTarget的交互过程,以及触摸事件处理。从图标拖拽到放置、动画效果及目标检测,展示了完整的移动体验逻辑。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Launcher的长按—移动—松开

一、滑动

在上一篇文章代码流程中 addInScreen’中添加了长按监听器。给图标设置的监听器是Launcher对象,他实现了onLongClick方法。
在这里插入图片描述
接着之前的代码:
ItemLongClickListener.java

    public static OnLongClickListener INSTANCE_WORKSPACE =
            ItemLongClickListener::onWorkspaceItemLongClick;
private static boolean onWorkspaceItemLongClick(View v) {
        Launcher launcher = Launcher.getLauncher(v.getContext());
        beginDrag(v, launcher, (ItemInfo) v.getTag(), new DragOptions());
        return true;
    }
    public static void beginDrag(View v, Launcher launcher, ItemInfo info,
            DragOptions dragOptions) {
        CellLayout.CellInfo longClickCellInfo = new CellLayout.CellInfo(v, info);
        launcher.getWorkspace().startDrag(longClickCellInfo, dragOptions);
    }

Workspace.java

public void startDrag(CellLayout.CellInfo cellInfo, DragOptions options) {
        View child = cellInfo.cell;
        //点击的图标设置为不可见
        child.setVisibility(INVISIBLE);
        beginDragShared(child, this, options);
    }
    public void beginDragShared(View child, DragSource source, DragOptions options) {
        Object dragObject = child.getTag();
        beginDragShared(child, source, (ItemInfo) dragObject, new DragPreviewProvider(child), options);
    }
public DragView beginDragShared(View child, DragSource source, ItemInfo dragObject,
            DragPreviewProvider previewProvider, DragOptions dragOptions) {
		//将点击的图标放弃焦点,按下状态设置为false
		child.clearFocus();
        child.setPressed(false);

        // The drag bitmap follows the touch point around on the screen,生成随手动的bitmap
        final Bitmap b = previewProvider.createDragBitmap();
        if (child instanceof BubbleTextView) {
            dragRect = new Rect(); //dragRect用于计算拖动层的偏移量
            ((BubbleTextView) child).getIconBounds(dragRect);
            dragLayerY += dragRect.top;
            dragVisualizeOffset = new Point(- halfPadding, halfPadding);
        } else if (child instanceof FolderIcon) {
           //...
        } else if (previewProvider instanceof ShortcutDragPreviewProvider) {
           //...
        }
        
        DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source,
                dragObject, dragVisualizeOffset, dragRect, scale * iconScale, scale, dragOptions);
        dv.setIntrinsicIconScaleFactor(dragOptions.intrinsicIconScaleFactor);
        return dv;
    }

DragController.java

    public DragView startDrag(Bitmap b, int dragLayerX, int dragLayerY,
            DragSource source, ItemInfo dragInfo, Point dragOffset, Rect dragRegion,
            float initialDragViewScale, float dragViewScaleOnDrop, DragOptions options) {

        mDragObject = new DropTarget.DragObject(); //包含拖拽时的信息
        //1.通过上面的bitmap生成跟随手移动的View
        final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX,
                registrationY, initialDragViewScale, dragViewScaleOnDrop, scaleDps);
        dragView.setItemInfo(dragInfo);
        //标识在拖拽中
        mDragObject.dragComplete = false;
        //将View显示在某个位置上(滑动的时候这个坐标一直变)      	
        dragView.show(mMotionDownX, mMotionDownY);
        //2.生成动画
        callOnDragStart(); 
        //3.处理滑动事件ACTION_MOVE
        handleMoveEvent(mMotionDownX, mMotionDownY);  
        return dragView;
    }
    private void callOnDragStart() {
    	//拖拽监听器
        for (DragListener listener : new ArrayList<>(mListeners)) {
            listener.onDragStart(mDragObject, mOptions);//此后开始接收滑动触摸的消息了
        }
    }

WorkSpace.java

@Override
    public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
        
        CellLayout layout = (CellLayout) mDragInfo.cell.getParent().getParent();
        layout.markCellsAsUnoccupiedForView(mDragInfo.cell);
        // Always enter the spring loaded mode
        mLauncher.getStateManager().goToState(SPRING_LOADED);
    }

LauncherStateManager.java

private void goToState(LauncherState state, boolean animated, long delay,
            final Runnable onCompleteRunnable) {     
       	//生成动画
        AnimatorSet animation = createAnimationToNewWorkspaceInternal(
                state, builder, onCompleteRunnable);
        Runnable runnable = new StartAnimRunnable(animation);
        mUiHandler.post(runnable);
    }

二、触摸

DropTarget是一个可放置(drop)区域的抽象,也就是我们松开手的时候想要把图标放到某个东西上,这个东西就是DropTarget,实现他的都是View,比如说文件夹,Workspace,删除区等等

DropTarget.java
在这里插入图片描述

  • onDragEnter是当拖动图标到某一DropTarget(蓝色),边缘,刚刚进入DropTarget范围内的时候所调用的内容。比如拖动桌面的一个快捷方式到桌面顶端的删除区域,“删除”两字和手中的图标会变红
  • onDragOver是在某一DropTarget内部移动的时候会调用的回调,比如我们把手上的图标移动到两个图标中间的时候,会发生挤位的情况(就是桌面已有图标让出空位),基本上每个ACTION_MOVE操作都会调用他。
  • onDragExit是从某一DropTarget拖出时候会进行的回调,比如onDragEnter时变红的“删除”和图标会在这个调用中恢复正常。
  • onDrop是松手时候发生的调用,做一些放下时候的操作,比如删除快捷方式的时候会在onDrop里面开始删除的操作。

DragController.java

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];
        //检测进入某个dropTarget,其实现上上图的方法
        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); //检测是否滑动到下一页
		
        if (mIsInPreDrag && mOptions.preDragCondition != null
                && mOptions.preDragCondition.shouldStartDrag(mDistanceSinceScroll)) {
            callOnDragStart();
        }
    }

DragLayer.java

 @Override
    public boolean onTouchEvent(MotionEvent ev) {
    	//drgalayer的ontouchevent被DragController接管
        return mActiveController.onControllerTouchEvent(ev);
    }

DragController.java, DragController implements DragDriver.EventListener

private DragDriver mDragDriver = null;
public boolean onControllerTouchEvent(MotionEvent ev) {
        return mDragDriver.onTouchEvent(ev);  //调到了DragDriver 
    }

DragDriver.java abstract类

public boolean onTouchEvent(MotionEvent ev) {
        final int action = ev.getAction();
        switch (action) {
            case MotionEvent.ACTION_MOVE:
                mEventListener.onDriverDragMove(ev.getX(), ev.getY()); 
                break;
            case MotionEvent.ACTION_UP:
                mEventListener.onDriverDragMove(ev.getX(), ev.getY());
                mEventListener.onDriverDragEnd(ev.getX(), ev.getY());
                break;
            case MotionEvent.ACTION_CANCEL:
                mEventListener.onDriverDragCancel();
                break;
        }
        return true;
    }

DragController.java DragController implements DragDriver.EventListener

   @Override
    public void onDriverDragMove(float x, float y) {
        final int[] dragLayerPos = getClampedDragLayerPos(x, y);
        handleMoveEvent(dragLayerPos[0], dragLayerPos[1]);  //处理移动
    }
    
    @Override
    public void onDriverDragEnd(float x, float y) { //手指抬起来时触发ACTION_UP,所以执行此方法
        DropTarget dropTarget;
        Runnable flingAnimation = mFlingToDeleteHelper.getFlingAnimation(mDragObject);
        if (flingAnimation != null) { //判断是不是在扔图标
            dropTarget = mFlingToDeleteHelper.getDropTarget();
        } else {
            dropTarget = findDropTarget((int) x, (int) y, mCoordinatesTemp);
        }
        //找到dropTarget,调用下面两个方法
        drop(dropTarget, flingAnimation);  
        endDrag();
    }

DragController.java

 private void drop(DropTarget dropTarget, Runnable flingAnimation) {
        mDragObject.dragComplete = true;
        if (dropTarget.acceptDrop(mDragObject)) { //比如放在已经满的hotseat,就不accept
             dropTarget.onDrop(mDragObject, mOptions);  //具体的droptarget中的drop
        }
        dispatchDropComplete(dropTargetAsView, accepted); //drop 完成删除view等
    }

DragController.java

    private void dispatchDropComplete(View dropTarget, boolean accepted) {
        mDragObject.dragSource.onDropCompleted(dropTarget, mDragObject, accepted);
    }

WorkSpace.java

//onDrop代码内容非常复杂
public void onDrop(final DragObject d, DragOptions options) {
        		//经过一系列计算得到mTargetCell,也就是要drop的位置
                mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int)
                        mDragViewVisualCenter[1], spanX, spanY, dropTargetLayout, mTargetCell);
                //是不是在另一个图标上,是就创建文件夹,或者直接放进文件夹
                if (createUserFolderIfNecessary(cell, container,
                        dropTargetLayout, mTargetCell, distance, false, d.dragView) ||
                        addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
                                distance, d, false)) {
                    return;
                }
				//经过一系列计算,判断拖拽对象是不是在另一页,是的话就addInScreen
                if (foundCell) {
                    if (getScreenIdForPageIndex(mCurrentPage) != screenId && !hasMovedIntoHotseat) {
                        snapScreen = getPageIndexForScreenId(screenId);
                        snapToPage(snapScreen);
                    }
                    addInScreen(cell, container, screenId, mTargetCell[0], mTargetCell[1],
                            info.spanX, info.spanY);
                    }
                    
                // 如果没有在其他页就更新位置更新数据库
                 mLauncher.getModelWriter().modifyItemInDatabase(info, container, screenId,
                            lp.cellX, lp.cellY, item.spanX, item.spanY);
                            
                    //找不到位置就放回原位置
                    layout.markCellsAsOccupiedForView(cell);
                }
            }
           mLauncher.getDropTargetBar().onDragEnd();   //最后dropend        
    }

WorkSpace.java

public void onDropCompleted(final View target, final DragObject d,final boolean success) {
        if (success) {
            removeWorkspaceItem(mDragInfo.cell); //删除拖拽对象和view
        }
        View cell = getHomescreenIconByItemId(d.originalDragInfo.id);
        cell.setVisibility(VISIBLE); //设置view可见
        mDragInfo = null; //将拖拽信息置为null
    }

DragController.java

//接上面,dorp完了就是endDrag了
private void endDrag() {
     mDragObject.dragView = null; //清理dragview
     callOnDragEnd();
     //释放VelocityTracker,在drop里面 用来检查是否在扔的动作计算,计算速度,x方向,y方向
     mFlingToDeleteHelper.releaseVelocityTracker(); 
    }
    private void callOnDragEnd() {
        for (DragListener listener : new ArrayList<>(mListeners)) {
            listener.onDragEnd();
        }
    }

参考文章
https://fookwood.com/launcher-drag-up
https://fookwood.com/launcher-drag-move

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值