news 2026/6/10 2:38:59

wms项目之T分屏与分割线部分

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
wms项目之T分屏与分割线部分

https://blog.csdn.net/learnframework/article/details/131082401

堆栈日志

updateDivideBounds java.lang.Exception at com.android.wm.shell.common.split.SplitLayout.updateDivideBounds(SplitLayout.java:357) at com.android.wm.shell.common.split.SplitLayout.lambda$flingDividePosition$3(SplitLayout.java:451) at com.android.wm.shell.common.split.SplitLayout.$r8$lambda$hexmhTsCBFvDZRT1F14N6vZjPGA(Unknown Source:0) at com.android.wm.shell.common.split.SplitLayout$$ExternalSyntheticLambda3.onAnimationUpdate(Unknown Source:2) at android.animation.ValueAnimator.animateValue(ValueAnimator.java:1653) at android.animation.ValueAnimator.animateBasedOnTime(ValueAnimator.java:1440) at android.animation.ValueAnimator.doAnimationFrame(ValueAnimator.java:1572) at android.animation.AnimationHandler.doAnimationFrame(AnimationHandler.java:307) at android.animation.AnimationHandler.-$$Nest$mdoAnimationFrame(Unknown Source:0) at android.animation.AnimationHandler$1.doFrame(AnimationHandler.java:86) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1229) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1239) at android.view.Choreographer.doCallbacks(Choreographer.java:899) at android.view.Choreographer.doFrame(Choreographer.java:827) at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1214) at android.os.Handler.handleCallback(Handler.java:942) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loopOnce(Looper.java:201) at android.os.Looper.loop(Looper.java:288) at android.app.ActivityThread.main(ActivityThread.java:7898) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)

2. 拖动逻辑相关代码分析:

总结
用户触摸分割线

onTouch 检测拖拽触发(超过 mTouchSlop)

计算新位置 → updateDivideBounds(position)

重新计算三个区域 Bounds

onLayoutSizeChanging 在 VSync 点构建 Transaction

updateSurfaceBounds 确定上下/左右 Stage 顺序

applySurfaceChanges 原子写入:
- 分割线位置 + 最高层级
- 两个 Stage 的位置 + 裁剪
- IME 适配
- 阴影特效

t.apply() → SurfaceFlinger 统一合成新一帧

区分 "View" 与 "Surface":你的注释多次混淆这两个概念。分屏性能优化的关键就在于绕过 Android View 层级,直接操作底层 SurfaceControl。
Transaction 机制:Android 12+ 的分屏大量使用 SurfaceControl.Transaction,目的是多图层原子更新,注释应强调这一点。
Leash 模式:mRootLeash 是 Android 11+ 引入的 "Leash"(牵引层)机制,用于将整个 Task 的 Surface 挂接到一个父层,方便整体移动/缩放。理解这一点有助于看懂 onResizing 里做了什么。

