Jetpack Compose实现列表点击后右侧滑入页面及左滑关闭方案咨询
Jetpack Compose实现侧滑页面切换(右滑进入+左滑关闭)
方案选择
两种实现方式都可行,取决于你的场景:
- Navigation Component + 自定义转场动画:适合独立页面级别的跳转,符合Jetpack导航架构规范,便于管理页面栈。
- 动画Composable:适合页面内的局部面板/弹窗场景,实现更轻量,无需依赖导航组件。
方案一:Navigation Component实现
核心逻辑
- 为导航配置自定义的右滑进入、左滑退出动画;
- 在详情页添加左滑手势检测,触发页面回退;
- 通过导航参数传递列表项数据。
代码示例
@Composable fun MainScreen(navController: NavHostController) { // 模拟列表数据 val items = List(10) { "Item $it" } LazyColumn { items(items) { item -> ListItem( headlineContent = { Text(item) }, modifier = Modifier .fillMaxWidth() .clickable { // 导航到详情页并传递数据 navController.navigate("detail/$item") } ) } } } @Composable fun DetailScreen(navController: NavHostController, item: String) { var dragOffset by remember { mutableStateOf(0f) } val screenWidth = LocalConfiguration.current.screenWidthDp.dp.toPx() val dismissThreshold = screenWidth * 0.3f // 左滑关闭手势 val dragModifier = Modifier.detectDragGestures( onDrag = { _, dragAmount -> // 只处理水平拖动,限制拖动范围在0到屏幕宽度之间 dragOffset = (dragOffset + dragAmount.x).coerceIn(0f, screenWidth) }, onDragEnd = { if (dragOffset >= dismissThreshold) { // 超过阈值,关闭页面 navController.popBackStack() } else { // 重置位置 dragOffset = 0f } } ) Box( modifier = Modifier .fillMaxSize() .offset { IntOffset(dragOffset.toInt(), 0) } .then(dragModifier), contentAlignment = Alignment.Center ) { Text(text = "Detail for: $item", fontSize = 20.sp) } } @Composable fun AppNavHost(navController: NavHostController) { val slideInTransition = remember { slideInHorizontally(initialOffsetX = { it }) + fadeIn() } val slideOutTransition = remember { slideOutHorizontally(targetOffsetX = { it }) + fadeOut() } NavHost( navController = navController, startDestination = "main" ) { composable("main") { MainScreen(navController) } composable( route = "detail/{item}", // 配置自定义转场动画 enterTransition = { slideInTransition }, exitTransition = { slideOutTransition } ) { backStackEntry -> val item = backStackEntry.arguments?.getString("item") ?: "" DetailScreen(navController, item) } } } // 在Activity中使用 class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { val navController = rememberNavController() AppNavHost(navController) } } }
方案二:动画Composable实现(无导航)
核心逻辑
- 用状态变量控制详情页的显示/隐藏;
- 通过
animateOffsetAsState实现右滑进入/退出动画; - 添加左滑手势检测,触发关闭状态;
- 点击列表项时传递数据并切换显示状态。
代码示例
@Composable fun SlidePanelDemo() { val items = List(10) { "Item $it" } var selectedItem by remember { mutableStateOf<String?>(null) } var dragOffset by remember { mutableStateOf(0f) } val screenWidth = LocalConfiguration.current.screenWidthDp.dp.toPx() val dismissThreshold = screenWidth * 0.3f // 动画偏移量 val animatedOffset by animateOffsetAsState( targetValue = if (selectedItem != null) Offset.Zero else Offset(screenWidth, 0f), animationSpec = tween(durationMillis = 300) ) Box(modifier = Modifier.fillMaxSize()) { // 列表页面 LazyColumn(modifier = Modifier.fillMaxSize()) { items(items) { item -> ListItem( headlineContent = { Text(item) }, modifier = Modifier .fillMaxWidth() .clickable { selectedItem = item dragOffset = 0f } ) } } // 详情面板(覆盖在列表上) selectedItem?.let { item -> Box( modifier = Modifier .fillMaxSize() .offset { IntOffset((animatedOffset.x + dragOffset).toInt(), animatedOffset.y.toInt()) } .background(Color.White) .detectDragGestures( onDrag = { _, dragAmount -> dragOffset = (dragOffset + dragAmount.x).coerceIn(0f, screenWidth) }, onDragEnd = { if (dragOffset >= dismissThreshold) { selectedItem = null } else { dragOffset = 0f } } ), contentAlignment = Alignment.Center ) { Column { Text(text = "Detail: $item", fontSize = 20.sp) Spacer(modifier = Modifier.height(16.dp)) Text("左滑可以关闭面板") } } } } } // 调用示例 @Composable fun App() { SlidePanelDemo() }
内容的提问来源于stack exchange,提问作者Pawandeep Singh
相关产品推荐
相关产品推荐

