Jetpack Compose坐标系工作原理及启动页Logo动画定位问题
解决方案
1. 移除全局坐标依赖,改用父容器相对定位
放弃positionInWindow()获取全局坐标,直接基于BoxWithConstraints的内部尺寸计算位置,避免跨平台下系统状态栏、窗口装饰对坐标的干扰。
2. 统一使用graphicsLayer处理缩放与偏移
Modifier.offset和Modifier.scale是独立变换,易出现位置偏差。改用Modifier.graphicsLayer并设置transformOrigin = TransformOrigin.TopLeft,让缩放以Logo左上角为原点,简化位置计算逻辑。
3. 同步动画参数,避免异步错位
将偏移和缩放参数封装到一个数据类中,用单个Animatable控制,确保三个动画属性同步执行。
4. 基于容器尺寸动态计算起始/目标值
- 初始状态:Logo居中显示,计算公式为
(容器宽度 - 原始Logo宽度×初始缩放)/2、(容器高度 - 原始Logo高度×初始缩放)/2。 - 目标状态:Logo位于父容器左上角10dp处,缩放为0.2f,偏移值直接设为10dp对应的像素值。
修改后的代码
import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.tween import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.draw.graphicsLayer import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.IntSize // 封装动画状态参数,统一控制偏移与缩放 private data class SplashAnimationState( val translationX: Float, val translationY: Float, val scale: Float ) @Composable fun SplashScreen(sizeProvider: WindowSizeProvider) { BoxWithConstraints(modifier = Modifier.fillMaxSize()) { val containerWidth = maxWidth.value val containerHeight = maxHeight.value val imgSize = IntSize(1460, 320) // 原始Logo尺寸(像素) val initialScale = 1f val finalScale = 0.2f val padding = 10.dp.toPx() // 目标位置距左上角的间距 // 初始状态:Logo在容器中心 val initialState = SplashAnimationState( translationX = (containerWidth - imgSize.width * initialScale) / 2, translationY = (containerHeight - imgSize.height * initialScale) / 2, scale = initialScale ) // 目标状态:左上角偏移padding,缩放0.2f val targetState = SplashAnimationState( translationX = padding, translationY = padding, scale = finalScale ) val animatableState = remember { Animatable(initialState) } val animationSpec = tween<SplashAnimationState>( durationMillis = 2000, easing = FastOutSlowInEasing ) LaunchedEffect(Unit) { delay(2000) animatableState.animateTo(targetState, animationSpec) } Box(modifier = Modifier.fillMaxSize()) { Image( painter = painterResource(Res.drawable.logo_light), contentDescription = null, modifier = Modifier .graphicsLayer( translationX = animatableState.value.translationX, translationY = animatableState.value.translationY, scaleX = animatableState.value.scale, scaleY = animatableState.value.scale, transformOrigin = TransformOrigin.TopLeft // 以左上角为缩放原点,避免位置偏移 ) ) } } }
关键改动说明
transformOrigin = TransformOrigin.TopLeft:设置缩放原点为Logo左上角,缩放时不会改变Logo左上角的相对位置,偏移计算更直观。- 封装动画状态:用单个
Animatable控制所有动画参数,保证偏移和缩放同步执行,避免分开动画导致的错位。 - 动态容器尺寸:直接使用
BoxWithConstraints提供的maxWidth和maxHeight,无需额外存储状态,实时响应容器尺寸变化。 - 移除全局坐标:不再依赖
positionInWindow(),彻底解决跨平台下系统UI元素对坐标的干扰问题。
内容的提问来源于stack exchange,提问作者siliconeagle
相关产品推荐
相关产品推荐

