Android T多屏多显——应用双屏间拖拽移动功能(更新中)

2024-04-16 01:28

本文主要是介绍Android T多屏多显——应用双屏间拖拽移动功能(更新中),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

功能以及显示效果简介

需求:在双屏显示中,把启动的应用从其中一个屏幕中移动到另一个屏幕中。
操作:通过双指按压应用使其移动,如果移动的距离过小,我们就不移动到另一屏幕,否则移动到另一屏。
请添加图片描述

功能分析

多屏中移动应用至另一屏本质就是Task的移动。
从窗口层级结构的角度来说,就是把Display1中的DefaultTaskDisplayArea上的Task,移动到Display2中的DefaultTaskDisplayArea上。
容器结构简化树状图如下所示:
在这里插入图片描述

窗口层级结构简化树状图如下所示:
在这里插入图片描述

关键代码知识点

移动Task至另一屏幕

代码路径:frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java

    /*** Move root task with all its existing content to specified display.** @param rootTaskId Id of root task to move.* @param displayId  Id of display to move root task to.* @param onTop      Indicates whether container should be place on top or on bottom.*/void moveRootTaskToDisplay(int rootTaskId, int displayId, boolean onTop) {//根据displayId获取DisplayContentfinal DisplayContent displayContent = getDisplayContentOrCreate(displayId);if (displayContent == null) {throw new IllegalArgumentException("moveRootTaskToDisplay: Unknown displayId="+ displayId);}//调用moveRootTaskToTaskDisplayArea方法moveRootTaskToTaskDisplayArea(rootTaskId, displayContent.getDefaultTaskDisplayArea(),onTop);}

入参说明:
rootTaskId需要移动的Task的Id。可以通过Task中getRootTaskId()方法获取。
displayId需要移动到对应屏幕的Display的Id。可以通过DisplayContent中的getDisplayId()方法获取。
onTop移动后的Task是放在容器顶部还是底部。true表示顶部,false表示底部。
代码解释:
这个方法首先通过getDisplayContentOrCreate方法根据displayId获取DisplayContent,然后调用moveRootTaskToTaskDisplayArea方法进行移动。
其中传递参数displayContent.getDefaultTaskDisplayArea(),表示获取DisplayContent下面的DefaultTaskDisplayArea。

    /*** Move root task with all its existing content to specified task display area.** @param rootTaskId      Id of root task to move.* @param taskDisplayArea The task display area to move root task to.* @param onTop           Indicates whether container should be place on top or on bottom.*/void moveRootTaskToTaskDisplayArea(int rootTaskId, TaskDisplayArea taskDisplayArea,boolean onTop) {//获取Taskfinal Task rootTask = getRootTask(rootTaskId);if (rootTask == null) {throw new IllegalArgumentException("moveRootTaskToTaskDisplayArea: Unknown rootTaskId="+ rootTaskId);}final TaskDisplayArea currentTaskDisplayArea = rootTask.getDisplayArea();if (currentTaskDisplayArea == null) {throw new IllegalStateException("moveRootTaskToTaskDisplayArea: rootTask=" + rootTask+ " is not attached to any task display area.");}if (taskDisplayArea == null) {throw new IllegalArgumentException("moveRootTaskToTaskDisplayArea: Unknown taskDisplayArea=" + taskDisplayArea);}if (currentTaskDisplayArea == taskDisplayArea) {throw new IllegalArgumentException("Trying to move rootTask=" + rootTask+ " to its current taskDisplayArea=" + taskDisplayArea);}//把获取到的task重新挂载到了新display的taskDisplayArearootTask.reparent(taskDisplayArea, onTop);// Resume focusable root task after reparenting to another display area.//窗口或任务reparent之后,恢复焦点,激活相关任务的活动,并更新活动的可见性,以确保窗口管理器和用户界面的状态一致和正确。rootTask.resumeNextFocusAfterReparent();// TODO(multi-display): resize rootTasks properly if moved from split-screen.}

根据前面传递的TaskId获取到Task,在通过rootTask.reparent(taskDisplayArea, onTop);方法,把这个Task重新挂载到了新display的taskDisplayArea上。然后使用rootTask.resumeNextFocusAfterReparent();方法更新窗口焦点显示。

  • rootTask.reparent(taskDisplayArea, onTop);
    代码路径:frameworks/base/services/core/java/com/android/server/wm/Task.java

     void reparent(TaskDisplayArea newParent, boolean onTop) {if (newParent == null) {throw new IllegalArgumentException("Task can't reparent to null " + this);}if (getParent() == newParent) {throw new IllegalArgumentException("Task=" + this + " already child of " + newParent);}//通过调用 canBeLaunchedOnDisplay 方法检查任务是否可以在新父区域所在的显示设备上启动。if (canBeLaunchedOnDisplay(newParent.getDisplayId())) {//实际执行reparent的操作。reparent(newParent, onTop ? POSITION_TOP : POSITION_BOTTOM);//如果Task是一个叶子Task(即没有子Task的Task)if (isLeafTask()) {//调用新父区域的 onLeafTaskMoved 方法来通知新父区域叶子Task已经移动。newParent.onLeafTaskMoved(this, onTop);}} else {Slog.w(TAG, "Task=" + this + " can't reparent to " + newParent);}}
    

    其中reparent(newParent, onTop ? POSITION_TOP : POSITION_BOTTOM);实际执行reparent的操作。这里根据 onTop 的值来决定任务应该被放置在新父区域的顶部还是底部。我们再看看这方法的具体实现。
    代码路径:frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java

    void reparent(WindowContainer newParent, int position) {if (newParent == null) {throw new IllegalArgumentException("reparent: can't reparent to null " + this);}if (newParent == this) {throw new IllegalArgumentException("Can not reparent to itself " + this);}final WindowContainer oldParent = mParent;if (mParent == newParent) {throw new IllegalArgumentException("WC=" + this + " already child of " + mParent);}// Collect before removing child from old parent, because the old parent may be removed if// this is the last child in it.//记录reparent的容器(this)相关信息,这里的this指的是移动的Task,newParent是新的TaskDisplayAreamTransitionController.collectReparentChange(this, newParent);// The display object before reparenting as that might lead to old parent getting removed// from the display if it no longer has any child.//获取之前的DisplayContent和新的DisplayContentfinal DisplayContent prevDc = oldParent.getDisplayContent();final DisplayContent dc = newParent.getDisplayContent();//设置 mReparenting 为 true,表示正在执行reparent操作。//然后从旧父容器中移除当前容器,并将其添加到新父容器的指定位置。//最后,将 mReparenting 设置为 false,表示reparent操作完成。mReparenting = true;oldParent.removeChild(this);newParent.addChild(this, position);mReparenting = false;// Relayout display(s)//标记新父容器对应的显示内容为需要布局。//如果新父容器和旧父容器的显示内容不同,//则触发显示内容改变的通知,并标记旧显示内容也需要布局。//最后,调用layoutAndAssignWindowLayersIfNeeded方法确保显示内容按需进行布局和窗口层级的分配。dc.setLayoutNeeded();if (prevDc != dc) {onDisplayChanged(dc);prevDc.setLayoutNeeded();}getDisplayContent().layoutAndAssignWindowLayersIfNeeded();// Send onParentChanged notification here is we disabled sending it in setParent for// reparenting case.//处理窗口容器在父容器变更时的各种逻辑onParentChanged(newParent, oldParent);//处理窗口容器在不同父容器之间同步迁移的逻辑onSyncReparent(oldParent, newParent);}
    
  • rootTask.resumeNextFocusAfterReparent();
    代码路径:frameworks/base/services/core/java/com/android/server/wm/Task.java

        void resumeNextFocusAfterReparent() {//调整焦点adjustFocusToNextFocusableTask("reparent", true /* allowFocusSelf */,true /* moveDisplayToTop */);//恢复当前焦点任务的顶部活动mRootWindowContainer.resumeFocusedTasksTopActivities();// Update visibility of activities before notifying WM. This way it won't try to resize// windows that are no longer visible.//更新activities的可见性mRootWindowContainer.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,!PRESERVE_WINDOWS);}
    

这篇关于Android T多屏多显——应用双屏间拖拽移动功能(更新中)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/907459

相关文章

Android 悬浮窗开发示例((动态权限请求 | 前台服务和通知 | 悬浮窗创建 )

《Android悬浮窗开发示例((动态权限请求|前台服务和通知|悬浮窗创建)》本文介绍了Android悬浮窗的实现效果,包括动态权限请求、前台服务和通知的使用,悬浮窗权限需要动态申请并引导... 目录一、悬浮窗 动态权限请求1、动态请求权限2、悬浮窗权限说明3、检查动态权限4、申请动态权限5、权限设置完毕后

前端原生js实现拖拽排课效果实例

《前端原生js实现拖拽排课效果实例》:本文主要介绍如何实现一个简单的课程表拖拽功能,通过HTML、CSS和JavaScript的配合,我们实现了课程项的拖拽、放置和显示功能,文中通过实例代码介绍的... 目录1. 效果展示2. 效果分析2.1 关键点2.2 实现方法3. 代码实现3.1 html部分3.2

Android里面的Service种类以及启动方式

《Android里面的Service种类以及启动方式》Android中的Service分为前台服务和后台服务,前台服务需要亮身份牌并显示通知,后台服务则有启动方式选择,包括startService和b... 目录一句话总结:一、Service 的两种类型:1. 前台服务(必须亮身份牌)2. 后台服务(偷偷干

SpringBoot整合DeepSeek实现AI对话功能

《SpringBoot整合DeepSeek实现AI对话功能》本文介绍了如何在SpringBoot项目中整合DeepSeekAPI和本地私有化部署DeepSeekR1模型,通过SpringAI框架简化了... 目录Spring AI版本依赖整合DeepSeek API key整合本地化部署的DeepSeek

Python实现多路视频多窗口播放功能

《Python实现多路视频多窗口播放功能》这篇文章主要为大家详细介绍了Python实现多路视频多窗口播放功能的相关知识,文中的示例代码讲解详细,有需要的小伙伴可以跟随小编一起学习一下... 目录一、python实现多路视频播放功能二、代码实现三、打包代码实现总结一、python实现多路视频播放功能服务端开

css实现图片旋转功能

《css实现图片旋转功能》:本文主要介绍了四种CSS变换效果:图片旋转90度、水平翻转、垂直翻转,并附带了相应的代码示例,详细内容请阅读本文,希望能对你有所帮助... 一 css实现图片旋转90度.icon{ -moz-transform:rotate(-90deg); -webkit-transfo

Android kotlin语言实现删除文件的解决方案

《Androidkotlin语言实现删除文件的解决方案》:本文主要介绍Androidkotlin语言实现删除文件的解决方案,在项目开发过程中,尤其是需要跨平台协作的项目,那么删除用户指定的文件的... 目录一、前言二、适用环境三、模板内容1.权限申请2.Activity中的模板一、前言在项目开发过程中,尤

5分钟获取deepseek api并搭建简易问答应用

《5分钟获取deepseekapi并搭建简易问答应用》本文主要介绍了5分钟获取deepseekapi并搭建简易问答应用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需... 目录1、获取api2、获取base_url和chat_model3、配置模型参数方法一:终端中临时将加

C语言小项目实战之通讯录功能

《C语言小项目实战之通讯录功能》:本文主要介绍如何设计和实现一个简单的通讯录管理系统,包括联系人信息的存储、增加、删除、查找、修改和排序等功能,文中通过代码介绍的非常详细,需要的朋友可以参考下... 目录功能介绍:添加联系人模块显示联系人模块删除联系人模块查找联系人模块修改联系人模块排序联系人模块源代码如下

Java中使用Java Mail实现邮件服务功能示例

《Java中使用JavaMail实现邮件服务功能示例》:本文主要介绍Java中使用JavaMail实现邮件服务功能的相关资料,文章还提供了一个发送邮件的示例代码,包括创建参数类、邮件类和执行结... 目录前言一、历史背景二编程、pom依赖三、API说明(一)Session (会话)(二)Message编程客