一、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、基本介绍
-
Compose 引入的 WindowSizeClass API 简化了 Material Design 断点的实现
-
WindowSizeClass 将断点简化为 Compact、Medium、Expanded 三类
-
WindowSizeClass API 仍处于需要
@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)注解的 Alpha 版
2、基本使用
- 在模块级
build.gradle文件中添加依赖项
kotlin
implementation("androidx.compose.material3:material3-window-size-class")
- 在代码中使用
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")
}
}
}
}
}