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

news2024/12/23 5:45:09

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

实现效果如下:

99fdd6f2bacc40e586ebec33272195eb.gif

实现思路:

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

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

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

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

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

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

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

 

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

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2091768.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【u盘还原教程】如何把启动u盘恢复回普通U盘

之前制作ubuntu启动盘装双系统 1、插入U盘&#xff0c;右键点击“此电脑”&#xff0c;选择“管理”&#xff0c;在“计算机管理”的面板中点击打开“磁盘管理”&#xff0c;会看到目前电脑上的所有磁盘&#xff0c;找到U盘的索引名&#xff08;如图标识&#xff0c;这里是“…

2024最新VMware17安装Windows10详细记录

本次将带来虚拟机VMware Workstation 17 pro安装Win10的教学&#xff0c;可用于各种软件测试&#xff0c;这里虽然只是示范了win10安装教学&#xff0c;实际上可以安装很多系统&#xff0c;步骤都差不多&#xff1b; 下载 一、下载虚拟机软件 下载方式一&#xff1a;官网下载…

虚拟机安装docker时yum错误及及解决方案

** Could not resolve host: mirrorlist.centos.org; 未知的错误 ** 出现这种错误&#xff0c;先尝试 ping www.baidu.com&#xff0c;然后再尝试 ping mirrorlist.centos.org 如果&#xff0c;baidu.com可以ping通&#xff0c;mirrorlist.centos.org 不能ping通&#xff0…

【三十四】springboot+easyRule初识规则引擎

代码场景&#xff1a;厂里有几个员工&#xff0c;现在厂长颁布了新的厂规关于薪资发放&#xff0c;如下&#xff1a; 1、加班时长超过80小时的&#xff0c;一个小时10块钱&#xff1b;不满80小时的&#xff0c;不算加班。2、上班打卡迟到3次以下的不扣钱&#xff0c;3次以上的一…

期权交易误区分享:喜欢重仓!

今天带你了解期权交易误区分享&#xff1a;喜欢重仓&#xff01;期权交易虽然吸引人&#xff0c;但也有不少容易掉进去的坑。 有的投资者被单个期权的百倍利润吸引&#xff0c;喜欢“一口吃成胖子”。 重仓买入虚值和重度虚值的期权&#xff0c;当标的有大涨或大跌时&#xf…

零基础Opencv学习(一)

一、显示图片 #include "opencv2/opencv.hpp" #include "opencv2/core/core.hpp" #include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp"cv::Mat image cv::imread("E:/OpencvStudyTest/1.png"…

量化投资策略与技术学习PART1.1:量化选股之再谈多因子模型(二)

在上一个多因子模型中&#xff0c;我手动对各个因子进行了回测&#xff0c;但是数据结果并不是十分理想&#xff0c;难道基本面指标真的和股票走势关系不大么&#xff1f; 这里我还是准备再测试一下&#xff0c;策略如下&#xff1a; &#xff08;1&#xff09;首先我获取了一下…

Doped code 介绍

doped是一款Python软件&#xff0c;用于缺陷超单元计算的生成、前/后处理和分析&#xff0c;以高效、可重复、用户友好、功能强大且完全可定制的方式实施缺陷模拟工作流程。 https://doped.readthedocs.io/en/latest/ 教程页面提供了演示代码功能和用法&#xff0c; 该软件包的…

考试评分系统设计与实现/基于django的在线考试系统

摘要 随着互联网技术的不断发展&#xff0c;各行各业的工作学习的模式都发生了不小的变化&#xff0c;们通过互联网技术不仅能够提高工作效率还能够降低出错的几率。而对于考试评分&#xff0c;一个专业的系统可以帮助管理者更加有效管理在考试评分&#xff0c;可以帮助提高克服…

vaspup2.0介绍

实时软件库:https://github.com/kavanase/vaspup2.0 vaspup是一个bash脚本集合&#xff0c;可以有效地生成和分析VASP收敛测试计算。 最初的vaspup是由Alex Ganose开发&#xff0c;用于基态能量收敛测试和POTCAR生成。 vaspup2.0的功能包括: 基态能量相对于ENCUT和k点密度的收敛…

