继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

如何在Jetpack Compose中为插槽参数设置类型限制

一只甜甜圈
关注TA
已关注
手记 243
粉丝 55
获赞 117
受限的灵活性……

插槽API模式(Slots API Pattern)帮助我们通过提供通用的可组合插槽参数来创建灵活的组件。

然而,在没有一些限制的情况下,保持设计和行为的一致性几乎是不可能的,特别是在设计像ToolbarButton这样的通用系统组件(例如工具栏或按钮)时……

在接下来的这篇博客中,我们将结合灵活性和限制,打造出完美的组件 👌。

我们试着用工具栏来试试吧。
保持“Toolbar”为“工具栏”。

让我们从创建一些基本组件开始吧。我们需要一个工具栏,它的actions只接受一组预定义的子组件,而body插槽则非常灵活,

    @Composable  
    fun Toolbar(  
        modifier: Modifier = Modifier,  
        actions: (@Composable () -> Unit)? = null,  
        /* 其他可选参数... */  
        body: @Composable () -> Unit,  
    ){  
      /*...*/   
    }  

    // 子组件...  
    @Composable  
    fun ToolbarIcon(  
        imageVector: ImageVector,  
        onClick: () -> Unit,  
        tint: Color = LocalContentColor.current,  
    ) {  
      /*...*/  
    }  

    @Composable  
    fun ToolbarAnimatedVisibility(  
        visible: Boolean,  
        content: @Composable AnimatedVisibilityScope.() -> Unit,  
    ) {  
      /*...*/  
    }  

    @Composable  
    fun ToolbarButton(  
        onClick: () -> Unit,  
        text: String,  
    ){  
      /*...*/  
    }  

    // 其他任何子组件,比如自定义弹出窗口...

注意: 我省略了子组件中的 modifier 参数,以保持完全掌控,因此没有给这些组件添加任何额外样式。

目前,导航栏接受任何可组合组件。为了限制这种做法,我们可以使用 layoutId 修饰符为每个子组件分配一个独特的 ID,然后在导航栏中验证这些唯一标识。

    @Composable  
    fun ToolbarIcon(/*...*/) {  
      Icon(  
        modifier = Modifier.layoutId(工具栏图标布局ID)  
        /*...*/  
      )  
    }  

    private object 工具栏图标布局ID

注意:关键是要限制其他开发人员访问 layoutId,从而使开发人员不能将其赋值给其他组件,进而绕过限制。

接下来,在测量阶段中,我们检查 layoutId 以验证组件的 ID。

    @Composable  
    fun RequireLayoutId(  
        layoutIds: List<Any>,  
        content: @Composable () -> Unit,  
        errorMessage: () -> Any,  
    ) {  
        Layout(content) { measurables, _ ->  
            // 检查所有子元素的布局ID  
            确保(measurables.all { it.layoutId in layoutIds }, errorMessage)  

            // 实际上不测量或布局任何子元素  
            layout(0, 0) {}  
        }  
    }

在这个功能中,我们读取组件列表的内容并验证这些ID是否有效。

例如,将三个 ToolbarIcons 和一个 ToolbarButton 传给工具栏的 action 槽位是可以接受的。任何其他类型的组件都会引发运行时异常。

最后一步,我们应该在工具栏的最开始使用这个功能。


    @Composable  
    fun Toolbar(/*...*/) {  
        if (actions != null) {  
            RequireLayoutId(  
                layoutIds = listOf(  
                    ToolbarIconLayoutId,  
                    ToolbarButton,  
                ),  
                content = { actions() },  
                errorMessage = { "操作仅限于工具栏组件" }  
            )   
        }  
        // ...  
    }  

    // 示例如下  
    @Composable  
    fun HomeScreen() {  
        Toolbar(  
            actions = {  
                // 可接受 ✅  
                ToolbarIcon()  

                // 可接受 ✅  
                ToolbarButton()  

                // 会引发错误 ❌  
                Box {  
                    ToolbarIcon()  
                }  
            },  
            body = { /*...*/ }  
        )  
    }

现在看起来一切都很好,但所有子组件在任何地方都能访问到!

要解决这个问题,我们可以自定义一个作用域,将我们的所有子组件都包含进去。

    @file:Suppress("ABSTRACT_COMPOSABLE_DEFAULT_PARAMETER_VALUE")  

    interface ToolbarScope {  
        @Composable  
        fun ToolbarIcon(/*...*/) {}  

        companion object {  
            private val instance = object : ToolbarScope {}  
            internal operator fun invoke() = instance  

            // 如果您的 UI 工具包没有单独打包...  
            internal object ToolbarIconLayoutId  
        }  
    }  

    @Composable  
    fun Toolbar(  
        actions: @Composable (ToolbarScope.() -> Unit)? = null,  
    ) {  
        if (actions != null) {  
            // 需要布局ID  
            /*...*/  
            content = { ToolbarScope().actions() },  
        }   
        with(ToolbarScope()) {  
            // 例如...  
            Row {  
                body()  
                actions()  
            }  
        }  
        // ...  
    }

这样做让这些组件从外部更难访问。如果有人试图在不合适的地方使用它们,审查时很容易发现。

另一个选择是将所有子组件放入一个 ToolbarDefaults 对象。这样就稍微难以不小心访问。

    object ToolbarDefaults {  
        @Composable  
        fun ToolbarIcon(/*注释...*/) {}  
    }

或者更详细翻译:

    object 工具栏默认设置 {  
        @组合式注解  
        fun 工具栏图标(/*注释...*/) {}  
    }
实际中的组件限制措施 💙

Getcontact中,屏幕数量超过700后,我们发现灵活性与限制相结合带来了很大的好处。

  1. 一致的UI/UX: 应用看起来更清晰,行为和动画在应用中保持一致。
  2. 减少UI错误: 限制减少了间距、波纹和动画错误,减少了测试人员和开发人员的来回沟通。
  3. 防止设计师偏离正轨: 使用高度灵活的组件,我们可以实现任何设计——即使它不符合设计系统。但是有了这些限制,我们可以停下来问,“为什么这个组件与其他组件的不同之处如此显著?” 并且只有在符合设计系统的情况下才继续前进。
实用资源 🖇️ 最后

实施限制并不容易,而且只有在确实需要的时候才应该使用。然而,当正确实施,它可以帮所有团队省去很多麻烦。

哦,顺便说一句,订阅或者随便做点什么吧,随便 :)

回头见,你一会儿再聊吧…

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP