You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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
重叠现象2

问题根因与修复方案

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 19:21:30