仿华为车机UI--图标从Workspace拖动到Hotseat同时保留图标在原来位置
基于Android13 Launcher3,原生系统如果把图标从Workspace拖动到Hotseat里则Workspace就没有了,需求是执行拖拽动作后,图标同时保留在原位置。
实现效果如下:

实现思路:
1.如果在workspace中拖动,则保留原来“改变图标位置”的功能
2.如果拖拽到Hotseat,则添加到Hotseat的同时也在原来位置复制一个一样的View
3.每次改变都要存到数据库以保存当前状态
思路非常简单,但是要找适当的位置添加适当的代码不简单。需要读懂原生代码。
分析了源代码后,发现应该参考Workspace.java的onDrop与onDropExternal代码来实现。
step1: 读懂代码后添加注释(中文为添加的注释)和添加onDropToHotseat()的调用
@Overridepublic 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的 startfinal int[] touchXY = new int[] { (int) mDragViewVisualCenter[0],(int) mDragViewVisualCenter[1] };onDropExternal(touchXY, dropTargetLayout, d);Log.d("drop","onDropExternal!");//从别的地方(AllApp等)拖到worksapce的 end} else {//从workspace拖到workspace startfinal View cell = mDragInfo.cell;boolean droppedOnOriginalCellDuringTransition = false;if (dropTargetLayout != null && !d.cancelled) {//有地方可放且没有取消拖动 start// Move internallyboolean 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 vacantItemInfo 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 dropif (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) {//目的地有空出位置 startint 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 timeif (snapScreen != mCurrentPage && !hasMovedIntoHotseat) {snapToPage(snapScreen);snappedToNewPage = true;}final ItemInfo info = (ItemInfo) cell.getTag();/*这一段实现把cell从原来的父View中remove掉,添加到目的layout里去 start*/if (hasMovedLayouts) {// Reparent the view 这段非常关键,重新安排父View startLog.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 Hotseatif(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) {//这段处理的是widgetfinal CellLayout cellLayout = dropTargetLayout;// We post this call so that the widget has a chance to be placed// in its final locationfinal 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 startif (!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 positionCellLayout.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已经绘图 statif (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;}//过渡的过程中拖到原来的位置 endfinal 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 copynewItemInfo = ((WorkspaceItemFactory) info).makeWorkspaceItem(mLauncher);//d.dragInfo = info;}if (info instanceof WorkspaceItemInfo) {// Came from all apps prediction row -- make a copynewItemInfo = 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.YeLog.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 movemLauncher.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);}
相关文章:
仿华为车机UI--图标从Workspace拖动到Hotseat同时保留图标在原来位置
基于Android13 Launcher3,原生系统如果把图标从Workspace拖动到Hotseat里则Workspace就没有了,需求是执行拖拽动作后,图标同时保留在原位置。 实现效果如下: 实现思路: 1.如果在workspace中拖动,则保留原来“改变图标…...
C++ 中的 override 和 overload的区别
目录 1.Overload(重载) 2.override(重写) 3.override 和 overload 的根本区别 4.override 和 overload 的实际应用 5.override 和 overload 的常见误区 6.总结 1.Overload(重载) 定义:在同一个作用域内,可以声明几个功能类似的函数名相同的函数&am…...
spring boot3框架@Validated失效
项目中使用的springboot3.2.1,在使用Validated校验controller里参数时始终不生效;在网上查了相关资料,添加了spring-boot-starter-validation依赖但还是不行 经过层层调试,终于发现问题; springboot3添加Validated后校验的是 ja…...
UE5引擎工具链知识点
当我们提到“引擎工具链的开发”时,通常指的是为游戏开发或其他类型的软件开发创建一系列工具和技术栈的过程。这包括但不限于游戏引擎本身(如Unity或Unreal Engine),以及围绕这些引擎构建的各种工具和服务,比如用于构…...
Python的图像算术与逻辑运算详解
一.图像加法运算 图像加法运算主要有两种方法。第一种是调用Numpy库实现,目标图像像素为两张图像的像素之和;第二种是通过OpenCV调用add()函数实现。第二种方法的函数原型如下: dst add(src1, src2[, dst[, mask[, dtype]]]) – src1表示第…...
WSL 下的 CentOS 装 Docker
WSL 下的 CentOS 装 Docker 卸载旧版本安装前的准备工作1. 安装 yum-utils2. 添加阿里云的 yum 镜像仓库3. 快速生成 Yum 缓存 安装Docker启动docker运行 hello-world卸载 Docker 引擎参考资料 卸载旧版本 sudo yum remove docker \ docker-client \ docker-client-latest \ d…...
v0.dev快速开发
探索v0.dev:次世代开发者之利器 今之技艺日新月异,开发者之工具亦随之进步不辍。v0.dev者,新兴之开发者利器也,迅速引起众多开发者之瞩目。本文将引汝探究v0.dev之基本功能与优势,助汝速速上手,提升开发之…...
python之字符串
创建字符串 s "Hello, World!"常用字符串操作 获取字符串长度 length len(s) print(length) # 输出: 13字符串拼接 s1 "Hello" s2 "World" s3 s1 ", " s2 "!" print(s3) # 输出: Hello, World!重复字符串 s …...
算法打卡 Day28(回溯算法)-组合总数 + 组合总数 Ⅱ+ 电话号码的字母组合
文章目录 Leetcode 17-电话号码的字母组合题目描述解题思路 Leetcode 39-组合总数题目描述解题思路 Leetcode 216-组合总数 Ⅲ题目描述解题思路 Leetcode 17-电话号码的字母组合 题目描述 https://leetcode.cn/problems/letter-combinations-of-a-phone-number/description/ …...
【Hadoop|MapReduce篇】MapReduce概述
1. MapReduce定义 MapReduce是一个分布式运算程序的编程框架,是用户开发“基于Hadoop的数据分析应用”的核心框架。 MapReduce核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序,并发运行在一个Hadoop集群上。 2. Map…...
设置Virtualbox虚拟机共享文件夹
由于工作环境的原因,选择Virtualbox的方式安装虚拟操作系统,常用的操作系统为ubuntu,不知道道友是否也曾遇到这样的问题,就是虚拟机和主机进行文件拖拽的时候,会因为手抖造成拖拽失败,虚拟机界面显示大个的…...
从零开始的机器学习之旅
尊敬的读者们,在这个快速发展的数字时代,机器学习无疑已经成为了科技领域的一颗璀璨明星。它如同一把打开未来之门的钥匙,让我们能够窥探到数据背后的无限可能。今天,我将带领大家开启一段从零开始的机器学习之旅,让我…...
开源还是封闭?人工智能的两难选择
这篇文章于 2024 年 7 月 29 日首次出现在 The New Stack 上。人工智能正处于软件行业的完美风暴中,现在马克扎克伯格 (Mark Zuckerberg) 正在呼吁开源 AI。 关于如何控制 AI 的三个强大观点正在发生碰撞: 1 . 所有 AI 都应该是开…...
Prometheus 服务监控
官网:https://prometheus.io Prometheus 是什么 Prometheus 是一个开源的系统监控和报警工具,专注于记录和存储时间序列数据(time-series data)。它最初由 SoundCloud 开发,并已成为 CNCF(云原生计算基金会…...
建模杂谈系列252 规则的串行改并行
说明 提到规则,还是需要看一眼RETE算法: Rete算法是一种用于高效处理基于规则的系统中的模式匹配问题的算法,广泛应用于专家系统、推理引擎和生产系统。它的设计目的是在大量规则和数据的组合中快速找到满足特定规则条件的模式。 Rete算法…...
0.ffmpeg面向对象oopc
因为查rtsp相关问题,接触了下ffmpeg源码,发现它和linux内核一样,虽然都是c写的,但是都是面向对象的思想,c的面向对象称之为oopc。 这让我想起来一件好玩的事,有些搞linux内核驱动的只会c的开发人员不知道l…...
KDD2024参会笔记-Day1
知乎想法:链接 听的第一场汇报:RAG Meeting LLMs 综述论文:https://arxiv.org/pdf/2405.06211 PPT:https://advanced-recommender-systems.github.io/RAG-Meets-LLMs/2024-KDD-RAG-Meets-LLM-tutorial-Part1.pdf 检索࿱…...
Java操作Elasticsearch的实用指南
Java操作Elasticsearch的实用指南 一、创建索引二、增删改查 一、创建索引 在ElasticSearch中索引相当于mysql中的表,mapping相当于表结构,所以第一步我们要先创建索引。 假设我们有一张文章表的数据需要同步到ElasticSearch,首先需要根据数据库表创建…...
数据库系统 第42节 数据库索引简介
数据库索引是数据库表中一个或多个列的数据结构,用于加快数据检索速度。除了基础的B-Tree索引,其他类型的索引针对特定的数据类型和查询模式提供了优化。以下是几种不同类型的索引及其使用场景的详细说明和示例代码。 1. 位图索引 (Bitmap Index) 位图…...
C++11 --- 智能指针
序言 在使用 C / C 进行编程时,许多场景都需要我们在堆上申请空间,堆内存的申请和释放都需要我们自己进行手动管理。这就存在容易造成堆内存泄露(忘记释放),二次释放,程序发生异常时内存泄露等问题…...
23-Oracle 23 ai 区块链表(Blockchain Table)
小伙伴有没有在金融强合规的领域中遇见,必须要保持数据不可变,管理员都无法修改和留痕的要求。比如医疗的电子病历中,影像检查检验结果不可篡改行的,药品追溯过程中数据只可插入无法删除的特性需求;登录日志、修改日志…...
Day131 | 灵神 | 回溯算法 | 子集型 子集
Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣(LeetCode) 思路: 笔者写过很多次这道题了,不想写题解了,大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...
Java 加密常用的各种算法及其选择
在数字化时代,数据安全至关重要,Java 作为广泛应用的编程语言,提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景,有助于开发者在不同的业务需求中做出正确的选择。 一、对称加密算法…...
2025盘古石杯决赛【手机取证】
前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来,实在找不到,希望有大佬教一下我。 还有就会议时间,我感觉不是图片时间,因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...
Angular微前端架构:Module Federation + ngx-build-plus (Webpack)
以下是一个完整的 Angular 微前端示例,其中使用的是 Module Federation 和 npx-build-plus 实现了主应用(Shell)与子应用(Remote)的集成。 🛠️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...
Docker 本地安装 mysql 数据库
Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker ;并安装。 基础操作不再赘述。 打开 macOS 终端,开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...
JS设计模式(4):观察者模式
JS设计模式(4):观察者模式 一、引入 在开发中,我们经常会遇到这样的场景:一个对象的状态变化需要自动通知其他对象,比如: 电商平台中,商品库存变化时需要通知所有订阅该商品的用户;新闻网站中࿰…...
Web中间件--tomcat学习
Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机,它可以执行Java字节码。Java虚拟机是Java平台的一部分,Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...
Rust 开发环境搭建
环境搭建 1、开发工具RustRover 或者vs code 2、Cygwin64 安装 https://cygwin.com/install.html 在工具终端执行: rustup toolchain install stable-x86_64-pc-windows-gnu rustup default stable-x86_64-pc-windows-gnu 2、Hello World fn main() { println…...
HybridVLA——让单一LLM同时具备扩散和自回归动作预测能力:训练时既扩散也回归,但推理时则扩散
前言 如上一篇文章《dexcap升级版之DexWild》中的前言部分所说,在叠衣服的过程中,我会带着团队对比各种模型、方法、策略,毕竟针对各个场景始终寻找更优的解决方案,是我个人和我司「七月在线」的职责之一 且个人认为,…...
