仿华为车机UI--图标从Workspace拖动到Hotseat同时保留图标在原来位置

基于Android13 Launcher3,原生系统如果把图标从Workspace拖动到Hotseat里则Workspace就没有了,需求是执行拖拽动作后,图标同时保留在原位置。

实现效果如下:

实现思路:

1.如果在workspace中拖动,则保留原来"改变图标位置"的功能

2.如果拖拽到Hotseat,则添加到Hotseat的同时也在原来位置复制一个一样的View

3.每次改变都要存到数据库以保存当前状态

思路非常简单,但是要找适当的位置添加适当的代码不简单。需要读懂原生代码。

分析了源代码后,发现应该参考Workspace.java的onDrop与onDropExternal代码来实现。

step1: 读懂代码后添加注释(中文为添加的注释)和添加onDropToHotseat()的调用

java 复制代码
@Override
    public void onDrop(final DragObject d, DragOptions options) {
        mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter);
        CellLayout dropTargetLayout = mDropToLayout;
        Log.d("drop","onDrop!");
        // We want the point to be mapped to the dragTarget.
        if (dropTargetLayout != null) {
            mapPointFromDropLayout(dropTargetLayout, mDragViewVisualCenter);
        }

        boolean droppedOnOriginalCell = false;

        boolean snappedToNewPage = false;
        boolean resizeOnDrop = false;
        Runnable onCompleteRunnable = null;
        if (d.dragSource != this || mDragInfo == null) {//从别的地方(AllApp等)拖到worksapce的 start
            final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0],
                    (int) mDragViewVisualCenter[1] };
            onDropExternal(touchXY, dropTargetLayout, d);
	        Log.d("drop","onDropExternal!");//从别的地方(AllApp等)拖到worksapce的 end
        } else {//从workspace拖到workspace start
            final View cell = mDragInfo.cell;
            boolean droppedOnOriginalCellDuringTransition = false;

            if (dropTargetLayout != null && !d.cancelled) {//有地方可放且没有取消拖动 start
                // Move internally
                boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout);
                boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
		Log.d("drop","hasMovedIntoHotseat :"+hasMovedIntoHotseat);
                int container = hasMovedIntoHotseat ?
                        LauncherSettings.Favorites.CONTAINER_HOTSEAT :
                        LauncherSettings.Favorites.CONTAINER_DESKTOP;
                int 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.getDistanceFromWorkspaceCellVisualCenter(
                        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 (createUserFolderIfNecessary(cell, container,
                        dropTargetLayout, mTargetCell, distance, false, d)
                        || addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
                                distance, d, false)) {
                    mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
                    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 = d.dragInfo;
                int minSpanX = item.spanX;
                int minSpanY = item.spanY;
                if (item.minSpanX > 0 && item.minSpanY > 0) {
                    minSpanX = item.minSpanX;
                    minSpanY = item.minSpanY;
                }

                droppedOnOriginalCell = item.screenId == screenId && item.container == container
                        && item.cellX == mTargetCell[0] && item.cellY == mTargetCell[1];
                droppedOnOriginalCellDuringTransition = droppedOnOriginalCell && mIsSwitchingState;

                // When quickly moving an item, a user may accidentally rearrange their
                // workspace. So instead we move the icon back safely to its original position.
                boolean returnToOriginalCellToPreventShuffling = !isFinishedSwitchingState()
                        && !droppedOnOriginalCellDuringTransition && !dropTargetLayout
                        .isRegionVacant(mTargetCell[0], mTargetCell[1], spanX, spanY);
                int[] resultSpan = new int[2];
                if (returnToOriginalCellToPreventShuffling) {
                    mTargetCell[0] = mTargetCell[1] = -1;
                } else {
                    //让目的地Layout重新布局一下顺序,腾出drop的位置
                    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;
                    WidgetSizes.updateWidgetSizeRanges(awhv, mLauncher, resultSpan[0],
                            resultSpan[1]);
                }

                if (foundCell) {//目的地有空出位置 start
                    int targetScreenIndex = getPageIndexForScreenId(screenId);
                    int snapScreen = getLeftmostVisiblePageForIndex(targetScreenIndex);
                    // On large screen devices two pages can be shown at the same time, and snap
                    // isn't needed if the source and target screens appear at the same time
                    if (snapScreen != mCurrentPage && !hasMovedIntoHotseat) {
                        snapToPage(snapScreen);
                        snappedToNewPage = true;
                    }
                    final ItemInfo info = (ItemInfo) cell.getTag();
                    /*这一段实现把cell从原来的父View中remove掉,添加到目的layout里去 start*/
                    if (hasMovedLayouts) {
                        // Reparent the view  这段非常关键,重新安排父View start
                        Log.d("drop","drop to different layout!!");//表示放在了不同的layout里

                        CellLayout parentCell = getParentCellLayoutForView(cell);
                        if (parentCell != null) {
                            parentCell.removeView(cell);//如果注释这句,就会报错,说明view不能有两个parent
                        } else if (mDragInfo.cell instanceof LauncherAppWidgetHostView) {
                            d.dragView.detachContentView(/* reattachToPreviousParent= */ false);
                        } else if (FeatureFlags.IS_STUDIO_BUILD) {
                            throw new NullPointerException("mDragInfo.cell has null parent");
                        }

                        addInScreen(cell, container, screenId, mTargetCell[0], mTargetCell[1],
                                info.spanX, info.spanY);
                        //假如我们在此调用getParentCellLayoutForView(cell);得到的parentCell就是目的layout了
                        // Reparent the view  这段非常关键,重新安排父View end
                        //added by Kevin.Ye for create keep dragObject when dropped to Hotseat
                        if(hasMovedIntoHotseat)
                            onDropToHotseat(d);
                        //end
                    }
                    /*这一段实现把cell从原来的父View中remove掉,添加到目的layout里去 end*/
                    // update the item's position after drop
                    /*把目的地位置,作为这个cell的位置 start*/
                    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;
                    /*把目的地位置,作为这个cell的位置 end*/
                    if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
                            cell instanceof LauncherAppWidgetHostView) {//这段处理的是widget
                        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
                                && !options.isAccessibleDrag) {
                            onCompleteRunnable = () -> {
                                if (!isPageInTransition()) {
                                    AppWidgetResizeFrame.showForWidget(hostView, cellLayout);
                                }
                            };
                        }
                    }
                    //更新到数据库里去
                    mLauncher.getModelWriter().modifyItemInDatabase(info, container, screenId,
                            lp.cellX, lp.cellY, item.spanX, item.spanY);


                } else {//没有找到位置drop start

                    if (!returnToOriginalCellToPreventShuffling) {
                        onNoCellFound(dropTargetLayout, d.dragInfo, d.logInstanceId);
                    }
                    if (mDragInfo.cell instanceof LauncherAppWidgetHostView) {
                        d.dragView.detachContentView(/* reattachToPreviousParent= */ true);
                    }

                    // 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);
                }//没有找到位置 end
            } else {//处理取消drag start
                // When drag is cancelled, reattach content view back to its original parent.
                if (mDragInfo.cell instanceof LauncherAppWidgetHostView) {
                    d.dragView.detachContentView(/* reattachToPreviousParent= */ true);
                }
                //处理取消drag end
            }

            final CellLayout parent = (CellLayout) cell.getParent().getParent();
            if (d.dragView.hasDrawn()) {//拖动View已经绘图 stat
                if (droppedOnOriginalCellDuringTransition) {//过渡的过程中拖到原来的位置 start
                    // Animate the item to its original position, while simultaneously exiting
                    // spring-loaded mode so the page meets the icon where it was picked up.
                    final RunnableList callbackList = new RunnableList();
                    final Runnable onCompleteCallback = onCompleteRunnable;
                    mLauncher.getDragController().animateDragViewToOriginalPosition(
                            /* onComplete= */ callbackList::executeAllAndDestroy, cell,
                            SPRING_LOADED.getTransitionDuration(mLauncher, true /* isToState */));
                    mLauncher.getStateManager().goToState(NORMAL, /* delay= */ 0,
                            onCompleteCallback == null
                                    ? null
                                    : forSuccessCallback(
                                            () -> callbackList.add(onCompleteCallback)));
                    mLauncher.getDropTargetBar().onDragEnd();
                    parent.onDropChild(cell);
                    return;
                }//过渡的过程中拖到原来的位置 end
                final ItemInfo info = (ItemInfo) cell.getTag();
                boolean isWidget = info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
                        || info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
                if (isWidget) {//桌面小组件drop到新位置
                    int animationType = resizeOnDrop ? ANIMATE_INTO_POSITION_AND_RESIZE :
                            ANIMATE_INTO_POSITION_AND_DISAPPEAR;
                    animateWidgetDrop(info, parent, d.dragView, null, animationType, cell, false);
                } else {//图标动画移动到新位置,但如果没有drop到目的地,则会回到原来的位置
                    int duration = snappedToNewPage ? ADJACENT_SCREEN_DROP_DURATION : -1;
                    mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration,
                            this);
                }
            } else {
                d.deferDragViewCleanupPostAnimation = false;
                cell.setVisibility(VISIBLE);
            }
            parent.onDropChild(cell);

            mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY,
                    onCompleteRunnable == null ? null : forSuccessCallback(onCompleteRunnable));
            mStatsLogManager.logger().withItemInfo(d.dragInfo).withInstanceId(d.logInstanceId)
                    .log(LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED);
        }

        if (d.stateAnnouncer != null && !droppedOnOriginalCell) {
            d.stateAnnouncer.completeAction(R.string.item_moved);
        }
    }