Linux 配置wireshark 分析thread 使用nRF-Sniffer dongle

Linux 配置wireshark nRF-Sniffer-for-802.15.4 1.下载固件和配置文件 https://github.com/NordicSemiconductor/nRF-Sniffer-for-802.15.4 2.烧写固件 使用nRF Connect for Desktop 中的 programmer 4.3烧写 https://www.nordicsemi.com/Products/Development-tools/nrf-conne…

python07-单元测试框架unittest1-1

前言 单元测试是软件开发中不可或缺的一部分&#xff0c;可以帮助开发人员确保代码的正确性、可靠性和稳定性&#xff0c;python是一种广泛使用的程序语言&#xff0c;提供了多种单元测试工具&#xff0c;最常用的是unittest。本文将介绍unittest package, 包括如何编写测试Tes…

干货分享|分享一款高效的文件搜索工具 Everything

介绍&#xff1a;Everything软件是一款高效的文件搜索工具&#xff0c;主要用于快速定位计算机中的文件和文件夹。 官网地址&#xff1a;voidtools 下载方法&#xff1a;只需依据电脑配置与个人需求选择合适的版本下载&#xff0c;安装过程中一路默认即可轻松完成设置。 注&…

C语言刷题日记(附详解)(3)

一、选填部分 第一题: 以下的变量定义语句中&#xff0c;合法的是( ) A. byte a 128; B. boolean b null; C. long c 123L; D. float d 0.9239; 思路提示&#xff1a;观察选项时不要马虎&#xff0c;思考一下各种类型变量的取值范围&#xff0c;以及其初始化的形式是…

基于yolov8的课堂行为检测系统python源码+onnx模型+评估指标曲线+精美GUI界面

【算法介绍】 基于YOLOv8的课堂行为检测系统是现代教育技术的创新应用&#xff0c;该系统利用YOLOv8这一先进的深度学习算法&#xff0c;实现了对学生课堂行为的自动、高效和精准监测。YOLOv8在目标检测领域以其卓越的性能和速度著称&#xff0c;通过对学生上课视频或实时摄像…

深入学习AI大模型服务平台的选型应用相关技术和问诊咨询

AI大模型服务平台的选择和应用涉及到多个技术层面和业务需求的考量。下面我会详细介绍几个关键的技术点和应用场景&#xff0c;帮助您更好地理解和选择AI大模型服务平台。 技术选型 1.1 大模型种类 语言模型&#xff1a;如BERT、GPT-3、文心一言等&#xff0c;适用于自然语言…

自动驾驶---什么是Frenet坐标系?

1 背景 为什么提出Frenet坐标系&#xff1f;Frenet坐标系的提出主要是为了解决自动驾驶系统在路径规划的问题&#xff0c;它基于以下几个原因&#xff1a; 符合人类的驾驶习惯&#xff1a; 人类驾驶员在驾驶过程中&#xff0c;通常不会关心自己距离起点的横向和纵向距离&#x…

C++学习笔记——约瑟夫问题

一、题目描述 二、代码 #include<iostream>using namespace std;int main() {int n;//新建变量n int m;//新建变量m cin >>n;//键盘输入n cin >>m;//键盘输入m int a[n];//初始化数组 for(int i0;i<n;i){a[i] i1;}int* p &a[0];//指针指向数组的第一…

AIGC大师秘籍:六步法打造精准文字提示词

&#x1f31f; 引言&#xff1a; 在AIGC&#xff08;人工智能生成内容&#xff09;的奇幻世界里&#xff0c;编写优质的文字提示词&#xff08;Prompt&#xff09;就像是掌握了一门魔法&#xff0c;能够召唤出高质量的内容。今天&#xff0c;我将向你揭露一个六步法的秘密&…

【LeetCode】918. 环形子数组的最大和

1. 题目 2. 分析 单调队列的经典应用。 3. 代码 class Solution:def maxSubarraySumCircular(self, nums: List[int]) -> int:# 使用单调队列的解法# 转换为求区间长度不超过len(nums)内的最大和k len(nums)nums nums nums# 求出前缀和prefixSum [0] * len(nums) pre…