Compose动画中有一个Interrupted中断效果,当上一个动画还没有执行完成时,立刻触发下一个动画,控件会从当前状态往新的状态平滑过渡。
问题
先看下面动图,分别使用了AnimateAsState和Transition来触发动画,动画规格都是2秒的tween动画,当触发打断时,效果却是不一样的,Transition动画似乎突变了,和设计的平滑过渡不一致。
源码
- offsetOne,offsetTwo和offsetThree都是用的是tween(durationMillis = 2000)
@Preview
@Composable
fun AnimationExample() {
var boxRight by remember { mutableStateOf(false) }
var iconIsPlay by remember { mutableStateOf(false) }
val updateTransition = updateTransition(
targetState = boxRight,
label = "updateTransition"
)
val offsetOne = updateTransition.animateOffset(
targetValueByState = { boxRight ->
if (boxRight) {
Offset(x = 350f, y = 0f)
} else {
Offset(x = 0f, y = 0f)
}
},
transitionSpec = {
tween(durationMillis = 2000)
// spring(stiffness = 50f)
}
)
LaunchedEffect(updateTransition.isRunning) {
iconIsPlay = updateTransition.isRunning
}
val offsetTwo by animateOffsetAsState(
targetValue = if (boxRight) {
Offset(x = 350f, y = 0f)
} else {
Offset(x = 0f, y = 0f)
},
animationSpec = tween(durationMillis = 2000)
)
val transitionState = remember { MutableTransitionState(false) }
val rememberOffset = rememberTransition(transitionState = transitionState)
val offsetThree = rememberOffset.animateOffset(
targetValueByState = { boxRight ->
if (boxRight) {
Offset(x = 350f, y = 0f)
} else {
Offset(x = 0f, y = 0f)
}
},
transitionSpec = {
tween(durationMillis = 2000)
// 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(offsetOne.value.x.dp, offsetOne.value.y.dp)
.background(Color(0xFF2196F3))
)
}
Spacer(modifier = Modifier.height(10.dp))
Text(text = "RememberTransition")
Box(
modifier = Modifier
.width(400.dp)
.height(50.dp)
.background(Color(0x405E5D5D))
) {
Box(
modifier = Modifier
.size(50.dp)
.offset(offsetThree.value.x.dp, offsetThree.value.y.dp)
.background(Color(0xFFCDDC39))
)
}
Spacer(modifier = Modifier.height(10.dp))
Text(text = "AnimateAsState")
Box(
modifier = Modifier
.width(400.dp)
.height(50.dp)
.background(Color(0x405E5D5D))
) {
Box(
modifier = Modifier
.size(50.dp)
.offset(offsetTwo.x.dp, offsetTwo.y.dp)
.background(Color(0xFF4CAF50))
)
}
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 {
boxRight = !boxRight
transitionState.targetState = boxRight
}
)
}
}
- 接下来将动画全部换成弹簧动画
spring(stiffness = 50f),看看效果:当换成spring动画后发现,Transition的打断动画和AnimateAsState一样了,打断后就不瞬间突变了。
根因
在Transition.kt中可以看到源码如下:
val specWithoutDelay =
if (isInterrupted && !isSeeking) {
// When interrupted, use the default spring, unless the spec is also a spring.
if (animationSpec is SpringSpec<*>) animationSpec else interruptionSpec
} else { animationSpec }
可以看到当动画规格是spring的时候,打断后的新动画保留animationSpec,否则使用默认的interruptionSpec。那么默认的interruptionSpec则是spring(visibilityThreshold = visibilityThreshold),默认的弹性刚度(弹性刚度越大,动画速度越快)是StiffnessMedium: Float = 1500f
private val interruptionSpec: FiniteAnimationSpec<T>
init {
val visibilityThreshold: T? =
VisibilityThresholdMap.get(typeConverter)?.let {
val vector = typeConverter.convertToVector(initialValue)
for (id in 0 until vector.size) {
vector[id] = it
}
typeConverter.convertFromVector(vector)
}
interruptionSpec = spring(visibilityThreshold = visibilityThreshold)
}
总结:当transition设置的动画不是spring类型时,transition的中断动画并不是突变,而是默认的spring动画,刚度1500f,所以看起来速度非常快。