Compose LazyColumn中ExoPlayer返回列表页时高度跳动问题求助
问题:从详情页返回列表页时ExoPlayer高度跳动
在Jetpack Compose中实现了以下功能:
- LazyColumn顶部放置占屏幕高度一半的ExoPlayer播放器,下方是可跳转至详情页的列表项
- 使用Accompanist Navigation实现页面间上下滑动转场:点击列表项时详情页向上滑入,返回时向下滑出
- 遇到的问题:从详情页返回列表页时,顶部VideoPlayer视图出现高度跳动(先变大,随后恢复到计算好的半屏高度)
原因分析
- 屏幕尺寸计算时机偏差:通过
context.resources.displayMetrics计算屏幕高度时,从详情页返回的重组阶段可能获取到临时不准确的尺寸值,导致高度计算出现波动 - LazyColumn重组特性影响:LazyColumn的item在重组时会重新触发测量布局,而AndroidView的初始化与尺寸约束存在时序差,造成初始布局高度异常
- 转场动画的布局波动:页面转场过程中,导航宿主的尺寸可能处于动态变化状态,VideoPlayer的高度约束未能及时生效
解决方案
1. 使用Compose官方API获取稳定屏幕尺寸
替代原生DisplayMetrics,改用LocalWindowInfo获取屏幕高度,确保在Compose生命周期内尺寸值稳定:
@Composable fun ListScreen(navController: NavHostController?) { val windowInfo = LocalWindowInfo.current val screenHeightDp = remember(windowInfo) { windowInfo.screenSize.height.value.toDp() } val videoPlayerHeight = remember(screenHeightDp) { screenHeightDp / 2 } Box { LazyColumn { item { VideoPlayerViewCompose(videoPlayerHeight) } items(sampleList) { sampleItem -> ListItemCompose(sampleItem) { navController?.navigate("details_screen/${sampleItem.id}") } } } } }
2. 给VideoPlayer添加稳定约束并避免过度重建
在VideoPlayerViewCompose中固定尺寸约束,同时用高度作为AndroidView的key,仅在高度变化时重建视图:
@Composable fun VideoPlayerViewCompose(height: Dp) { Box(modifier = Modifier .height(height) .fillMaxWidth()) { AndroidView( factory = { context -> Logger.d { "PlayerView factory created" } PlayerView(context).apply { hideController() useController = false player = exoPlayer resizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) clipToOutline = true } }, update = { playerView -> val lifecycleOwner = LocalLifecycleOwner.current playerView.player?.apply { when (lifecycleOwner.lifecycle.currentState) { Lifecycle.State.RESUMED -> play() else -> pause() } } }, key = height ) } }
3. 固定导航宿主尺寸避免转场波动
给AnimatedNavHost添加fillMaxSize修饰符,确保导航容器始终占满屏幕,消除转场过程中的布局尺寸变化:
@Composable fun AppNavigationCompose() { val navAnimatedController = rememberAnimatedNavController() val tweenSpec = tween<Int>(durationMillis = 300) AnimatedNavHost( navController = navAnimatedController, startDestination = "list_screen", modifier = Modifier.fillMaxSize() ) { composable("list_screen") { ListScreen(navAnimatedController) } composable( route = "details_screen/{id}", arguments = listOf( navArgument(name = "id") { type = NavType.IntType } ), enterTransition = { slideInVertically( initialOffsetY = { fullHeight -> fullHeight }, animationSpec = tweenSpec ) }, exitTransition = { slideOutVertically( targetOffsetY = { fullHeight -> fullHeight }, animationSpec = tweenSpec ) }, popEnterTransition = { slideInVertically( initialOffsetY = { fullHeight -> fullHeight }, animationSpec = tweenSpec ) }, popExitTransition = { slideOutVertically( targetOffsetY = { fullHeight -> fullHeight }, animationSpec = tweenSpec ) } ) { navBackStackEntry -> DetailsScreen( listItemId = navBackStackEntry.arguments!!.getInt("id"), navController = navAnimatedController ) } } }
4. 保存LazyColumn滚动状态
用rememberLazyListState保存列表滚动状态,返回时恢复到之前的位置,减少不必要的重组:
@Composable fun ListScreen(navController: NavHostController?) { // ... 其他代码 ... val listState = rememberLazyListState() Box { LazyColumn(state = listState) { // ... 内容项 ... } } }
内容的提问来源于stack exchange,提问作者srinu
相关产品推荐
相关产品推荐

