Jetpack Compose懒列表中AndroidView卡顿重叠优化方案
问题描述
开发过程中遇到两类异常:
- 以占满全屏的
AndroidView作为子项时,滑动LazyColumn或VerticalPager过程中会出现数毫秒级界面卡顿,滑动响应滞涩 - 滑动过程中存在子项重叠的显示异常
排查阶段曾将AndroidView承载的PlayerView替换为TextView做对照验证,排除PlayerView本身问题,推测故障与AndroidView实现逻辑、VerticalPager本身特性、组件全屏占满的布局模式相关。
相关实现代码
ViewScreen 实现
@OptIn(ExperimentalPagerApi::class) @Composable fun VideoScreen() { val pagerState = rememberPagerState() Box { VerticalPager( count = videos.size, state = pagerState, horizontalAlignment = Alignment.CenterHorizontally, itemSpacing = 10.dp ) { index -> VideoPlayer( vid = videos[index], shouldPlay = false ) } } }
VideoPlayer 组件实现
@Composable fun VideoPlayer( vid: Video, shouldPlay: Boolean ) { val exoPlayer = rememberExoPlayerWithLifecycle(vid.url) val playerView = rememberPlayerView(exoPlayer) AndroidView( factory = { playerView }, modifier = Modifier, update = { exoPlayer.playWhenReady = shouldPlay } ) DisposableEffect(key1 = true) { onDispose { exoPlayer.release() } } }
rememberExoPlayerWithLifecycle 实现
@Composable fun rememberExoPlayerWithLifecycle( url: String ): ExoPlayer { val context = LocalContext.current val exoPlayer = remember(url) { ExoPlayer.Builder(context).build().apply { videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT repeatMode = Player.REPEAT_MODE_ONE setHandleAudioBecomingNoisy(true) val defaultDataSource = DefaultHttpDataSource.Factory() val source = ProgressiveMediaSource.Factory(defaultDataSource) .createMediaSource(MediaItem.fromUri(url)) setMediaSource(source) prepare() } } var appInBackground by remember { mutableStateOf(false) } val lifecycleOwner = LocalLifecycleOwner.current DisposableEffect(key1 = lifecycleOwner, appInBackground) { val lifecycleObserver = getExoPlayerLifecycleObserver(exoPlayer, appInBackground) { appInBackground = it } lifecycleOwner.lifecycle.addObserver(lifecycleObserver) onDispose { lifecycleOwner.lifecycle.removeObserver(lifecycleObserver) } } return exoPlayer }
rememberPlayerView 实现
@Composable fun rememberPlayerView(exoPlayer: ExoPlayer): PlayerView { val context = LocalContext.current val playerView = remember { PlayerView(context).apply { layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT) useController = false resizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM player = exoPlayer setShowBuffering(SHOW_BUFFERING_ALWAYS) } } DisposableEffect(key1 = true) { onDispose { playerView.player = null } } return playerView }
项目级 build.gradle 配置
buildscript { ext { compose_version = '1.3.0-beta01' } } plugins { ... id 'org.jetbrains.kotlin.android' version '1.7.10' apply false } ...
模块级 build.gradle 依赖配置
... dependencies { ... //Pager implementation "com.google.accompanist:accompanist-pager:0.26.2-beta" //Media 3 def mediaVersion = "1.0.0-beta02" implementation "androidx.media3:media3-exoplayer:$mediaVersion" implementation "androidx.media3:media3-ui:$mediaVersion" ... }
重叠异常现象


问题根因与修复方案
1. 滑动卡顿问题
核心触发原因:
- 当前使用的Compose、Accompanist Pager、Media3均为beta版本,早期beta版
AndroidView和Compose UI的渲染同步逻辑存在缺陷,滑动时View层级重绘、重排耗时高 - 现有实现会为所有Pager子项提前初始化ExoPlayer实例,滑动过程中即使非当前页的播放器也在占用渲染资源,触发主线程阻塞
修复方式:
- 升级依赖到稳定版:Compose升级到1.3.0+正式版,Accompanist Pager升级到和Compose版本匹配的0.28.0+稳定版,Media3升级到1.0.0+正式版
- 不要在子项进入组合时就初始化ExoPlayer,通过
pagerState.settledPage判断当前停留的页面,仅对当前完全可见的页面初始化播放器并启动播放,预加载页仅初始化PlayerView不做播放器prepare操作 - 给
AndroidView加上Modifier.fillMaxSize()明确尺寸,避免布局阶段反复测量
2. 子项重叠问题
核心触发原因:
代码中手动给PlayerView设置了MATCH_PARENT的layoutParams,早期版本AndroidView会根据自身modifier给承载的View分配布局参数,手动设置的布局参数会脱离Compose的布局约束,导致View绘制时超出父容器边界,覆盖相邻的Pager子项。
修复方式:
- 删除rememberPlayerView中手动设置layoutParams的代码,
AndroidView会自动根据自身modifier给承载的View分配合适的布局参数 - 给
VerticalPager加上clipToBounds = true配置,强制超出页面边界的内容被裁剪,不会覆盖相邻页面 - 如果需要保留10dp的itemSpacing,给每个子项的
AndroidView外层套一个布局容器,给AndroidView加Modifier.fillMaxSize().padding(bottom = 10.dp),避免间距区域被视频内容覆盖
内容的提问来源于stack exchange,提问作者Astro
相关产品推荐
相关产品推荐

