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")
                }
            }
        }
    }
}
相关推荐
泡海椒34 分钟前
告别 iText 繁杂配置:jquick-pdf 极简 PDF 生成实战(零基础上手)
java·开发语言·pdf
Bs_MoneyMagnet4 小时前
基于springboot+vue的滑雪场票务与装备租赁系统的设计与实现 源码+文档
java·vue.js·spring boot·后端·spring·毕业设计·计算机毕业设计
Mr YiRan7 小时前
网络请求API监控与网络切换埋点
android·网络
野生技术架构师9 小时前
2026 Java 面试全套总结,八股 + 场景 + AI 相关面试考点
java·人工智能·面试
香吧香10 小时前
java服务异常日志只打印异常类型,没有堆栈定位分析
java·jvm·异常
qq_25183645710 小时前
AI 疾病自查功能SpringbootAI项目实战 · 技术实现文档
java·ai·ai编程
逆境不可逃11 小时前
Pi Agent 学习笔记:对话太长以后,如何压缩上下文
java
MetaLite11 小时前
JDK 8~26 核心特性一览:从 Stream 到 Scoped Values
java
美狐美颜sdk11 小时前
直播APP开发技术栈详解:视频美颜SDK、人脸识别与实时渲染
android·人工智能·音视频·美颜sdk·直播美颜sdk
wno70411 小时前
Spring Boot WebFlux增删改查
java·spring boot·后端