在我的例子中,我在棋盘上移动一些游戏棋子.有时碎片在长距离移动,有时碎片在短距离移动.

我使用animateOffsetAsState()来实现动画,并按如下方式传递新的偏移量:

val offset by animateOffsetAsState(
        targetValue = position.toOffset(squareSize),
        animationSpec = tween(200, easing = LinearEasing)
)

每个动画运行200毫秒.所以对于短距离,棋子移动得非常慢,而对于长距离,棋子移动得非常快,以覆盖整个距离.

有没有一些内置的动画规范可以用来定义我想要使用的速度,而不是时间,以便无论距离如何,动画总是以相同的速度运行?

推荐答案

由于速度为t=x/v,因此可以计算将哪个持续时间传递给补间,并将其作为

private fun calculateDuration(
    targetValue: Offset,
    velocity: Float
): Int {
    val xPos = targetValue.x
    val yPos = targetValue.y

    val distance = sqrt(xPos * xPos + yPos + yPos)
    return (distance / velocity * 1000).toInt()
}

以恒定的速度创造一种状态

@Composable
fun animateConstantSpeedOffsetAsState(
    initialOffset: Offset = Offset.Zero,
    targetValue: Offset,
    velocity: Float,
    label: String = "OffsetAnimation",
    finishedListener: ((Offset) -> Unit)? = null
): State<Offset> {

    require(velocity > 0f)

    var previousOffset by remember {
        mutableStateOf(initialOffset)
    }

    val durationMillis by remember {
        mutableIntStateOf(calculateDuration(targetValue.minus(previousOffset), velocity))
    }.apply {
        val duration = calculateDuration(targetValue.minus(previousOffset), velocity)
        if (duration > 0) {
            this.intValue = duration
        }
    }

    previousOffset = targetValue

    val animationSpec = tween<Offset>(
        durationMillis = durationMillis,
        easing = LinearEasing
    )
    return animateValueAsState(
        targetValue,
        Offset.VectorConverter,
        animationSpec,
        label = label,
        finishedListener = {
            previousOffset = targetValue
            finishedListener?.invoke(it)
        }
    )
}

红色圆圈使用AnimateAsSate,而绿色圆圈使用恒速

enter image description here

演示

@Preview
@Composable
private fun AnimationVelocityTest() {

    var isClicked by remember {
        mutableStateOf(false)
    }

    val target1 = if (isClicked.not()) Offset.Zero
    else Offset(500f, 500f)

    val target2 = if (isClicked.not()) Offset.Zero
    else Offset(1000f, 1000f)


    val offset1 by animateOffsetAsState(
        targetValue = target1,
        animationSpec = tween(4000, easing = LinearEasing),
        label = ""
    )

    val offset2 by animateOffsetAsState(
        targetValue = target2,
        animationSpec = tween(4000, easing = LinearEasing),
        label = ""
    )

    val offset3 by animateConstantSpeedOffsetAsState(
        targetValue = target1,
        velocity = 250f,
    )

    val offset4 by animateConstantSpeedOffsetAsState(
        targetValue = target2,
        velocity = 250f,
    )

    Canvas(
        modifier = Modifier.fillMaxSize()
            .padding(20.dp)
            .clickable {
                isClicked = isClicked.not()
            }
    ) {
        drawCircle(
            color = Color.Red,
            radius = 50f,
            center = offset1
        )

        translate(top = 100f) {
            drawCircle(
                color = Color.Red,
                radius = 50f,
                style = Stroke(4.dp.toPx()),
                center = offset2
            )
        }

        translate(top = 200f) {
            drawCircle(
                color = Color.Green,
                radius = 50f,
                center = offset3
            )
        }

        translate(top = 300f) {
            drawCircle(
                color = Color.Green,
                radius = 50f,
                style = Stroke(4.dp.toPx()),
                center = offset4
            )
        }
    }
}

Another 演示

@Preview
@Composable
private fun AnimationVelocityTest2() {
    var isClicked by remember {
        mutableStateOf(false)
    }

    var dynamicTarget by remember {
        mutableStateOf(Offset.Zero)
    }

    LaunchedEffect(isClicked) {
        if (isClicked) {
            dynamicTarget = dynamicTarget.plus(Offset(100f, 100f))
        }
    }

    val offset by animateConstantSpeedOffsetAsState(
        targetValue = dynamicTarget,
        velocity = 100f,
    )

    Canvas(
        modifier = Modifier.fillMaxSize()
            .padding(20.dp)
            .clickable {
                isClicked = isClicked.not()
            }
    ) {

        drawRect(
            color = Color.Magenta,
            size = Size(100f, 100f),
            style = Stroke(4.dp.toPx()),
            topLeft = offset
        )
    }
}

Android相关问答推荐

如何正确增加LazyStream中的变量

房间打开创建回调java.nio.channels. OverlappingFilLockResponse

如何将Hilt添加到Android Studio中的Kotlin项目中?

使用不同的Google帐户登录

如何处理穿戴构图上的长点击事件?

Android Gradle/Groovy,如何将文件复制到APK

两首合成曲的喷气背包动画

如何在Jetpack Compose中将对象的移动从一个路径平滑地切换到另一个路径?

判断文本视图是否为单行

如何制作安卓';s FileProvider在Android 11上使用外部存储+

使用lazyColumn迁移paging3的旧代码

使用 Kotlin 在 Android 中导航时如何防止 ViewModel 被杀死?

Jetpack Compose 中带有权重的行和 AnimatedVisibility 会 destruct UI

如何使用 Jetpack Compose 制作两个圆圈

PayUCheckoutPro Android SDK 实现问题

缺少类 com.google.android.datatransport.runtime.ForcedSender

使用 capacitor cordova 插件的 Android Studio 错误

为什么官方文档用大写字母表示val变量?

如何在 Android Studio 中使用 Github Copilot?

我可以在不解密的情况下使用 JSch 获取加密的 SSH 私钥的类型或 fingerprint 吗?