Lazy 集合运行时架构
1. 所有权边界
UI Contract 拥有 Renderer 无关的 Item Table、Key、Revision、Span 与 Padding Policy、滚动快照
和 Connector 命令。UI Foundation 把列表、网格、Pager 和 Tab 声明转换为带 Key 的逻辑内容。
Android Renderer 继续使用 RecyclerView 及其 Layout Manager 负责滚动、回收、焦点、无障碍、
嵌套滚动、Fling 和边缘效果。
应用拥有集合数据和受控 Pager 选择;ViewCompose 拥有逻辑 Session 与可保存状态;Android 拥有当前 物理 Holder 与几何。这三种身份相互独立。
2. 声明与 Session 身份
每个 Item 与页面 Key 在容器内都必须非空且唯一。插入、删除或移动后,同一个 Key 必须继续表示 同一份逻辑内容。重复 Key 会原子拒绝候选声明,不会弱化 Diff。
contentRevision 是延迟内容读取的普通非 State 输入的语义版本。Key、Revision、捕获的框架环境、
Kind、Content Type 和网格 Span 相等时,可以保留已提交 Item 与 Session。在该 Session 渲染时读取
的可观察 State 仍独立跟踪。框架无法推断任意 Kotlin Lambda Capture,因此漏掉变化输入属于正确性
错误,而不只是错过优化。
单个 Item、Sticky Header、页面和 Tab 必须提供显式 Revision。StaticContentRevision 是声明没有
捕获变化普通值的具名承诺。批量 Item 声明默认以 Item 值为 Revision Selector,只有值相等性覆盖
全部普通输入时才安全。
每个独立挂载的 Lazy Item 或 Pager 页面必须只发出一个根节点。该根节点是原生 Holder 的测量和放置 边界。Tab 是父级组合中的 Eager Keyed Child,不创建 Lazy Session。
3. 普通列表与不可变快照
普通 List 声明在每次父级组合时都会重新评估顺序、成员、Key、Content Type、Revision 和网格
Span Selector。框架不信任 List 引用身份或相等性,因为 Kotlin List 可能存在可变别名。得到的
Keyed Entry 仍可复用所有未受影响的已提交 Session,因此运行 Selector 不代表重新渲染全部 Item。
LazyItemsSnapshot 是显式的整表快速路径。toLazyItemsSnapshot() 浅拷贝有序 Item 引用并分配
不透明身份,不会评估 Selector。容器保留当前和上一个成功提交的 Snapshot/Environment 组合;精确
命中时以常数时间返回完整已评估 Item Table,不运行 Selector 或扫描 Key。
顺序、成员、保留的 Item 数据、Selector Capture 或普通 Item Content Capture 变化时,应用必须 替换该快照。新 Environment 总是 Miss,以保证 Theme、资源、Locale、布局方向、Density 和 Font Scale 正确。Selector 失败或 Key 重复不会发布缓存结果;重试会重新评估整个候选。
4. Renderer 映射与复用
Android Adapter 消费 Q3 LazyItemTable。有限的 Foundation 声明发布 Indexed Table;紧凑型集成
可以按需计算 Position 并发布中立的 Range Update。Stable ID 会避免冲突并延迟分配。Table 拥有
Key 到 Position 的查询,并可提供 Sticky Header 元数据,且不会让 Android 类型进入 UI Contract。
contentType 只划分物理结构兼容的内容。Model 值和 Revision 不属于 Content Type。一个已挂载容器
最多接受 1,024 种 Kind/Type 组合,使动态 Taxonomy 直接失败,而不是保留无界 View Type 历史。
只有一个类型已证明同步成本有界后,RecyclerView Prefetch 才能准备未挂载的 Tree。准备阶段不能 激活 Remember State、Effect、Overlay、原生 Commit 回调或已提交诊断。首次 Attach 会用当前 State 激活。普通缓存 Detach 让已激活 Session 保持存活;Recycle 会终止逻辑 Key Session。
跨 Key 的 Mounted Tree 复用要求 Tree 内每个 Interop AndroidView 都提供 onReset。旧 Session 与
Effect 在 Reset 前结束。有界缓存只携带已 Reset 的物理 Tree;逻辑 Key State 不会进入缓存或
RecyclerView Pool。缓存淘汰或容器释放会且只会调用一次 onRelease。
5. 布局、状态与 Pager 契约
LazyListState 观察最新不可变锚点、可见 Item 几何、边界、方向和滚动状态。立即命令也更新保留的
锚点;动画命令依赖 Renderer 快照。宿主重建只保存首个可见 Index 与像素 Offset;临时几何和 Motion
仍属于已挂载布局状态。
网格 Cell 是 Renderer 无关 Policy。GridCells.Fixed 保留精确正数列数;GridCells.Adaptive 根据
内部宽度、Density、间距和最小尺寸推导至少一列。FullLine 跟随物理列数变化,不改变 Item 的逻辑
身份、Remembered State 或 Effect。
Pager 选择由 currentPage 和 onPageChanged 控制。PagerState 观察 Current、Settled、Target、
Offset、Count 和 Motion,并向已挂载 Presentation 发送命令;它不会在 Detach 或重建后替代受控选择。
TabRow 可以共享该 State 以获得 Indicator 进度,同时保留 Eager Keyed Tab 身份。
6. 焦点、无障碍与持久化
每个逻辑 Item Key 拥有一个子 Saveable State Registry,因此兄弟行可以复用局部 Saveable Key。 Detach 与 Reorder 按 Item Key 保留 Map。固定在顶部的 Sticky Header 副本是不拥有状态的 Presentation Replica,不能覆盖所有者的持久状态。普通 Header 仍是唯一无障碍节点,避免 TalkBack 重复播报。
获得焦点的后代通过真实垂直 Scroll Owner 使用 Android Child Rectangle 传播。即使关闭用户直接 滚动,程序焦点可见性仍有效。内容可能被遮挡的 Pager 页面应提供自己的垂直 Scroll Owner;Pager 自身只拥有离散页面移动。应用任务和可选 Paging 边界见 Lazy 集合指南。