Compose笔记(六十六)--ModalNavigationDrawer

这一节主要了解一下Compose中的ModalNavigationDrawer,在Jetpack Compose开发中,ModalNavigationDrawer是一个用于实现模态导航抽屉的核心组件,它允许用户通过侧滑手势或点击菜单图标触发一个覆盖在主内容之上的抽屉菜单,提供页面切换、功能导航等功能。简单总结如下:

API:

drawerContent:定义抽屉菜单的内容。

drawerState:控制抽屉的打开/关闭状态。

gesturesEnabled:是否启用侧滑手势。

scrimColor:抽屉背景的遮罩层颜色。

content:抽屉外部的主内容。

一般场景:

1 页面导航 通过抽屉菜单切换不同页面

2 功能入口 集中管理应用的核心功能

3 账号管理 提供快速访问账号设置、个人资料的入口

栗子:

Kotlin 复制代码
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Home
import androidx.compose.material.icons.outlined.Menu
import androidx.compose.material.icons.outlined.Message
import androidx.compose.material.icons.outlined.Person
import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material3.DrawerValue
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalDrawerSheet
import androidx.compose.material3.ModalNavigationDrawer
import androidx.compose.material3.NavigationDrawerItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.rememberDrawerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ModalNavigationDrawerDemo() {
    val coroutineScope = rememberCoroutineScope()
    val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
    data class NavItem(
        val title: String,
        val icon: @Composable () -> Unit
    )
    val navItems = remember {
        listOf(
            NavItem("首页", { Icon(Icons.Outlined.Home, contentDescription = null) }),
            NavItem("消息", { Icon(Icons.Outlined.Message, contentDescription = null) }),
            NavItem("我的", { Icon(Icons.Outlined.Person, contentDescription = null) }),
            NavItem("设置", { Icon(Icons.Outlined.Settings, contentDescription = null) })
        )
    }
    val selectedItemIndex = remember { mutableStateOf(0) }

    ModalNavigationDrawer(
        drawerState = drawerState,
        drawerContent = {
            ModalDrawerSheet {
                LazyColumn(
                    modifier = Modifier.padding(16.dp),
                    verticalArrangement = Arrangement.spacedBy(8.dp)
                ) {
                    items(navItems.size) { index ->
                        val item = navItems[index]
                        NavigationDrawerItem(
                            label = { Text(text = item.title) },
                            selected = selectedItemIndex.value == index,
                            onClick = {
                                selectedItemIndex.value = index
                                coroutineScope.launch {
                                    drawerState.close()
                                }
                            },
                            icon = item.icon,
                            modifier = Modifier.padding(horizontal = 8.dp)
                        )
                    }
                }
            }

        },
        content = {
            Scaffold(
                topBar = {
                    TopAppBar(
                        title = { Text(text = navItems[selectedItemIndex.value].title) },
                        navigationIcon = {
                            IconButton(
                                onClick = {
                                    coroutineScope.launch {
                                        drawerState.open()
                                    }
                                }
                            ) {
                                Icon(Icons.Outlined.Menu, contentDescription = "打开抽屉")
                            }
                        }
                    )
                }
            ) { innerPadding ->
                Column(
                    modifier = Modifier
                        .fillMaxSize()
                        .padding(innerPadding)
                        .padding(32.dp),
                    horizontalAlignment = Alignment.CenterHorizontally,
                    verticalArrangement = Arrangement.Center
                ) {
                    Text(
                        text = "当前页面:${navItems[selectedItemIndex.value].title}",
                        style = MaterialTheme.typography.headlineSmall
                    )
                    Text(
                        text = "可通过左侧手势滑动打开抽屉",
                        style = MaterialTheme.typography.bodyMedium,
                        modifier = Modifier.padding(top = 16.dp)
                    )
                }
            }
        }
    )
}
Kotlin 复制代码
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Home
import androidx.compose.material.icons.outlined.Menu
import androidx.compose.material.icons.outlined.Message
import androidx.compose.material.icons.outlined.Person
import androidx.compose.material3.Badge
import androidx.compose.material3.DrawerValue
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalDrawerSheet
import androidx.compose.material3.ModalNavigationDrawer
import androidx.compose.material3.NavigationDrawerItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.rememberDrawerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.launch


