当前位置: 首页 > news >正文

AOSP Android14 Launcher3——RectFSpringAnim窗口动画类详解

在阅读源码研究从第三方应用回到桌面的过程中,接触到RectFSpringAnim这个类,确认这个类就是最终动画的实现类。因此对这个类的源码进行阅读分析。

本文来详细分析 quickstep/src/com/android/quickstep/util/RectFSpringAnim.java 这个类。

核心作用 (Core Purpose):

RectFSpringAnim 的核心作用是提供一个基于物理弹簧模型 (Spring Physics) 的动画机制,用于平滑地、带有物理惯性地将一个矩形 (RectF) 从起始状态 (位置和大小) 动画到目标状态 (位置和大小)

它主要用于 QuickstepTransitionManager 中处理**应用窗口关闭(返回桌面)**时的动画,模拟窗口从全屏状态自然地、带有速度感地收缩并移动到其在 Launcher 上的对应图标或小部件位置的过程。

工作原理 (How it Works):

与传统的基于时间插值器 (Time Interpolator) 的动画(如 ValueAnimator, ObjectAnimator) 不同,RectFSpringAnim 使用的是 AndroidX DynamicAnimation 库中的 SpringAnimation 和自定义的 FlingSpringAnim。其原理是:

  1. 分离动画维度: 它并不直接动画 RectF 的 left, top, right, bottom 四个值。而是将矩形的动画分解为三个独立的、但同时进行的物理模拟:

    • 水平中心 (RECT_CENTER_X): 使用 FlingSpringAnim 控制矩形水平中心点 (centerX) 的动画。FlingSpringAnim 结合了初始的抛掷 (Fling) 速度和最终稳定到目标值的弹簧 (Spring) 效果。
    • 垂直位置 (RECT_Y): 使用 FlingSpringAnim 控制矩形一个垂直参考点的动画。这个参考点由 mTracking 变量决定:
      • TRACKING_TOP: 动画矩形的 top 值。
      • TRACKING_BOTTOM: 动画矩形的 bottom 值。
      • TRACKING_CENTER: 动画矩形的 centerY 值。
        选择哪个参考点取决于动画的具体场景(例如,窗口是从屏幕上方移入还是下方移入,目标位置更靠近哪边)。
    • 缩放进度 (RECT_SCALE_PROGRESS): 使用标准的 SpringAnimation 控制一个从 0 到 1 的进度值 (mCurrentScaleProgress)。这个进度值随后被用来线性插值计算矩形当前的宽度和高度(从起始宽高到目标宽高)。
  2. 物理参数: 每个维度的弹簧动画都由物理参数控制:

    • Stiffness (刚度): 弹簧的“硬度”,影响回弹的速度和振荡频率。 (mStiffnessX, mStiffnessY, mRectStiffness)。
    • Damping Ratio (阻尼比): 控制振荡衰减的速度。值小于 1 会产生振荡,等于 1 是临界阻尼(最快到达且不振荡),大于 1 是过阻尼(缓慢到达)。(mDampingX, mDampingY)。
  3. 动画启动 (start 方法):

    • 接收一个初始速度 (velocityPxPerMs),通常是用户手指离开屏幕时的速度。
    • 对速度进行阻尼处理 (OverScroll.dampedScroll),防止过大的速度导致动画过于剧烈或不自然。
    • 根据起始/目标位置、阻尼后的速度、物理参数(刚度、阻尼比)创建并启动 RECT_CENTER_XRECT_YFlingSpringAnim
    • 根据Y轴速度和物理参数创建并启动 RECT_SCALE_PROGRESSSpringAnimation
    • 通知外部监听器动画开始。
  4. 动画更新 (onUpdate 方法):

    • 当任何一个底层的 SpringAnimationFlingSpringAnim 更新其值时,会触发其对应 FloatPropertyCompatsetValue 方法,进而调用 RectFSpringAnimonUpdate()
    • onUpdate() 中:
      • 根据当前的 mCurrentScaleProgress 插值计算出当前的宽度 (currentWidth) 和高度 (currentHeight)。
      • 根据当前的 mCurrentCenterX, mCurrentY 以及 mTracking 模式,结合计算出的 currentWidth, currentHeight重新构建出完整的 mCurrentRect (当前的矩形状态)。例如,如果 mTracking == TRACKING_TOP,则 mCurrentRect.top = mCurrentYmCurrentRect.bottom = mCurrentY + currentHeight
      • 遍历所有注册的 OnUpdateListener,调用它们的 onUpdate(mCurrentRect, mCurrentScaleProgress) 方法,将最新计算出的矩形状态和缩放进度传递出去。
  5. 动画结束 (maybeOnEnd 方法):

    • 每个底层的弹簧动画结束后,会设置对应的结束标志 (mRectXAnimEnded, mRectYAnimEnded, mRectScaleAnimEnded)。
    • maybeOnEnd() 会检查是否所有三个动画都已结束。
    • 只有当全部结束后,才会通知外部的 Animator.AnimatorListener 动画结束,并设置 setCanRelease(true)(用于 RemoteAnimationTargets 的资源释放检查)。
  6. 配置 (SpringConfig 子类):

    • 提供了不同的配置类(DefaultSpringConfig, TaskbarHotseatSpringConfig)来为不同的动画场景(如普通返回桌面 vs 返回到 Taskbar/Hotseat 上的图标)提供预设的、经过调整的物理参数(刚度、阻尼、追踪模式),以达到最佳的视觉效果。