step2:同样在Workspace.java中添加增加的接口 onDropToHotseat(DragObject d)

java 复制代码
/*
    * Added by Kevin.Ye
    * when a shortcut was dropped to Hotseat,we create a new one in original position
    * */
    private void onDropToHotseat(DragObject d){
        ItemInfo info = d.dragInfo;
        WorkspaceItemInfo newItemInfo = null;
        View view;
        switch (info.itemType) {
            case ITEM_TYPE_APPLICATION:
            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
            case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
            case LauncherSettings.Favorites.ITEM_TYPE_SEARCH_ACTION:
                if (info instanceof WorkspaceItemFactory) {
                    // Came from all apps -- make a copy
                    newItemInfo = ((WorkspaceItemFactory) info).makeWorkspaceItem(mLauncher);
                    //d.dragInfo = info;
                }
                if (info instanceof WorkspaceItemInfo) {
                    // Came from all apps prediction row -- make a copy
                    newItemInfo = new WorkspaceItemInfo((WorkspaceItemInfo) info);
                    //d.dragInfo = info;
                }
                view = mLauncher.createShortcut((WorkspaceItemInfo) info);
                break;
            case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
                view = FolderIcon.inflateFolderAndIcon(R.layout.folder_icon, mLauncher, (ViewGroup) getChildAt(0),
                        (FolderInfo) info);
                break;
            default:
                throw new IllegalStateException("Unknown item type: " + info.itemType);
        }

        //final ContentResolver cr = getContext().getContentResolver();
        //newItemInfo.id = LauncherSettings.Settings.call(cr, LauncherSettings.Settings.METHOD_NEW_ITEM_ID).getInt(LauncherSettings.Settings.EXTRA_VALUE);
        if(newItemInfo == null)
            return;
        newItemInfo.id = ItemInfo.NO_ID;//this is very important,otherwise it won't be add new shortcut in database marked by Kevin.Ye
        Log.d("drop","info.id:"+info.id+" newItemInfo.id:"+newItemInfo.id);
        // Add the item to DB before adding to screen ensures that the container and other// values of the info is properly updated.
        Log.d("drop","new shortcut container:"+newItemInfo.container+" screenId:"+newItemInfo.screenId+" cellX:"+newItemInfo.cellX
                +" cellY:"+newItemInfo.cellY);
        //info.id = ItemInfo.NO_ID;//it is very important as we need to add new one to database not move
        mLauncher.getModelWriter().addOrMoveItemInDatabase(newItemInfo, newItemInfo.container, newItemInfo.screenId, newItemInfo.cellX, newItemInfo.cellY);
        addInScreen(view, newItemInfo.container, newItemInfo.screenId, newItemInfo.cellX, newItemInfo.cellY,newItemInfo.spanX, newItemInfo.spanY);
    }
相关推荐
maomi_95268 分钟前
头歌实训之游标触发器
android
南客先生19 分钟前
音视频项目在微服务领域的趋势场景题深度解析
java·微服务·面试·性能优化·音视频·高并发
xcLeigh1 小时前
HTML5好看的水果蔬菜在线商城网站源码系列模板8
java·前端·html5
Alsn861 小时前
11.Spring Boot 3.1.5 中使用 SpringDoc OpenAPI(替代 Swagger)生成 API 文档
java·spring boot·后端
周胡杰1 小时前
鸿蒙文件上传-从前端到后端详解,对比jq请求和鸿蒙arkts请求区别,对比new FormData()和鸿蒙arktsrequest.uploadFile
前端·华为·harmonyos·鸿蒙·鸿蒙系统
liyongjun63162 小时前
Java List分页工具
java·后端
唯鹿2 小时前
AI生成Flutter UI代码实践(一)
人工智能·flutter·ui
橙子199110162 小时前
请简述一下什么是 Kotlin?它有哪些特性?
android·开发语言·kotlin
猎人everest2 小时前
Spring Boot集成Spring Cloud 2024(不使用Feign)
java·spring boot·spring cloud
贫道绝缘子3 小时前
【Android】四大组件之Service
android