Compose中如何替代实现ActivityOptions的裁剪揭露与缩放启动动画?
在Compose中实现Activity启动时的缩放/揭露动画
针对自定义启动器场景,你可以通过以下两种方案实现需求,核心是绕过“无法获取Compose子View”的限制:
方案一:利用Compose布局坐标复用系统原生动画
系统的ActivityOptions动画本质依赖的是目标区域的坐标参数,而非传入的View实例。因此可以通过Compose的Modifier.onGloballyPositioned获取组件的屏幕坐标,再传入原生动画方法即可。
实现步骤与代码示例
- 获取Compose组件的窗口坐标,转换为系统所需的
Rect - 复用
makeClipRevealAnimation/makeScaleUpAnimation构建动画参数 - 启动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
相关产品推荐
相关产品推荐