在 Launcher3 中的作用:

RectFSpringAnim 在 Launcher3 (Quickstep) 中扮演着一个特定场景下的动画引擎角色:

  1. 驱动窗口关闭动画: 它是 QuickstepTransitionManager 在处理应用关闭返回桌面动画时的核心驱动力之一 (尤其是在 createWallpaperOpenAnimations -> getClosingWindowAnimators 中被创建和使用)。
// 应用关闭返回桌面动画
//quickstep/src/com/android/launcher3/QuickstepTransitionManager.javaprotected RectFSpringAnim getClosingWindowAnimators(AnimatorSet animation,RemoteAnimationTarget[] targets, View launcherView, PointF velocityPxPerS,RectF closingWindowStartRectF, float startWindowCornerRadius) {FloatingIconView floatingIconView = null;FloatingWidgetView floatingWidget = null;RectF targetRect = new RectF();...... 省略boolean useTaskbarHotseatParams = mDeviceProfile.isTaskbarPresent && isInHotseat;//创建RectFSpringAnim动画实例RectFSpringAnim anim = new RectFSpringAnim(useTaskbarHotseatParams? new TaskbarHotseatSpringConfig(mLauncher, closingWindowStartRectF, targetRect): new DefaultSpringConfig(mLauncher, mDeviceProfile, closingWindowStartRectF,targetRect));// Hook up floating views to the closing window animators.// note the coordinate of closingWindowStartRect is based on launcherRect closingWindowStartRect = new Rect();closingWindowStartRectF.round(closingWindowStartRect);Rect closingWindowOriginalRect =new Rect(0, 0, mDeviceProfile.widthPx, mDeviceProfile.heightPx);if (floatingIconView != null) {anim.addAnimatorListener(floatingIconView);floatingIconView.setOnTargetChangeListener(anim::onTargetPositionChanged);floatingIconView.setFastFinishRunnable(anim::end);FloatingIconView finalFloatingIconView = floatingIconView;// We want the window alpha to be 0 once this threshold is met, so that the// FolderIconView can be seen morphing into the icon shape.final float windowAlphaThreshold = 1f - SHAPE_PROGRESS_DURATION;RectFSpringAnim.OnUpdateListener runner = new SpringAnimRunner(targets, targetRect,closingWindowStartRect, closingWindowOriginalRect, startWindowCornerRadius) {@Overridepublic void onUpdate(RectF currentRectF, float progress) {finalFloatingIconView.update(1f, currentRectF, progress, windowAlphaThreshold,getCornerRadius(progress), false);super.onUpdate(currentRectF, progress);}};anim.addOnUpdateListener(runner);} else if (floatingWidget != null) {anim.addAnimatorListener(floatingWidget);floatingWidget.setOnTargetChangeListener(anim::onTargetPositionChanged);floatingWidget.setFastFinishRunnable(anim::end);final float floatingWidgetAlpha = isTransluscent ? 0 : 1;FloatingWidgetView finalFloatingWidget = floatingWidget;RectFSpringAnim.OnUpdateListener runner = new SpringAnimRunner(targets, targetRect,closingWindowStartRect, closingWindowOriginalRect, startWindowCornerRadius) {@Overridepublic void onUpdate(RectF currentRectF, float progress) {final float fallbackBackgroundAlpha =1 - mapBoundToRange(progress, 0.8f, 1, 0, 1, EXAGGERATED_EASE);final float foregroundAlpha =mapBoundToRange(progress, 0.5f, 1, 0, 1, EXAGGERATED_EASE);finalFloatingWidget.update(currentRectF, floatingWidgetAlpha, foregroundAlpha,fallbackBackgroundAlpha, 1 - progress);super.onUpdate(currentRectF, progress);}};anim.addOnUpdateListener(runner);} else {// If no floating icon or widget is present, animate the to the default window// target rect.anim.addOnUpdateListener(new SpringAnimRunner(targets, targetRect, closingWindowStartRect, closingWindowOriginalRect,startWindowCornerRadius));}// Use a fixed velocity to start the animation.animation.addListener(new AnimatorListenerAdapter() {@Overridepublic void onAnimationStart(Animator animation) {anim.start(mLauncher, mDeviceProfile, velocityPxPerS);}});return anim;}
  1. 提供窗口变换参数: 它的 OnUpdateListener (通常是 QuickstepTransitionManager.SpringAnimRunner 的实例) 在每一帧接收到计算好的 mCurrentRect。这个 mCurrentRect 就代表了应用窗口 Leash 在该帧应该呈现的大小和在 Launcher 坐标系下的位置
