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中断动画的特殊性。 如果喜欢我的文章,请点赞收藏 :)

相关推荐
程序员JerrySUN6 小时前
Valgrind Memcheck 全解析教程:6个程序说明基础内存错误
android·java·linux·运维·开发语言·学习
经典19926 小时前
mysql 性能优化之Explain讲解
android·mysql·性能优化
Kiri霧8 小时前
Kotlin集合与空值
android·开发语言·kotlin
亿刀10 小时前
为什么要学习Flutter编译过程
android·flutter
suqingxiao10 小时前
android虚拟机(AVD)报错The emulator process for AVD xxx has terminated
android
whysqwhw10 小时前
OkHttp Cookie 处理机制全解析
android
Evan_ZGYF丶11 小时前
【RK3576】【Android14】ADB工具说明与使用
android·驱动开发·android14·rk3576
幻雨様11 小时前
UE5多人MOBA+GAS 番外篇:移植Lyra的伤害特效(没用GameplayCue,因为我失败了┭┮﹏┭┮)
android·ue5
狂浪天涯11 小时前
Android 16 显示系统 | 从View 到屏幕系列 - 4 | GraphicBuffer & Gralloc
android·操作系统