序
本文主要介绍分屏的分割线和分割区域线,分割线的拖拽和分割线交换上下分屏。这里需要区分的是分屏的分割线和分割区域线是两个不同的概念,分屏的分割线指的就是我们肉眼看到的分割线,而分割区域线则是把屏幕分割成多份,用于在分割线拖动时,对分屏的显示大小进行调整或退出分屏。
我们需要注意分割线和分割区域线的区别,这里先来看看分割线的组成,以及分割区域线是如何设定的。
分屏分割线
分屏的分割线和我们实际看到的与代码表现有所不同,这里简单介绍差异点。
分割线的组成
从分割线布局split_divider.xml中,我们可以看到,其自定义布局DividerView代表分割线的实现,其他组成部分有着相应的自定义布局,分割线的本质就是一个矩形View。
分割线显示效果
分割线的创建
代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
protected void onFinishInflate() {
super.onFinishInflate();
mDividerBar = findViewById(R.id.divider_bar);
mHandle = findViewById(R.id.docked_divider_handle);
mBackground = findViewById(R.id.docked_divider_background);
mDividerRoundedCorner = findViewById(R.id.divider_rounded_corner);
mTouchElevation = getResources().getDimensionPixelSize(
R.dimen.docked_stack_divider_lift_elevation);
mDoubleTapDetector = new GestureDetector(getContext(), new DoubleTapListener());
mInteractive = true;
setOnTouchListener(this);
mHandle.setAccessibilityDelegate(mHandleDelegate);
}
这个方法主要就是获取了这些控件的id,docked_divider_handle
是实际去显示、操作的控件,监听onTouch方法控制其他拖动。
mHandle
就是DividerHandleView对象,这个控件就是一个自定义View。
代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
/** Inflates {@link DividerView} on to the root surface. */
void init(SplitLayout splitLayout, InsetsState insetsState) {
if (mDividerView != null || mViewHost != null) {
throw new UnsupportedOperationException(
"Try to inflate divider view again without release first");
}
mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this,
"SplitWindowManager");
mDividerView = (DividerView) LayoutInflater.from(mContext)
.inflate(R.layout.split_divider, null /* root */);
final Rect dividerBounds = splitLayout.getDividerBounds();
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
dividerBounds.width(), dividerBounds.height(), TYPE_DOCK_DIVIDER,
FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL | FLAG_WATCH_OUTSIDE_TOUCH
| FLAG_SPLIT_TOUCH | FLAG_SLIPPERY,
PixelFormat.TRANSLUCENT);
lp.token = new Binder();
lp.setTitle(mWindowName);
lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
lp.accessibilityTitle = mContext.getResources().getString(R.string.accessibility_divider);
mViewHost.setView(mDividerView, lp);
mDividerView.setup(splitLayout, this, mViewHost, insetsState);
}
在SplitWindowManager的init方法中,通过inflate去加载了split_divider.xml布局。
分割线的属性
分割线的整体宽度
代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
private void updateDividerConfig(Context context) {
......
mDividerSize = resources.getDimensionPixelSize(R.dimen.split_divider_bar_width);
......
}
即R.dimen.split_divider_bar_width
。
代码路径:frameworks/base/libs/WindowManager/Shell/res/values/dimen.xml
<dimen name="split_divider_bar_width">10dp</dimen>
代码中的默认值为10dp。
例如我们把这个值改为50dp后,效果如下:
从该图中我们就可以看出分割线整体宽度变大。
分割线拖动条的颜色和宽高
代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java
public DividerHandleView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mPaint.setColor(getResources().getColor(R.color.docked_divider_handle, null));
mPaint.setAntiAlias(true);
mWidth = getResources().getDimensionPixelSize(R.dimen.split_divider_handle_width);
mHeight = getResources().getDimensionPixelSize(R.dimen.split_divider_handle_height);
mCurrentWidth = mWidth;
mCurrentHeight = mHeight;
mTouchingWidth = mWidth > mHeight ? mWidth / 2 : mWidth;
mTouchingHeight = mHeight > mWidth ? mHeight / 2 : mHeight;
}
这里我们可以看到通过docked_divider_handle
修改颜色
代码路径:frameworks/base/libs/WindowManager/Shell/res/values/colors.xml
<color name="docked_divider_handle">#ffffff</color>
默认为白色。
通过split_divider_handle_width
宽度和split_divider_handle_height
修改高度
<!-- Divider handle size for split screen -->
<dimen name="split_divider_handle_width">72dp</dimen>
<dimen name="split_divider_handle_height">3dp</dimen>
这里宽度默认为72dp,高度为3dp。
这里我们修改颜色为 #FFC0CB (粉红) ,宽度为 150dp ,高度为 10dp ,效果如下图:
分割区域线
这里5条分割线划分了5个区域,当分割线到了不同的区域对分屏大小以及是否退出做出判断。
创建过程堆栈
创建分割区域线是在,TaskOrganizer.onTaskAppeared(Task出现)和TaskOrganizer.onTaskInfoChanged(Task信息更变)场景中触发。我们这里主要关注在分屏流程中重写的StageCoordinator.onTaskAppeared和StageCoordinator.onTaskInfoChanged这两个流程。
触发TaskOrganizer.onTaskAppeared创建
顶部
DividerSnapAlgorithm: SnapTarget
DividerSnapAlgorithm: java.lang.Exception
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm$SnapTarget.<init>(DividerSnapAlgorithm.java:473)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.calculateTargets(DividerSnapAlgorithm.java:294)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.<init>(DividerSnapAlgorithm.java:138)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.<init>(DividerSnapAlgorithm.java:112)
DividerSnapAlgorithm: at com.android.wm.shell.common.split.SplitLayout.getSnapAlgorithm(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:589)
DividerSnapAlgorithm: at com.android.wm.shell.common.split.SplitLayout.<init>(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:156)
DividerSnapAlgorithm: at com.android.wm.shell.splitscreen.StageCoordinator.onTaskAppeared(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:1781)
DividerSnapAlgorithm: at com.android.wm.shell.ShellTaskOrganizer.onTaskAppeared(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:490)
DividerSnapAlgorithm: at com.android.wm.shell.ShellTaskOrganizer.onTaskAppeared(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:477)
DividerSnapAlgorithm: at android.window.TaskOrganizer$1.lambda$onTaskAppeared$4(TaskOrganizer.java:328)
DividerSnapAlgorithm: at android.window.TaskOrganizer$1.$r8$lambda$Z3SZqVKLE-2zO9NE5htsmlBghFs(Unknown Source:0)
DividerSnapAlgorithm: at android.window.TaskOrganizer$1$$ExternalSyntheticLambda5.run(Unknown Source:6)
DividerSnapAlgorithm: at android.os.Handler.handleCallback(Handler.java:958)
DividerSnapAlgorithm: at android.os.Handler.dispatchMessage(Handler.java:99)
DividerSnapAlgorithm: at android.os.Looper.loopOnce(Looper.java:205)
DividerSnapAlgorithm: at android.os.Looper.loop(Looper.java:294)
DividerSnapAlgorithm: at android.os.HandlerThread.run(HandlerThread.java:67)
中上部
DividerSnapAlgorithm: SnapTarget
DividerSnapAlgorithm: java.lang.Exception
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm$SnapTarget.<init>(DividerSnapAlgorithm.java:473)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm$SnapTarget.<init>(DividerSnapAlgorithm.java:469)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.maybeAddTarget(DividerSnapAlgorithm.java:357)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.addNonDismissingTargets(DividerSnapAlgorithm.java:318)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.addRatio16_9Targets(DividerSnapAlgorithm.java:348)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.calculateTargets(DividerSnapAlgorithm.java:300)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.<init>(DividerSnapAlgorithm.java:138)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.<init>(DividerSnapAlgorithm.java:112)
DividerSnapAlgorithm: at com.android.wm.shell.common.split.SplitLayout.getSnapAlgorithm(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:589)
DividerSnapAlgorithm: at com.android.wm.shell.common.split.SplitLayout.<init>(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:156)
DividerSnapAlgorithm: at com.android.wm.shell.splitscreen.StageCoordinator.onTaskAppeared(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:1781)
DividerSnapAlgorithm: at com.android.wm.shell.ShellTaskOrganizer.onTaskAppeared(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:490)
DividerSnapAlgorithm: at com.android.wm.shell.ShellTaskOrganizer.onTaskAppeared(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:477)
DividerSnapAlgorithm: at android.window.TaskOrganizer$1.lambda$onTaskAppeared$4(TaskOrganizer.java:328)
DividerSnapAlgorithm: at android.window.TaskOrganizer$1.$r8$lambda$Z3SZqVKLE-2zO9NE5htsmlBghFs(Unknown Source:0)
DividerSnapAlgorithm: at android.window.TaskOrganizer$1$$ExternalSyntheticLambda5.run(Unknown Source:6)
DividerSnapAlgorithm: at android.os.Handler.handleCallback(Handler.java:958)
DividerSnapAlgorithm: at android.os.Handler.dispatchMessage(Handler.java:99)
DividerSnapAlgorithm: at android.os.Looper.loopOnce(Looper.java:205)
DividerSnapAlgorithm: at android.os.Looper.loop(Looper.java:294)
DividerSnapAlgorithm: at android.os.HandlerThread.run(HandlerThread.java:67)
中部
DividerSnapAlgorithm: SnapTarget
DividerSnapAlgorithm: java.lang.Exception
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm$SnapTarget.<init>(DividerSnapAlgorithm.java:473)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm$SnapTarget.<init>(DividerSnapAlgorithm.java:469)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.addMiddleTarget(DividerSnapAlgorithm.java:364)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.addNonDismissingTargets(DividerSnapAlgorithm.java:319)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.addRatio16_9Targets(DividerSnapAlgorithm.java:348)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.calculateTargets(DividerSnapAlgorithm.java:300)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.<init>(DividerSnapAlgorithm.java:138)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.<init>(DividerSnapAlgorithm.java:112)
DividerSnapAlgorithm: at com.android.wm.shell.common.split.SplitLayout.getSnapAlgorithm(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:589)
DividerSnapAlgorithm: at com.android.wm.shell.common.split.SplitLayout.<init>(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:156)
DividerSnapAlgorithm: at com.android.wm.shell.splitscreen.StageCoordinator.onTaskAppeared(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:1781)
DividerSnapAlgorithm: at com.android.wm.shell.ShellTaskOrganizer.onTaskAppeared(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:490)
DividerSnapAlgorithm: at com.android.wm.shell.ShellTaskOrganizer.onTaskAppeared(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:477)
DividerSnapAlgorithm: at android.window.TaskOrganizer$1.lambda$onTaskAppeared$4(TaskOrganizer.java:328)
DividerSnapAlgorithm: at android.window.TaskOrganizer$1.$r8$lambda$Z3SZqVKLE-2zO9NE5htsmlBghFs(Unknown Source:0)
DividerSnapAlgorithm: at android.window.TaskOrganizer$1$$ExternalSyntheticLambda5.run(Unknown Source:6)
DividerSnapAlgorithm: at android.os.Handler.handleCallback(Handler.java:958)
DividerSnapAlgorithm: at android.os.Handler.dispatchMessage(Handler.java:99)
DividerSnapAlgorithm: at android.os.Looper.loopOnce(Looper.java:205)
DividerSnapAlgorithm: at android.os.Looper.loop(Looper.java:294)
DividerSnapAlgorithm: at android.os.HandlerThread.run(HandlerThread.java:67)
中下部
DividerSnapAlgorithm: SnapTarget
DividerSnapAlgorithm: java.lang.Exception
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm$SnapTarget.<init>(DividerSnapAlgorithm.java:473)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm$SnapTarget.<init>(DividerSnapAlgorithm.java:469)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.maybeAddTarget(DividerSnapAlgorithm.java:357)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.addNonDismissingTargets(DividerSnapAlgorithm.java:320)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.addRatio16_9Targets(DividerSnapAlgorithm.java:348)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.calculateTargets(DividerSnapAlgorithm.java:300)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.<init>(DividerSnapAlgorithm.java:138)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.<init>(DividerSnapAlgorithm.java:112)
DividerSnapAlgorithm: at com.android.wm.shell.common.split.SplitLayout.getSnapAlgorithm(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:589)
DividerSnapAlgorithm: at com.android.wm.shell.common.split.SplitLayout.<init>(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:156)
DividerSnapAlgorithm: at com.android.wm.shell.splitscreen.StageCoordinator.onTaskAppeared(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:1781)
DividerSnapAlgorithm: at com.android.wm.shell.ShellTaskOrganizer.onTaskAppeared(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:490)
DividerSnapAlgorithm: at com.android.wm.shell.ShellTaskOrganizer.onTaskAppeared(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:477)
DividerSnapAlgorithm: at android.window.TaskOrganizer$1.lambda$onTaskAppeared$4(TaskOrganizer.java:328)
DividerSnapAlgorithm: at android.window.TaskOrganizer$1.$r8$lambda$Z3SZqVKLE-2zO9NE5htsmlBghFs(Unknown Source:0)
DividerSnapAlgorithm: at android.window.TaskOrganizer$1$$ExternalSyntheticLambda5.run(Unknown Source:6)
DividerSnapAlgorithm: at android.os.Handler.handleCallback(Handler.java:958)
DividerSnapAlgorithm: at android.os.Handler.dispatchMessage(Handler.java:99)
DividerSnapAlgorithm: at android.os.Looper.loopOnce(Looper.java:205)
DividerSnapAlgorithm: at android.os.Looper.loop(Looper.java:294)
DividerSnapAlgorithm: at android.os.HandlerThread.run(HandlerThread.java:67)
底部
DividerSnapAlgorithm: SnapTarget
DividerSnapAlgorithm: java.lang.Exception
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm$SnapTarget.<init>(DividerSnapAlgorithm.java:473)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.calculateTargets(DividerSnapAlgorithm.java:312)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.<init>(DividerSnapAlgorithm.java:138)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.<init>(DividerSnapAlgorithm.java:112)
DividerSnapAlgorithm: at com.android.wm.shell.common.split.SplitLayout.getSnapAlgorithm(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:589)
DividerSnapAlgorithm: at com.android.wm.shell.common.split.SplitLayout.<init>(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:156)
DividerSnapAlgorithm: at com.android.wm.shell.splitscreen.StageCoordinator.onTaskAppeared(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:1781)
DividerSnapAlgorithm: at com.android.wm.shell.ShellTaskOrganizer.onTaskAppeared(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:490)
DividerSnapAlgorithm: at com.android.wm.shell.ShellTaskOrganizer.onTaskAppeared(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:477)
DividerSnapAlgorithm: at android.window.TaskOrganizer$1.lambda$onTaskAppeared$4(TaskOrganizer.java:328)
DividerSnapAlgorithm: at android.window.TaskOrganizer$1.$r8$lambda$Z3SZqVKLE-2zO9NE5htsmlBghFs(Unknown Source:0)
DividerSnapAlgorithm: at android.window.TaskOrganizer$1$$ExternalSyntheticLambda5.run(Unknown Source:6)
DividerSnapAlgorithm: at android.os.Handler.handleCallback(Handler.java:958)
DividerSnapAlgorithm: at android.os.Handler.dispatchMessage(Handler.java:99)
DividerSnapAlgorithm: at android.os.Looper.loopOnce(Looper.java:205)
DividerSnapAlgorithm: at android.os.Looper.loop(Looper.java:294)
DividerSnapAlgorithm: at android.os.HandlerThread.run(HandlerThread.java:67)
从上述堆栈中,不同位置的分割线的创建流程从TaskOrganizer.onTaskAppeared
到DividerSnapAlgorithm.calculateTarget
流程基本相同,后续创建时有不同方法调用的差异。
触发TaskOrganizer.onTaskInfoChanged创建
顶部
DividerSnapAlgorithm: SnapTarget
DividerSnapAlgorithm: java.lang.Exception
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm$SnapTarget.<init>(DividerSnapAlgorithm.java:473)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.calculateTargets(DividerSnapAlgorithm.java:294)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.<init>(DividerSnapAlgorithm.java:138)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.<init>(DividerSnapAlgorithm.java:112)
DividerSnapAlgorithm: at com.android.wm.shell.common.split.SplitLayout.getSnapAlgorithm(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:589)
DividerSnapAlgorithm: at com.android.wm.shell.common.split.SplitLayout.updateConfiguration(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:335)
DividerSnapAlgorithm: at com.android.wm.shell.splitscreen.StageCoordinator.onTaskInfoChanged(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:1806)
DividerSnapAlgorithm: at com.android.wm.shell.ShellTaskOrganizer.onTaskInfoChanged(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:529)
DividerSnapAlgorithm: at android.window.TaskOrganizer$1.lambda$onTaskInfoChanged$6(TaskOrganizer.java:340)
DividerSnapAlgorithm: at android.window.TaskOrganizer$1.$r8$lambda$FmJPvZyGqAGeVe9o6dSQkNL3f3g(Unknown Source:0)
DividerSnapAlgorithm: at android.window.TaskOrganizer$1$$ExternalSyntheticLambda3.run(Unknown Source:4)
DividerSnapAlgorithm: at android.os.Handler.handleCallback(Handler.java:958)
DividerSnapAlgorithm: at android.os.Handler.dispatchMessage(Handler.java:99)
DividerSnapAlgorithm: at android.os.Looper.loopOnce(Looper.java:205)
DividerSnapAlgorithm: at android.os.Looper.loop(Looper.java:294)
DividerSnapAlgorithm: at android.os.HandlerThread.run(HandlerThread.java:67)
中上部
DividerSnapAlgorithm: SnapTarget
DividerSnapAlgorithm: java.lang.Exception
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm$SnapTarget.<init>(DividerSnapAlgorithm.java:473)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm$SnapTarget.<init>(DividerSnapAlgorithm.java:469)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.maybeAddTarget(DividerSnapAlgorithm.java:357)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.addNonDismissingTargets(DividerSnapAlgorithm.java:318)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.addRatio16_9Targets(DividerSnapAlgorithm.java:348)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.calculateTargets(DividerSnapAlgorithm.java:300)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.<init>(DividerSnapAlgorithm.java:138)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.<init>(DividerSnapAlgorithm.java:112)
DividerSnapAlgorithm: at com.android.wm.shell.common.split.SplitLayout.getSnapAlgorithm(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:589)
DividerSnapAlgorithm: at com.android.wm.shell.common.split.SplitLayout.updateConfiguration(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:335)
DividerSnapAlgorithm: at com.android.wm.shell.splitscreen.StageCoordinator.onTaskInfoChanged(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:1806)
DividerSnapAlgorithm: at com.android.wm.shell.ShellTaskOrganizer.onTaskInfoChanged(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:529)
DividerSnapAlgorithm: at android.window.TaskOrganizer$1.lambda$onTaskInfoChanged$6(TaskOrganizer.java:340)
DividerSnapAlgorithm: at android.window.TaskOrganizer$1.$r8$lambda$FmJPvZyGqAGeVe9o6dSQkNL3f3g(Unknown Source:0)
DividerSnapAlgorithm: at android.window.TaskOrganizer$1$$ExternalSyntheticLambda3.run(Unknown Source:4)
DividerSnapAlgorithm: at android.os.Handler.handleCallback(Handler.java:958)
DividerSnapAlgorithm: at android.os.Handler.dispatchMessage(Handler.java:99)
DividerSnapAlgorithm: at android.os.Looper.loopOnce(Looper.java:205)
DividerSnapAlgorithm: at android.os.Looper.loop(Looper.java:294)
DividerSnapAlgorithm: at android.os.HandlerThread.run(HandlerThread.java:67)
中部
DividerSnapAlgorithm: SnapTarget
DividerSnapAlgorithm: java.lang.Exception
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm$SnapTarget.<init>(DividerSnapAlgorithm.java:473)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm$SnapTarget.<init>(DividerSnapAlgorithm.java:469)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.addMiddleTarget(DividerSnapAlgorithm.java:364)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.addNonDismissingTargets(DividerSnapAlgorithm.java:319)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.addRatio16_9Targets(DividerSnapAlgorithm.java:348)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.calculateTargets(DividerSnapAlgorithm.java:300)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.<init>(DividerSnapAlgorithm.java:138)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.<init>(DividerSnapAlgorithm.java:112)
DividerSnapAlgorithm: at com.android.wm.shell.common.split.SplitLayout.getSnapAlgorithm(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:589)
DividerSnapAlgorithm: at com.android.wm.shell.common.split.SplitLayout.updateConfiguration(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:335)
DividerSnapAlgorithm: at com.android.wm.shell.splitscreen.StageCoordinator.onTaskInfoChanged(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:1806)
DividerSnapAlgorithm: at com.android.wm.shell.ShellTaskOrganizer.onTaskInfoChanged(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:529)
DividerSnapAlgorithm: at android.window.TaskOrganizer$1.lambda$onTaskInfoChanged$6(TaskOrganizer.java:340)
DividerSnapAlgorithm: at android.window.TaskOrganizer$1.$r8$lambda$FmJPvZyGqAGeVe9o6dSQkNL3f3g(Unknown Source:0)
DividerSnapAlgorithm: at android.window.TaskOrganizer$1$$ExternalSyntheticLambda3.run(Unknown Source:4)
DividerSnapAlgorithm: at android.os.Handler.handleCallback(Handler.java:958)
DividerSnapAlgorithm: at android.os.Handler.dispatchMessage(Handler.java:99)
DividerSnapAlgorithm: at android.os.Looper.loopOnce(Looper.java:205)
DividerSnapAlgorithm: at android.os.Looper.loop(Looper.java:294)
DividerSnapAlgorithm: at android.os.HandlerThread.run(HandlerThread.java:67)
中下部
DividerSnapAlgorithm: SnapTarget
DividerSnapAlgorithm: java.lang.Exception
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm$SnapTarget.<init>(DividerSnapAlgorithm.java:473)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm$SnapTarget.<init>(DividerSnapAlgorithm.java:469)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.maybeAddTarget(DividerSnapAlgorithm.java:357)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.addNonDismissingTargets(DividerSnapAlgorithm.java:320)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.addRatio16_9Targets(DividerSnapAlgorithm.java:348)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.calculateTargets(DividerSnapAlgorithm.java:300)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.<init>(DividerSnapAlgorithm.java:138)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.<init>(DividerSnapAlgorithm.java:112)
DividerSnapAlgorithm: at com.android.wm.shell.common.split.SplitLayout.getSnapAlgorithm(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:589)
DividerSnapAlgorithm: at com.android.wm.shell.common.split.SplitLayout.updateConfiguration(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:335)
DividerSnapAlgorithm: at com.android.wm.shell.splitscreen.StageCoordinator.onTaskInfoChanged(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:1806)
DividerSnapAlgorithm: at com.android.wm.shell.ShellTaskOrganizer.onTaskInfoChanged(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:529)
DividerSnapAlgorithm: at android.window.TaskOrganizer$1.lambda$onTaskInfoChanged$6(TaskOrganizer.java:340)
DividerSnapAlgorithm: at android.window.TaskOrganizer$1.$r8$lambda$FmJPvZyGqAGeVe9o6dSQkNL3f3g(Unknown Source:0)
DividerSnapAlgorithm: at android.window.TaskOrganizer$1$$ExternalSyntheticLambda3.run(Unknown Source:4)
DividerSnapAlgorithm: at android.os.Handler.handleCallback(Handler.java:958)
DividerSnapAlgorithm: at android.os.Handler.dispatchMessage(Handler.java:99)
DividerSnapAlgorithm: at android.os.Looper.loopOnce(Looper.java:205)
DividerSnapAlgorithm: at android.os.Looper.loop(Looper.java:294)
DividerSnapAlgorithm: at android.os.HandlerThread.run(HandlerThread.java:67)
底部
DividerSnapAlgorithm: SnapTarget
DividerSnapAlgorithm: java.lang.Exception
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm$SnapTarget.<init>(DividerSnapAlgorithm.java:473)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.calculateTargets(DividerSnapAlgorithm.java:312)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.<init>(DividerSnapAlgorithm.java:138)
DividerSnapAlgorithm: at com.android.internal.policy.DividerSnapAlgorithm.<init>(DividerSnapAlgorithm.java:112)
DividerSnapAlgorithm: at com.android.wm.shell.common.split.SplitLayout.getSnapAlgorithm(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:589)
DividerSnapAlgorithm: at com.android.wm.shell.common.split.SplitLayout.updateConfiguration(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:335)
DividerSnapAlgorithm: at com.android.wm.shell.splitscreen.StageCoordinator.onTaskInfoChanged(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:1806)
DividerSnapAlgorithm: at com.android.wm.shell.ShellTaskOrganizer.onTaskInfoChanged(go/retraceme 2e8d06de93b83950fc6dc605b7c564ee793c76f9bb3b4cc9e7c312a0136fcba5:529)
DividerSnapAlgorithm: at android.window.TaskOrganizer$1.lambda$onTaskInfoChanged$6(TaskOrganizer.java:340)
DividerSnapAlgorithm: at android.window.TaskOrganizer$1.$r8$lambda$FmJPvZyGqAGeVe9o6dSQkNL3f3g(Unknown Source:0)
DividerSnapAlgorithm: at android.window.TaskOrganizer$1$$ExternalSyntheticLambda3.run(Unknown Source:4)
DividerSnapAlgorithm: at android.os.Handler.handleCallback(Handler.java:958)
DividerSnapAlgorithm: at android.os.Handler.dispatchMessage(Handler.java:99)
DividerSnapAlgorithm: at android.os.Looper.loopOnce(Looper.java:205)
DividerSnapAlgorithm: at android.os.Looper.loop(Looper.java:294)
DividerSnapAlgorithm: at android.os.HandlerThread.run(HandlerThread.java:67)
从上述堆栈中,不同位置的分割线的创建流程从TaskOrganizer.onTaskInfoChanged
到DividerSnapAlgorithm.calculateTarget
流程基本相同,后续创建时有不同方法调用的差异。
这两个堆栈的共同的终点为SplitLayout.getSnapAlgorithm,也就是说最终分割线的创建流程是从这里开始的。
代码分析
这里我们分别以StageCoordinator.onTaskAppeared和StageCoordinator.onTaskInfoChanged这两个流程为出发点,跟踪代码逻辑。最后再从两个起点共同的终点SplitLayout.getSnapAlgorithm开始分析。
分屏task appeared (StageCoordinator.onTaskAppeared)
分屏的布局会在系统启动之后预留相关task。因此会触发TaskOrganizer.onTaskAppeared
调用到分屏的StageCoordinator.onTaskAppeared
方法。
代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@Override
@CallSuper
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
if (mRootTaskInfo != null || taskInfo.hasParentTask()) {
throw new IllegalArgumentException(this + "\n Unknown task appeared: " + taskInfo);
}
mRootTaskInfo = taskInfo;
mRootTaskLeash = leash;
//如果mSplitLayout为空,则初始化分屏布局
if (mSplitLayout == null) {
mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext,
mRootTaskInfo.configuration, this, mParentContainerCallbacks,
mDisplayController, mDisplayImeController, mTaskOrganizer,
PARALLAX_ALIGN_CENTER /* parallaxType */);
mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout);
}
//挂载对应stage到分屏task
onRootTaskAppeared();
}
这里我们只关注SplitLayout
的初始化
mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext,
mRootTaskInfo.configuration, this, mParentContainerCallbacks,
mDisplayController, mDisplayImeController, mTaskOrganizer,
PARALLAX_ALIGN_CENTER /* parallaxType */);
这里可以看出TAG + "SplitDivider"
就是Surface上分割线名称为StageCoordinatorSplitDivider的来源。
代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
public SplitLayout(String windowName, Context context, Configuration configuration,
SplitLayoutHandler splitLayoutHandler,
SplitWindowManager.ParentContainerCallbacks parentContainerCallbacks,
DisplayController displayController, DisplayImeController displayImeController,
ShellTaskOrganizer taskOrganizer, int parallaxType) {
//初始化一些参数
mContext = context.createConfigurationContext(configuration);
mOrientation = configuration.orientation;
mRotation = configuration.windowConfiguration.getRotation();
mDensity = configuration.densityDpi;
mSplitLayoutHandler = splitLayoutHandler;
mDisplayController = displayController;
mDisplayImeController = displayImeController;
//创建SplitWindowManager
mSplitWindowManager = new SplitWindowManager(windowName, mContext, configuration,
parentContainerCallbacks);
mTaskOrganizer = taskOrganizer;
mImePositionProcessor = new ImePositionProcessor(mContext.getDisplayId());
mSurfaceEffectPolicy = new ResizingEffectPolicy(parallaxType);
//更新分屏相关参数
updateDividerConfig(mContext);
//设置分屏区域大小,这里读取的就是设备屏幕的宽高
mRootBounds.set(configuration.windowConfiguration.getBounds());
//创建分割线区域DividerSnapAlgorithm
mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
resetDividerPosition();
mDimNonImeSide = mContext.getResources().getBoolean(R.bool.config_dimNonImeAttachedSide);
updateInvisibleRect();
}
这个方法主要就是用于分屏布局的初始化,我们主要看看mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
用于创建分屏分割线,这里mRootBounds
传递的就是前面获取的设备屏幕的宽高。
分屏task信息Changed (tageCoordinator.onTaskInfoChanged)
分屏操作会涉及到应用bounds发生改变,因此会触发TaskOrganizer.onTaskInfoChanged
调用到分屏的StageCoordinator.onTaskInfoChanged
方法。
代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@Override
@CallSuper
public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
if (mRootTaskInfo == null || mRootTaskInfo.taskId != taskInfo.taskId) {
throw new IllegalArgumentException(this + "\n Unknown task info changed: " + taskInfo);
}
mRootTaskInfo = taskInfo;
//mSplitLayout不为空,
if (mSplitLayout != null
//调用updateConfiguration判断分屏参数是否发生改变,从而更新
&& mSplitLayout.updateConfiguration(mRootTaskInfo.configuration)
//MainStage存在
&& mMainStage.isActive()
//shell动画开关
&& !ENABLE_SHELL_TRANSITIONS) {
// Clear the divider remote animating flag as the divider will be re-rendered to apply
// the new rotation config.
mIsDividerRemoteAnimating = false;
//更新分屏布局,实际上就是重新初始化SplitWindowManager
mSplitLayout.update(null /* t */);
//修改分屏布局的size变化
onLayoutSizeChanged(mSplitLayout);
}
}
这里我们主要关注mSplitLayout.updateConfiguration(mRootTaskInfo.configuration)
代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
/** Applies new configuration, returns {@code false} if there's no effect to the layout. */
public boolean updateConfiguration(Configuration configuration) {
// Update the split bounds when necessary. Besides root bounds changed, split bounds need to
// be updated when the rotation changed to cover the case that users rotated the screen 180
// degrees.
// Make sure to render the divider bar with proper resources that matching the screen
// orientation.
//获取一些参数
final int rotation = configuration.windowConfiguration.getRotation();
final Rect rootBounds = configuration.windowConfiguration.getBounds();
final int orientation = configuration.orientation;
final int density = configuration.densityDpi;
final int uiMode = configuration.uiMode;
//判断当前参数和之前的参数值是否有变化
if (mOrientation == orientation
&& mRotation == rotation
&& mDensity == density
&& mUiMode == uiMode
&& mRootBounds.equals(rootBounds)) {
return false;
}
//更新各个参数
mContext = mContext.createConfigurationContext(configuration);
mSplitWindowManager.setConfiguration(configuration);
mOrientation = orientation;
mTempRect.set(mRootBounds);
mRootBounds.set(rootBounds);
mRotation = rotation;
mDensity = density;
mUiMode = uiMode;
//创建分割线区域DividerSnapAlgorithm
mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
updateDividerConfig(mContext);
initDividerPosition(mTempRect);
updateInvisibleRect();
return true;
}
从代码中可以看出,如果task信息没有发生改变就不会重复创建分割线。
分割线区域创建
代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
private DividerSnapAlgorithm getSnapAlgorithm(Context context, Rect rootBounds) {
final boolean isLandscape = isLandscape(rootBounds);
final Rect insets = getDisplayStableInsets(context);
// Make split axis insets value same as the larger one to avoid bounds1 and bounds2
// have difference for avoiding size-compat mode when switching unresizable apps in
// landscape while they are letterboxed.
//判断横竖屏场景适配insets
if (!isLandscape) {
final int largerInsets = Math.max(insets.top, insets.bottom);
insets.set(insets.left, largerInsets, insets.right, largerInsets);
}
//创建分割区域线DividerSnapAlgorithm
return new DividerSnapAlgorithm(
context.getResources(),
rootBounds.width(),
rootBounds.height(),
mDividerSize,
!isLandscape,
insets,
isLandscape ? DOCKED_LEFT : DOCKED_TOP /* dockSide */);
}
这个方法就是根据横竖屏调整相关参数,然后创建分屏分割线。
new DividerSnapAlgorithm(
context.getResources(),
rootBounds.width(),
rootBounds.height(),
mDividerSize,
!isLandscape,
insets,
isLandscape ? DOCKED_LEFT : DOCKED_TOP /* dockSide */);
在这个方法中传递了一些参数:
rootBounds.width()
:屏幕宽度
rootBounds.height()
:屏幕高度
mDividerSize
:分割线整体宽度,这个值是在前面SplitLayout.updateDividerConfig方法中通过split_divider_bar_width
属性设置的值,代码中默认值为10dp。
isLandscape
:设备横竖屏状态,返回true表示横屏,返回alse表示竖屏。(我们这里只关注竖屏情况)
insets
:用来指定一个View或者布局的内边距,也可以用于控制View在容器内的位置。
代码路径:frameworks/base/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize,
boolean isHorizontalDivision, Rect insets, int dockSide) {
this(res, displayWidth, displayHeight, dividerSize, isHorizontalDivision, insets,
dockSide, false /* minimized */, true /* resizable */);
}
public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize,
boolean isHorizontalDivision, Rect insets, int dockSide, boolean isMinimizedMode,
boolean isHomeResizable) {
......
//根据不同的设备类型获取对应的值
mSnapMode = isMinimizedMode ? SNAP_MODE_MINIMIZED :
res.getInteger(com.android.internal.R.integer.config_dockedStackDividerSnapMode);
......
//计算5条分割区域线
calculateTargets(isHorizontalDivision, dockSide);
//分别获取5条分割区域线
//中上部
mFirstSplitTarget = mTargets.get(1);
//中下部
mLastSplitTarget = mTargets.get(mTargets.size() - 2);
//顶部
mDismissStartTarget = mTargets.get(0);
//底部
mDismissEndTarget = mTargets.get(mTargets.size() - 1);
//中部
mMiddleTarget = mTargets.get(mTargets.size() / 2);
mMiddleTarget.isMiddleTarget = true;
}
1、我们先看看mSnapMode
的值是怎么确定的。
mSnapMode = isMinimizedMode ? SNAP_MODE_MINIMIZED :
res.getInteger(com.android.internal.R.integer.config_dockedStackDividerSnapMode);
-
isMinimizedMode为true
mSnapMode
值为SNAP_MODE_MINIMIZED
-
竖屏 代码路径:frameworks/base/core/res/res/values/config.xml
<!-- Controls the snap mode for the docked stack divider 0 - 3 snap targets: left/top has 16:9 ratio, 1:1, and right/bottom has 16:9 ratio 1 - 3 snap targets: fixed ratio, 1:1, (1 - fixed ratio) 2 - 1 snap target: 1:1 --> <integer name="config_dockedStackDividerSnapMode">0</integer>
-
sw600dp 代码路径:frameworks/base/core/res/res/values-sw600dp/config.xml
<integer name="config_dockedStackDividerSnapMode">1</integer>
-
横屏 代码路径:frameworks/base/core/res/res/values-land/config.xml
<integer name="config_dockedStackDividerSnapMode">2</integer>
这里根据不同的情况,获取的mSnapMode
值不同,我们这里只讨论竖屏场景,因此这里mSnapMode
值为0。
2、在看看calculateTargets
方法,用于计算5条分割区域线。
计算后的值会存储到mTargets
变量中,这个变量定义如下:
private final ArrayList<SnapTarget> mTargets = new ArrayList<>();
3、最后分别获取5条分割区域线
//中上部
mFirstSplitTarget = mTargets.get(1);
//中下部
mLastSplitTarget = mTargets.get(mTargets.size() - 2);
//顶部
mDismissStartTarget = mTargets.get(0);
//底部
mDismissEndTarget = mTargets.get(mTargets.size() - 1);
//中部
mMiddleTarget = mTargets.get(mTargets.size() / 2);
分割区域线计算
private void calculateTargets(boolean isHorizontalDivision, int dockedSide) {
mTargets.clear();
int dividerMax = isHorizontalDivision
? mDisplayHeight
: mDisplayWidth;
int startPos = -mDividerSize;
if (dockedSide == DOCKED_RIGHT) {
startPos += mInsets.left;
}
//首先添加SnapTarget.FLAG_DISMISS_START的SnapTarget,即上分屏最顶部分割线
mTargets.add(new SnapTarget(startPos, startPos, SnapTarget.FLAG_DISMISS_START,
0.35f));
//根据mSnapMode来分别创建分割情况
switch (mSnapMode) {
//正常竖屏就是SNAP_MODE_16_9,一般会创建3个分割线
case SNAP_MODE_16_9:
addRatio16_9Targets(isHorizontalDivision, dividerMax);
break;
case SNAP_FIXED_RATIO:
addFixedDivisionTargets(isHorizontalDivision, dividerMax);
break;
//正常横屏就是SNAP_ONLY_1_1,所以只有中间一个分割线
case SNAP_ONLY_1_1:
addMiddleTarget(isHorizontalDivision);
break;
case SNAP_MODE_MINIMIZED:
addMinimizedTarget(isHorizontalDivision, dockedSide);
break;
}
//最后添加SnapTarget.FLAG_DISMISS_END的SnapTarget,即下分屏最底部分割线
mTargets.add(new SnapTarget(dividerMax, dividerMax, SnapTarget.FLAG_DISMISS_END, 0.35f));
}
这个方法中根据不同情况计算了各个分割线的位置,FLAG_DISMISS_START表示最顶部分割线,FLAG_DISMISS_END表示最底部分割线。前面说过值讨论竖屏情况,即mSnapMode
值为0的情况。
/**
* 3 snap targets: left/top has 16:9 ratio (for videos), 1:1, and right/bottom has 16:9 ratio
*/
private static final int SNAP_MODE_16_9 = 0;
/**
* 3 snap targets: fixed ratio, 1:1, (1 - fixed ratio)
*/
private static final int SNAP_FIXED_RATIO = 1;
/**
* 1 snap target: 1:1
*/
private static final int SNAP_ONLY_1_1 = 2;
/**
* 1 snap target: minimized height, (1 - minimized height)
*/
private static final int SNAP_MODE_MINIMIZED = 3;
从代码中我们可以看到SNAP_MODE_16_9 = 0
,因此这里另外的三个分割在addRatio16_9Targets
方法中计算。
private void addRatio16_9Targets(boolean isHorizontalDivision, int dividerMax) {
//计算各个位置
int start = isHorizontalDivision ? mInsets.top : mInsets.left;
int end = isHorizontalDivision
? mDisplayHeight - mInsets.bottom
: mDisplayWidth - mInsets.right;
int startOther = isHorizontalDivision ? mInsets.left : mInsets.top;
int endOther = isHorizontalDivision
? mDisplayWidth - mInsets.right
: mDisplayHeight - mInsets.bottom;
float size = 9.0f / 16.0f * (endOther - startOther);
int sizeInt = (int) Math.floor(size);
int topPosition = start + sizeInt;
int bottomPosition = end - sizeInt - mDividerSize;
//这里计算了topPosition,bottomPosition两个位置,这里就是3分割线的上下分割线位置
addNonDismissingTargets(isHorizontalDivision, topPosition, bottomPosition, dividerMax);
}
private void addNonDismissingTargets(boolean isHorizontalDivision, int topPosition,
int bottomPosition, int dividerMax) {
//中上部
maybeAddTarget(topPosition, topPosition - getStartInset());
//中部
addMiddleTarget(isHorizontalDivision);
//中下部
maybeAddTarget(bottomPosition,
dividerMax - getEndInset() - (bottomPosition + mDividerSize));
}
这里中上部和中下部计算调用的是相同的方法,不同是传递的值不同用来确定上下分割线位置。
分割区域线简图
分割线的拖拽
onTouch方法
代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@Override
public boolean onTouch(View v, MotionEvent event) {
if (mSplitLayout == null || !mInteractive) {
return false;
}
if (mDoubleTapDetector.onTouchEvent(event)) {
return true;
}
// Convert to use screen-based coordinates to prevent lost track of motion events while
// moving divider bar and calculating dragging velocity.
event.setLocation(event.getRawX(), event.getRawY());
final int action = event.getAction() & MotionEvent.ACTION_MASK;
final boolean isLandscape = isLandscape();
//touchPos的值区分横竖屏,横屏记录X点,竖屏记录Y点
final int touchPos = (int) (isLandscape ? event.getX() : event.getY());
switch (action) {
case MotionEvent.ACTION_DOWN:
mVelocityTracker = VelocityTracker.obtain();
mVelocityTracker.addMovement(event);
//设置当前触摸状态
setTouching();
//记录起始触摸位置
mStartPos = touchPos;
mMoving = false;
mSplitLayout.onStartDragging();
break;
case MotionEvent.ACTION_MOVE:
mVelocityTracker.addMovement(event);
//拖动一定距离才允许移动,这里mTouchSlop就是阈值
if (!mMoving && Math.abs(touchPos - mStartPos) > mTouchSlop) {
mStartPos = touchPos;
mMoving = true;
}
//允许拖动
if (mMoving) {
//mSplitLayout.getDividePosition()获取当前分割线的位置
//touchPos - mStartPos表示拖动的距离
//两者相加获得拖动后的分割线位置
final int position = mSplitLayout.getDividePosition() + touchPos - mStartPos;
mLastDraggingPosition = position;
//更新分割线和上下分屏bounds
mSplitLayout.updateDivideBounds(position);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
releaseTouching();
if (!mMoving) {
mSplitLayout.onDraggingCancelled();
break;
}
//mVelocityTracker加速度相关,拖动速度问题
mVelocityTracker.addMovement(event);
mVelocityTracker.computeCurrentVelocity(1000 /* units */);
final float velocity = isLandscape
? mVelocityTracker.getXVelocity()
: mVelocityTracker.getYVelocity();
//获取松手后的分割线位置
final int position = mSplitLayout.getDividePosition() + touchPos - mStartPos;
//计算当前分割线位置在5个区域的哪个部分
final DividerSnapAlgorithm.SnapTarget snapTarget =
mSplitLayout.findSnapTarget(position, velocity, false /* hardDismiss */);
//滑动到对应的分割区域
mSplitLayout.snapToTarget(position, snapTarget);
mMoving = false;
break;
}
return true;
}
更新并通知bounds变化(拖动中ACTION_MOVE)
移动过程中我们主要看看bounds相关变化的实现。 代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
/**
* Updates bounds with the passing position. Usually used to update recording bounds while
* performing animation or dragging divider bar to resize the splits.
*/
void updateDivideBounds(int position) {
//更新分屏和分割线bounds
updateBounds(position);
//通知分屏size变化
mSplitLayoutHandler.onLayoutSizeChanging(this, mSurfaceEffectPolicy.mParallaxOffset.x,
mSurfaceEffectPolicy.mParallaxOffset.y);
}
这个方法主要做两件事 1.更新分屏和分割线bounds 2.通知分屏size变化显示图标
更新分割线和上下分屏bounds
private void updateBounds(int position) {
//传递分割线位置、上下分屏bounds、分割线bounds参数
updateBounds(position, mBounds1, mBounds2, mDividerBounds, true /* setEffectBounds */);
}
position
:分割线位置
mBounds1
:竖屏情况代表上分屏bounds,横屏情况代表左分屏bounds
mBounds2
:竖屏情况代表下分屏bounds,横屏情况代表右分屏bounds
mDividerBounds
:分割线bounds,其包含
传递各个参数给updateBounds更新bounds值。
/** Updates recording bounds of divider window and both of the splits. */
private void updateBounds(int position, Rect bounds1, Rect bounds2, Rect dividerBounds,
boolean setEffectBounds) {
dividerBounds.set(mRootBounds);
bounds1.set(mRootBounds);
bounds2.set(mRootBounds);
final boolean isLandscape = isLandscape(mRootBounds);
if (isLandscape) {
position += mRootBounds.left;
dividerBounds.left = position - mDividerInsets;
dividerBounds.right = dividerBounds.left + mDividerWindowWidth;
bounds1.right = position;
bounds2.left = bounds1.right + mDividerSize;
} else {
// mRootBounds.top值一般为0
position += mRootBounds.top;
//计算分割线矩形顶部到屏幕顶部的距离
// mDividerInsets通过SplitLayout.updateDividerConfig计算所得值
dividerBounds.top = position - mDividerInsets;
//计算分割线矩形底部部到屏幕顶部的距离
// mDividerWindowWidth同样SplitLayout.updateDividerConfig计算所得值
dividerBounds.bottom = dividerBounds.top + mDividerWindowWidth;
//更新上分屏底部的值
bounds1.bottom = position;
//更新下分屏顶部的值,mDividerSize是分割线整体宽度
bounds2.top = bounds1.bottom + mDividerSize;
}
DockedDividerUtils.sanitizeStackBounds(bounds1, true /** topLeft */);
DockedDividerUtils.sanitizeStackBounds(bounds2, false /** topLeft */);
if (setEffectBounds) {
mSurfaceEffectPolicy.applyDividerPosition(position, isLandscape);
}
}
通过SplitLayout.updateDividerConfig
设置了mDividerInsets
、mDividerWindowWidth
和mDividerSize
的值。
这个方法主要就是更新了mDividerBounds
(分割线bounds)和mBounds1
(上分屏bounds)、mBounds2
(下分屏bounds)。
注:android U(14)中 上分屏默认为SideStage,下分屏默认为MainStage,T(13)中相反。
通知分屏bounds变化
代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@Override
public void onLayoutSizeChanging(SplitLayout layout, int offsetX, int offsetY) {
final SurfaceControl.Transaction t = mTransactionPool.acquire();
t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
//更新SurfaceBounds
updateSurfaceBounds(layout, t, true /* applyResizingOffset */);
//分别使用mTempRect1获取MainStage的bounds和mTempRect2获取SideStage的bounds
getMainStageBounds(mTempRect1);
getSideStageBounds(mTempRect2);
//更新下分屏size变化
mMainStage.onResizing(mTempRect1, mTempRect2, t, offsetX, offsetY, mShowDecorImmediately);
//更新上分屏size变化
mSideStage.onResizing(mTempRect2, mTempRect1, t, offsetX, offsetY, mShowDecorImmediately);
t.apply();
mTransactionPool.release(t);
}
更新SurfaceBounds
代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
void updateSurfaceBounds(@Nullable SplitLayout layout, @NonNull SurfaceControl.Transaction t,
boolean applyResizingOffset) {
final StageTaskListener topLeftStage =
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
final StageTaskListener bottomRightStage =
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
(layout != null ? layout : mSplitLayout).applySurfaceChanges(t, topLeftStage.mRootLeash,
bottomRightStage.mRootLeash, topLeftStage.mDimLayer, bottomRightStage.mDimLayer,
applyResizingOffset);
}
这个方法主要就获取对应的stage传递他们的leash用来后续更新对应的SurfaceBounds。 代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
/** Apply recorded surface layout to the {@link SurfaceControl.Transaction}. */
public void applySurfaceChanges(SurfaceControl.Transaction t, SurfaceControl leash1,
SurfaceControl leash2, SurfaceControl dimLayer1, SurfaceControl dimLayer2,
boolean applyResizingOffset) {
//获取分割线图层
final SurfaceControl dividerLeash = getDividerLeash();
if (dividerLeash != null) {
//获取前面更新的分割线bounds(mDividerBounds),将其保存到mTempRect中
getRefDividerBounds(mTempRect);
//更新图层位置
t.setPosition(dividerLeash, mTempRect.left, mTempRect.top);
// Resets layer of divider bar to make sure it is always on top.
t.setLayer(dividerLeash, Integer.MAX_VALUE);
}
//获取更新后的mBounds1(上分屏bounds),将其保存到mTempRect中
getRefBounds1(mTempRect);
//更新上分屏图层位置
t.setPosition(leash1, mTempRect.left, mTempRect.top)
.setWindowCrop(leash1, mTempRect.width(), mTempRect.height());
//获取更新后的mBounds2(下分屏bounds),将其保存到mTempRect中
getRefBounds2(mTempRect);
//更新下分屏图层位置
t.setPosition(leash2, mTempRect.left, mTempRect.top)
.setWindowCrop(leash2, mTempRect.width(), mTempRect.height());
if (mImePositionProcessor.adjustSurfaceLayoutForIme(
t, dividerLeash, leash1, leash2, dimLayer1, dimLayer2)) {
return;
}
mSurfaceEffectPolicy.adjustDimSurface(t, dimLayer1, dimLayer2);
if (applyResizingOffset) {
mSurfaceEffectPolicy.adjustRootSurface(t, leash1, leash2);
}
}
获取上下分屏bounds
getMainStageBounds(mTempRect1);
getSideStageBounds(mTempRect2);
这个方法就是分别使用mTempRect1获取MainStage的bounds和mTempRect2获取SideStage的bounds。
代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
private void getSideStageBounds(Rect rect) {
if (mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT) {
mSplitLayout.getBounds1(rect);
} else {
mSplitLayout.getBounds2(rect);
}
}
private void getMainStageBounds(Rect rect) {
if (mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT) {
mSplitLayout.getBounds2(rect);
} else {
mSplitLayout.getBounds1(rect);
}
}
这里从代码中可以看出,MainStage获取的是mBounds2
,SideStage获取的是mBounds1
。
更新上下分屏size变化显示图标
//更新下分屏size变化显示图标
mMainStage.onResizing(mTempRect1, mTempRect2, t, offsetX, offsetY, mShowDecorImmediately);
//更新上分屏size变化显示图标
mSideStage.onResizing(mTempRect2, mTempRect1, t, offsetX, offsetY, mShowDecorImmediately);
MainStage和SideStage分别调用了onResizing方法。 代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
void onResizing(Rect newBounds, Rect sideBounds, SurfaceControl.Transaction t, int offsetX,
int offsetY, boolean immediately) {
if (mSplitDecorManager != null && mRootTaskInfo != null) {
mSplitDecorManager.onResizing(mRootTaskInfo, newBounds, sideBounds, t, offsetX,
offsetY, immediately);
}
}
代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
/** Showing resizing hint. */
public void onResizing(ActivityManager.RunningTaskInfo resizingTask, Rect newBounds,
Rect sideBounds, SurfaceControl.Transaction t, int offsetX, int offsetY,
boolean immediately) {
if (mResizingIconView == null) {
return;
}
//设置新bounds
if (!mIsResizing) {
mIsResizing = true;
mOldBounds.set(newBounds);
}
mResizingBounds.set(newBounds);
mOffsetX = offsetX;
mOffsetY = offsetY;
//这里判断更新后的bounds的宽是否大于之前bounds的宽或者更新后bounds的高大于之前bounds的高
//只有放大的区域才会显示应用图标,缩小的区域不会显示图标
final boolean show =
newBounds.width() > mOldBounds.width() || newBounds.height() > mOldBounds.height();
//根据mShown标志判断是否显示动画
final boolean update = show != mShown;
if (update && mFadeAnimator != null && mFadeAnimator.isRunning()) {
// If we need to animate and animator still running, cancel it before we ensure both
// background and icon surfaces are non null for next animation.
mFadeAnimator.cancel();
}
//设置好mBackgroundLeash、mGapBackgroundLeash、mIcon和mIconLeash等参数
......
if (update) {
if (immediately) {
//设置leash可见性
t.setVisibility(mBackgroundLeash, show);
t.setVisibility(mIconLeash, show);
} else {
//启动相关显示的动画
startFadeAnimation(show, false, null);
}
//更新mShown标志
mShown = show;
}
}
分割线滑动到对应区域(松手ACTION_UP)
这里我们主要看看松手后,分割线是怎么滑动到对应区域的。主要分为两个步骤: 1.先找到松手时,分割线在哪个区域内; 2.滑动到对应的区域中
找到对应区域
final DividerSnapAlgorithm.SnapTarget snapTarget =
mSplitLayout.findSnapTarget(position, velocity, false /* hardDismiss */);
这里传递放手时的position
,为后面的计算做准备。
velocity
,为加速度相关参数,不做重点。
hardDismiss
,这个值一般为false,对松手时的效果有影响。
代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
/**
* Returns {@link DividerSnapAlgorithm.SnapTarget} which matches passing position and velocity.
* If hardDismiss is set to {@code true}, it will be harder to reach dismiss target.
*/
public DividerSnapAlgorithm.SnapTarget findSnapTarget(int position, float velocity,
boolean hardDismiss) {
return mDividerSnapAlgorithm.calculateSnapTarget(position, velocity, hardDismiss);
}
这里就是把参数传递到了DividerSnapAlgorithm的calculateSnapTarget中去计算。
代码路径:frameworks/base/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
/**
* @param position the top/left position of the divider
* @param velocity current dragging velocity
* @param hardDismiss if set, make it a bit harder to get reach the dismiss targets
*/
public SnapTarget calculateSnapTarget(int position, float velocity, boolean hardDismiss) {
//当前分割线位置小于中上部(mFirstSplitTarget.position),并且加速度小于-mMinDismissVelocityPxPerSecond
if (position < mFirstSplitTarget.position && velocity < -mMinDismissVelocityPxPerSecond) {
//返回到最顶部
return mDismissStartTarget;
}
//当前分割线位置大于中下部(mLastSplitTarget.position),并且加速度大于mMinDismissVelocityPxPerSecond
if (position > mLastSplitTarget.position && velocity > mMinDismissVelocityPxPerSecond) {
//返回到最顶部
return mDismissEndTarget;
}
if (Math.abs(velocity) < mMinFlingVelocityPxPerSecond) {
return snap(position, hardDismiss);
}
if (velocity < 0) {
return mFirstSplitTarget;
} else {
return mLastSplitTarget;
}
}
其中mMinFlingVelocityPxPerSecond
和mMinDismissVelocityPxPerSecond
为根据计算得出的固定值。
mMinFlingVelocityPxPerSecond =
MIN_FLING_VELOCITY_DP_PER_SECOND * res.getDisplayMetrics().density;
mMinDismissVelocityPxPerSecond =
MIN_DISMISS_VELOCITY_DP_PER_SECOND * res.getDisplayMetrics().density;
没有加速时,velocity
默认值为0,因此通常情况会走到Math.abs(velocity) < mMinFlingVelocityPxPerSecond
这种场景。
private SnapTarget snap(int position, boolean hardDismiss) {
if (shouldApplyFreeSnapMode(position)) {
return new SnapTarget(position, position, SnapTarget.FLAG_NONE);
}
int minIndex = -1;
float minDistance = Float.MAX_VALUE;
//遍历所有分割区域
int size = mTargets.size();
for (int i = 0; i < size; i++) {
SnapTarget target = mTargets.get(i);
//计算分割线到每个分割区域的距离
float distance = Math.abs(position - target.position);
//hardDismiss为false
if (hardDismiss) {
distance /= target.distanceMultiplier;
}
//找到距离最近的哪个区域,记录下期下标
if (distance < minDistance) {
minIndex = i;
minDistance = distance;
}
}
//获取最终的分割区域
return mTargets.get(minIndex);
}
这个方法主要就是通过遍历每个分割区域,从而找到分割线距离最近的区域。
滑动到对应区域
mSplitLayout.snapToTarget(position, snapTarget);
这里传递了snapTarget
就是对应前面SplitLayout.findSnapTarget找到了对应的分割区域线。
代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
/**
* Sets new divide position and updates bounds correspondingly. Notifies listener if the new
* target indicates dismissing split.
*/
public void snapToTarget(int currentPosition, DividerSnapAlgorithm.SnapTarget snapTarget) {
switch (snapTarget.flag) {
//FLAG_DISMISS_START:代表分割线上滑到顶部,导致上分屏退出情况。
//即在5个分割区域的最顶部松手
case FLAG_DISMISS_START:
flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
() -> mSplitLayoutHandler.onSnappedToDismiss(false /* bottomOrRight */,
EXIT_REASON_DRAG_DIVIDER));
break;
//FLAG_DISMISS_END:代表分割线一直向下滑动,一直到下分屏退出情况。
//即在5个分割区域的最底部松手
case FLAG_DISMISS_END:
flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
() -> mSplitLayoutHandler.onSnappedToDismiss(true /* bottomOrRight */,
EXIT_REASON_DRAG_DIVIDER));
break;
//分割线在5个分割区域的中上、中间、中下三个区域部分松手。
default:
flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
() -> setDividePosition(snapTarget.position, true /* applyLayoutChange */));
break;
}
}
这个方法对分割线在不同情况的区域做了不同的处理,调用flingDividePosition方法并传递相关参数以及回调。
传递参数:
currentPosition
:当前分割线位置
snapTarget.position
:找到的分割区域线位置
FLING_RESIZE_DURATION
:动画的时间,固定值250,即0.25秒
回调函数:
-
退出分屏
() -> mSplitLayoutHandler.onSnappedToDismiss(false /* bottomOrRight */, EXIT_REASON_DRAG_DIVIDER)
这个方法是在分割线滑动至最顶部或者最底部时,退出分屏所需要做的处理。根据传递的参数
bottomOrRight
的值来判断是上分屏退出(false),还是下分屏退出(true)。 分屏退出流程见 Android U 分屏——退出流程 。 -
更新最终分屏bounds
() -> setDividePosition(snapTarget.position, true /* applyLayoutChange */)
这个方法也是只有分割线在5个分割区域的中上、中间、中下三个区域部分松手时才会触发。
void setDividePosition(int position, boolean applyLayoutChange) { mDividePosition = position; updateBounds(mDividePosition); if (applyLayoutChange) { mSplitLayoutHandler.onLayoutSizeChanged(this); } }
updateBounds(mDividePosition);
这个方法在ACTION_MOVE操作中已经调用过了,这里只是最终确定bounds,SplitLayoutHandler.onLayoutSizeChanged
这个方法主要就是去调用StageCoordinator.updateWindowBounds
,最终通知system_server去修改bounds,详情见Android U 分屏——SystemUI侧处理。
最后讲一下松手后的动画flingDividePosition,前面已经确定了传递过来的各个参数和回调方法。
@VisibleForTesting
void flingDividePosition(int from, int to, int duration,
@Nullable Runnable flingFinishedCallback) {
//当前分割线位置和分割线区域位置相同,则不进行动画处理
if (from == to) {
// No animation run, still callback to stop resizing.
mSplitLayoutHandler.onLayoutSizeChanged(this);
if (flingFinishedCallback != null) {
flingFinishedCallback.run();
}
InteractionJankMonitorUtils.endTracing(
CUJ_SPLIT_SCREEN_RESIZE);
return;
}
if (mDividerFlingAnimator != null) {
mDividerFlingAnimator.cancel();
}
//设置ValueAnimator动画
mDividerFlingAnimator = ValueAnimator
.ofInt(from, to)
.setDuration(duration);
mDividerFlingAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
//添加对应的监听
mDividerFlingAnimator.addUpdateListener(
//这里更新动画回调实际上就是ACTION_MOVE中调用的updateDivideBounds操作
animation -> updateDivideBounds((int) animation.getAnimatedValue()));
mDividerFlingAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (flingFinishedCallback != null) {
flingFinishedCallback.run();
}
InteractionJankMonitorUtils.endTracing(
CUJ_SPLIT_SCREEN_RESIZE);
mDividerFlingAnimator = null;
}
@Override
public void onAnimationCancel(Animator animation) {
mDividerFlingAnimator = null;
}
});
//播放动画
mDividerFlingAnimator.start();
}
这里from
和to
代表的就是当前分割线位置和分割线区域位置,整体来说就是常规的ValueAnimator动画。
其中lambda表示如果不好理解我们可以转换成匿名内部类。
mDividerFlingAnimator.addUpdateListener(
animation -> updateDivideBounds((int) animation.getAnimatedValue()));
转换如下,含义相同:
mDividerFlingAnimator.addUpdateListener(
new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(@androidx.annotation.NonNull ValueAnimator animation) {
SplitLayout.this.updateDivideBounds((int) animation.getAnimatedValue());
}
});
分割线交换上下分屏
通过双击分割线进行上下分屏的交换。
onDoubleTap方法
代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
private class DoubleTapListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onDoubleTap(MotionEvent e) {
if (mSplitLayout != null) {
mSplitLayout.onDoubleTappedDivider();
}
return true;
}
@Override
public boolean onDoubleTapEvent(@NonNull MotionEvent e) {
return true;
}
}
设置一个GestureDetector.SimpleOnGestureListener的监听,onDoubleTap方法中实现。调用了SplitLayout.onDoubleTappedDivider。
代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
void onDoubleTappedDivider() {
mSplitLayoutHandler.onDoubleTappedDivider();
}
default void onDoubleTappedDivider() {
}
这里调用了SplitLayoutHandler.onDoubleTappedDivider接口,该接口的实现在StageCoordinator中。
代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
public void onDoubleTappedDivider() {
switchSplitPosition("double tap");
}
最终调用switchSplitPosition方法切换分屏。
上下分屏交换
代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
void switchSplitPosition(String reason) {
final SurfaceControl.Transaction t = mTransactionPool.acquire();
mTempRect1.setEmpty();
//根据mSideStagePosition的值确认上分屏(竖屏)或左分屏(横屏)对应的是mSideStage还是mMainStage。
final StageTaskListener topLeftStage =
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
//获取一个上分屏(竖屏)或左分屏(横屏)截图
final SurfaceControl topLeftScreenshot = ScreenshotUtils.takeScreenshot(t,
topLeftStage.mRootLeash, mTempRect1, Integer.MAX_VALUE - 1);
//根据mSideStagePosition的值确认下分屏(竖屏)或右分屏(横屏)对应的是mMainStage还是mSideStage。
final StageTaskListener bottomRightStage =
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
//获取一个下分屏(竖屏)或右分屏(横屏)截图
final SurfaceControl bottomRightScreenshot = ScreenshotUtils.takeScreenshot(t,
bottomRightStage.mRootLeash, mTempRect1, Integer.MAX_VALUE - 1);
//交换上下分屏
mSplitLayout.splitSwitching(t, topLeftStage.mRootLeash, bottomRightStage.mRootLeash,
insets -> {
WindowContainerTransaction wct = new WindowContainerTransaction();
//设置分屏位置,这里调用reverseSplitPosition交换分屏位置
setSideStagePosition(reverseSplitPosition(mSideStagePosition), wct);
mSyncQueue.queue(wct);
mSyncQueue.runInSync(st -> {
//更新SurfaceBounds
updateSurfaceBounds(mSplitLayout, st, false /* applyResizingOffset */);
//设置surface位置
st.setPosition(topLeftScreenshot, -insets.left, -insets.top);
st.setPosition(bottomRightScreenshot, insets.left, insets.top);
//动画处理
final ValueAnimator va = ValueAnimator.ofFloat(1, 0);
va.addUpdateListener(valueAnimator-> {
final float progress = (float) valueAnimator.getAnimatedValue();
t.setAlpha(topLeftScreenshot, progress);
t.setAlpha(bottomRightScreenshot, progress);
t.apply();
});
va.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(
@androidx.annotation.NonNull Animator animation) {
t.remove(topLeftScreenshot);
t.remove(bottomRightScreenshot);
t.apply();
mTransactionPool.release(t);
}
});
va.start();
});
});
//log打印
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Switch split position: %s", reason);
mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
getSideStagePosition(), mSideStage.getTopChildTaskUid(),
mSplitLayout.isLandscape());
}
这个方法整体来说分为下面这几个步骤: 1.确定上下分屏对应的是MainStage还是SideStage,并且获取对应的截图 2.通过SplitLayout.splitSwitching方法交换分屏
- 设置分屏位置(setSideStagePosition)
- 同步更新surface(updateSurfaceBounds)
- 动画处理(ValueAnimator)
我们先看看splitSwitching做了哪些事情
/** Switch both surface position with animation. */
public void splitSwitching(SurfaceControl.Transaction t, SurfaceControl leash1,
SurfaceControl leash2, Consumer<Rect> finishCallback) {
//获取横竖屏状态
final boolean isLandscape = isLandscape();
//获取insets
final Rect insets = getDisplayStableInsets(mContext);
//设置对应insets
insets.set(isLandscape ? insets.left : 0, isLandscape ? 0 : insets.top,
isLandscape ? insets.right : 0, isLandscape ? 0 : insets.bottom);
//计算当前分割线位置(只有三种情况:上中,中,下中)
final int dividerPos = mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(
isLandscape ? mBounds2.width() : mBounds2.height()).position;
final Rect distBounds1 = new Rect();
final Rect distBounds2 = new Rect();
final Rect distDividerBounds = new Rect();
// Compute dist bounds.
//计算并更新bounds
updateBounds(dividerPos, distBounds2, distBounds1, distDividerBounds,
false /* setEffectBounds */);
// Offset to real position under root container.
distBounds1.offset(-mRootBounds.left, -mRootBounds.top);
distBounds2.offset(-mRootBounds.left, -mRootBounds.top);
distDividerBounds.offset(-mRootBounds.left, -mRootBounds.top);
//动画处理
ValueAnimator animator1 = moveSurface(t, leash1, getRefBounds1(), distBounds1,
-insets.left, -insets.top);
ValueAnimator animator2 = moveSurface(t, leash2, getRefBounds2(), distBounds2,
insets.left, insets.top);
ValueAnimator animator3 = moveSurface(t, getDividerLeash(), getRefDividerBounds(),
distDividerBounds, 0 /* offsetX */, 0 /* offsetY */);
AnimatorSet set = new AnimatorSet();
set.playTogether(animator1, animator2, animator3);
set.setDuration(FLING_SWITCH_DURATION);
set.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mDividePosition = dividerPos;
updateBounds(mDividePosition);
//动画结束时调用回调函数finishCallback并传递insets
finishCallback.accept(insets);
}
});
set.start();
}
这里的finishCallback做的也就是前面说到的设置分屏位置、同步更新和动画处理。
其中设置分屏位置setSideStagePosition(reverseSplitPosition(mSideStagePosition), wct);
,先来看看里面调用的函数reverseSplitPosition。
代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
@SplitScreenConstants.SplitPosition
public static int reverseSplitPosition(@SplitScreenConstants.SplitPosition int position) {
switch (position) {
case SPLIT_POSITION_TOP_OR_LEFT:
return SPLIT_POSITION_BOTTOM_OR_RIGHT;
case SPLIT_POSITION_BOTTOM_OR_RIGHT:
return SPLIT_POSITION_TOP_OR_LEFT;
case SPLIT_POSITION_UNDEFINED:
default:
return SPLIT_POSITION_UNDEFINED;
}
}
这里其实就对mSideStagePosition
原本的值进行取反,在传递给setSideStagePosition处理。
void setSideStagePosition(@SplitPosition int sideStagePosition,
@Nullable WindowContainerTransaction wct) {
setSideStagePosition(sideStagePosition, true /* updateBounds */, wct);
}
private void setSideStagePosition(@SplitPosition int sideStagePosition, boolean updateBounds,
@Nullable WindowContainerTransaction wct) {
if (mSideStagePosition == sideStagePosition) return;
//更新mSideStagePosition
mSideStagePosition = sideStagePosition;
//通知stage位置发生改变
sendOnStagePositionChanged();
if (mSideStageListener.mVisible && updateBounds) {
if (wct == null) {
// onLayoutChanged builds/applies a wct with the contents of updateWindowBounds.
//调用updateWindowBounds,最终通知system_server去修改
onLayoutSizeChanged(mSplitLayout);
} else {
updateWindowBounds(mSplitLayout, wct);
sendOnBoundsChanged();
}
}
}