Jetpack Compose:如何将Composable作为参数传递并运行
封装可复用的抽屉导航组件(Jetpack Compose)
核心思路
把抽屉的UI逻辑、状态控制封装成高阶Composable,通过参数接收需要包裹的页面内容,彻底消除导航代码里的重复逻辑。
第一步:封装抽屉基础组件
先写通用的抽屉容器,把抽屉状态、切换逻辑、抽屉内容、页面内容都作为参数传入:
@Composable fun DrawerWrapper( drawerState: DrawerState, onDrawerToggle: () -> Unit, drawerContent: @Composable () -> Unit, pageTitle: String, content: @Composable () -> Unit // 接收需要包裹的页面Composable ) { ModalNavigationDrawer( drawerState = drawerState, drawerContent = drawerContent, content = { // 内置统一的顶部栏(带抽屉切换按钮),避免每个页面重复写 Scaffold( topBar = { TopAppBar( title = { Text(pageTitle) }, navigationIcon = { IconButton(onClick = onDrawerToggle) { Icon(Icons.Default.Menu, contentDescription = "打开抽屉") } } ) } ) { paddingValues -> // 给页面内容添加内边距,避免被顶部栏遮挡 Box(modifier = Modifier.padding(paddingValues)) { content() } } } ) }
第二步:状态管理方案
根据你的需求选择局部或全局状态:
1. 局部状态(单页面独立控制抽屉)
如果每个页面的抽屉状态不需要共享,就在页面内部创建状态:
@Composable fun HomeScreen() { val drawerState = rememberDrawerState(DrawerValue.Closed) val scope = rememberCoroutineScope() DrawerWrapper( drawerState = drawerState, onDrawerToggle = { scope.launch { drawerState.apply { if (isClosed) open() else close() } } }, pageTitle = "首页", drawerContent = { // 自定义抽屉菜单 LazyColumn { item { Text("我的收藏", modifier = Modifier.padding(16.dp)) } item { Text("设置", modifier = Modifier.padding(16.dp)) } } } ) { // 这里放原HomeScreen的所有内容 Column(modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally) { Text("首页内容区域") } } }
2. 全局状态(多页面共享抽屉状态)
如果多个页面需要保持抽屉状态一致(比如打开抽屉切换页面后保持打开),用ViewModel统一管理:
class DrawerViewModel : ViewModel() { val drawerState = DrawerState(DrawerValue.Closed) fun toggleDrawer() { viewModelScope.launch { drawerState.apply { if (isClosed) open() else close() } } } } // 在导航宿主中使用全局状态 @Composable fun AppNavigation() { val navController = rememberNavController() val drawerViewModel = viewModel<DrawerViewModel>() DrawerWrapper( drawerState = drawerViewModel.drawerState, onDrawerToggle = { drawerViewModel.toggleDrawer() }, pageTitle = when(navController.currentDestination?.route) { "home" -> "首页" "profile" -> "个人中心" else -> "默认标题" }, drawerContent = { // 全局统一的抽屉菜单,点击可跳转页面 LazyColumn { item { TextButton(onClick = { navController.navigate("home") drawerViewModel.toggleDrawer() }) { Text("首页") } } item { TextButton(onClick = { navController.navigate("profile") drawerViewModel.toggleDrawer() }) { Text("个人中心") } } } } ) { // 导航宿主,内部页面无需再处理抽屉逻辑 NavHost(navController = navController, startDestination = "home") { composable("home") { HomeScreenContent() } // 只写页面核心内容 composable("profile") { ProfileScreenContent() } } } }
第三步:适配部分页面需要抽屉的场景
如果不是所有页面都需要抽屉,直接在需要的页面路由里单独包裹DrawerWrapper即可:
NavHost(navController = navController, startDestination = "login") { // 不需要抽屉的页面,直接写内容 composable("login") { LoginScreen() } // 需要抽屉的页面,用DrawerWrapper包裹 composable("home") { val drawerState = rememberDrawerState(DrawerValue.Closed) val scope = rememberCoroutineScope() DrawerWrapper( drawerState = drawerState, onDrawerToggle = { scope.launch { drawerState.toggle() } }, pageTitle = "首页", drawerContent = { /* 抽屉菜单 */ } ) { HomeScreenContent() } } }
内容的提问来源于stack exchange,提问作者Dolev Dublon
相关产品推荐
相关产品推荐