public boolean onTouch(View v, MotionEvent event) { // android.util.Log.i("wsc_DividerView", "onTouch", new Exception()); 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(); 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; break; case MotionEvent.ACTION_MOVE: mVelocityTracker.addMovement(event); if (!mMoving && Math.abs(touchPos - mStartPos) > mTouchSlop) { mStartPos = touchPos; mMoving = true; } if (mMoving) { final int position = mSplitLayout.getDividePosition() + touchPos - mStartPos; mSplitLayout.updateDivideBounds(position); } break; /** * 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) { android.util.Log.i("wsc_SplitLayout", "updateDivideBounds", new Exception()); updateBounds(position); mSplitLayoutHandler.onLayoutSizeChanging(this); } @Override public void onLayoutSizeChanging(SplitLayout layout) { final SurfaceControl.Transaction t = mTransactionPool.acquire(); t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId()); setResizingSplits(true /* resizing */);//设置分割线大小 //更新分割线位置界限 updateSurfaceBounds(layout, t, true /* applyResizingOffset */);//把对应的DividerView进行更新 mMainStage.onResizing(getMainStageBounds(), t);//通知主屏幕,你的大小有更新 mSideStage.onResizing(getSideStageBounds(), t);//通知副屏,你大小有更新 t.apply(); mTransactionPool.release(t);//线程释放 } 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); } /** * Return if this layout is landscape. */ public boolean isLandscape() { return isLandscape(mRootBounds); } /** 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) { mTempRect.set(getRefDividerBounds()); 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); } mTempRect.set(getRefBounds1()); //设置上下分配的位置 t.setPosition(leash1, mTempRect.left, mTempRect.top) .setWindowCrop(leash1, mTempRect.width(), mTempRect.height()); mTempRect.set(getRefBounds2()); 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); } } 我要探究分屏分割线功能,从以下代码: 中帮我分析,随便指出我中文注释(自己分析的)的不足和错误,在讲讲各调用方法的作用功能 总结 用户触摸分割线 ↓ onTouch 检测拖拽触发(超过 mTouchSlop) ↓ 计算新位置 → updateDivideBounds(position) ↓ 重新计算三个区域 Bounds ↓ onLayoutSizeChanging 在 VSync 点构建 Transaction ↓ updateSurfaceBounds 确定上下/左右 Stage 顺序 ↓ applySurfaceChanges 原子写入: - 分割线位置 + 最高层级 - 两个 Stage 的位置 + 裁剪 - IME 适配 - 阴影特效 ↓ t.apply() → SurfaceFlinger 统一合成新一帧 区分 "View" 与 "Surface":你的注释多次混淆这两个概念。分屏性能优化的关键就在于绕过 Android View 层级,直接操作底层 SurfaceControl。 Transaction 机制:Android 12+ 的分屏大量使用 SurfaceControl.Transaction,目的是多图层原子更新,注释应强调这一点。 Leash 模式:mRootLeash 是 Android 11+ 引入的 "Leash"(牵引层)机制,用于将整个 Task 的 Surface 挂接到一个父层,方便整体移动/缩放。理解这一点有助于看懂 onResizing 里做了什么。

3. 关于分割线的代码分析

所有拖动都是在这里 @Override public boolean onTouch(View v, MotionEvent event) { // android.util.Log.i("wsc_DividerView", "onTouch", new Exception()); 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(); //这里你是横屏还是竖屏? 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; break; //移动 case MotionEvent.ACTION_MOVE: mVelocityTracker.addMovement(event); //有绝对值,要不要触发移动 if (!mMoving && Math.abs(touchPos - mStartPos) > mTouchSlop) { mStartPos = touchPos;//初始位移点更新 mMoving = true; } if (mMoving) { //最主要是这里,获取一下当前分割线位置,这个是比较重要的,直接决定updateDivideBounds更新的区域 //当前分割线的位置等于原来分割线位置+触摸位置-开始位置,后面加个绝对值比较好 final int position = mSplitLayout.getDividePosition() + touchPos - mStartPos; mSplitLayout.updateDivideBounds(position); } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: //松手部分 releaseTouching(); if (!mMoving) break; //加速度计算,先不分析 mVelocityTracker.addMovement(event); mVelocityTracker.computeCurrentVelocity(1000 /* units */); final float velocity = isLandscape ? mVelocityTracker.getXVelocity() : mVelocityTracker.getYVelocity(); //计算当前分割线位置 final int position = mSplitLayout.getDividePosition() + touchPos - mStartPos; //找Target,也就是五大target,分别为顶,上中,中间,下中,底部,拖动落点在哪,就让落点往哪个落点靠 final DividerSnapAlgorithm.SnapTarget snapTarget = mSplitLayout.findSnapTarget(position, velocity, false /* hardDismiss */); // position上面已经更新位置,snapTarget为滑动目标,意义不知道,可能为吸附目标? // snapTarget可能为分割线一个参照判断点,判断当前触摸点属那个位置好进行判定吸附 //找到了target,那就滑到哪个target中去,看参数就知道,拿到位置和target,开始操作 mSplitLayout.snapToTarget(position, snapTarget); mMoving = false; break; } return true; } 由以上move跳到下面这里来,更新bounds,根据分割线更新bounds /** * 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) { android.util.Log.i("wsc_SplitLayout", "updateDivideBounds", new Exception()); //靠着当前分割线位置更新bounds updateBounds(position); mSplitLayoutHandler.onLayoutSizeChanging(this); } /** Updates recording bounds of divider window and both of the splits. */ private void updateBounds(int position) { mDividerBounds.set(mRootBounds); mBounds1.set(mRootBounds); mBounds2.set(mRootBounds); final boolean isLandscape = isLandscape(mRootBounds); //判断横述屏 if (isLandscape) { position += mRootBounds.left; mDividerBounds.left = position - mDividerInsets; mDividerBounds.right = mDividerBounds.left + mDividerWindowWidth; mBounds1.right = position; mBounds2.left = mBounds1.right + mDividerSize; } else { //微小偏移 position += mRootBounds.top; mDividerBounds.top = position - mDividerInsets; //在这里,分割线顶部一般是0 mDividerBounds.bottom = mDividerBounds.top + mDividerWindowWidth; // mBounds1只需要更新下 //上分屏是bottom变,top不变,下分配相反, //所以此句bottom更新 mBounds1.bottom = position;// mBounds2.top = mBounds1.bottom + mDividerSize; } DockedDividerUtils.sanitizeStackBounds(mBounds1, true /** topLeft */); DockedDividerUtils.sanitizeStackBounds(mBounds2, false /** topLeft */); mSurfaceEffectPolicy.applyDividerPosition(position, isLandscape); } @Override public void onLayoutSizeChanging(SplitLayout layout) { final SurfaceControl.Transaction t = mTransactionPool.acquire(); t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId()); setResizingSplits(true /* resizing */);//设置分割线大小 //更新分割线位置界限, 绘制 updateSurfaceBounds(layout, t, true /* applyResizingOffset */);//把对应的DividerView进行更新 mMainStage.onResizing(getMainStageBounds(), t);//通知主屏幕,你的大小有更新 mSideStage.onResizing(getSideStageBounds(), t);//通知副屏,你大小有更新 t.apply(); mTransactionPool.release(t);//线程释放 } 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); } //主要对上区域,下区域的bounds进行更新 /** 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) { //这个 mTempRect.set(getRefDividerBounds());矩形区域就是获得的Divider分割线区域 mTempRect.set(getRefDividerBounds()); // 设置这个分割线区域,其实left没变,top是变的 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); } mTempRect.set(getRefBounds1()); //分割线重新设置好了,再重新设置上下屏 t.setPosition(leash1, mTempRect.left, mTempRect.top) .setWindowCrop(leash1, mTempRect.width(), mTempRect.height()); mTempRect.set(getRefBounds2()); 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); } } /** Showing resizing hint. */ public void onResizing(ActivityManager.RunningTaskInfo resizingTask, Rect newBounds, SurfaceControl.Transaction t) { if (mResizingIconView == null) { return; } if (!mIsResizing) { mIsResizing = true; mBounds.set(newBounds); } //更新条件:新Bounds是新的比就的宽和高,然后就会重新更新Bounds //show就算要展示出背景家apk那个log的东西,是单独图层 final boolean show = newBounds.width() > mBounds.width() || newBounds.height() > mBounds.height(); final boolean animate = show != mShown; 下面是松手逻辑, case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: //松手部分 releaseTouching(); if (!mMoving) break; //加速度计算,先不分析 mVelocityTracker.addMovement(event); mVelocityTracker.computeCurrentVelocity(1000 /* units */); final float velocity = isLandscape ? mVelocityTracker.getXVelocity() : mVelocityTracker.getYVelocity(); //计算当前分割线位置 final int position = mSplitLayout.getDividePosition() + touchPos - mStartPos; //找Target,也就是五大target,分别为顶,上中,中间,下中,底部,拖动落点在哪,就让落点往哪个落点靠 final DividerSnapAlgorithm.SnapTarget snapTarget = mSplitLayout.findSnapTarget(position, velocity, false /* hardDismiss */); // position上面已经更新位置,snapTarget为滑动目标,意义不知道,可能为吸附目标? // snapTarget可能为分割线一个参照判断点,判断当前触摸点属那个位置好进行判定吸附 //找到了target,那就滑到哪个target中去,看参数就知道,拿到位置和target,开始操作 mSplitLayout.snapToTarget(position, snapTarget); mMoving = false; break; } return true; /** * 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) { case FLAG_DISMISS_START: //触摸正在滑动的位置 flingDividePosition(currentPosition, snapTarget.position, () -> mSplitLayoutHandler.onSnappedToDismiss(false /* bottomOrRight */)); break; case FLAG_DISMISS_END: flingDividePosition(currentPosition, snapTarget.position, () -> mSplitLayoutHandler.onSnappedToDismiss(true /* bottomOrRight */)); break; default: flingDividePosition(currentPosition, snapTarget.position, () -> setDividePosition(snapTarget.position, true /* applyLayoutChange */)); break; } } //在松手时有一个动画自己滑过去,就是这个方法实现,顺便把那个分割线滑道对应的target中 @VisibleForTesting void flingDividePosition(int from, int to, @Nullable Runnable flingFinishedCallback) { if (from == to) { // No animation run, still callback to stop resizing. mSplitLayoutHandler.onLayoutSizeChanged(this); return; } InteractionJankMonitorUtils.beginTracing(InteractionJankMonitor.CUJ_SPLIT_SCREEN_RESIZE, mSplitWindowManager.getDividerView(), "Divider fling"); ValueAnimator animator = ValueAnimator .ofInt(from, to) .setDuration(250); animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); animator.addUpdateListener( animation -> updateDivideBounds((int) animation.getAnimatedValue())); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (flingFinishedCallback != null) { flingFinishedCallback.run(); } InteractionJankMonitorUtils.endTracing( InteractionJankMonitor.CUJ_SPLIT_SCREEN_RESIZE); } @Override public void onAnimationCancel(Animator animation) { setDividePosition(to, true /* applyLayoutChange */); } }); animator.start(); }

(找Target,也就是五大target,分别为顶,上中,中间,下中,底部,)松手逻辑mSplitLayout.snapToTarget(position, snapTarget)为开始,看后面的代码注释

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/10 2:26:38

i.MX50硬件设计避坑指南:特殊引脚与电气特性深度解析

1. 项目概述:从引脚信号到系统稳定性的设计基石在嵌入式硬件设计的江湖里,处理器数据手册中的“特殊信号”和“电气特性”章节,往往是新手工程师最容易忽略,却又最能让老手栽跟头的地方。我见过太多项目,原理图逻辑清晰…

作者头像 李华
网站建设 2026/6/10 2:22:12

当高校“找上门”:GEO企业如何成为AI内容人才的“实践基地”?

6月4日上午,福建师范大学协和学院副院长黄滨带领文化产业系党政领导及辅导员一行,专程来到福州一家GEO(生成式引擎优化)企业走访调研。不是简单的“走亲戚”,而是一场“访企拓岗促就业”的专项行动——把学生送到对的企…

作者头像 李华
网站建设 2026/6/10 2:19:16

强力桌面分区神器NoFences:告别杂乱,重获高效工作空间

强力桌面分区神器NoFences:告别杂乱,重获高效工作空间 【免费下载链接】NoFences 🚧 Open Source Stardock Fences alternative 项目地址: https://gitcode.com/gh_mirrors/no/NoFences 你是否厌倦了每次打开电脑都要在数十个图标中寻…

作者头像 李华
网站建设 2026/6/10 2:17:02

OMARS设计如何完美衔接DSD与RSD

之前,在使用Minitab设计实验(DOE)时,我们经常会陷入一个进退两难的境地:究竟是向预算妥协,还是向统计功效低头?今年2月份,Minitab,LLC宣布收购Effex,将Effex集…

作者头像 李华
网站建设 2026/6/10 2:15:56

MPV播放器终极配置指南:从零构建专业级媒体播放体验

MPV播放器终极配置指南:从零构建专业级媒体播放体验 【免费下载链接】mpv_PlayKit 🔄 mpv player 播放器折腾记录 Windows conf | 中文注释配置 汉化文档 快速帮助入门 | mpv-lazy 懒人包 Win11 x64 config | 着色器 shader 滤镜 filter 整合方案 项目…

作者头像 李华