SystemUI 开发之四张图看懂NotificationStackScrollLayout工作机制(九)

119 阅读2分钟

0x00. 核心组件

1、组件职责

  • NotificationStackScrollLayout: 主控制器,协调所有组件
  • AmbientState: 状态中心,存储和提供当前的环境状态
  • StackScrollAlgorithm: 计算引擎,根据状态计算布局
  • StackStateAnimator: 动画执行器,让状态变化平滑过渡
  • ExpandableViewState: 视图状态(ViewState),存储了通知在某一时刻应该具备的所有状态

2、通知容器

NotificationStackScrollLayout(NSSL) 是控制器,负责协调所有组件的工作。例如接收到各种系统事件(如滑动、状态切换),并将这些数据填充进 AmbientState ,并调用计算引擎 StackScrollAlgorithm 为每一条通知计算出对应的 ExpandableViewState 坐标。

3、 AmbientStateExpandableViewState

  • AmbientState(输入): 它是一个单例对象(在 NSSL 生命周期内),记录了当前屏幕的宏观状态。比如:用户滚到了哪里?QS(快捷设置)拉下来了多少?现在是不是在 AOD(息屏显示)模式?哪些通知正在被拖拽?
  • ExpandableViewState(输出): 每一条通知(ExpandableNotificationRow)都有一个属于自己的 ViewState。它描述了**微观属性,**是每个通知 ExpandableNotificationRow 的目标状态。比如:这行通知应该在 Y 轴什么位置?它的透明度是多少?它的高度现在该是多少?

4、计算引擎

  • StackScrollAlgorithm.resetViewStates(AmbientState) 是计算的入口方法,它的输入参数就是AmbientState ,计算结果输出到每条通知ExpandableNotificationRow对应的ExpandableViewState 对象中

5、动画执行

StackStateAnimator 会遍历这些 ExpandableViewState。它不仅知道目标值是多少,还知道 View 当前值是多少。如果两者不一致,它就会启动属性动画(Property Animator),让 View 动起来。

0x01. 组件关系图

展示整体架构和组件间的持有关系

graph TB
    subgraph "NotificationStackScrollLayout 通知堆栈滚动布局"
        NSSL[NotificationStackScrollLayout<br/>主容器ViewGroup]

        subgraph "子View管理"
            Children[ExpandableNotificationRow<br/>通知行]
            Shelf[NotificationShelf<br/>底部架子]
            Footer[FooterView<br/>底部视图]
            Empty[EmptyShadeView<br/>空白视图]
        end

        subgraph "核心组件"
            AS[AmbientState<br/>环境状态管理器]
            SSA[StackScrollAlgorithm<br/>布局算法引擎]
            StateAnimator[StackStateAnimator<br/>状态动画执行器]
        end

        subgraph "辅助组件"
            SwipeHelper[NotificationSwipeHelper<br/>滑动处理]
            ExpandHelper[ExpandHelper<br/>展开手势]
            RoundnessManager[NotificationRoundnessManager<br/>圆角管理]
            SectionsManager[NotificationSectionsManager<br/>分组管理]
        end
    end

    %% NSSL 持有核心组件
    NSSL -->|持有实例| AS
    NSSL -->|持有实例| SSA
    NSSL -->|持有实例| StateAnimator

    %% NSSL 持有辅助组件
    NSSL -->|持有实例| SwipeHelper
    NSSL -->|持有实例| ExpandHelper
    NSSL -->|持有实例| RoundnessManager
    NSSL -->|持有实例| SectionsManager

    %% NSSL 管理子View
    NSSL -->|addView/removeView| Children
    NSSL -->|管理| Shelf
    NSSL -->|管理| Footer
    NSSL -->|管理| Empty

    %% AmbientState 的作用
    AS -->|提供状态给| SSA
    AS -->|提供状态给| StateAnimator
    AS -->|提供状态给| Shelf

    %% 数据流向
    NSSL -->|1.更新状态| AS
    SSA -->|2.读取状态| AS
    SSA -->|3.计算ViewState| Children
    Children -->|4.存储状态| ViewState[ExpandableViewState]
    StateAnimator -->|5.执行动画| ViewState
    ViewState -->|6.应用到| Children

    style NSSL fill:#e1f5ff
    style AS fill:#fff4e1
    style SSA fill:#e8f5e8
    style StateAnimator fill:#ffe8f5

0x02. 时序图

展示完整的布局更新流程中各组件的交互顺序

