用途
该条目属于 Kotlin UI(Compose)工程实践类陷阱说明,针对「可选控件不渲染自身,却无法隐藏他人为其添加的外层容器」这一问题,给出契约声明、调用方门控方式与代码审计验证手段。
主要主张
- 契约随组件声明:在声明组件的同一文件中导出可用性判定(如
expect fun isOptionalControlAvailable(): Boolean),并在注释中明确「为控件提供独立容器的布局在判定为 false 时也必须隐藏容器」。 - 不要在各调用点重复推导:若多个调用点自行根据平台检查推导「是否可用」,最终会不一致,而不一致的一方恰好会画出空盒。统一由一个与组件共用同一平台代码的导出判定函数负责。
- 门控只在该加时加:仅当调用方为控件增加了容器时才需要门控;直接裸调用时控件自身为空是不可见的。文中示例中同一控件在分段组里被
if (available)包裹、在普通行里裸调用,两者都正确,因此「每个调用点都必须门控」的审计结论是错的。 - 分段组的形状按位置分配:端帽、圆角、分隔线由位置决定;删掉中间槽位算术仍成立,删掉首尾槽位则端帽需移动,否则组外侧出现两个直角。应从构建出的列表推导形状,而非按索引硬编码。
- 无点击槽位需要真实分支:可点击与不可点击的容器是不同的 composable,语义不同。传入
onClick = {}会保留涟漪、无障碍点击动作并继续消费点击,导致内部自有点击的控件收不到事件。应依据onClick != null分支,在 else 中输出普通容器。 - 判定与组件作为一对平台声明同文件:把控件桩为不渲染的目标,必须在同一次修改中把判定桩为 false;分文件维护会漂移,且直到构建该目标才暴露。
- 判定须轻量且纯:它在组合期、每次重组、布局循环内被调用,常量返回的平台函数可以;任何 I/O、读 store、访问服务都应提升为状态向下传,否则组会卡顿。
- 区分消失与保留空间:加权行中移除槽位会把宽度重新分配给兄弟控件,导致其余控件尺寸变化。永久不可用通常应移除,瞬态不可用应保留占位,否则组会反复跳动。
- 「不渲染就不占成本」不成立:带
weight(1f)或背景的空容器仍会占据行份额并绘制容器颜色。
验证方式
文中给出以下审计命令:
确认判定函数存在且声明在组件旁:
grep -rn --include='*.kt' --exclude-dir=build "expect fun is[A-Za-z]*Available(): Boolean" .
列出组件调用点及其上方门控,人工确认未门控的调用点确实没有添加容器:
F=$(grep -rl --include='*.kt' --exclude-dir=build "expect fun is[A-Za-z]*Available(): Boolean" .)
C=$(grep -oE "^expect fun [A-Za-z]+" "$F" | awk '{print $3}' | head -1)
grep -rn --include='*.kt' --exclude-dir=build -B8 "^ *$C(" . | grep -E "$C\(|Available\(\)"
确认槽位使用真实分支而非空点击:
grep -rn --include='*.kt' --exclude-dir=build -A6 "onClick: (() -> Unit)?" .
在特性不可用的设备或目标上打开界面并清点组内单元格:多出一个、间隙变宽或外侧直角,都说明槽位存活得比控件更久。
适用场景与限制
适用于 Kotlin/Compose 多平台 UI 中带可选控件、分段组、加权布局的实现与代码审查。条目为工程经验说明,未提供产品化工具、安装方式或收费信息;命令为审计脚本示例,需在实际仓库环境中运行,具体行为依仓库结构而定。