用途:这是一个面向 Compose 列表多选的 @Stable 状态持有类,由屏幕持有、由列表行读取,适合需要长按进入多选、点按切换选中、全选和数量上限控制的列表界面。
主要能力:
- 暴露两块可观察状态,即模式是否开启(isActive)和已选 id 集合(selected);同时提供 count、isFull、isSelected(id) 等派生查询。
- start(id) 用于长按进入该模式并选中一项;toggle(id) 在激活时添加或移除;toggleSelectAll(ids) 在达到上限前填充,或在已全选时清空;exit() 关闭模式并清空。
- 所有新增选中项都经过同一个私有 add(id):它拒绝空白与重复 id,检查是否达到 MAX_SELECTION,并在超限时触发上限提示后返回 false,从而保证上限不可被新增的第四种入口绕过。删除操作直接调用 remove 或 clear,不做额外检查。
- add 返回 Boolean,便于全选循环在超限时立即停止,避免重复触发上限提示。
使用方式:在屏幕层用 remember 构建持有者,并把上限提示文案作为构造参数传入;行组件读取 isSelected(id) 与 count 等状态;长按调用 start(id),激活时点按调用 toggle(id);全选按钮调用 toggleSelectAll,并对“可达集合是否已全选”做截断比较(candidates.take(MAX_SELECTION).all { ... }),已全选则清空并保持激活。
适用场景:分页列表、支持拖拽重排的列表等多选场景;这类列表形状会变化,因此必须按稳定 id 而非索引记录选中项。
限制与注意事项:
- 按索引键控的选择在列表增长或重排后会指向不同的行,导致批量操作作用在错误对象上。
- 上限检查必须只有一处,数值常量也不得在各调用点重复定义。
- 点按清空到零项时应调用 exit() 退出模式;全选再次触发时的清空应保持激活,二者语义不同。
- count 和 isFull 应作为基于快照列表的派生 getter,而不是并列的缓存状态。
- @Stable 要求所有公开可读属性只通过快照系统变化,加入普通 var 会破坏该承诺。
- 以本地化字符串作为 remember 的键会在语言切换时重建并丢失选择;若需跨配置变更或进程死亡保留选择,应放到屏幕的 view model 中。
- 在持有者内部上报上限提示会使状态类依赖 UI 通知 API,且无法在主线程之外执行;若在意,可改为返回更丰富的结果由屏幕渲染,但上限决策仍应留在持有者内。
验证方法:用 grep 检查后备列表只有一个写入路径(一个 add、一个 remove、两处 clear,其余为读取);检查上限常量仅由检查与截断引用;比对 selectionMode 与 onSelectToggle 在各文件的出现次数(注意模式不带尾随空格);手工测试选满后再全选应只出现一次提示;在可重排列表中选择两行后拖动第三行到二者之间,确认选中的仍是原来两行。