ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

AestheticDialogs 的 UI 模型与信号设计:为什么 Compose 对话框里不该出现 Lambda

AestheticDialogs 的 UI 模型与信号设计:为什么 Compose 对话框里不该出现 Lambda AestheticDialogs 的 UI 模型与信号设计为什么 Compose 对话框里不该出现 Lambda【免费下载链接】AestheticDialogs An Android Library built with Jetpack Compose for fluid, beautiful, custom Dialogs.项目地址: https://gitcode.com/gh_mirrors/ae/AestheticDialogsAestheticDialogs 是一个基于 Jetpack Compose 的 Android 对话框设计系统为应用提供确认、提醒、选择、输入、通知等完整对话框组件。它在 2.0 版本做了一个对新手来说可能反直觉的决定UI 模型里一个 Lambda 都不许出现。这个决定背后是 Compose 重组机制里三个很实际的成本。这篇文章带你从零理解它的 UI 模型 信号Signal 设计以及为什么它让对话框变得可预览、可测试、可控。先看问题教科书式对话框为什么难维护用 Compose 自带的AlertDialog写对话框时代码通常长这样一堆嵌套的 Lambda——外层onDismissRequest负责点击遮罩关闭按钮各自携带onClick点击后立即调用dismiss()把自己关掉。这种写法有两个隐蔽的问题行为藏在 UI 里用户点一下按钮对话框自己决定消失。你想记录一次用户主动取消和一次误触遮罩的区别做不到因为两者都只触发了同一个onDismissRequest。组件无法测试要验证按钮行为你必须真的构建出 UI、真的去点它按钮背后的逻辑被 Lambda 锁死在组合里。下面这张图是 AestheticDialogs 的提醒对话框Alert 组件在深色模式下的渲染效果——错误提示、重试按钮、取消按钮全部由一个纯数据模型驱动UI 模型用纯数据描述要显示什么AestheticDialogs 的做法是对话框的长相完全由一个UI 模型决定。它是一个密封接口sealed interface加若干不可变数据类Immutabledata class每个视觉变体对应一个子类。以确认对话框为例删除相册这种危险操作对应Destructive子类AestheticConfirmationDialog( uiModel ConfirmationDialogUiModel.Destructive( title Delete this album?, message The 24 photos inside it will be deleted too., confirmLabel Delete album, cancelLabel Keep it, isConfirming uiState.isDeleting, ), onSignal { signal - /* 见下文 */ }, )注意几个设计细节变体是保证出来的不是配置出来的。Destructive子类没有tone参数——危险操作永远显示错误色调和红色按钮调用方没有机会把它配错。模型只有原始数据String、Boolean、枚举。没有Color、没有Dp、没有 Lambda。样式由主题系统统一解决详见 docs/ARCHITECTURE.md 的主题章节。模型是密封层级以后新增一个变体比如带插图的提醒编译器会强制你处理所有分支而不是让某个角落悄悄漏掉。模型定义在 ConfirmationDialogUiModel.kt 这类文件中每个对话框家族都有自己的models/目录。为什么模型里不能出现 Lambda架构文档里给出了四条理由对理解 Compose 很有价值重组成本。Lambda 每次重组都会生成新对象导致本可跳过的子树被迫重新组合。数据类配合Immutable则可以被 Compose 快速比对跳过。不可比较。带 Lambda 的对象无法做值相等比较状态没变就别重绘这个基本前提就断了。无法单元测试。你没法断言一个 Lambda 的行为等于另一个 Lambda但你可以轻松断言模型 X 应该映射成变体 Y。无法预览。Preview 里随手构造一个模型是几行代码的事构造一组 Lambda 回调则毫无意义。一句话总结数据进事件出。模型负责显示什么回调负责发生了什么两者严格分离。信号Signal一个类型化回调告诉你发生了什么既然按钮的 Lambda 被拿掉了用户点击去哪了AestheticDialogs 为每个组件定义了一个类型化回调onSignal参数是密封接口的信号sealed interface ConfirmationDialogSignal { data object Confirmed : ConfirmationDialogSignal data object Cancelled : ConfirmationDialogSignal data object Dismissed : ConfirmationDialogSignal }这里有三个值得新手学习的点Cancelled和Dismissed被刻意分开。按取消按钮是一个决定点遮罩或按返回键是一次退却。埋点统计、确定要离开吗这类流程都依赖这种区分不关心的调用方也可以用一个when分支合并处理语义依然清晰。对话框永远不自己关闭。它发出Dismissed信号由你决定是否把它移出组合。这意味着对话框在不在屏幕上这个状态的唯一归属者是你的ViewModel组件本身完全无状态。信号可以携带数据。比如选择对话框的SelectionDialogSignal.ItemClicked携带被点击项的id搜索变化则有SearchQueryChanged携带查询词——但注意它只是上报点击点一下是加入还是移除选择集由你决定。选择模型的完整定义在 SelectionDialogUiModel.kt是理解信号携带数据的最好范例。输入与通知同一套规则不同的细节输入对话框Text/Password两种变体保留了一个普通 LambdaonValueChange。因为文本框的输入回调只有一种交互把它包装成信号只会徒增仪式感——这是信号不是到处反射性使用的典型反例。而校验则完全属于你的状态你把errorText和isConfirmEnabled放进模型对话框只负责渲染绝不替你判断邮箱地址格式不对。通知横幅Notification则展示了这套设计的另一面它不是模态对话框而是锚定在屏幕边缘的横幅由AestheticNotificationHost承载同样通过信号上报关闭请求什么时候模型不够用最后一个诚实的例外AestheticContentDialog的中间区域是一个composable 插槽参数而不是模型字段——因为一段任意 UI不是数据无法塞进数据类。这个逃生舱保留了对话框的框架自适应宽度、遮罩、无障碍面板让自定义内容不至于退化成一个裸的Dialog {}。小结三条可以带走的经验✅UI 模型 纯不可变数据可比较、可测试、可预览重组开销最小✅一个类型化onSignal回调替代 N 个分散的 Lambda事件语义决定 vs 退却从此可区分✅状态所有权明确对话框何时出现在屏幕上永远由调用方的状态决定组件从不自我关闭。这套模型 信号的分离正是 AestheticDialogs 七种对话框共享同一套帧、主题和无障碍保证的关键。更多分层架构细节Component → Variant → Primitive → Tokens可阅读 docs/ARCHITECTURE.md 获取。【免费下载链接】AestheticDialogs An Android Library built with Jetpack Compose for fluid, beautiful, custom Dialogs.项目地址: https://gitcode.com/gh_mirrors/ae/AestheticDialogs创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表