sequenceDiagram
    participant NSSL as NotificationStackScrollLayout
    participant AS as AmbientState
    participant SSA as StackScrollAlgorithm
    participant Animator as StackStateAnimator
    participant Row as ExpandableNotificationRow
    participant VS as ExpandableViewState

    Note over NSSL: 用户操作触发(滚动/添加/展开等)

    NSSL->>NSSL: requestChildrenUpdate()
    activate NSSL

    NSSL->>NSSL: 注册 OnPreDrawListener

    Note over NSSL: 等待下一帧 PreDraw

    NSSL->>NSSL: onPreDraw() → updateChildren()

    NSSL->>AS: 更新状态<br/>setScrollY/setExpandFraction/etc
    activate AS
    AS-->>NSSL: 状态已更新
    deactivate AS

    NSSL->>SSA: resetViewStates(mAmbientState)
    activate SSA

    SSA->>AS: 读取环境状态<br/>getScrollY/getStackTranslation/etc
    activate AS
    AS-->>SSA: 返回状态值
    deactivate AS

    loop 遍历每个子View
        SSA->>Row: 获取 ViewState
        Row-->>SSA: 返回 ExpandableViewState

        SSA->>SSA: 计算位置、高度、透明度等

        SSA->>VS: 更新 ViewState<br/>yTranslation/alpha/height/etc
    end

    SSA-->>NSSL: 所有 ViewState 已计算
    deactivate SSA

    alt 需要动画
        NSSL->>NSSL: startAnimationToState()

        NSSL->>NSSL: generateAllAnimationEvents()
        Note over NSSL: 生成动画事件列表

        NSSL->>Animator: startAnimationForEvents(events)
        activate Animator

        loop 每个动画事件
            Animator->>VS: 读取目标状态
            Animator->>Animator: 创建属性动画
            Animator->>Row: 应用动画<br/>setTranslationY/setAlpha/etc
        end

        Note over Animator: 动画执行中...

        Animator-->>NSSL: onAnimationFinished()
        deactivate Animator

    else 不需要动画
        NSSL->>NSSL: applyCurrentState()

        loop 遍历每个子View
            NSSL->>Row: applyViewState()
            Row->>VS: 读取状态
            Row->>Row: 直接设置属性<br/>setTranslationY/setAlpha/etc
        end
    end

    NSSL->>NSSL: updateBackground()<br/>updateViewShadows()

    deactivate NSSL

    Note over NSSL,Row: 布局更新完成,等待下一次更新

0x03. 类图

展示各类的属性、方法以及类之间的依赖/组合/关联关系

classDiagram
    class NotificationStackScrollLayout {
        -AmbientState mAmbientState
        -StackScrollAlgorithm mStackScrollAlgorithm
        -StackStateAnimator mStateAnimator
        -int mOwnScrollY
        -boolean mNeedsAnimation
        -ArrayList~AnimationEvent~ mAnimationEvents

        +addContainerView(View v)
        +requestChildrenUpdate()
        -updateChildren()
        -startAnimationToState()
        -applyCurrentState()
        +onHeightChanged(ExpandableView view)
        +setOwnScrollY(int y)
    }

    class AmbientState {
        -int mScrollY
        -float mStackTranslation
        -boolean mDimmed
        -boolean mHideSensitive
        -float mOverScrollTopAmount
        -float mOverScrollBottomAmount
        -ExpandableView mActivatedChild
        -int mLayoutHeight
        -int mLayoutMinHeight
        -NotificationShelf mShelf

        +getScrollY() int
        +setScrollY(int y)
        +getStackTranslation() float
        +setStackTranslation(float translation)
        +isDimmed() boolean
        +setDimmed(boolean dimmed)
        +getLayoutHeight() int
    }

    class StackScrollAlgorithm {
        -StackScrollAlgorithmState mTempAlgorithmState
        -boolean mIsExpanded

        +resetViewStates(AmbientState ambientState)
        +getStackScrollState(AmbientState ambientState, StackScrollAlgorithmState algorithmState)
        -updatePositionsForState(StackScrollAlgorithmState algorithmState, AmbientState ambientState)
        -clampPositionToShelf(ExpandableViewState childViewState, NotificationShelf shelf)
        +getGapHeightForChild(...) float
        -computeCornerRoundnessForPinnedHun(...)
    }

    class StackStateAnimator {
        -NotificationStackScrollLayout mHostLayout
        -ArrayList~AnimationProperties~ mAnimationProperties
        -AnimationFilter mAnimationFilter
        -boolean mAnimationRunning

        +startAnimationForEvents(ArrayList~AnimationEvent~ events, long delay)
        -processAnimationEvents(ArrayList~AnimationEvent~ events)
        -startStackAnimations(...)
        -startViewAnimations(...)
        +animateOverScrollToAmount(float amount, boolean onTop)
        -onAnimationFinished()
    }

    class ExpandableNotificationRow {
        -ExpandableViewState mViewState
        -NotificationEntry mEntry
        -boolean mUserExpanded
        -int mActualHeight

        +getViewState() ExpandableViewState
        +applyViewState()
        +setUserExpanded(boolean expanded)
        +getActualHeight() int
        +setActualHeight(int height)
    }

    class ExpandableViewState {
        +float yTranslation
        +float zTranslation
        +float alpha
        +int height
        +boolean dimmed
        +boolean hideSensitive
        +boolean inShelf
        +float scale

        +applyToView(View view)
        +animateTo(View child, AnimationProperties properties)
        +copyFrom(ExpandableViewState viewState)
    }

    class AnimationEvent {
        +ExpandableView mChangingView
        +int animationType
        +AnimationFilter filter
        +long length
        +View viewAfterChangingView

        +combineLength(ArrayList~AnimationEvent~ events) long
    }

    class AnimationFilter {
        +boolean animateAlpha
        +boolean animateY
        +boolean animateZ
        +boolean animateHeight
        +boolean animateDimmed
        +boolean hasDelays

        +combineFilter(AnimationFilter filter)
        +applyCombination(AnimationFilter filter)
    }

    %% 组合关系
    NotificationStackScrollLayout *-- AmbientState : 持有
    NotificationStackScrollLayout *-- StackScrollAlgorithm : 持有
    NotificationStackScrollLayout *-- StackStateAnimator : 持有
    NotificationStackScrollLayout o-- ExpandableNotificationRow : 管理多个

    %% 依赖关系
    StackScrollAlgorithm ..> AmbientState : 读取状态
    StackScrollAlgorithm ..> ExpandableViewState : 计算并更新
    StackStateAnimator ..> AnimationEvent : 处理
    StackStateAnimator ..> ExpandableViewState : 动画到目标状态
    StackStateAnimator ..> AnimationFilter : 使用

    %% 关联关系
    ExpandableNotificationRow --> ExpandableViewState : 持有
    AnimationEvent --> ExpandableNotificationRow : 引用
    AnimationEvent --> AnimationFilter : 持有

    %% 继承关系
    ExpandableNotificationRow --|> ExpandableView : 继承

