Jetpack Compose - Material Design 断点范围、WindowSizeClass、针对不同屏幕尺寸创建预览、四类导航栏

一、Material Design 断点范围

  • 官方文档:https://m3.material.io/foundations/layout/breakpoints/overview
断点 屏幕宽度(dp) 典型设备
Compact(紧凑) 0 - 599 竖屏手机
Medium(中等) 600 - 839 横屏手机、竖屏折叠屏、小平板
Expanded(扩展) 840 - 1199 横屏平板、桌面大窗口
Large(大) 1200 - 1599 桌面、ChromeBook
Extra-large(超大) 1600 及以上 大桌面显示器

二、WindowSizeClass

1、基本介绍
  1. Compose 引入的 WindowSizeClass API 简化了 Material Design 断点的实现

  2. WindowSizeClass 将断点简化为 Compact、Medium、Expanded 三类

  3. WindowSizeClass API 仍处于需要 @OptIn(ExperimentalMaterial3WindowSizeClassApi::class) 注解的 Alpha 版

2、基本使用
  1. 在模块级 build.gradle 文件中添加依赖项
kotlin 复制代码
implementation("androidx.compose.material3:material3-window-size-class")
  1. 在代码中使用
kotlin 复制代码
val windowSize = calculateWindowSizeClass(this)

Log.i(TAG, "windowSize: ${windowSize}")

三、针对不同屏幕尺寸创建预览

  • 在 Preview 注解形参中设置 widthDp 值,结合 WindowSizeClass
kotlin 复制代码
@Preview(showBackground = true)
@Composable
fun ReplyAppCompactPreview() {
    ReplyTheme {
        Surface {
            ReplyApp(WindowWidthSizeClass.Compact)
        }
    }
}
kotlin 复制代码
@Preview(showBackground = true, widthDp = 700)
@Composable
fun ReplyAppMediumPreview() {
    ReplyTheme {
        Surface {
            ReplyApp(windowSize = WindowWidthSizeClass.Medium)
        }
    }
}
kotlin 复制代码
@Preview(showBackground = true, widthDp = 1000)
@Composable
fun ReplyAppExpandedPreview() {
    ReplyTheme {
        Surface {
            ReplyApp(windowSize = WindowWidthSizeClass.Expanded)
        }
    }
}

四、四类导航栏

