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

Compose中如何替代实现ActivityOptions的裁剪揭露与缩放启动动画?

在Compose中实现Activity启动时的缩放/揭露动画

针对自定义启动器场景,你可以通过以下两种方案实现需求,核心是绕过“无法获取Compose子View”的限制:

方案一:利用Compose布局坐标复用系统原生动画

系统的ActivityOptions动画本质依赖的是目标区域的坐标参数,而非传入的View实例。因此可以通过Compose的Modifier.onGloballyPositioned获取组件的屏幕坐标,再传入原生动画方法即可。

实现步骤与代码示例

  1. 获取Compose组件的窗口坐标,转换为系统所需的Rect
  2. 复用makeClipRevealAnimation/makeScaleUpAnimation构建动画参数
  3. 启动Activity时传入动画Bundle
// 扩展函数:将Compose的Rect转换为Android原生Rect
fun androidx.compose.ui.geometry.Rect.toAndroidRect(): android.graphics.Rect {
    return android.graphics.Rect(
        left.toInt(),
        top.toInt(),
        right.toInt(),
        bottom.toInt()
    )
}

@Composable
fun LauncherAppIcon(packageName: String) {
    var iconBounds by remember { mutableStateOf(androidx.compose.ui.geometry.Rect.Zero) }
    val context = LocalContext.current

    Box(
        modifier = Modifier
            .size(64.dp)
            .background(Color.Blue)
            // 获取组件相对于窗口的坐标
            .onGloballyPositioned { coordinates ->
                val windowOffset = coordinates.localToWindow(Offset.Zero)
                iconBounds = androidx.compose.ui.geometry.Rect(
                    left = windowOffset.x,
                    top = windowOffset.y,
                    right = windowOffset.x + coordinates.size.width,
                    bottom = windowOffset.y + coordinates.size.height
                )
            }
            .clickable {
                // 获取目标应用启动Intent
                val launchIntent = context.packageManager.getLaunchIntentForPackage(packageName)?.apply {
                    flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
                    sourceBounds = iconBounds.toAndroidRect()
                }

                launchIntent?.let {
                    // 构建缩放动画参数(替换为makeClipRevealAnimation即可实现揭露效果)
                    val animationOptions = ActivityOptions.makeScaleUpAnimation(
                        LocalView.current, // 传入根View不影响,动画基于传入的坐标计算
                        iconBounds.left.toInt(),
                        iconBounds.top.toInt(),
                        iconBounds.width.toInt(),
                        iconBounds.height.toInt()
                    ).toBundle()
                    context.startActivity(it, animationOptions)
                }
            }
    ) {
        // 这里放置应用图标
        Icon(Icons.Default.Android, contentDescription = "App Icon")
    }
}

关键说明

  • onGloballyPositioned会在组件布局完成后回调,确保获取的坐标是最新的
  • localToWindow将Compose的局部坐标转换为窗口坐标,与原生View的getBoundsOnScreen逻辑一致
  • 虽然传入LocalView.current作为动画的View参数,但系统实际会基于我们传入的坐标值计算动画区域,因此可以正常工作

方案二:Compose自定义动画+Activity过渡

如果需要完全自定义过渡效果,可以在当前Activity播放Compose动画后,再启动目标Activity并关闭系统默认动画。这种方式适合需要非标准动画的场景,但视觉连贯性略逊于系统原生动画。

代码示例

@Composable
fun CustomAnimatedLauncherIcon(packageName: String) {
    var isLaunching by remember { mutableStateOf(false) }
    val transition = updateTransition(isLaunching, label = "LaunchAnimation")
    val context = LocalContext.current

    // 定义缩放和透明度动画
    val scale by transition.animateFloat(
        transitionSpec = { tween(300) },
        label = "Scale"
    ) { if (it) 1.5f else 1f }
    val alpha by transition.animateFloat(
        transitionSpec = { tween(300) },
        label = "Alpha"
    ) { if (it) 0f else 1f }

    Box(
        modifier = Modifier
            .size(64.dp)
            .background(Color.Blue)
            .scale(scale)
            .alpha(alpha)
            .clickable { isLaunching = true }
    ) {
        Icon(Icons.Default.Android, contentDescription = "App Icon")
    }

    // 动画结束后启动目标Activity
    LaunchedEffect(isLaunching) {
        if (isLaunching) {
            delay(300) // 等待动画完成
            val launchIntent = context.packageManager.getLaunchIntentForPackage(packageName)?.apply {
                flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
            }
            launchIntent?.let {
                // 关闭系统默认过渡动画,替换为自定义进入动画
                val options = ActivityOptions.makeCustomAnimation(
                    context,
                    0, // 当前Activity无退出动画
                    android.R.anim.fade_in // 目标Activity淡入动画
                ).toBundle()
                context.startActivity(it, options)
                isLaunching = false
            }
        }
    }
}

关键说明

  • 通过updateTransition管理启动状态,触发Compose动画
  • 动画结束后启动目标Activity,关闭当前Activity的退出动画,避免视觉断层
  • 可以替换android.R.anim.fade_in为自定义的动画资源,实现更多效果

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 19:55:13