0x04. 流程图

展示从用户操作到最终渲染的完整数据流

flowchart TD
    Start([用户操作<br/>滚动/添加/展开通知]) --> Request[NSSL.requestChildrenUpdate]

    Request --> CheckFlag{mChildrenUpdateRequested?}
    CheckFlag -->|false| Register[注册 OnPreDrawListener]
    CheckFlag -->|true| End1([已注册,等待执行])

    Register --> SetFlag[mChildrenUpdateRequested = true]
    SetFlag --> Invalidate[invalidate 触发重绘]

    Invalidate --> PreDraw[下一帧: OnPreDraw]

    PreDraw --> UpdateChildren[updateChildren]

    UpdateChildren --> UpdateScroll[updateScrollStateForAddedChildren<br/>处理新添加View的滚动]

    UpdateScroll --> SetAmbient[更新 AmbientState<br/>- setScrollY<br/>- setCurrentScrollVelocity<br/>- setAnchorViewIndex等]

    SetAmbient --> CallAlgo[调用 StackScrollAlgorithm<br/>resetViewStates]

    CallAlgo --> AlgoRead[算法读取 AmbientState<br/>- getScrollY<br/>- getStackTranslation<br/>- getLayoutHeight等]

    AlgoRead --> AlgoCalc[遍历子View计算<br/>- yTranslation位置<br/>- alpha透明度<br/>- height高度<br/>- zTranslation深度<br/>- dimmed/scale等]

    AlgoCalc --> AlgoWrite[更新各子View的<br/>ExpandableViewState]

    AlgoWrite --> CheckAnim{需要动画?<br/>mNeedsAnimation}

    CheckAnim -->|true| GenEvents[生成动画事件<br/>generateAllAnimationEvents]
    CheckAnim -->|false| ApplyDirect[直接应用状态<br/>applyCurrentState]

    GenEvents --> StartAnim[StackStateAnimator<br/>startAnimationForEvents]

    StartAnim --> ProcessEvents[处理动画事件<br/>- ANIMATION_TYPE_ADD<br/>- ANIMATION_TYPE_REMOVE<br/>- ANIMATION_TYPE_CHANGE_POSITION等]

    ProcessEvents --> CreateAnim[创建属性动画<br/>ObjectAnimator]

    CreateAnim --> RunAnim[执行动画<br/>改变View属性]

    RunAnim --> AnimEnd[动画结束回调<br/>onAnimationFinished]

    ApplyDirect --> Loop1[遍历所有子View]
    Loop1 --> ApplyState[调用 child.applyViewState<br/>直接设置属性]

    AnimEnd --> UpdateBg[updateBackground<br/>更新背景]
    ApplyState --> UpdateBg

    UpdateBg --> UpdateShadow[updateViewShadows<br/>更新阴影]

    UpdateShadow --> UpdateClip[updateClippingToTopRoundedCorner<br/>更新圆角裁剪]

    UpdateClip --> ClearFlag[mChildrenUpdateRequested = false<br/>mNeedsAnimation = false]

    ClearFlag --> End2([更新完成])

    style Start fill:#e1f5ff
    style AmbientState fill:#fff4e1
    style CallAlgo fill:#e8f5e8
    style StartAnim fill:#ffe8f5
    style End2 fill:#e1ffe1