0x00. 核心组件
1、组件职责
- NotificationStackScrollLayout: 主控制器,协调所有组件
- AmbientState: 状态中心,存储和提供当前的环境状态
- StackScrollAlgorithm: 计算引擎,根据状态计算布局
- StackStateAnimator: 动画执行器,让状态变化平滑过渡
- ExpandableViewState: 视图状态(
ViewState),存储了通知在某一时刻应该具备的所有状态
2、通知容器
NotificationStackScrollLayout(NSSL) 是控制器,负责协调所有组件的工作。例如接收到各种系统事件(如滑动、状态切换),并将这些数据填充进 AmbientState ,并调用计算引擎 StackScrollAlgorithm 为每一条通知计算出对应的 ExpandableViewState 坐标。
3、 AmbientState 与 ExpandableViewState
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