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

Jetpack Compose中ExoPlayer在网格布局下相邻视频重叠溢出的问题求助

Jetpack Compose中ExoPlayer在网格布局下相邻视频重叠溢出的问题求助

各位大佬好!之前提过类似的问题还没解决,现在补充细节再来求助,实在是踩坑踩得没辙了😭

我的Android应用用Jetpack Compose做了个媒体网格,每个格子里可能是图片或者视频。现在遇到个奇怪的问题:在3个项的网格里,第1和第3个是视频,中间那个竖屏视频居然直接突破父容器的边界,重叠到左边第一个视频的区域里了!我试过给视频、容器加各种布局约束,比如clipToBounds()、固定宽高、强制aspectRatio,全都没用。

后来我发现这个问题只在两个ExoPlayer实例相邻的时候才会触发:比如在4个项的网格里,第4个是竖屏视频,它上面的项是图片,这个视频就规规矩矩待在自己的容器里,完全不会溢出。

下面是我相关的代码片段,大家帮忙看看哪里出问题了:

MediaPreviewGrid 组件

@Composable
fun MediaPreviewGrid(
    mediaItems: List<MimeiFileType>,
    viewModel: TweetViewModel,
    onFullScreenVideo: ((String, MimeiId) -> Unit)? = null, // Callback for full-screen video
) {
    val tweet by viewModel.tweetState.collectAsState()
    val navController = LocalNavController.current
    val maxItems = when (mediaItems.size) {
        1 -> 1
        2, 3 -> mediaItems.size
        else -> 4
    }
    // Set up sequential playback for multiple videos
    val videoMids = remember {
        limitedMediaList.mapIndexedNotNull { index, item ->
            if (inferMediaTypeFromAttachment(item) == MediaType.Video) item.mid else null
        }
    }

    Box(
        modifier = Modifier
            .fillMaxWidth()
            .wrapContentWidth(Alignment.CenterHorizontally)
    ) {
        when (limitedMediaList.size) {
            1 -> {
                val aspectRatio = if (aspectRatioOf(limitedMediaList[0]) > 0.8f) {
                    aspectRatioOf(limitedMediaList[0])
                } else {
                    0.8f
                }
                MediaItemView(
                    limitedMediaList,
                    modifier = Modifier
                        .fillMaxWidth()
                        .aspectRatio(aspectRatio)
                        .clipToBounds()
                        .clickable {
                            val params = MediaViewerParams(
                                mediaItems.map {
                                    MediaItem(
                                        getMediaUrl(it.mid, tweet.author?.baseUrl.orEmpty()).toString(),
                                        it.type
                                    )
                                },
                                0,
                                tweet.mid,
                                tweet.authorId
                            )
                            navController.navigate(NavTweet.MediaViewer(params))
                        },
                    index = 0,
                    numOfHiddenItems = if (mediaItems.size > maxItems) mediaItems.size - maxItems else 0,
                    autoPlay = firstVideoIndex == 0,
                    inPreviewGrid = true,
                    viewModel = viewModel,
                    onFullScreenVideo = onFullScreenVideo
                )
            }
            2 -> {}
            3 -> {}
            4 -> {}
        }
    }
}

MediaItemView 组件

@Composable
fun MediaItemView(
    mediaItems: List<MimeiFileType>,
    modifier: Modifier = Modifier,
    index: Int,
    numOfHiddenItems: Int = 0, // add a PLUS sign to indicate more items not shown
    autoPlay: Boolean = false, // autoplay first video item, index 0
    inPreviewGrid: Boolean = true, // use real aspectRatio when not displaying in preview grid.
    viewModel: TweetViewModel,
    onFullScreenVideo: ((String, MimeiId) -> Unit)? = null // Callback for full-screen video
) {
    val tweet by viewModel.tweetState.collectAsState()
    val attachments = mediaItems.map {
        val inferredType = inferMediaTypeFromAttachment(it)
        val mediaUrl = getMediaUrl(it.mid, tweet.author?.baseUrl.orEmpty()).toString()
        MediaItem(mediaUrl, inferredType)
    }
    val attachment = attachments[index]
    val navController = LocalNavController.current

    Box(
        modifier = modifier
            .background(Color.Gray.copy(alpha = 0.1f))
            .clipToBounds(),
        contentAlignment = Alignment.Center
    ) {
        when (attachment.type) {
            MediaType.Image -> {
                // Use a Box with clickable modifier to handle image clicks
                Box(
                    modifier = modifier
                        .clipToBounds()
                        .clickable { goto(index) }
                ) {
                    ImageViewer(
                        attachment.url,
                        modifier = Modifier.fillMaxSize(),
                        enableLongPress = false // Disable long press to allow clickable to work
                    )
                }
            }
            MediaType.Video -> {
                VideoPreview(
                    url = attachment.url,
                    modifier = modifier,
                    index = index,
                    autoPlay = autoPlay,
                    inPreviewGrid = inPreviewGrid,
                    aspectRatio = mediaItems[index].aspectRatio,
                    callback = { goto(index) },
                    videoMid = mediaItems[index].mid
                )
            }
            MediaType.Audio -> {
                val backgroundModifier = if (index % 2 != 0) { // Check if index is odd
                    modifier.background(Color.Black.copy(alpha = 0.05f)) // Slightly darker background
                } else {
                    modifier
                }
                Box(
                    modifier = backgroundModifier
                        .clipToBounds()
                        .clickable { goto(index) }
                ) {
                    AudioPreview(mediaItems, index, Modifier.fillMaxSize(), tweet)
                }
            }
            else -> {
                // add link to download other file type
                BlobLink(mediaItems[index], attachment.url, modifier)
            }
        }

        if (numOfHiddenItems > 0) {
            /**
             * Show a PLUS sign and number to indicate more items not shown
             * */
            Box(
                modifier = Modifier
                    .matchParentSize()
                    .background(Color(0x40FFFFFF)), // Lighter shaded background
                contentAlignment = Alignment.Center
            ) {
                Row(modifier = Modifier.align(Alignment.Center)) {
                    Icon(
                        imageVector = Icons.Outlined.Add,
                        contentDescription = null,
                        tint = Color.White,
                        modifier = Modifier
                            .size(50.dp)
                            .alpha(0.8f)
                    )
                    Text(
                        text = numOfHiddenItems.toString(),
                        color = Color.White,
                        fontSize = 50.sp, // Adjust this value as needed
                        textAlign = TextAlign.Center,
                        modifier = Modifier
                            .alpha(0.8f)
                    )
                }
            }
        }
    }
}

VideoPreview 组件(核心ExoPlayer封装)

@Composable
fun VideoPreview(
    url: String,
    modifier: Modifier,
    index: Int,
    autoPlay: Boolean = false,
    inPreviewGrid: Boolean = true,
    aspectRatio: Float?,
    callback: (Int) -> Unit,
    videoMid: MimeiId
) {
    // 这里是ExoPlayer的Compose封装实现,比如使用PlayerView配合AndroidView
    // 核心逻辑是初始化ExoPlayer并加载视频资源
}

我现在的猜测是,ExoPlayer的底层View可能绕过了Compose的布局约束?或者是多个Player实例之间有什么层级冲突?有没有大佬遇到过类似的情况,给点思路或者解决方案吧,实在是卡在这里好久了🙏

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.07 13:19:34