Android U 分屏——分割线相关

92 阅读18分钟

本文主要介绍分屏的分割线和分割区域线,分割线的拖拽和分割线交换上下分屏。这里需要区分的是分屏的分割线和分割区域线是两个不同的概念,分屏的分割线指的就是我们肉眼看到的分割线,而分割区域线则是把屏幕分割成多份,用于在分割线拖动时,对分屏的显示大小进行调整或退出分屏。

我们需要注意分割线和分割区域线的区别,这里先来看看分割线的组成,以及分割区域线是如何设定的。

分屏分割线

分屏的分割线和我们实际看到的与代码表现有所不同,这里简单介绍差异点。

分割线的组成

在这里插入图片描述 从分割线布局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.onTaskAppearedDividerSnapAlgorithm.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.onTaskInfoChangedDividerSnapAlgorithm.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设置了mDividerInsetsmDividerWindowWidthmDividerSize的值。 这个方法主要就是更新了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;
        }
    }

其中mMinFlingVelocityPxPerSecondmMinDismissVelocityPxPerSecond为根据计算得出的固定值。

        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秒 回调函数:

  1. 退出分屏

    () -> mSplitLayoutHandler.onSnappedToDismiss(false /* bottomOrRight */,
            EXIT_REASON_DRAG_DIVIDER)
    

    这个方法是在分割线滑动至最顶部或者最底部时,退出分屏所需要做的处理。根据传递的参数bottomOrRight的值来判断是上分屏退出(false),还是下分屏退出(true)。 分屏退出流程见 Android U 分屏——退出流程

  2. 更新最终分屏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();
    }

这里fromto代表的就是当前分割线位置和分割线区域位置,整体来说就是常规的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();
            }
        }
    }