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

Exo Player动画失效:点击按钮后无法实现平滑显隐过渡

解决ExoPlayer在Compose中淡入淡出动画失效的问题

问题出在AnimatedVisibility会直接添加/移除Compose组件,而你用AndroidView包裹的PlayerView是原生View,Compose在处理原生View的移除时会直接从父布局中删除,导致exit动画被中断,看不到渐变效果;淡入时同理,原生View突然被添加,动画无法生效。

下面是两种可行的解决方案:

方案一:使用透明度动画控制(推荐)

通过animateFloatAsState控制透明度,不直接移除PlayerView组件,只是改变其透明度,这样原生View不会被销毁/添加,动画能正常执行:

@Composable
fun TestPlayerScreen(
    modifier: Modifier = Modifier,
) {
    val context = LocalContext.current
    val exoPlayer = remember {
        ExoPlayer.Builder(context).build()
    }
    val mediaSource = remember {
        MediaItem.Builder()
            .setUri("http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4")
            .build()
    }
    var showPlayer by remember { mutableStateOf(true) }
    // 控制透明度的动画状态
    val alpha by animateFloatAsState(
        targetValue = if (showPlayer) 1f else 0f,
        animationSpec = tween(5000),
        label = "playerAlpha"
    )

    DisposableEffect(mediaSource) {
        exoPlayer.setMediaItem(mediaSource)
        exoPlayer.prepare()
        onDispose {
            exoPlayer.release()
        }
    }

    Column {
        Box(
            modifier = modifier
                .width(300.dp)
                .height(200.dp)
                .alpha(alpha)
                // 透明度为0时禁止交互,避免误触
                .pointerInput(alpha) {
                    if (alpha == 0f) {
                        awaitPointerEventScope { awaitPointerEvent() }
                    }
                }
        ) {
            AndroidView(
                factory = { ctx ->
                    PlayerView(ctx).apply { player = exoPlayer }
                }
            )
        }
        Button(onClick = { showPlayer = !showPlayer }) {
            Text("Show/Hide player")
        }
    }
}

方案二:用Transition API管理动画状态

通过updateTransition统一管理显示状态和动画,同时在动画结束后控制布局高度,避免占用多余空间:

@Composable
fun TestPlayerScreen(
    modifier: Modifier = Modifier,
) {
    val context = LocalContext.current
    val exoPlayer = remember {
        ExoPlayer.Builder(context).build()
    }
    val mediaSource = remember {
        MediaItem.Builder()
            .setUri("http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4")
            .build()
    }
    var showPlayer by remember { mutableStateOf(true) }
    // 创建过渡状态管理
    val transition = updateTransition(showPlayer, label = "playerTransition")
    val alpha by transition.animateFloat(
        transitionSpec = { tween(5000) },
        label = "playerAlpha"
    ) { visible -> if (visible) 1f else 0f }

    DisposableEffect(mediaSource) {
        exoPlayer.setMediaItem(mediaSource)
        exoPlayer.prepare()
        onDispose {
            exoPlayer.release()
        }
    }

    Column {
        Box(
            modifier = modifier
                .width(300.dp)
                .alpha(alpha)
                // 动画结束后隐藏布局高度
                .height(if (showPlayer || alpha > 0f) 200.dp else 0.dp)
        ) {
            AndroidView(
                factory = { ctx ->
                    PlayerView(ctx).apply { player = exoPlayer }
                }
            )
        }
        Button(onClick = { showPlayer = !showPlayer }) {
            Text("Show/Hide player")
        }
    }
}

核心思路

AnimatedVisibility的问题在于它会触发组件的添加/移除,而原生View无法在这个过程中正确执行Compose的动画逻辑。改用透明度动画后,PlayerView始终存在于布局中,只是通过透明度变化实现显示/隐藏,完美适配原生View的生命周期。

内容的提问来源于stack exchange,提问作者Rodion

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 01:04:58