Jetpack Compose(七)-对话框和其他组件

一、Dialog

Dialog组件的参数如下:

kotlin 复制代码
@Composable
fun Dialog(
    onDismissRequest: () -> Unit,   //当用户试图取消对话框时执行
    properties: DialogProperties = DialogProperties(),    //用于进一步定义对话框的行为
    content: @Composable () -> Unit    //对话框的布局样式
) {...}

其中content允许我们通过传入自己的Composable组件来描述Dialog页面。下面是一个简单的例子:

kotlin 复制代码
@Composable
fun Greeting() {
    var showDialog by remember { mutableStateOf(false) }

    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Button(   //用于显示Dialog
            onClick = { showDialog = true },    //修改showDialog的值
            modifier = Modifier.padding(16.dp)
        ) {
            Icon(imageVector = Icons.Default.Delete, contentDescription = null)
            Text(text = "Show Dialog")
        }

        //showDialog为true,展示Dialog
        if (showDialog) {
            Dialog(
                onDismissRequest = { showDialog = false },
                content = {   //定义Dialog的样式
                    Column(
                        modifier = Modifier
                            .width(250.dp)
                            .height(200.dp)
                            .background(Color.White)
                            .padding(25.dp)
                    ) {
                        Text(text = "删除文件")
                        Spacer(modifier = Modifier.height(10.dp))
                        Text(text = "您确定要删除这个文件吗?")
                        Spacer(modifier = Modifier.height(30.dp))
                        Row(
                            modifier = Modifier.fillMaxWidth(),
                            horizontalArrangement = Arrangement.SpaceBetween
                        ) {
                            Button(
                                onClick = {
                                    // 执行删除操作
                                    showDialog = false
                                }
                            ) {
                                Text(text = "确定")
                            }

                            Button(
                                onClick = {
                                    // 取消删除操作
                                    showDialog = false
                                }
                            ) {
                                Text(text = "取消")
                            }
                        }
                    }
                },
            )
        }
    }
}

具体看看UI效果

properties参数定制一些对话框特有的行为,看源码:

kotlin 复制代码
@Immutable
class DialogProperties constructor(
    val dismissOnBackPress: Boolean = true,    //是否按返回键可以关闭对话框
    val dismissOnClickOutside: Boolean = true,    //触摸对话框外部是否可以关闭对话框
    val securePolicy: SecureFlagPolicy = SecureFlagPolicy.Inherit,
    val usePlatformDefaultWidth: Boolean = true,    //对话框内容的宽度是否应该是限于平台默认值,小于屏幕宽度
    val decorFitsSystemWindows: Boolean = true
) {...}

Compose的对话框不像传统视图的对话框那样通过show()dismiss()等命令式的方式显隐,它像不同的Composable组件一样,显示与否要看是否在重组中被执行,所以它的显示与否要依赖状态控制。Dialog和普通Composable组件的不同在于其底层需要依赖独立的Window进行显示。

二、AlertDialog警告对话框

AlertDialog是在Dialog的基础上封装出来的,所以很多参数很相似,它的具体参数如下:

kotlin 复制代码
@Composable
fun AlertDialog(
    onDismissRequest: () -> Unit,
    buttons: @Composable () -> Unit,     //定义一些功能按钮
    modifier: Modifier = Modifier,    
    title: (@Composable () -> Unit)? = null,    //对话框的标题,不是必须的
    text: @Composable (() -> Unit)? = null,     //对话框的内容,不是必须的
    shape: Shape = MaterialTheme.shapes.medium,
    backgroundColor: Color = MaterialTheme.colors.surface,
    contentColor: Color = contentColorFor(backgroundColor),
    properties: DialogProperties = DialogProperties()
) {...}

除了上面这个还有另一个创建AlertDialog的方法,并且这个方法是在上面方法的基础上封装出来的,源码如下:

kotlin 复制代码
@Composable
fun AlertDialog(
    onDismissRequest: () -> Unit,
    confirmButton: @Composable () -> Unit,   //定义了确定按钮
    modifier: Modifier = Modifier,
    dismissButton: @Composable (() -> Unit)? = null,   //定义了取消按钮
    title: @Composable (() -> Unit)? = null,
    text: @Composable (() -> Unit)? = null,
    shape: Shape = MaterialTheme.shapes.medium,
    backgroundColor: Color = MaterialTheme.colors.surface,
    contentColor: Color = contentColorFor(backgroundColor),
    properties: DialogProperties = DialogProperties()
) {...}

下面通过使用AlertDialog改造上一个例子中的代码以实现与上一个例子一样的效果,代码如下:

kotlin 复制代码
@Composable
fun Greeting() {
    var showDialog by remember { mutableStateOf(false) }

    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Button(    //用于显示Dialog
            onClick = { showDialog = true },    //修改showDialog的值
            modifier = Modifier.padding(16.dp)
        ) {
            Icon(imageVector = Icons.Default.Delete, contentDescription = null)
            Text(text = "Show Dialog")
        }

        //showDialog为true,展示Dialog
        if (showDialog) {
            AlertDialog(
                onDismissRequest = { showDialog = false },
                title = { Text(text = "删除文件") },
                text = { Text(text = "您确定要删除这个文件吗?") },
                buttons = {
                    Row(
                        modifier = Modifier
                            .fillMaxWidth()
                            .padding(bottom = 20.dp),
                        horizontalArrangement = Arrangement.Center
                    ) {
                        Button(
                            onClick = {
                                // 执行删除操作
                                showDialog = false
                            }
                        ) {
                            Text(text = "确定")
                        }
                        Spacer(modifier = Modifier.width(50.dp))
                        Button(
                            onClick = {
                                // 取消删除操作
                                showDialog = false
                            }
                        ) {
                            Text(text = "取消")
                        }
                    }
                }
            )
        }
    }
}

UI效果

三、TopAppBar

类似于Toolbar的功能,先看看它的参数:

kotlin 复制代码
@Composable
fun TopAppBar(
    modifier: Modifier = Modifier,
    backgroundColor: Color = MaterialTheme.colors.primarySurface,
    contentColor: Color = contentColorFor(backgroundColor),
    elevation: Dp = AppBarDefaults.TopAppBarElevation,      //应用栏的海拔高度
    contentPadding: PaddingValues = AppBarDefaults.ContentPadding,   //应用栏内容的内边距
    content: @Composable RowScope.() -> Unit       //具体布局实现
) {
    AppBar(...)
}

可以看到TopAppBar是在AppBar的基础上封装出来的,除了上面这个方法还有另一个创建TopAppBar的方法,如下:

kotlin 复制代码
@Composable
fun TopAppBar(
    title: @Composable () -> Unit,
    modifier: Modifier = Modifier,
    navigationIcon: @Composable (() -> Unit)? = null,    //显示在TopAppBar上开始时的图标,通常应该是一个IconButton或IconToggleButton,例如返回键头
    actions: @Composable RowScope.() -> Unit = {},   //显示在TopAppBar上结束的图标,默认有RowScope作用域横向排列
    backgroundColor: Color = MaterialTheme.colors.primarySurface,
    contentColor: Color = contentColorFor(backgroundColor),
    elevation: Dp = AppBarDefaults.TopAppBarElevation
) {
    AppBar(...)
}

下面看一个具体的使用的例子:

kotlin 复制代码
@Composable
fun Greeting() {
    Column {
        TopAppBar(
            elevation = 4.dp,
            title = {
                Text("TopAppBar")
            },
            backgroundColor = MaterialTheme.colors.primarySurface,
            navigationIcon = {    //返回箭头
                IconButton(onClick = { }) {
                    Icon(Icons.Filled.ArrowBack, null)
                }
            }, actions = {       //末尾添加分享和设置按钮
                IconButton(onClick = { }) {
                    Icon(Icons.Filled.Share, null)
                }
                IconButton(onClick = { }) {
                    Icon(Icons.Filled.Settings, null)
                }
            })

        Text("Hello World")
    }
}

UI效果

四、Snackbar

Snackbar的参数如下:

kotlin 复制代码
@Composable
fun Snackbar(
    modifier: Modifier = Modifier,
    action: @Composable (() -> Unit)? = null,     //将动作比如以按钮的形式添加到Snackbar上
    actionOnNewLine: Boolean = false,        //是否将动作以新的一行显示
    shape: Shape = MaterialTheme.shapes.small,
    backgroundColor: Color = SnackbarDefaults.backgroundColor,
    contentColor: Color = MaterialTheme.colors.surface,
    elevation: Dp = 6.dp,
    content: @Composable () -> Unit
) {...}

Snackbar的应用场景有哪些(GPT的回答)?

Jetpack Compose中的Snackbar组件主要用于在屏幕底部显示短暂的提示或消息。它的常见应用场景包括:

  • 操作反馈:在用户执行操作后,给出简短的反馈提示,比如"保存成功"。
  • 警告信息:当某些操作无法执行或出现错误时,向用户展示警告或错误信息。
  • 确认操作:在可能产生破坏性后果的操作前,使用Snackbar提示用户进行确认。
  • 通知信息:向用户展示一些没有及时需要交互的通知信息。
  • 撤销操作:提供一个按钮允许用户撤销最近执行的操作。
  • 临时交互:Snackbar可以包含按钮等简单交互,用于临时操作。

相比DialogSnackbar出现在屏幕底部,对主要内容影响较小,适合用于展示轻量级的提示信息。

需要注意的是,Snackbar不应该过度使用,或者用于展示太多重要的内容,这会影响交互流程与用户体验。合理使用Snackbar可以在不打断用户的情况下提供有效的反馈。

下面是一个简单的例子:

kotlin 复制代码
@Composable
fun Greeting() {
    Column(modifier = Modifier.fillMaxSize()) {
        val (snackbarVisibleState, setSnackBarState) = remember { mutableStateOf(false) }

        Box(modifier = Modifier.weight(1F)) {
            Button(         //用于显示和隐藏Snackbar
                onClick = { setSnackBarState(!snackbarVisibleState) }) {
                if (snackbarVisibleState) {
                    Text("隐藏Snackbar")
                } else {
                    Text("显示Snackbar")
                }
            }
        }
        
        //Snackbar
        if (snackbarVisibleState) {
            Snackbar(
                action = {
                    Button(onClick = {}) {
                        Text("升级App")
                    }
                },
                modifier = Modifier.padding(8.dp)
            ) { Text(text = "检测到有新版本") }
        }
    }
}

UI效果

五、Card

CardView的效果类似,下面是它的参数

kotlin 复制代码
@Composable
@NonRestartableComposable
fun Card(
    modifier: Modifier = Modifier,
    shape: Shape = MaterialTheme.shapes.medium,    //圆角大小
    backgroundColor: Color = MaterialTheme.colors.surface,
    contentColor: Color = contentColorFor(backgroundColor),
    border: BorderStroke? = null,     //外边框
    elevation: Dp = 1.dp,      //阴影大小
    content: @Composable () -> Unit
) {...}

一个简单的例子

kotlin 复制代码
@Composable
fun Greeting() {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(15.dp)
            .clickable {
                showToast("Card被点击了")
            },
        elevation = 10.dp
    ) {
        Column(
            modifier = Modifier.padding(15.dp)
        ) {
            Text(text = "welcome to Jetpack Compose Playground")
        }
    }
}

UI效果

Card最常见的需求应该是调整阴影和圆角大小,下面是一个通过Slider控件动态调整Card阴影和圆角大小的例子:

kotlin 复制代码
@Composable
fun Greeting() {
    var cardElevation by remember { mutableStateOf(8.dp) }
    var cardCornerSize by remember { mutableStateOf(16.dp) }

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        verticalArrangement = Arrangement.spacedBy(16.dp)
    ) {
        // 可调整圆角和阴影大小的Card
        Card(
            modifier = Modifier
                .fillMaxWidth()
                .height(100.dp)
                .padding(16.dp)
                .background(Color.White),
            elevation = cardElevation,  // 阴影大小
            shape = RoundedCornerShape(cardCornerSize)  //圆角大小
        ) {
            Box(
                modifier = Modifier.fillMaxSize(),
                contentAlignment = Alignment.Center
            ) {
                Text(text = "Customized Card")
            }
        }

        Text(text = "调整阴影大小")
        //用Slider调整阴影大小
        Slider(
            value = cardElevation.value,
            onValueChange = { newValue ->
                cardElevation = newValue.dp
            },
            valueRange = 0f..16f,  //调整阴影大小的范围
            steps = 17,
            modifier = Modifier.fillMaxWidth()
        )

        Text(text = "调整圆角大小")
        //用Slider调整圆角大小
        Slider(
            value = cardCornerSize.value,
            onValueChange = { newValue ->
                cardCornerSize = newValue.dp
            },
            valueRange = 0f..32f, //调整圆角大小的范围
            steps = 33,
            modifier = Modifier.fillMaxWidth()
        )
    }
}

UI效果

六、Divider

画细实线。参数如下:

kotlin 复制代码
fun Divider(
    modifier: Modifier = Modifier,
    color: Color = MaterialTheme.colors.onSurface.copy(alpha = DividerAlpha),
    thickness: Dp = 1.dp,    //线的厚度
    startIndent: Dp = 0.dp    //起始偏移量
) {

UI效果

那在Compose中如何绘制虚线呢?下面是一个例子:

kotlin 复制代码
@Composable
fun Greeting() {
    Canvas(
        modifier = Modifier
            .padding(0.dp, 10.dp, 0.dp, 10.dp)
            .fillMaxWidth()
    ) {
        drawLine(
            start = Offset(0f, size.height / 2),
            end = Offset(size.width, size.height / 2),
            color = Color.Black,
            strokeWidth = 5f,     //线的高度,5像素
            pathEffect = dashPathEffect(
                floatArrayOf(10.dp.toPx(), 2.dp.toPx()),    //虚线的绘制长度和间隔
                5.dp.toPx()   //第一段和最后一段的偏移量,如果给0就正常按10-2-10-2...绘制
            )
        )
    }
}

UI效果

七、BadgedBox

BadgedBox的参数如下:

kotlin 复制代码
@Composable
fun BadgedBox(
    badge: @Composable BoxScope.() -> Unit,
    modifier: Modifier = Modifier,
    content: @Composable BoxScope.() -> Unit,
) {...}

BadgedBoxJetpack Compose 中的一个组件,用于在屏幕上显示一个带有文本或图标的小窗口。它可以用于在应用程序中显示通知、提醒、错误信息、进度条、广告等信息。下面是一个简单的例子:

kotlin 复制代码
@Composable
fun Greeting() {
    //BottomNavigation
    BottomNavigation {
        //BottomNavigationItem
        BottomNavigationItem(
            icon = {
                //BadgedBox
                BadgedBox(badge = { Badge { Text("8") } }) {
                    Icon(
                        Icons.Filled.Favorite,
                        contentDescription = null
                    )
                }
            },
            selected = false,
            onClick = {})
    }
}

UI效果

进一步完善一个点赞的例子,点击"加"点赞数增加,点击"减"点赞数减少,并伴随着UI的变化:

kotlin 复制代码
@Composable
fun Greeting() {
    var likeNum by remember { mutableStateOf(0) }
    val color = if (likeNum > 0) Color.Red else Color.Gray

    Column(modifier = Modifier.fillMaxSize()) {
        Spacer(modifier = Modifier.height(10.dp))
        //BadgedBox
        BadgedBox(badge = {
            if (likeNum > 0) {
                Badge {
                    Text("$likeNum")
                }
            }
        }) {
            Icon(
                Icons.Filled.Favorite,
                contentDescription = null,
                tint = color
            )
        }

        //按钮加
        Spacer(modifier = Modifier.height(20.dp))
        Button(onClick = { likeNum++ }) {
            Text(text = "加")
        }

        //按钮减
        Spacer(modifier = Modifier.height(20.dp))
        Button(onClick = { if (likeNum > 0) likeNum-- }) {
            Text(text = "减")
        }
    }
}

UI效果

八、DropdownMenu

DropdownMenu可以创建一个下拉菜单。先看看它的参数:

kotlin 复制代码
@Composable
fun DropdownMenu(
    expanded: Boolean,      //是否展开
    onDismissRequest: () -> Unit,    //定义消失时的行为
    modifier: Modifier = Modifier,   
    offset: DpOffset = DpOffset(0.dp, 0.dp),
    properties: PopupProperties = PopupProperties(focusable = true),   //各种焦点或触摸行为
    content: @Composable ColumnScope.() -> Unit    //具体布局实现
) {...}

下面是一个简单的例子:

kotlin 复制代码
@Composable
fun Greeting() {
    var expanded by remember { mutableStateOf(false) }
    var selectedGender by remember { mutableStateOf("请选择性别") }
    val items = listOf("男", "女")

    Box(
        modifier = Modifier
            .fillMaxSize()
            .wrapContentSize(Alignment.TopStart)
    ) {
        OutlinedButton(onClick = { expanded = true }) {    //点击按钮展开DropdownMenu
            Text(text = selectedGender)
            Icon(imageVector = Icons.Filled.ArrowDropDown, contentDescription = null)
        }

        DropdownMenu(
            expanded = expanded,
            onDismissRequest = { expanded = false },
        ) {
            items.forEachIndexed { index, itemStr ->
                DropdownMenuItem(onClick = {
                    selectedGender = items[index]
                    expanded = false
                }) {
                    Text(text = itemStr)
                }
            }
        }
    }
}

UI效果

九、ModalBottomSheetLayout

先看一下参数:

kotlin 复制代码
@Composable
@ExperimentalMaterialApi
fun ModalBottomSheetLayout(
    sheetContent: @Composable ColumnScope.() -> Unit,
    modifier: Modifier = Modifier,
    sheetState: ModalBottomSheetState =
        rememberModalBottomSheetState(Hidden),
    sheetShape: Shape = MaterialTheme.shapes.large,
    sheetElevation: Dp = ModalBottomSheetDefaults.Elevation,
    sheetBackgroundColor: Color = MaterialTheme.colors.surface,
    sheetContentColor: Color = contentColorFor(sheetBackgroundColor),
    scrimColor: Color = ModalBottomSheetDefaults.scrimColor,
    content: @Composable () -> Unit
) {...}

这是一个实验性质的API,参数同样是一些圆角、高度、颜色等,具体看一些例子可能清楚一些,下面是一个例子。

kotlin 复制代码
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun Greeting() {
    val fullHeight = LocalConfiguration.current.screenHeightDp.dp
    val state = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
    val scope = rememberCoroutineScope()
    ModalBottomSheetLayout(
        sheetState = state,
        sheetContent = {
            //自己随意定制
            LazyColumn(modifier = Modifier.height(fullHeight / 2)) {
                items(50) {
                    ListItem(
                        text = { Text("Item $it") },
                        icon = {
                            Icon(
                                Icons.Default.Favorite,
                                contentDescription = null
                            )
                        }
                    )
                }
            }
        }
    ) {
        //点击按钮显示Sheet
        Button(onClick = { scope.launch { state.show() } }) {
            Text("Click to show sheet")
        }
    }
}

UI效果

但是快速下拉会把整个ModalBottomSheetLayout拉下来,所以可能还需要做手势拦截,这里就不展开了。另外点击外部区域也会消失,这个暂时也没找到解决办法。如果有需求刚好是底部弹出,且下拉和点击外部消失,那用它还是可以的,下面是一个相对实用的例子:

kotlin 复制代码
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun Greeting() {
    val state = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
    val scope = rememberCoroutineScope()
    ModalBottomSheetLayout(
        sheetState = state,
        sheetContent = {
            Column(
                modifier = Modifier.fillMaxWidth(),
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                Spacer(modifier = Modifier.height(10.dp))
                Text("确认要删除文件吗?")
                Spacer(modifier = Modifier.height(16.dp))
                TextButton(onClick = { /* Handle confirmation */ }) {
                    Text("确认")
                }
                Divider()
                TextButton(onClick = { /* Handle cancel */ }) {
                    Text("取消")
                }
            }
        }
    ) {
        //点击按钮显示Sheet
        Button(onClick = {
            scope.launch {
                state.show()
            }
        }) {
            Text("Click to show sheet")
        }
    }
}

UI效果(点击外部消失和下拉消失)

如果对于底部弹窗有很强的定制需求,可以自己封装或借助第三方库,例如bottomsheetdialog-compose

十、ModalDrawer

参数

kotlin 复制代码
@Composable
@OptIn(ExperimentalMaterialApi::class)
fun ModalDrawer(
    drawerContent: @Composable ColumnScope.() -> Unit,
    modifier: Modifier = Modifier,
    drawerState: DrawerState = rememberDrawerState(DrawerValue.Closed),
    gesturesEnabled: Boolean = true,
    drawerShape: Shape = MaterialTheme.shapes.large,
    drawerElevation: Dp = DrawerDefaults.Elevation,
    drawerBackgroundColor: Color = MaterialTheme.colors.surface,
    drawerContentColor: Color = contentColorFor(drawerBackgroundColor),
    scrimColor: Color = DrawerDefaults.scrimColor,
    content: @Composable () -> Unit
) {...}

ModalDrawer是一个专门用于创建模态抽屉的组件,它可以在页面上打开一个模态抽屉,并且可以在抽屉中显示一些内容。ModalDrawer通常用于实现类似于Android中的Navigation Drawer的效果。下面看一个例子:

kotlin 复制代码
@Composable
fun Greeting() {
    val drawerState = rememberDrawerState(DrawerValue.Closed)
    val imageVector = if (drawerState.isOpen) Icons.Filled.ArrowBack else Icons.Filled.List
    val scope = rememberCoroutineScope()

    Column {
        //在TopAppBar下面创建ModalDrawer
        TopAppBar(elevation = 4.dp,
            title = {
                Text("TopAppBar")
            },
            backgroundColor = MaterialTheme.colors.primarySurface,
            navigationIcon = {    //返回箭头
                IconButton(onClick = {
                    scope.launch {
                        if (drawerState.isOpen) {   //修改图标,点击图标可以开启或关闭ModalDrawer
                            drawerState.close()
                        } else {
                            drawerState.open()
                        }
                    }
                }) {
                    Icon(imageVector, null)
                }
            })

        //ModalDrawer
        ModalDrawer(drawerState = drawerState, drawerContent = {
            Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
                Button(onClick = {
                    scope.launch {
                        drawerState.close()
                    }
                }) {
                    Text("关闭ModalDrawer")
                }
            }
        }, content = {
            Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
                Text(text = "Activity主要显示内容的区域", textAlign = TextAlign.Center)
            }
        })
    }

}

UI效果

十一、Scaffold

Scaffold就是在ModalDrawer的基础上封装出来的脚手架,它有更快捷的定义UI的方式,看它的参数:

kotlin 复制代码
@Composable
fun Scaffold(
    modifier: Modifier = Modifier,
    scaffoldState: ScaffoldState = rememberScaffoldState(),
    topBar: @Composable () -> Unit = {},       //topBar
    bottomBar: @Composable () -> Unit = {},    //bottomBar
    snackbarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(it) },
    floatingActionButton: @Composable () -> Unit = {},     //FAB
    floatingActionButtonPosition: FabPosition = FabPosition.End,
    isFloatingActionButtonDocked: Boolean = false,
    drawerContent: @Composable (ColumnScope.() -> Unit)? = null,    //抽屉
    drawerGesturesEnabled: Boolean = true,
    drawerShape: Shape = MaterialTheme.shapes.large,
    drawerElevation: Dp = DrawerDefaults.Elevation,
    drawerBackgroundColor: Color = MaterialTheme.colors.surface,
    drawerContentColor: Color = contentColorFor(drawerBackgroundColor),
    drawerScrimColor: Color = DrawerDefaults.scrimColor,
    backgroundColor: Color = MaterialTheme.colors.background,
    contentColor: Color = contentColorFor(backgroundColor),
    content: @Composable (PaddingValues) -> Unit
) {
    val child = @Composable { childModifier: Modifier ->
        Surface(modifier = childModifier, color = backgroundColor, contentColor = contentColor) {
            ScaffoldLayout(...)
        }
    }

    if (drawerContent != null) {
        ModalDrawer(...)
    } else {
        child(modifier)
    }
}

聚合了很多控件的脚手架,改造上一个ModalDrawer例子中的代码,将TopAppBar的代码塞到topBar参数中:

kotlin 复制代码
@SuppressLint("UnusedMaterialScaffoldPaddingParameter")
@Composable
fun Greeting1() {
    val drawerState = rememberScaffoldState(rememberDrawerState(DrawerValue.Closed))
    val scope = rememberCoroutineScope()

    Scaffold(topBar = {     //TopAppBar的代码塞到topbar参数中
        TopAppBar(elevation = 4.dp,
            title = {
                Text("TopAppBar")
            },
            backgroundColor = MaterialTheme.colors.primarySurface,
            navigationIcon = {    //返回箭头
                IconButton(onClick = {
                    scope.launch {
                        drawerState.drawerState.open()
                    }
                }) {
                    Icon(Icons.Filled.List, null)
                }
            })
    }, scaffoldState = drawerState, drawerContent = {
        Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
            Button(onClick = {
                scope.launch {
                    drawerState.drawerState.close()
                }
            }) {
                Text("关闭Drawer")
            }
        }
    }, content = {
        Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
            Text(text = "Activity主要显示内容的区域", textAlign = TextAlign.Center)
        }
    })
}

UI效果

可以看到跟上一个例子的区别是Drawer出现的时候TopAppBar被遮挡了。

十二、BottomSheetScaffold

又是一个封装的脚手架,参数如下:

kotlin 复制代码
@Composable
@ExperimentalMaterialApi
fun BottomSheetScaffold(
    sheetContent: @Composable ColumnScope.() -> Unit,
    modifier: Modifier = Modifier,
    scaffoldState: BottomSheetScaffoldState = rememberBottomSheetScaffoldState(),
    topBar: (@Composable () -> Unit)? = null,
    snackbarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(it) },
    floatingActionButton: (@Composable () -> Unit)? = null,
    floatingActionButtonPosition: FabPosition = FabPosition.End,
    sheetGesturesEnabled: Boolean = true,
    sheetShape: Shape = MaterialTheme.shapes.large,
    sheetElevation: Dp = BottomSheetScaffoldDefaults.SheetElevation,
    sheetBackgroundColor: Color = MaterialTheme.colors.surface,
    sheetContentColor: Color = contentColorFor(sheetBackgroundColor),
    sheetPeekHeight: Dp = BottomSheetScaffoldDefaults.SheetPeekHeight,   //sheet收缩时的高度
    drawerContent: @Composable (ColumnScope.() -> Unit)? = null,
    drawerGesturesEnabled: Boolean = true,
    drawerShape: Shape = MaterialTheme.shapes.large,
    drawerElevation: Dp = DrawerDefaults.Elevation,
    drawerBackgroundColor: Color = MaterialTheme.colors.surface,
    drawerContentColor: Color = contentColorFor(drawerBackgroundColor),
    drawerScrimColor: Color = DrawerDefaults.scrimColor,
    backgroundColor: Color = MaterialTheme.colors.background,
    contentColor: Color = contentColorFor(backgroundColor),
    content: @Composable (PaddingValues) -> Unit
) {...}

下面是一个例子,默认展示3条,用户想要操作更多向上划出:

kotlin 复制代码
enum class Page(val title: String, val icon: Int) {
    HOME("home", R.drawable.ic_home),
    SEARCH("search", R.drawable.ic_search),
    PROFILE("profile", R.drawable.ic_profile),
    SETTING("setting", R.drawable.ic_setting),
    MESSAGE("message", R.drawable.ic_message),
    PHONE("phone", R.drawable.ic_phone),
    SHARE("share", R.drawable.ic_share)
}


@OptIn(ExperimentalMaterialApi::class)
@Composable
fun Greeting() {
    val scope = rememberCoroutineScope()
    val scaffoldState = rememberBottomSheetScaffoldState()
    val page = Page.values()

    BottomSheetScaffold(
        scaffoldState = scaffoldState,
        sheetPeekHeight = 110.dp,
        drawerBackgroundColor = Color.LightGray,
        sheetContent = {
            LazyVerticalGrid(
                columns = GridCells.Fixed(3),
                // content padding
                contentPadding = PaddingValues(
                    start = 12.dp,
                    top = 0.dp,
                    end = 12.dp,
                    bottom = 0.dp
                ),
                content = {
                    items(page.size) { index ->
                        Card(
                            modifier = Modifier
                                .padding(6.dp, 10.dp)
                                .fillMaxWidth()
                                .clickable {
                                    //scope.launch { scaffoldState.bottomSheetState.collapse() }
                                },
                            elevation = 6.dp
                        ) {
                            Column(
                                modifier = Modifier
                                    .fillMaxWidth()
                                    .padding(10.dp)
                            ) {
                                Icon(
                                    bitmap = ImageBitmap.imageResource(page[index].icon),
                                    contentDescription = page[index].title
                                )
                                Text(text = page[index].title)
                            }
                        }
                    }
                }
            )
        }, content = { _ ->
            Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
                Text("Activity主要显示内容的区域")
            }
        })
}

UI效果

十三、BottomNavigation

BottomNavigation底部导航,参数如下:

kotlin 复制代码
@Composable
fun BottomNavigation(
    modifier: Modifier = Modifier,
    backgroundColor: Color = MaterialTheme.colors.primarySurface,
    contentColor: Color = contentColorFor(backgroundColor),
    elevation: Dp = BottomNavigationDefaults.Elevation,
    content: @Composable RowScope.() -> Unit   //包N个BottomNavigationItem
) {...}

下面是一个简单的例子

kotlin 复制代码
@Composable
fun Greeting() {
    var text by remember { mutableStateOf("Home") }

    Column(
        modifier = Modifier
            .fillMaxSize()
    ) {
        Box(
            modifier = Modifier
                .weight(1F)
                .fillMaxWidth(),
            contentAlignment = Alignment.Center
        ) {
            Text(text = text)
        }

        BottomNavigation {
            BottomNavigationItem(
                selected = true,
                onClick = { text =  "Home" },
                icon = { Icon(bitmap = ImageBitmap.imageResource(id = R.drawable.ic_home), contentDescription = "Home") },
                label = { Text(text = "Home") }
            )
            BottomNavigationItem(
                selected = false,
                onClick = { text =  "Search" },
                icon = { Icon(bitmap = ImageBitmap.imageResource(id = R.drawable.ic_search), contentDescription = "Search") },
                label = { Text(text = "Search") }
            )
            BottomNavigationItem(
                selected = false,
                onClick = { text =  "Profile" },
                icon = { Icon(bitmap =ImageBitmap.imageResource(id = R.drawable.ic_profile), contentDescription = "Profile") },
                label = { Text(text = "Profile") }
            )
        }
    }
}

UI效果

十四、NavigationRail

侧面导航NavigationRail,参数如下:

kotlin 复制代码
@Composable
fun NavigationRail(
    modifier: Modifier = Modifier,
    backgroundColor: Color = MaterialTheme.colors.surface,
    contentColor: Color = contentColorFor(backgroundColor),
    elevation: Dp = NavigationRailDefaults.Elevation,
    header: @Composable (ColumnScope.() -> Unit)? = null,
    content: @Composable ColumnScope.() -> Unit
) {...}

下面是一个例子:

kotlin 复制代码
enum class Page(val title: String) {
    HOME("home"),
    SEARCH("Search"),
    PROFILE("profile")
}

@Composable
fun Greeting() {
    var selectedItem by remember { mutableStateOf(0) }
    val pages = Page.values()
    Row {
        //创建NavigationRail
        NavigationRail {
            pages.forEachIndexed { index, item ->
                when (item) {
                    Page.HOME -> {
                        ////创建NavigationRail
                        NavigationRailItem(
                            label = { Text(item.title) },
                            icon = {
                                Icon(
                                    bitmap = ImageBitmap.imageResource(id = R.drawable.ic_home),
                                    contentDescription = null
                                )
                            },
                            selected = selectedItem == index,
                            onClick = { selectedItem = index })
                    }

                    Page.SEARCH -> {
                        NavigationRailItem(label = { Text(item.title) },
                            icon = {
                                Icon(
                                    bitmap = ImageBitmap.imageResource(id = R.drawable.ic_search),
                                    contentDescription = null
                                )
                            },
                            selected = selectedItem == index,
                            onClick = { selectedItem = index })
                    }

                    Page.PROFILE -> {
                        NavigationRailItem(label = { Text(item.title) },
                            icon = {
                                Icon(
                                    bitmap = ImageBitmap.imageResource(id = R.drawable.ic_profile),
                                    contentDescription = null
                                )
                            },
                            selected = selectedItem == index,
                            onClick = { selectedItem = index }
                        )
                    }
                }
            }
        }

        Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
            Text(pages[selectedItem].title)
        }
    }
}

UI效果

十五、Surface

Surface 是一个抽象的类,它表示一个可以绘制图形的区域,它的参数如下:

kotlin 复制代码
@Composable
fun Surface(
    modifier: Modifier = Modifier,
    shape: Shape = RectangleShape,
    color: Color = MaterialTheme.colors.surface,
    contentColor: Color = contentColorFor(color),
    border: BorderStroke? = null,
    elevation: Dp = 0.dp,
    content: @Composable () -> Unit
) {...}

常用的用法有:

1、提供背景、圆角、边框和阴影

kotlin 复制代码
@Composable
fun Greeting() {
    Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
        Surface(
            color = Color.LightGray, elevation = 8.dp, shape = RoundedCornerShape(8.dp)
        ) {
            Text(
                text = "Surface中的文本", modifier = Modifier.padding(16.dp)
            )
        }
    }
}

UI效果

顺着这个思路可以封装各种边框圆角等卡片样式的布局。

2、绘制以生成自定义图形

kotlin 复制代码
@Composable
fun Greeting() {
    Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
        Surface(
            color = Color.White,
            shape = RectangleShape,
            elevation = 4.dp
        ) {
            //绘制矩形
            Canvas(modifier = Modifier.size(300.dp)) {
                drawRect(
                    color = Color.LightGray,
                    style = Fill,
                    topLeft = Offset(75.dp.toPx(), 75.dp.toPx()),
                    size = Size(150.dp.toPx(), 150.dp.toPx())
                )
            }
        }
    }
}

UI效果

参考了以下内容

Jetpack Compose博物馆

实体书 Jetpack Compose从入门到实战

Jetpack-Compose-Playground

其他内容

相关推荐
l and24 分钟前
Git 行尾换行符,导致无法进入游戏
android·git
程序媛小果26 分钟前
基于Django+python的Python在线自主评测系统设计与实现
android·python·django
梁同学与Android1 小时前
Android --- 在AIDL进程间通信中,为什么使用RemoteCallbackList 代替 ArrayList?
android
Frank_HarmonyOS3 小时前
【无标题】Android消息机制
android
凯文的内存5 小时前
Android14 OTA升级速度过慢问题解决方案
android·ota·update engine·系统升级·virtual ab
VinRichard5 小时前
Android 常用三方库
android
Aileen_0v06 小时前
【玩转OCR | 腾讯云智能结构化OCR在图像增强与发票识别中的应用实践】
android·java·人工智能·云计算·ocr·腾讯云·玩转腾讯云ocr
江上清风山间明月9 小时前
Flutter DragTarget拖拽控件详解
android·flutter·ios·拖拽·dragtarget
debug_cat12 小时前
AndroidStudio Ladybug中编译完成apk之后定制名字kts复制到指定目录
android·android studio
编程洪同学16 小时前
Spring Boot 中实现自定义注解记录接口日志功能
android·java·spring boot·后端