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

相关推荐
Android出海12 小时前
Google Play账户与App突遭封禁?紧急应对与快速重构上架策略
android·网络·重构·新媒体运营·产品运营·内容运营
恋猫de小郭12 小时前
Flutter 官方 LLM 动态 UI 库 flutter_genui 发布,让 App UI 自己生成 UI
android·前端·flutter
锅拌饭12 小时前
saveEnabled导致的Fragment大量泄露
android
叽哥12 小时前
Kotlin学习第 3 课:Kotlin 流程控制:掌握逻辑分支与循环的艺术
android·java·kotlin
CYRUS_STUDIO12 小时前
别让 so 裸奔!移植 OLLVM 到 NDK 并集成到 Android Studio
android·android studio·llvm
尚久龙12 小时前
安卓学习 之 图片控件和图片按钮
android·java·学习·手机·android studio·安卓
东风西巷13 小时前
Don‘t Sleep:保持电脑唤醒,确保任务不间断
android·电脑·软件需求
tangweiguo0305198713 小时前
FlutterActivity vs FlutterFragmentActivity:全面对比与最佳实践
android·flutter
葱段13 小时前
【Flutter】TextField 监听长按菜单粘贴点击事件
android·flutter·dart
用户0913 小时前
Gradle 现代化任务依赖方案
android·kotlin