compose动画从底层基础到顶层高级应用(三)核心API之--Transition

上一篇文章中,学习了AnimateAsState,每个动画各自使用各自的时钟,是独立的,那么当需要同时控制多个动画时,就需要用到Transition了。

概念

Transition 在状态级别管理所有子动画。可以使用 Transition.animateFloat、Transition.animateValue、animateColor 等以声明性方式创建子动画。内部有currentState和targetState两个状态,当 targetState和currentState不一样时就会触发动画。

构造Transition对象

updateTransition
定义:

updateTransition函数需要传入一个目标状态targetState

kotlin 复制代码
@Composable
public fun <T> updateTransition(targetState: T, label: String? = null): Transition<T>
示例代码:
  1. 代码中定义方块的状态枚举类型BoxState,以及承载枚举变化的状态toState
  2. updateTransition中传入目标状态,也就是toState
  3. 通过updateTransition.animateOffsetupdateTransition.animateColor分别定义不同目标状态时的数据。
  4. 在UI组件中分别应用offsetcolor,点击更改toState的值,也就更改updateTransition的目标状态,就会触发动画。
  5. Transition对象里可以获取currentState, targetState, isRunning等属性,示例中通过isRunning控制点击按钮的Icon变化。
kotlin 复制代码
enum class BoxState {
    Left, Right
}

@Preview
@Composable
fun AnimationExample() {

    var iconIsPlay by remember { mutableStateOf(false) }
    var toState by remember { mutableStateOf(BoxState.Left) }

    val updateTransition = updateTransition(
        targetState = toState,
        label = "updateTransition"
    )
    LaunchedEffect(updateTransition.isRunning) {
        iconIsPlay = updateTransition.isRunning
    }

    val offset by updateTransition.animateOffset(
        targetValueByState = { boxState ->
            when (boxState) {
                BoxState.Left -> Offset(x = 0f, y = 0f)
                BoxState.Right -> Offset(x = 350f, y = 0f)
            }
        },
        transitionSpec = {
            spring(stiffness = 50f)
        }
    )
    val color by updateTransition.animateColor(
        targetValueByState = { boxState ->
            when (boxState) {
                BoxState.Left -> Color(0xFF4CAF50)
                BoxState.Right -> Color(0xFF2196F3)
            }
        },
        transitionSpec = {
            spring(stiffness = 50f)
        }
    )

    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(text = "UpdateTransition")
        Box(
            modifier = Modifier
                .width(400.dp)
                .height(50.dp)
                .background(Color(0x405E5D5D))
        ) {
            Box(
                modifier = Modifier
                    .size(50.dp)
                    .offset(offset.x.dp, offset.y.dp)
                    .background(color)
            )
        }
        Spacer(modifier = Modifier.height(10.dp))
        Image(
            imageVector = if (iconIsPlay) {
                Icons.Filled.PauseCircle
            } else {
                Icons.Filled.PlayCircle
            },
            contentDescription = null,
            modifier = Modifier
                .size(50.dp)
                .clickable {
                    toState = if (toState == BoxState.Left) {
                        BoxState.Right
                    } else {
                        BoxState.Left
                    }
                }
        )
    }
}
效果:
rememberTransition
定义:

rememberTransition的参数换成了TransitionState,意思是需要我们构建一个TransitionState,在updateTransition中,内部也是将我们传入的targetState封装成TransitionState,因此rememberTransition更加底层。

kotlin 复制代码
@Composable
public fun <T> rememberTransition(
    transitionState: TransitionState<T>,
    label: String? = null
): Transition<T>
使用:

替换掉updateTransition中的toState为自定义的transitionState ,点击时候修改transitionState.targetState即可。 优势 :相比updateTransition的优势是,组件第一次组合时时可以立刻触发动画,构建transitionState对象后加上 LaunchedEffect(Unit) { transitionState.targetState = BoxState.Right }即可,适用于UI启动动画的场合。

kotlin 复制代码
//    var toState by remember { mutableStateOf(BoxState.Left) }
//
//    val updateTransition = updateTransition(
//        targetState = toState,
//        label = "updateTransition"
//    )
    val transitionState = remember { MutableTransitionState(BoxState.Left) }
    val rememberTransition = rememberTransition(
        transitionState = transitionState
    )
    LaunchedEffect(rememberTransition.isRunning) {
        iconIsPlay = rememberTransition.isRunning
    }
