Jetpack Compose自定义抽屉上滑退出动画卡顿求优化
Jetpack Compose Top Sheet抽屉上滑关闭卡顿修复方案
问题描述
在Jetpack Compose中实现了类似Top Sheet的自定义抽屉,其余功能正常,但上滑关闭抽屉时,退出动画不流畅,会卡顿一秒后Card才消失。
问题根源
当前代码在拖拽结束时直接将offsetY重置为0并立刻设置isDrawerVisible = false,导致手动状态重置与AnimatedVisibility的退出动画冲突,打断了平滑过渡流程,进而出现卡顿。同时使用mutableFloatStateOf直接管理偏移量,没有利用Compose动画API衔接拖拽后的状态过渡。
修复方案
- 使用
animateFloatAsState管理偏移量,让拖拽结束后的状态变化自动触发平滑动画 - 拖拽结束时根据拖拽方向和距离阈值判断是否关闭,让动画自然过渡而非硬重置状态
- 调整
AnimatedVisibility的进出动画,确保与拖拽位置衔接一致 - 添加拖拽阈值判断,提升交互体验
修改后的完整代码
// 替换原有状态定义 val offsetY by animateFloatAsState( targetValue = targetOffsetY, animationSpec = tween(durationMillis = 300) ) var targetOffsetY by remember { mutableFloatStateOf(0f) } var isDrawerVisible by remember { mutableStateOf(false) } var direction by remember { mutableIntStateOf(-1) } val drawerHeight = remember { mutableIntStateOf(0) } // 记录抽屉高度用于计算拖拽阈值 // ** Drawer ** AnimatedVisibility( visible = isDrawerVisible, enter = slideInVertically(initialOffsetY = { -it }), exit = slideOutVertically(targetOffsetY = { -it }) ) { Box( modifier = Modifier .offset { IntOffset(0, offsetY.toInt()) } .onSizeChanged { drawerHeight.intValue = it.height } // 初始化抽屉高度 .pointerInput(Unit) { coroutineScope { detectDragGestures( onDragEnd = { Timber.d("DRAG_DEBUG:: Drag End") val closeThreshold = drawerHeight.intValue / 3 // 设置关闭阈值为抽屉高度的1/3 // 上滑拖拽且偏移量超过阈值,触发关闭流程 if (direction == 2 && offsetY <= -closeThreshold) { targetOffsetY = -drawerHeight.intValue.toFloat() // 等待动画结束后再隐藏抽屉,避免动画冲突 launch { delay(300) // 与动画时长保持一致 isDrawerVisible = false targetOffsetY = 0f // 重置偏移量,确保下次打开正常显示 } } else { // 拖拽距离不足,回弹到初始位置 targetOffsetY = 0f } } ) { change, dragAmount -> change.consume() val (_, y) = dragAmount direction = if (y > 0) 1 else 2 // 1=下滑,2=上滑 // 限制拖拽范围:只能向上拖拽(offsetY ≤0),且不能超过抽屉自身高度 val newOffset = offsetY + y if (newOffset >= -drawerHeight.intValue && newOffset <= 0) { targetOffsetY = newOffset } } } } .fillMaxWidth() ) { // 原有Card内容保持不变 Card( modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(bottomStart = 10.dp, bottomEnd = 10.dp), colors = CardDefaults.cardColors(containerColor = Color.White), elevation = CardDefaults.cardElevation(defaultElevation = 5.dp), ) { Column(modifier = Modifier.padding(15.dp)) { Row(verticalAlignment = Alignment.CenterVertically) { Image( modifier = Modifier.clickable { // 点击关闭按钮时触发滑出动画 targetOffsetY = -drawerHeight.intValue.toFloat() launch { delay(300) isDrawerVisible = false targetOffsetY = 0f } }, painter = painterResource(id = R.drawable.icon_drawer_close), contentDescription = stringResource(id = R.string.text_close) ) Spacer(modifier = Modifier.weight(1f)) Image( modifier = Modifier.height(53.dp), painter = painterResource(id = R.drawable.icon_drawer_logo), contentDescription = stringResource(id = R.string.text_close), contentScale = ContentScale.FillHeight ) Spacer(modifier = Modifier.weight(1f)) Icon( modifier = Modifier.size(50.dp), imageVector = Icons.Default.Close, contentDescription = "", tint = Color.White ) } Spacer(modifier = Modifier.height(10.dp)) LazyVerticalGrid(columns = GridCells.Fixed(4)) { items(viewModel.getDrawerList(context)) { Column( modifier = Modifier.padding(top = 15.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Image( painter = painterResource(id = it.icon), contentDescription = "" ) MyText( modifier = Modifier.padding(top = 5.dp), text = it.title, fontSize = 13.sp ) } } } Spacer(modifier = Modifier.height(25.dp)) Image( modifier = Modifier.fillMaxWidth().height(4.dp), painter = painterResource(id = R.drawable.icon_drawer_bar), contentDescription = "", contentScale = ContentScale.FillHeight ) } } } }
关键修改说明
- 动画化状态管理:通过
animateFloatAsState自动处理偏移量的平滑过渡,替代手动赋值的生硬变化。 - 拖拽阈值控制:基于抽屉高度设置关闭阈值,只有拖拽足够距离才触发关闭,减少误操作。
- 动画衔接优化:关闭时先执行滑出动画,延迟后再隐藏抽屉,避免与
AnimatedVisibility的动画逻辑冲突。 - 拖拽范围限制:防止过度拖拽超出合理范围,保证交互的合理性。
- 统一关闭逻辑:点击关闭按钮时执行与拖拽关闭一致的动画,保持交互体验统一。
内容的提问来源于stack exchange,提问作者Abdul Waheed
相关产品推荐
相关产品推荐

