[KtJC] Drawer 和 Tabber

作者: 何晓杰Dev | 来源:发表于2019-12-19 16:25 被阅读0次

    Drawer

    在 JC 里要实现侧滑抽屉(Drawer)是比较方便的,代码非常直观,其函数定义为:

    fun ModalDrawerLayout(
        drawerState: DrawerState,                // 指出当前抽屉的状态
        onStateChange: (DrawerState) -> Unit,    // 改变抽屉状态的函数
        gesturesEnabled: Boolean = true,         // 是否允许手势
        drawerContent: @Composable() () -> Unit, // 抽屉的布局
        bodyContent: @Composable() () -> Unit    // 主界面的布局
    )
    

    所以我们可以在代码中直接写:

    enum class ScreenType { first, second, third }
    
    @Composable
    fun mainUI(ctx: Context?, name: String, color: Color = Color.Black) {
        val (drawerState, onDrawerStateChange) = +state { DrawerState.Closed }
        var uiState by +state { ScreenType.first }
        MaterialTheme {
            ModalDrawerLayout(
                drawerState = drawerState,
                onStateChange = onDrawerStateChange,
                gesturesEnabled = true,
                drawerContent = {
                    Column(modifier = Expanded) {
                        Row(modifier = Spacing(16.dp)) {
                            VectorImage(id = R.drawable.ic_launcher_foreground)
                        }
                        Divider(color = Color.LightGray)
                        Button(text = "main", onClick = {
                            uiState = ScreenType.first
                            onDrawerStateChange(DrawerState.Closed)
                        })
                        HeightSpacer(height = 4.dp)
                        Button(text = "second", onClick = {
                            uiState = ScreenType.second
                            onDrawerStateChange(DrawerState.Closed)
                        })
                        HeightSpacer(height = 4.dp)
                        Button(text = "third", onClick = {
                            uiState = ScreenType.third
                            onDrawerStateChange(DrawerState.Closed)
                        })
                    }
                }
            ) {
                MainContent(uiState) {
                    onDrawerStateChange(DrawerState.Opened)
                }
            }
        }
    }
    
    @Composable
    fun MainContent(uiState: ScreenType, onOpen: () -> Unit) {
        when(uiState) {
            ScreenType.first -> ui1(onOpen = onOpen)
            ScreenType.second -> ui2(onOpen = onOpen)
            ScreenType.third -> ui3(onOpen = onOpen)
        }
    }
    

    这里定义了在抽屉内有三个按钮,并且点击后更换主界面布局,这里有一个同样关于 State 的小技巧。

    在上一篇里已经介绍过数据绑定或者说状态保存的内容,使用 @Model 注解来标记即可,但是这需要有一个类型来承载,这里有一个更方便的方法,即是把变量委托给 state 对象,这样当变量发生改变时,也会自动刷新界面。此处的 uiState 也因此变成了一个可以用于刷新界面的成员。

    这里有一个小概念可能比较绕,简单说一下,这里有两行代码:

    val (drawerState, onDrawerStateChange) = +state { DrawerState.Closed }
    var uiState by +state { ScreenType.first }
    

    虽然第一句也使用了 state 方法,但是只是从中获取了两个 component,但是并没委托读写,换言之,就是一次性读取并且使用,这个时候对于 component 的改变并不会引起界面的刷新。

    而第二句使用了委托,即是把 uiState 变量的读写交给了 state,此时针对写入的操作将引起界面刷新。

    Tabber

    Tabber 即是常说的页签,之前通常用 Fragment + ViewPager 实现,但是在 JC 里显然不那么麻烦,直接上代码了:

    enum class Sections(val title: String) {
        Sec1("Section 1"),
        Sec2("Section 2"),
        Sec3("Section 3")
    }
    
    @Composable
    fun tabSample(onOpen: () -> Unit) {
        var section by +state { Sections.Sec1 }
        val sectionTitles = Sections.values().map { it.title }
        Column {
            TopAppBar(title = {
                Text(text = "JPSample")
            }) {
                VectorImageButton(id = R.drawable.ic_launcher_foreground) {
                    onOpen()
                }
            }
            TabRow(items = sectionTitles, selectedIndex = section.ordinal) { index, text ->
                Tab(text = text,selected = section.ordinal == index) {
                    section = Sections.values()[index]
                }
            }
            Container(modifier = Flexible(1f), alignment = Alignment.Center, expanded = true) {
                    when(section) {
                        Sections.Sec1 -> Text(text = "Tab 1")
                        Sections.Sec2 -> Text(text = "Tab 2")
                        Sections.Sec3 -> Text(text = "Tab 3")
                    }
            }
        }
    }
    

    此处的代码是与上面的 Drawer 联合使用的,单独使用则不需要传入 onOpen 参数。

    Container 函数内加入 modifier = Flexible(1f),可以实现将该 Container 按高度填充的效果,所以在其间添加 Text 可以被 alignment 影响到从而居中显示。

    问题

    目前 Drawer 还存在的问题是,不能对弹出层的宽度作出调整,它将永远占据屏幕80%左右的宽度,另外,滑动出 Drawer 的范围比较偏向屏幕中间,在屏幕边缘滑动将触发 Back 键(或许这是故意设计以应对全面屏的)。

    Tabber 存在的问题是,无法通过手势来滑动切换,只能点击切换,并且在 Tab 较多且设为 Tab 可滚动时,会产生一定的卡顿,当然目前作为玩具来说,它已经较为完善了。

    补充

    在上一篇文章中,讲到了吸底的 Container 的制作,采用了手动计算的方法来确定用于填充的 Container 的高度。而上面也提到了使用 modifier = Flexible(1f) 可以实现按高度填充,因此不再需要手动计算了,可以将代码修改一下以简单适应:

    Column {
        TopAppBar(title = { Text(text = "Sample")})
        Container(
                    modifier = Flexible(1f),
                    expanded = true) {
                    ... ...
        }
        Container(height = 40.dp, alignment = Alignment.BottomCenter, expanded = true) {
                    ... ...
        }
    }
    

    相关文章

      网友评论

        本文标题:[KtJC] Drawer 和 Tabber

        本文链接:https://www.haomeiwen.com/subject/zkmmnctx.html