效果:

加上自启动的逻辑的效果,可以看到启动后立刻执行了动画。

TransitionState的两种类型
  1. MutableTransitionState,默认的类型,通过更改它的targetState来触发动画,同时提供属性isIdle来获取动画是否完成。
  2. SeekableTransitionState,更加精细的控制动画,触发动画不能修改targetState了,而是使用下列三个函数:
  • animateTo:将动画执行到传入的目标状态,注意需要在协程作用域内调用,如下代码中。
kotlin 复制代码
                .clickable {
//                    transitionState.targetState =
//                        if (transitionState.targetState == BoxState.Left) {
//                            BoxState.Right
//                        } else {
//                            BoxState.Left
//                        }
                    scope.launch {
                        if (transitionState.targetState == BoxState.Left) {
                            transitionState.animateTo(BoxState.Right)
                        } else {
                            transitionState.animateTo(BoxState.Left)
                        }
                    }
                }
  • seekTo:允许控制动画的进度,比如增加一个slider来实现拖动触发动画,拖动的比例就是动画的时间比例。
kotlin 复制代码
Slider(
            value = sliderPosition,
            modifier = Modifier
                .width(400.dp)
                .wrapContentHeight(),
            onValueChange = { value ->
                sliderPosition = value
                scope.launch {
                    val target = if (transitionState.currentState == BoxState.Left) {
                        BoxState.Right
                    } else {
                        BoxState.Left
                    }
                    transitionState.seekTo(fraction = value, targetState = target)
                }
            }
        )
  • snapTo:直接跳转到目标状态,跳过动画过程,如下代码还是使用点击触发动画,将animateTo改为snapTo,可以看到往右时有动画,往左没有。
kotlin 复制代码
.clickable {
	scope.launch {
		if (transitionState.targetState == BoxState.Left) {
			transitionState.animateTo(BoxState.Right)
		} else {
            transitionState.snapTo(BoxState.Left)
		}
	}
}

子Transition

使用createChildTransition创建transition的子Transition,父子Transition使用同一个动画时钟,一般用于有依赖关系的动画场景。

定义:
kotlin 复制代码
@ExperimentalTransitionApi
@Composable
public inline fun <S, T> Transition<S>.createChildTransition(
    label: String = "ChildTransition",
    transformToChildState: @Composable (parentState: S) -> T,
): Transition<T>
使用:
kotlin 复制代码
    val childTransition = rememberTransition.createChildTransition { parentState ->
        when (parentState) {
            BoxState.Left -> TODO()
            BoxState.Right -> TODO()
        }
    }

构建动画

前面的示例代码中其实已经用到了animateOffsetanimateColor,还有其他的如下图:

animateValue

其中animateValue是通用的类型构建,和上一篇文章中的animateValueAsState是一样的逻辑,使用到了TwoWayConverter转换,具体不会使用可以阅读上一篇文章compose动画从底层基础到顶层高级应用(二)核心API之--AnimateAsState

kotlin 复制代码
@Composable
public inline fun <S, T, V : AnimationVector> Transition<S>.animateValue(
    typeConverter: TwoWayConverter<T, V>,
    noinline transitionSpec: @Composable Transition.Segment<S>.() -> FiniteAnimationSpec<T> = {
        spring()
    },
    label: String = "ValueAnimation",
    targetValueByState: @Composable (state: S) -> T
): State<T>

总结

当多个动画需要使用同一个时钟时,就要使用到Transition。但是Transition有一个不太好的地方是,如果动画不是采用的springSpec,中断动画不会使用定义的动画规格,而是使用默认设置的spring(),而且刚度1500f,看起来会非常快,就像突然跳转。具体可以阅读我的另一篇文章:Compose Transition中断动画的特殊性。 如果喜欢我的文章,请点赞收藏 :)

相关推荐
张拭心20 小时前
春节后,有些公司明确要求 AI 经验了
android·前端·人工智能
张拭心20 小时前
Android 17 来了!新特性介绍与适配建议
android·前端
Kapaseker1 天前
Compose 进阶—巧用 GraphicsLayer
android·kotlin
黄林晴1 天前
Android17 为什么重写 MessageQueue
android
阿巴斯甜2 天前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker2 天前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95272 天前
Andorid Google 登录接入文档
android
黄林晴2 天前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab2 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿3 天前
Android MediaPlayer 笔记
android