//quickstep/src/com/android/launcher3/QuickstepTransitionManager.java@Overridepublic void onUpdate(RectF currentRectF, float progress) {SurfaceTransaction transaction = new SurfaceTransaction();for (int i = mAppTargets.length - 1; i >= 0; i--) {RemoteAnimationTarget target = mAppTargets[i];SurfaceProperties builder = transaction.forSurface(target.leash);if (target.localBounds != null) {mTmpPos.set(target.localBounds.left, target.localBounds.top);} else {mTmpPos.set(target.position.x, target.position.y);}if (target.mode == MODE_CLOSING) {transferRectToTargetCoordinate(target, currentRectF, false, currentRectF);currentRectF.round(mCurrentRect);// Scale the target window to match the currentRectF.final float scale;// We need to infer the crop (we crop the window to match the currentRectF).if (mWindowStartBounds.height() > mWindowStartBounds.width()) {scale = Math.min(1f, currentRectF.width() / mWindowOriginalBounds.width());int unscaledHeight = (int) (mCurrentRect.height() * (1f / scale));int croppedHeight = mWindowStartBounds.height() - unscaledHeight;mTmpRect.set(0, 0, mWindowOriginalBounds.width(),mWindowStartBounds.height() - croppedHeight);} else {scale = Math.min(1f, currentRectF.height()/ mWindowOriginalBounds.height());int unscaledWidth = (int) (mCurrentRect.width() * (1f / scale));int croppedWidth = mWindowStartBounds.width() - unscaledWidth;mTmpRect.set(0, 0, mWindowStartBounds.width() - croppedWidth,mWindowOriginalBounds.height());}// Match size and position of currentRect.mMatrix.setScale(scale, scale);mMatrix.postTranslate(mCurrentRect.left, mCurrentRect.top);builder.setMatrix(mMatrix).setWindowCrop(mTmpRect).setAlpha(getWindowAlpha(progress)).setCornerRadius(getCornerRadius(progress) / scale);} else if (target.mode == MODE_OPENING) {mMatrix.setTranslate(mTmpPos.x, mTmpPos.y);builder.setMatrix(mMatrix).setAlpha(1f);}}mSurfaceApplier.scheduleApply(transaction);}
  1. 参数转换: SpringAnimRunner 接收到 mCurrentRect 后,会将其转换为应用到 SurfaceControl Leash 所需的 Matrix (变换矩阵,包含平移和缩放)、WindowCrop (裁剪区域) 和 CornerRadius (圆角)。
  2. 实现物理效果: 它使得窗口关闭动画不仅仅是简单的线性缩小和平移,而是带有速度感、惯性、可能的回弹或过冲效果,这得益于其底层的弹簧物理模型。这让过渡感觉更加生动和自然。
  3. 处理用户输入速度: 它能够接受用户手势结束时的速度作为输入,使得动画的初始状态能匹配用户的操作,增强了响应性。

总结:

RectFSpringAnim 是一个专门用于基于物理弹簧模型动画化矩形变换的工具类。在 Launcher3 中,它被 QuickstepTransitionManager 用来驱动应用关闭返回桌面时的窗口动画,通过模拟水平中心、垂直参考点和缩放进度的弹簧运动,计算出每一帧窗口应有的位置和大小,并将这些信息通过回调传递出去,最终应用到真实的窗口 Surface 上,实现了带有物理特性、流畅自然的过渡效果。

相关文章:

  • ComfyUI+Sonic实战,三步实现图片开口说话
  • 单个或批量实现-提取PDF文档中的合同号和姓名并按“合同号_姓名”格式重命名文件。
  • 【文献分享】Model-based evaluation提供了数据和代码
  • day48—双指针-通过删除字母匹配到字典最长单词(LeetCode-524)
  • rk3568main.cc解析
  • 多路转接select服务器
  • Node.js简介(nvm使用)
  • docker-compose搭建kafka
  • Git Flow分支模型
  • L2-2、示范教学与角色扮演:激发模型“模仿力“与“人格“
  • 从单模态到多模态:深度生成模型的演进历程
  • 【武汉理工大学第四届ACM校赛】copy
  • EAL4+与等保2.0:解读中国网络安全双标准
  • 用 Go 优雅地清理 HTML 并抵御 XSS——Bluemonday
  • 嵌入式---超声波测距模块
  • 时间模块 demo
  • 小白学习java第14天(上):数据库
  • 【目标检测】对YOLO系列发展的简单理解
  • 力扣2685(dfs)
  • 什么是管理思维?
  • 山东省检察院答澎湃:惩治网络售假,强化“全链条”刑事打击
  • 透纳仍是英国最好的艺术家,浦东美术馆有他的画展
  • 告别国泰海通,黄燕铭下一站将加盟东方证券,负责研究业务
  • 日媒:日本公明党党首将访华,并携带石破茂亲笔信
  • 全国总工会成立100周年,工运历史和发展成就展将对外展出
  • 荣膺劳伦斯大奖实至名归,杜普兰蒂斯的传奇没有极限