基于Android R版本分析
实现双屏联动的效果,需要关注两个部分:
- 手势识别;
- move stack进行移栈操作;
手势识别
基于原生手指识别的基础上,我们新定义自信的手指识别,例如:在3指滑动的情况下,实现Task move的操作;
实现方案
- 单独创建一个PointerEventListener的实现类,该实现类用于处理3指滑动的逻辑;
- 将该监听注册到DisplayContent中,这样每一个DisplayId都会对应一个PointerEventListener,每一个屏幕都可以响应3指滑动;
核心点
-
当前场景是否处于3指滑动, 针对非3指滑动场景下也需要进行处理;
-
MotionEvent.ACTION_DOWN、MotionEvent.ACTION_POINTER_DOWN、MotionEvent.ACTION_MOVE、MotionEvent.ACTION_POINTER_UP、MotionEvent.ACTION_UP 5个Event的响应;
-
开启move stack的临界值判断,例如:滑动间距 >= 10px时,触发move stack;
-
结束3指滑动之后,Task自动move的临界值判断,例如:
- 当move 间隔 >= 100px时,Task向右侧/左侧滑动,代表move stack成功;
- 当move 间隔 < 100px时,Task向右侧/左侧滑动,代表move stack未成功,Task自动回位;
-
主副屏的判断(默认主屏在左 ,副屏在右),例如:
- 主屏Task向副屏滑动时,主屏Task不能向左滑动,滑动过程中也不允许主屏Task向左滑动;
- 副屏Task向主屏滑动时,主屏Task不能向右滑动,滑动过程中也不允许副屏Task向右滑动;
move stack
移栈的操作,最核心的逻辑就是move stack,该接口的版本迭代:
- Android O 提供了moveStackToDisplay能力,在DisplayContent中实现;
- Android O ~ Android Q,该接口一致又DisplayContent提供;
- Android R开始,新增了RootWindowContainer类,moveStackToDisplay接口移植到RootWindowContainer中实现;
- Android S 开始,该接口名称修改为了moveRootTaskToDisplay ,Android T沿用moveRootTaskToDisplay;
核心点
在Android R版本中使用时,需要对比Android S ~ Android T的moveRootTaskToDisplay接口逻辑,各版本之间的实现逻辑上有一点的差异性;
双屏双图层
我们知道,在启动应用的时候,不会启动多个相同的应用进程,即无论存在多少个屏幕,同一个应用进程只会启动一个;那么对应的Task要不在Display 0显示,要不在Display 1中显示,无法做到两个屏幕同时显示同一个应用进程;
需要解决Display 0和Display 1同时显示Task画面;
实现方案
在Android R 及之后,在SurfaceControl提供了mirrorSurface接口:
java
/**
* Creates a mirrored hierarchy for the mirrorOf {@link SurfaceControl}
*
* Real Hierarchy Mirror
* SC (value that's returned)
* |
* A A'
* | |
* B B'
*
* @param mirrorOf The root of the hierarchy that should be mirrored.
* @return A SurfaceControl that's the parent of the root of the mirrored hierarchy.
*
* @hide
*/
public static SurfaceControl mirrorSurface(SurfaceControl mirrorOf) {
long nativeObj = nativeMirrorSurface(mirrorOf.mNativeObject);
SurfaceControl sc = new SurfaceControl();
sc.assignNativeObject(nativeObj, "mirrorSurface");
return sc;
}
Animator move
动画滑动的效果,本质上就是Task的偏移;
实现方案
滑动动画的实现方案:Matrix + SurfaceControl.Transaction
Matrix是Android中一个重要的类,提供了一些矩阵变换的方法,可以实现图形的缩放、旋转、平移和倾斜等操作;
start move

Display 0画面移动情况:
- Display 0是接受了3指触摸的右边移动了offsetX的距离,这时候屏幕Display 0的Task画面也要跟着向右平移offsetX;
Display 1画面移动情况:
- Display 1的Task画面应该最右边往左边有offsetX的画面,所以左边原点相对屏幕偏移距离就应该是 -(width - offsetX),这里应该是负数,因为屏幕最左边才是0;
Auto move
PointerEvent结束之后,Task画面并不是已经完全移动到另一个人屏幕中,Task画面大概念横跨两个屏幕,这样就需要判断Task auto move的临界值;
总结
在实现过程中,需要关注的几个点:
- 在move主屏的Task至副屏之后,主屏/副屏的界面实现不能为黑屏状态;
- 针对不同屏幕分辨率的时候,需要新增Scale操作;
- 针对不同屏幕density的时候,需要考虑解决方案;
- 在移栈的时候,有可能会出现黑屏闪烁的情况,这个问题需要调研(Activity重建导致的);
针对size和density不一致问题,需要在应用模块AndroidManifest.xml中新增属性:
ini
android:configChanges="density|screenSize|smallestScreenSize"
- density:像素密度
- screenSize:屏幕大小
- smallestScreenSize:屏幕的物理大小改变(可选)