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

相关推荐
zhangphil18 分钟前
Android Coil3视频封面抽取封面帧存Disk缓存,Kotlin
android·kotlin
猪哥帅过吴彦祖27 分钟前
Flutter SizeTransition:让你的UI动画更加丝滑
android·flutter
OperateCode2 小时前
Android Studio 格式规范
android
张风捷特烈2 小时前
鸿蒙纪·Flutter卷#02 | 已有 Flutter 项目鸿蒙化 · 3.27.4 版
android·flutter·harmonyos
QING6184 小时前
Media3 ExoPlayer 快速实现背景视频播放(干货)
android·前端·kotlin
用户2018792831674 小时前
PengdingIntent之“我想要的很简单时光还在你还在”
android
weiwuxian4 小时前
js与原生通讯版本演进
android·前端
wayne2144 小时前
Android 跨应用广播通信全攻略
android
叽哥5 小时前
flutter学习第 12 节:网络请求与 JSON 解析
android·flutter·ios
y东施效颦6 小时前
uni-app app端安卓和ios如何申请麦克风权限,唤起提醒弹框
android·ios·uni-app