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

Kotlin Decompose中的标签切换

临摹微笑
关注TA
已关注
手记 286
粉丝 32
获赞 169

这张图片来自 https://stock.adobe.com/br/search?k=magical+tree&asset_id=820824773 (来自Adobe Stock)

在之前的文章中,我们深入介绍了围绕Kotlin Decompose的概念及其如何用于构建整个屏幕流程中的导航,同时拥有某些称为组件的具有应用程序生命周期感知能力的组件,这些组件应确保应用程序交互的一致性和完整性。在这篇文章里,我们将演示一个非常常见的使用Decompose的导航模式,并进一步解释Children Compose组件的工作原理。我们将再次使用之前提到的项目,但现在我们将在登录、引导页、主枢纽和个人资料这四个屏幕之间建立标签式导航。

快来创建一个底部标签栏(底部导航栏)组件吧!

首先,先创建一个组件来表示整个应用顶部的标签条,该标签条将放置在我们的RootContent之上,并与Children组件一起分割UI空间为不同的部分。

首先,我们需要一个表示每个可选标签按钮的密封类。

    sealed class TabBarItem(  
        val image: ImageVector,  
        val title: String  
    ) {  
        object Login : TabBarItem(  
            image = Icons.Default.Add,  
            title = "登录"  
        )  
        object Onboarding : TabBarItem(  
            image = Icons.Default.Create,  
            title = "引导页"  
        )  
        object MainHub : TabBarItem(  
            image = Icons.Default.Home,  
            title = "主页"  
        )  
        object Profile : TabBarItem(  
            image = Icons.Default.AccountBox,  
            title = "个人主页"  
        )  
    }

这可以看作是我们适配器的又一层,因为它是一个需要转换成根 Config 的数据模型,我们以后再细说。

现在我们来创建一个简单的组件作为我们标签项组件的部分:

    @Composable  
    private fun TabComponent(  
        modifier: Modifier = Modifier,  
        item: TabBarItem,  
        isSelected: Boolean  
    ) {  
        // 下面的 Column 是一个垂直排列的容器,水平居中对齐
        Column(  
            modifier = modifier,  
            horizontalAlignment = Alignment.CenterHorizontally  
        ) {  
            // 根据isSelected的值来决定图标颜色,选中时为蓝色,未选中时为灰色
            Icon(  
                tint = if (isSelected) Color.Blue else Color.Gray,  
                contentDescription = null,  
                imageVector = item.image  
            )  
            // 一个高度为4dp的空白区域,用于分隔图标和标题
            Spacer(Modifier.height(4.dp))  
            // 显示标签项的标题,颜色根据isSelected的值决定,选中时为蓝色,未选中时为灰色
            Text(item.title, color = if (isSelected) Color.Blue else Color.Gray)  
        }  
    }

此组件将接收一个选项卡项,并根据其内容来填充图像和文本。我们还将传递一个布尔值,用以决定选项卡项的颜色。

    @Composable  
    fun 底部标签栏组件(  
        modifier: Modifier = Modifier,  
        项: List<TabBarItem>,  
        选择项: (TabBarItem) -> Unit  
    ) {  
        var 当前选中项: TabBarItem? by remember {  
            mutableStateOf(TabBarItem.Login)  
        }  

        Surface(  
            modifier = modifier  
                .fillMaxWidth(),  
            shape = RoundedCornerShape(  
                topStart = 8.dp,  
                topEnd = 8.dp,  
                bottomEnd = 0.dp,  
                bottomStart = 0.dp  
            ),  
            color = Color.White  
        ) {  
            Row(  
                modifier = Modifier  
                    .fillMaxWidth()  
                    .padding(8.dp),  
                horizontalArrangement = Arrangement.SpaceEvenly  
            ) {  
                项.forEach {  
                    TabComponent(  
                        modifier = Modifier  
                            .clickable {  
                                当前选中项 = it  
                                选择项(it)  
                            },  
                        item = it,  
                        isSelected = 当前选中项 == it  
                    )  
                }  
            }  
        }  
    }

最后,我们有一个 BottomTabBar 组件,它将包含选项卡项列表。我们还有一个名为 selectedItem 的状态变量,它将定义选中的选项卡,并在每次选择新选项卡时进行更新。

更新我们的根导航器 (RootNavigator)

我们现在稍微修改一下导航器的协议。每次我们切换到一个新的标签时,导航器应该用对应的配置替换当前屏幕。

    class RootNavigator(  
        private val navigation: StackNavigation<RootComponent.Config>,  
        private val screenStack: Value<ChildStack<*, RootComponent.Child>>  
    ) {  
        fun showTabItem(item: TabBarItem) {  
            when(item) {  
                is TabBarItem.Login -> navigation.bringToFront(RootComponent.Config.Login)  
                is TabBarItem.Onboarding -> navigation.bringToFront(RootComponent.Config.Onboarding)  
                is TabBarItem.MainHub -> navigation.bringToFront(RootComponent.Config.MainHub)  
                is TabBarItem.Profile -> navigation.bringToFront(RootComponent.Config.Profile)  
            }  
        } 
``

实际上,我们将 `TabBarItem` 映射到一个表示新选中标签的 `Config`。

将标签栏(Tab栏)包含到根屏幕上

我们现在将在`RootContent`可组合项中放置新的`BottomTabBar`,它将使用`Children`来划分屏幕空间。我们将使用一个`Scaffold`来创建新的布局结构。

![](https://imgapi.imooc.com/670f2358097a6aba14000995.jpg)

@Composable
fun RootContent(rootComponent: RootComponent) {
声明一个navigator变量,它是一个remember函数返回的值,该值由rootComponent的getNavigator方法获取。

框架(
    修改器 = Modifier.fillMaxSize(),
    底部栏 = {
        底部标签栏(
            项目 = listOf(
                TabBarItem.Login,
                TabBarItem.Onboarding,
                TabBarItem.MainHub,
                TabBarItem.Profile
            ),
            当选择 = { navigator.showTabItem(it) }
        )
    }) {
    子组件(rootComponent.stack) {
        根据子组件的实例 {
            如果子组件的实例是登录组件 -> LoginScreen(navigator)
            如果子组件的实例是引导组件 -> OnboardingScreen(navigator)
            如果子组件的实例是主页组件 -> MainHubScreen(navigator)
            如果子组件的实例是个人资料组件 -> ProfileScreen(navigator)
        }
    }
}

}



我们把 `BottomTabBar` 作为底部栏插入,并注意到 `Children` 是作为 `Scaffold` 的内容。每次我们切换标签时,导航器会相应地展示新的内容。同时,我们还从 `Children` 中去掉了动画参数,以实现最直观的标签切换效果。

这是我们得到的结果。

![](https://imgapi.imooc.com/670f235a0a41b57002960640.jpg)

# 结论部分

标签导航在 Decompose 中的实现与其他前端(如 UIKit、SwiftUI 和 Voyager)有所不同。我们手动创建一个新的底部栏,并添加标签项,实现了一些逻辑来将选中的屏幕置于前端。在我看来,这种实现方式甚至比其他前端框架更好,因为它为我们提供了更多的自由,我们可以自定义标签栏,以及标签项的展示方式。希望你喜欢这篇文章,并准备好利用标签栏创建你自己的流程。
打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP