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

其他内容

相关推荐
百锦再30 分钟前
Android Studio开发 SharedPreferences 详解
android·ide·android studio
青春给了狗41 分钟前
Android 14 修改侧滑手势动画效果
android
CYRUS STUDIO1 小时前
Android APP 热修复原理
android·app·frida·hotfix·热修复
火柴就是我2 小时前
首次使用Android Studio时,http proxy,gradle问题解决
android
limingade2 小时前
手机打电话时电脑坐席同时收听对方说话并插入IVR预录声音片段
android·智能手机·电脑·蓝牙电话·电脑打电话
浩浩测试一下2 小时前
计算机网络中的DHCP是什么呀? 详情解答
android·网络·计算机网络·安全·web安全·网络安全·安全架构
青春给了狗4 小时前
Android 14 系统统一修改app启动时图标大小和圆角
android
pengyu4 小时前
【Flutter 状态管理 - 柒】 | InheritedWidget:藏在组件树里的"魔法"✨
android·flutter·dart
居然是阿宋6 小时前
Kotlin高阶函数 vs Lambda表达式:关键区别与协作关系
android·开发语言·kotlin
凉、介6 小时前
PCI 总线学习笔记(五)
android·linux·笔记·学习·pcie·pci