@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ModalNavigationDrawerDemo() {
    val coroutineScope = rememberCoroutineScope()
    val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
    data class NavItem(
        val title: String,
        val icon: @Composable () -> Unit,
        val badgeCount: Int? = null
    )
    val navItems = remember {
        listOf(
            NavItem("首页", { Icon(Icons.Outlined.Home, contentDescription = null) }),
            NavItem("消息", { Icon(Icons.Outlined.Message, contentDescription = null) }, 99), // 带99条未读消息
            NavItem("我的", { Icon(Icons.Outlined.Person, contentDescription = null) })
        )
    }
    val selectedItemIndex = remember { mutableStateOf(0) }

    ModalNavigationDrawer(
        drawerState = drawerState,
        drawerContent = {
            ModalDrawerSheet(
                modifier = Modifier.width(280.dp)
            ) {
                LazyColumn(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(16.dp),
                    verticalArrangement = Arrangement.spacedBy(12.dp)
                ) {
                    item {
                        Text(
                            text = "我的导航",
                            style = MaterialTheme.typography.titleLarge,
                            modifier = Modifier.padding(horizontal = 8.dp, vertical = 16.dp)
                        )
                    }
                    items(navItems.size) { index ->
                        val item = navItems[index]
                        NavigationDrawerItem(
                            label = { Text(text = item.title) },
                            selected = selectedItemIndex.value == index,
                            onClick = {
                                selectedItemIndex.value = index
                                coroutineScope.launch {
                                    drawerState.close()
                                }
                            },
                            icon = item.icon,
                            badge = item.badgeCount?.let { count ->
                                {
                                    Badge {
                                        Text(text = count.toString(), fontSize = 12.sp)
                                    }
                                }
                            },
                            modifier = Modifier.padding(horizontal = 8.dp)
                        )
                    }
                }
            }
        },
        scrimColor = Color.Gray.copy(alpha = 0.5f),
        gesturesEnabled = false,
        content = {
            Scaffold(
                topBar = {
                    TopAppBar(
                        title = { Text(text = navItems[selectedItemIndex.value].title) },
                        navigationIcon = {
                            IconButton(
                                onClick = {
                                    coroutineScope.launch {
                                        if (drawerState.isOpen) {
                                            drawerState.close()
                                        } else {
                                            drawerState.open()
                                        }
                                    }
                                }
                            ) {
                                Icon(Icons.Outlined.Menu, contentDescription = "切换抽屉")
                            }
                        }
                    )
                }
            ) { innerPadding ->
                Column(
                    modifier = Modifier
                        .fillMaxSize()
                        .padding(innerPadding)
                        .padding(32.dp),
                    horizontalAlignment = Alignment.CenterHorizontally,
                    verticalArrangement = Arrangement.Center
                ) {
                    Text(
                        text = "自定义抽屉样式Demo",
                        style = MaterialTheme.typography.headlineSmall
                    )
                    Text(
                        text = "1. 自定义遮罩颜色\n2. 消息项带99条未读徽章\n3. 禁用手势滑动 \n4. 自定义抽屉宽度",
                        style = MaterialTheme.typography.bodyMedium,
                        modifier = Modifier.padding(top = 16.dp),
                    )
                }
            }
        }
    )
}

注意:

1 避免在drawerContent中使用复杂布局或高频更新的组件,可能导致卡顿。

2 drawerState.open()和drawerState.close()是挂起函数,必须在协程作用域。

3 ModalNavigationDrawer需要Compose Material 3支持。

相关推荐
小程故事多_803 小时前
从 Tokenizer 到 Next Token,穿透数据流,完整理解 Transformer 底层逻辑
android·深度学习·transformer
优选资源分享4 小时前
PicGo v3.0.3 新版 | 开源免费图床工具
javascript·笔记·electron
星恒随风5 小时前
Linux线程概念详解:从分页存储、页表、MMU与TLB,深入理解线程和进程的区别与联系
linux·运维·笔记·学习·状态模式
qq_284274055 小时前
数控机床编程基础与坐标系:编程六步流程、程序号/程序段/程序字格式,与右手定则下车床铣床XYZ轴判定(含考点)
笔记·学习·自动化·制造
问心无愧05136 小时前
[SUCTF 2019]EasySQL
笔记
ss2736 小时前
PG实战笔记 | 09 PostgreSQL psql客户端基本操作
数据库·笔记·postgresql
xxwl5856 小时前
区域赛分类题解
c++·笔记·学习·算法
三少爷的鞋6 小时前
Android 协程实践:为什么不应将 `supervisorScope` 作为常规方案
android
承渊政道7 小时前
【从零开始大模型开发与微调:基于PyTorch与ChatGLM】(从ChatGLM本地化到LoRA、PEFT的高级微调)
android·人工智能·pytorch·chatglm·本地化部署·大模型开发与微调
成都蜀味缘餐饮技术培训学校9 小时前
油炸串串技术培训实战指南,核心食材选购与预处理标准
数据仓库·经验分享·笔记·课程设计·微信公众平台