1、底部导航栏
(1)源码
kotlin 复制代码
@Composable
fun NavigationBar(
    modifier: Modifier = Modifier,
    containerColor: Color = NavigationBarDefaults.containerColor,
    contentColor: Color = MaterialTheme.colorScheme.contentColorFor(containerColor),
    tonalElevation: Dp = NavigationBarDefaults.Elevation,
    windowInsets: WindowInsets = NavigationBarDefaults.windowInsets,
    content: @Composable RowScope.() -> Unit
)
kotlin 复制代码
@Composable
fun RowScope.NavigationBarItem(
    selected: Boolean,
    onClick: () -> Unit,
    icon: @Composable () -> Unit,
    modifier: Modifier = Modifier,
    enabled: Boolean = true,
    label: @Composable (() -> Unit)? = null,
    alwaysShowLabel: Boolean = true,
    colors: NavigationBarItemColors = NavigationBarItemDefaults.colors(),
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
)
(2)演示
kotlin 复制代码
MaterialTheme {
    val paddingValues = WindowInsets.safeDrawing.asPaddingValues()
    val layoutDirection = LocalLayoutDirection.current
    Surface(
        modifier = Modifier
            .padding(
                start = paddingValues.calculateStartPadding(layoutDirection),
                end = paddingValues.calculateEndPadding(layoutDirection)
            )
    ) {
        data class NavItem(
            val title: String,
            val icon: ImageVector,
        )

        val navItems = listOf(
            NavItem("Home", Icons.Filled.Home),
            NavItem("Call", Icons.Filled.Call),
            NavItem("Email", Icons.Filled.Email),
            NavItem("Person", Icons.Filled.Person)
        )

        var selectedItemIndex by remember { mutableStateOf(0) }

        Column(
            modifier = Modifier.fillMaxSize()
        ) {
            Column(
                modifier = Modifier
                    .fillMaxWidth()
                    .weight(1f),
                horizontalAlignment = Alignment.CenterHorizontally,
                verticalArrangement = Arrangement.Center
            ) {
                when (selectedItemIndex) {
                    0 -> Text("Home")
                    1 -> Text("Call")
                    2 -> Text("Email")
                    3 -> Text("Person")
                }
            }

            NavigationBar {
                navItems.forEachIndexed { index, item ->
                    NavigationBarItem(
                        icon = { Icon(item.icon, contentDescription = item.title) },
                        label = { Text(item.title) },
                        selected = selectedItemIndex == index,
                        onClick = { selectedItemIndex = index },
                    )
                }
            }
        }
    }
}
2、侧边导航栏
(1)源码
kotlin 复制代码
@Composable
fun NavigationRail(
    modifier: Modifier = Modifier,
    containerColor: Color = NavigationRailDefaults.ContainerColor,
    contentColor: Color = contentColorFor(containerColor),
    header: @Composable (ColumnScope.() -> Unit)? = null,
    windowInsets: WindowInsets = NavigationRailDefaults.windowInsets,
    content: @Composable ColumnScope.() -> Unit
)
kotlin 复制代码
@Composable
fun NavigationRailItem(
    selected: Boolean,
    onClick: () -> Unit,
    icon: @Composable () -> Unit,
    modifier: Modifier = Modifier,
    enabled: Boolean = true,
    label: @Composable (() -> Unit)? = null,
    alwaysShowLabel: Boolean = true,
    colors: NavigationRailItemColors = NavigationRailItemDefaults.colors(),
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
)
(2)演示
kotlin 复制代码
MaterialTheme {
    val paddingValues = WindowInsets.safeDrawing.asPaddingValues()
    val layoutDirection = LocalLayoutDirection.current
    Surface(
        modifier = Modifier
            .padding(
                start = paddingValues.calculateStartPadding(layoutDirection),
                end = paddingValues.calculateEndPadding(layoutDirection)
            )
    ) {
        data class NavItem(
            val title: String,
            val icon: ImageVector,
        )

        val navItems = listOf(
            NavItem("Home", Icons.Filled.Home),
            NavItem("Call", Icons.Filled.Call),
            NavItem("Email", Icons.Filled.Email),
            NavItem("Person", Icons.Filled.Person)
        )

        var selectedItemIndex by remember { mutableStateOf(0) }

        Row(
            modifier = Modifier.fillMaxSize()
        ) {
            NavigationRail {
                navItems.forEachIndexed { index, item ->
                    NavigationRailItem(
                        icon = { Icon(item.icon, contentDescription = item.title) },
                        label = { Text(item.title) },
                        selected = selectedItemIndex == index,
                        onClick = { selectedItemIndex = index },
                    )
                }
            }

            Column(
                modifier = Modifier
                    .weight(1f)
                    .fillMaxHeight(),
                horizontalAlignment = Alignment.CenterHorizontally,
                verticalArrangement = Arrangement.Center
            ) {
                when (selectedItemIndex) {
                    0 -> Text("Home")
                    1 -> Text("Call")
                    2 -> Text("Email")
                    3 -> Text("Person")
                }
            }
        }
    }
}
3、永久抽屉导航栏
(1)源码
kotlin 复制代码
@Composable
fun PermanentNavigationDrawer(
    drawerContent: @Composable () -> Unit,
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
)
kotlin 复制代码
@Composable
fun PermanentDrawerSheet(
    modifier: Modifier = Modifier,
    drawerShape: Shape = RectangleShape,
    drawerContainerColor: Color = DrawerDefaults.containerColor,
    drawerContentColor: Color = contentColorFor(drawerContainerColor),
    drawerTonalElevation: Dp = DrawerDefaults.PermanentDrawerElevation,
    windowInsets: WindowInsets = DrawerDefaults.windowInsets,
    content: @Composable ColumnScope.() -> Unit
)
kotlin 复制代码
@Composable
fun NavigationDrawerItem(
    label: @Composable () -> Unit,
    selected: Boolean,
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    icon: (@Composable () -> Unit)? = null,
    badge: (@Composable () -> Unit)? = null,
    shape: Shape = NavigationDrawerTokens.ActiveIndicatorShape.value,
    colors: NavigationDrawerItemColors = NavigationDrawerItemDefaults.colors(),
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
)
(2)演示
kotlin 复制代码
MaterialTheme {
    val paddingValues = WindowInsets.safeDrawing.asPaddingValues()
    val layoutDirection = LocalLayoutDirection.current
    Surface(
        modifier = Modifier
            .padding(
                start = paddingValues.calculateStartPadding(layoutDirection),
                end = paddingValues.calculateEndPadding(layoutDirection)
            )
    ) {
        data class NavItem(
            val title: String,
            val icon: ImageVector,
        )

        val navItems = listOf(
            NavItem("Home", Icons.Filled.Home),
            NavItem("Call", Icons.Filled.Call),
            NavItem("Email", Icons.Filled.Email),
            NavItem("Person", Icons.Filled.Person)
        )

        var selectedItemIndex by remember { mutableStateOf(0) }

        PermanentNavigationDrawer(
            drawerContent = {
                PermanentDrawerSheet(Modifier.width(200.dp)) {
                    Column(
                        modifier = Modifier.fillMaxSize(),
                    ) {
                        navItems.forEachIndexed { index, item ->
                            NavigationDrawerItem(
                                icon = { Icon(item.icon, contentDescription = item.title) },
                                label = { Text(item.title) },
                                selected = selectedItemIndex == index,
                                onClick = { selectedItemIndex = index },
                            )
                        }
                    }
                }
            }
        ) {
            Column(
                modifier = Modifier.fillMaxSize(),
                horizontalAlignment = Alignment.CenterHorizontally,
                verticalArrangement = Arrangement.Center
            ) {
                when (selectedItemIndex) {
                    0 -> Text("Home")
                    1 -> Text("Call")
                    2 -> Text("Email")
                    3 -> Text("Person")
                }
            }
        }
    }
}
4、滑动抽屉导航栏
(1)源码
kotlin 复制代码
@Composable
fun ModalNavigationDrawer(
    drawerContent: @Composable () -> Unit,
    modifier: Modifier = Modifier,
    drawerState: DrawerState = rememberDrawerState(DrawerValue.Closed),
    gesturesEnabled: Boolean = true,
    scrimColor: Color = DrawerDefaults.scrimColor,
    content: @Composable () -> Unit
)
kotlin 复制代码
@Composable
fun ModalDrawerSheet(
    modifier: Modifier = Modifier,
    drawerShape: Shape = DrawerDefaults.shape,
    drawerContainerColor: Color = DrawerDefaults.containerColor,
    drawerContentColor: Color = contentColorFor(drawerContainerColor),
    drawerTonalElevation: Dp = DrawerDefaults.ModalDrawerElevation,
    windowInsets: WindowInsets = DrawerDefaults.windowInsets,
    content: @Composable ColumnScope.() -> Unit
)
kotlin 复制代码
@Composable
fun NavigationDrawerItem(
    label: @Composable () -> Unit,
    selected: Boolean,
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    icon: (@Composable () -> Unit)? = null,
    badge: (@Composable () -> Unit)? = null,
    shape: Shape = NavigationDrawerTokens.ActiveIndicatorShape.value,
    colors: NavigationDrawerItemColors = NavigationDrawerItemDefaults.colors(),
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
)
(2)演示
kotlin 复制代码
MaterialTheme {
    val paddingValues = WindowInsets.safeDrawing.asPaddingValues()
    val layoutDirection = LocalLayoutDirection.current
    Surface(
        modifier = Modifier
            .padding(
                start = paddingValues.calculateStartPadding(layoutDirection),
                end = paddingValues.calculateEndPadding(layoutDirection)
            )
    ) {
        data class NavItem(
            val title: String,
            val icon: ImageVector,
        )

        val navItems = listOf(
            NavItem("Home", Icons.Filled.Home),
            NavItem("Call", Icons.Filled.Call),
            NavItem("Email", Icons.Filled.Email),
            NavItem("Person", Icons.Filled.Person)
        )

        var selectedItemIndex by remember { mutableStateOf(0) }

        ModalNavigationDrawer(
            drawerContent = {
                ModalDrawerSheet(Modifier.width(200.dp)) {
                    Column(
                        modifier = Modifier.fillMaxSize(),
                    ) {
                        navItems.forEachIndexed { index, item ->
                            NavigationDrawerItem(
                                icon = { Icon(item.icon, contentDescription = item.title) },
                                label = { Text(item.title) },
                                selected = selectedItemIndex == index,
                                onClick = {
                                    selectedItemIndex = index
                                },
                            )
                        }
                    }
                }
            }
        ) {
            Column(
                modifier = Modifier.fillMaxSize(),
                horizontalAlignment = Alignment.CenterHorizontally,
                verticalArrangement = Arrangement.Center
            ) {
                when (selectedItemIndex) {
                    0 -> Text("Home")
                    1 -> Text("Call")
                    2 -> Text("Email")
                    3 -> Text("Person")
                }
            }
        }
    }
}
kotlin 复制代码
MaterialTheme {
    val paddingValues = WindowInsets.safeDrawing.asPaddingValues()
    val layoutDirection = LocalLayoutDirection.current
    Surface(
        modifier = Modifier
            .padding(
                start = paddingValues.calculateStartPadding(layoutDirection),
                end = paddingValues.calculateEndPadding(layoutDirection)
            )
    ) {
        data class NavItem(
            val title: String,
            val icon: ImageVector,
        )

        val navItems = listOf(
            NavItem("Home", Icons.Filled.Home),
            NavItem("Call", Icons.Filled.Call),
            NavItem("Email", Icons.Filled.Email),
            NavItem("Person", Icons.Filled.Person)
        )

        var selectedItemIndex by remember { mutableStateOf(0) }

        val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
        val scope = rememberCoroutineScope()

        ModalNavigationDrawer(
            drawerState = drawerState,
            drawerContent = {
                ModalDrawerSheet(Modifier.width(200.dp)) {
                    Column(
                        modifier = Modifier.fillMaxSize(),
                    ) {
                        navItems.forEachIndexed { index, item ->
                            NavigationDrawerItem(
                                icon = { Icon(item.icon, contentDescription = item.title) },
                                label = { Text(item.title) },
                                selected = selectedItemIndex == index,
                                onClick = {
                                    selectedItemIndex = index
                                    scope.launch {
                                        drawerState.close()
                                    }
                                },
                            )
                        }
                    }
                }
            }
        ) {
            Column(
                modifier = Modifier.fillMaxSize(),
                horizontalAlignment = Alignment.CenterHorizontally,
                verticalArrangement = Arrangement.Center
            ) {
                when (selectedItemIndex) {
                    0 -> Text("Home")
                    1 -> Text("Call")
                    2 -> Text("Email")
                    3 -> Text("Person")
                }
            }
        }
    }
}
相关推荐
零基础1233 分钟前
PDF 处理工具全攻略:pdf24、PDFgear、Adobe Acrobat DC 与 Stirling-PDF 深度对比
java·开发语言·pdf
Java后端的Ai之路5 分钟前
01-React基础教程
开发语言·前端·python·react.js·前端框架
恋猫de小郭11 分钟前
Dart 4.0 要彻底移除 dart:mirrors,Augmentations 应该要来了
android·前端·flutter
我的xiaodoujiao16 分钟前
Django 基础知识详细图文教程 14-Django 表单定义与使用
开发语言·后端·python·django
SL_staff17 分钟前
制造业数字化协同:为什么甘特图只是起点,不是终点
java·开源·全栈
(Charon)19 分钟前
【C++面试】单例模式:懒汉式与饿汉式的实现、区别与线程安全
开发语言·c++·面试
anew___34 分钟前
《从零手写操作系统 (29):管道与重定向进阶——命名管道、Here Document与Shell语法扩展》
java·开发语言·前端·javascript·网络
Mikko740 分钟前
JVM 线上排查实战(六):JFR 怎么用?JDK 8 要不要加 UnlockCommercialFeatures、jfr 命令在哪、录的文件为什么读不了
java·运维·jvm·后端
jimy11 小时前
虚函数和 RTTI实现运行时多态
开发语言·c++
奇牙coding1 小时前
GPT-5.5 API 报 401 但 GPT-5.4 正常怎么办?不是 Key 失效,是 Organization 头的强制校验变了
java·网络·gpt·ai