Launcher3拖拽分析之Workspace(基于Android 11 )

杨海
2023-12-01

Launcher3拖拽分析(基于Android 11 )

==== 先简单分析一下 Workspace#onDragOver()方法 ,后续还有完整的Launcher3源码原理方法解析=====

=== 分析在Workspace中拖拽item ===

DragView存在的意义:代替BubbleTextView,在DragLayer上滑动。为什么呢?因为这个BubbleTextView的父布局不是DragLayer,而我们知道子View滑动是不能超过父view的,所以想想,如果直接让BubbleTextView去滑动,那么它就不能跨布局,因为我们要让这个随手指拖拽的view能随意跨越布局,所以会使用一个可以在整个屏幕上滑动的view,这个就是DragView。那么顺理成章,DragLayer要处理滑动触摸事件。

DragObject:拖拽过程中,最最有权力的类,包含了拖拽所需的一切信息。只要拥有一个DragObject对象,就能获取关于一次拖拽所有的信息。所以你看各种拖拽相关的方法参数中都含有DragObject

Workspace#onDragOver()

// 手指在DropTarget中拖拽时候一直调用的方法onDragOver(讨论在Workspace中)

public void onDragOver(DragObject d){
     // 通过DragObject拿到源view的ItemInfo
        ItemInfo item = d.dragInfo;
    
     // 得到dragView中心点坐标,mDragViewVisualCenter是个坐标数组(x,y)
        mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter);
    
      // 拿到源view对象(小部件、文件夹、图标)
        final View child = (mDragInfo == null) ? null : mDragInfo.cell;
    
      // setDropLayoutForDrafObject()返回t表示DragView中心坐标落在了另一个可置放的区域
        if (setDropLayoutForDragObject(d, mDragViewVisualCenter[0], mDragViewVisualCenter[1])) {
            if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
                mSpringLoadedDragController.cancel();
            } else {
                // DragView拽到了下一个可置放的页面,也就是拖到了另一个celllayout(切换页面用了一个计时器)
                mSpringLoadedDragController.setAlarm(mDragTargetLayout);
            }
        }
    
    	// mDragTargetLayout在执行onDropEnter的时候会预先被赋值
    	if(mDragTargetLayout != null){
            
            // 通过CellLayout.fndNearestAree计算出最近的位置坐标,指的是cell的左上角坐标
            mTargetCell = findNearestArea(
                    (int) mDragViewVisualCenter[0],
                    (int) mDragViewVisualCenter[1],
                    minSpanX,
                    minSpanY,
                    mDragTargetLayout,
                    mTargetCell);
            
            // 重新排序后的 X
            int reorderX = mTargetCell[0];
            // 重新排序后的 Y
            int reorderY = mTargetCell[1];

            // 设置当前要放下的左上角坐标,设置拖拽模式为DRAG_MODE_NONE,将mLastReorderX/Y值初始化(均为-1)
            setCurrentDropOverCell(mTargetCell[0], mTargetCell[1]);
            
            // 计算Dragview中心到最近置放区域中心的距离,根据俩点坐标计算距离
            float targetCellDistance = mDragTargetLayout.getDistanceFromCell(
                    mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell);
            
            // =========== 1 ==============
            // 判断拖拽过程中用户是否是想将一个图标添加到文件夹中还是创建一个新的文件夹
            manageFolderFeedback(targetCellDistance, d);
            
             // 判断最近放置区域是否被占用,Rect相交法
            boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int)
                    mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX,
                    item.spanY, child, mTargetCell);
            
              if (!nearestDropOccupied) {
                // 没有被占用,虚拟化最近区域轮廓
                mDragTargetLayout.visualizeDropLocation(d.originalView, mOutlineProvider,
                        mTargetCell[0], mTargetCell[1], item.spanX, item.spanY, false, d);
            } else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER)
                    && !mReorderAlarm.alarmPending() && (mLastReorderX != reorderX ||
                    mLastReorderY != reorderY)) {
                // 这里的else if条件解读:
                // 前俩个是拖拽模式比较简单
                // mReorderAlarm.alarmPending:表示一次排序定时器任务是否挂起,挂起就不能重排序
                // 后面俩个满足其一,也就是和manageFolderFeedBack()方法中的一开始setDragMode联系了
                // 也就是如果上次和当前要排序的位置是一样的,那还排什么序呢,所有必须是某一个不相等才能排序
                int[] resultSpan = new int[2];
                mDragTargetLayout.performReorder((int) mDragViewVisualCenter[0],
                        (int) mDragViewVisualCenter[1], minSpanX, minSpanY, item.spanX, item.spanY,
                        child, mTargetCell, resultSpan, CellLayout.MODE_SHOW_REORDER_HINT);
                  
                // 重排序定时器任务
                ReorderAlarmListener listener = new ReorderAlarmListener(mDragViewVisualCenter,
                        minSpanX, minSpanY, item.spanX, item.spanY, d, child);
                mReorderAlarm.setOnAlarmListener(listener);
                // 执行Workspace#onAlarm()方法重排序
                mReorderAlarm.setAlarm(REORDER_TIMEOUT);
            }

            if (mDragMode == DRAG_MODE_CREATE_FOLDER || mDragMode == DRAG_MODE_ADD_TO_FOLDER ||
                    !nearestDropOccupied) {
                if (mDragTargetLayout != null) {
                    mDragTargetLayout.revertTempState();
                }
            }
            
            
        }
}

 // =========== 1 ==============
    private void manageFolderFeedback(float distance, DragObject dragObject) {
        
        // 这里为什么要再setDragMode呢,一方面是保证拖拽模式是NONE,最重要的是因为我们在拖拽的过程中可能会
        // 发生俩个快捷方式重叠创建文件夹、一个快捷方式添加到一个文件夹中、还有就是重排序情况
        // 在onDragOver中的最后一部分方法有个判断是否重排序的if条件中有个mLastReoderX/Y,只有之前的X/Y和
        // 当前的X/Y不等的时候才会发生重排序。所以这里的用了一个距离判断条件,是为了让mLastReorderX/Y恢复初始化值-1(就是执行了setDragMode方法),
        // 也就是每次走这里判断文件夹相关反馈的时候,保证mLastReorderX/y != reorderX/Y从而满足重排序的条件
        // mMaxDistanceForFolderCreation越大越不容易重排序,也就是越容易创建形成创建文件夹条件/添加到文件夹
        // mMaxDistanceForFolderCreation越小越容易重排序,也就是越不容易创建形成创建文件夹条件/添加到文件夹
        if (distance > mMaxDistanceForFolderCreation) {
            if ((mDragMode == DRAG_MODE_ADD_TO_FOLDER || mDragMode == DRAG_MODE_CREATE_FOLDER)) {
                // 将mLastReorderX/Y重新设置成初始值-1
                setDragMode(DRAG_MODE_NONE);
            }
            return;
        }
      	
        // 源View的ItemInfo
        ItemInfo info = dragObject.dragInfo;

        // 是否创建文件夹
        boolean userFolderPending = willCreateUserFolder(info, dragOverView, false);
        if (mDragMode == DRAG_MODE_NONE && userFolderPending) {

            mFolderCreateBg = new PreviewBackground();
            mFolderCreateBg.setup(mLauncher, mLauncher, null,
                    dragOverView.getMeasuredWidth(), dragOverView.getPaddingTop());

            mFolderCreateBg.isClipping = false;

            mFolderCreateBg.animateToAccept(mDragTargetLayout, mTargetCell[0], mTargetCell[1]);
            mDragTargetLayout.clearDragOutlines();
            setDragMode(DRAG_MODE_CREATE_FOLDER);

            if (dragObject.stateAnnouncer != null) {
                dragObject.stateAnnouncer.announce(WorkspaceAccessibilityHelper
                        .getDescriptionForDropOver(dragOverView, getContext()));
            }
            return;
        }

        // 调用workspace#willAddExistingUserFolder()判断文件夹是否接受item
        boolean willAddToFolder = willAddToExistingUserFolder(info, dragOverView);
        if (willAddToFolder && mDragMode == DRAG_MODE_NONE) {
            // 拿到文件夹View
            mDragOverFolderIcon = ((FolderIcon) dragOverView);
            // 执行FolderIcon.onDragEnter(),也就是进入FolderIcon了
            mDragOverFolderIcon.onDragEnter(info);
            if (mDragTargetLayout != null) {
                mDragTargetLayout.clearDragOutlines();
            }
            setDragMode(DRAG_MODE_ADD_TO_FOLDER);

            if (dragObject.stateAnnouncer != null) {
                dragObject.stateAnnouncer.announce(WorkspaceAccessibilityHelper
                        .getDescriptionForDropOver(dragOverView, getContext()));
            }
            return;
        }

        // 清除MODE,让其保持是NONE模式。这里的if条件不总是满足的,所以也需要一开始distance判断
        if (mDragMode == DRAG_MODE_ADD_TO_FOLDER && !willAddToFolder) {
            setDragMode(DRAG_MODE_NONE);
        }
        if (mDragMode == DRAG_MODE_CREATE_FOLDER && !userFolderPending) {
            setDragMode(DRAG_MODE_NONE);
        }
    }
 类似资料: