关注公众号学习更多知识
compose 为支持动画提供了大量的 api,通过这些 api 我们可以轻松实现动画效果
ps:这些 api 的原理与 Flutter 很接近,与原生的 api 相去甚远
你可以提前看看用 compose 实现的一个放大缩小动画,总的来说还是比较流畅:
所能处理属性的种类:Float、Color、Dp、Size、Bounds、Offset、Rect、Int、IntOffset 和 IntSize
通过 animate*AsState 我们可以实现单一属性的动画效果,我们只需要提供目标值就可以自动从当前进度动画过渡到目标值
@Composable
fun animSize() {
val enable = remember {
mutableStateOf(true)
}
val size =
animateSizeAsState(targetValue = if (enable.value) Size(50f, 50f) else Size(300f, 300f))
Column(
modifier = Modifier.fillMaxSize(1f),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Image(
modifier = Modifier
.size(size.value.width.dp, size.value.height.dp)
.clickable {
enable.value = !enable.value
},
painter = painterResource(id = R.drawable.apple),
contentDescription = ""
)
}
}
@Composable
fun animColor() {
val enable = remember {
mutableStateOf(true)
}
val colors = animateColorAsState(targetValue = if (enable.value) Color.Green else Color.Red)
val size = animateIntSizeAsState(
targetValue = if (enable.value) IntSize(100, 100) else IntSize(
300,
300
)
)
Column(
modifier = Modifier.fillMaxWidth(1f),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Box(
modifier = Modifier
.size(size.value.width.dp, size.value.height.dp)
.height(400.dp)
.background(
color = colors.value,
shape = if (enable.value) RectangleShape else CircleShape
)
) {
}
}
}
Animatable 是一个值容器,我们可以通过调用 animateTo 实现动画效果。动画执行过程中如果再次开启动画会中断当前动画。
Animatable 动画执行过程中值的变化是在协程中执行的,所以 animateTo 是一个挂起操作
@Composable
fun animChangeColor() {
val color = remember {
Animatable(Color.Red)
}
val state = remember {
mutableStateOf(true)
}
LaunchedEffect(state.value) {
color.animateTo(if (state.value) Color.Red else Color.Magenta)
}
Box(Modifier.fillMaxSize(1f), contentAlignment = Alignment.Center) {
Box(
modifier = Modifier
.background(color.value, shape = RoundedCornerShape(30.dp))
.size(200.dp)
.clickable {
state.value = !state.value
}, contentAlignment = Alignment.Center
) {
Text(
text = "颜色动画",
style = TextStyle(color = Color.White, fontSize = 40.sp)
)
}
}
}
使用 updateTransition 可以实现多个动画组合的效果。
例如:我们可以在动画执行过程中同时执行大小和颜色变化效果
本例中我们定义了一个枚举用来控制动画,枚举可以定义多个,分别用来对应动画的多个状态
@Composable
fun animupdateTransition() {
var state by remember {
mutableStateOf(BoxState.Collapsed)
}
val transition = updateTransition(targetState = state, label = "")
val round = transition.animateDp(label = "") {
when (it) {
BoxState.Collapsed -> 40.dp
BoxState.Expanded -> 100.dp
}
}
val color = transition.animateColor(label = "") {
when (it) {
BoxState.Collapsed -> Color.Red
BoxState.Expanded -> Color.Green
}
}
Box(Modifier.fillMaxSize(1f),contentAlignment = Alignment.Center) {
Box(
modifier = Modifier
.size(300.dp)
.background(
color.value,
shape = RoundedCornerShape(corner = CornerSize(round.value))
)
.clickable {
state =
if (state == BoxState.Collapsed) BoxState.Expanded else BoxState.Collapsed
},contentAlignment = Alignment.Center
) {
Text(text = "点击开始动画",style = TextStyle(color = Color.White,fontSize = 20.sp))
}
}
}
private enum class BoxState {
Collapsed,
Expanded
}
rememberInfiniteTransition 的使用和 updateTransition 基本一样,不同的是 rememberInfiniteTransition 的动画一旦开始便会一直反复运行下去,只有被移除动画才能结束
@Composable
fun rememberInfiniteTransition1() {
val infiniteTransition = rememberInfiniteTransition()
val color by infiniteTransition.animateColor(
initialValue = Color.Red,
targetValue = Color.Green,
animationSpec = infiniteRepeatable(
animation = tween(1000, easing = LinearEasing),
repeatMode = RepeatMode.Reverse
)
)
Box(Modifier.fillMaxSize(1f), contentAlignment = Alignment.Center) {
Box(
Modifier
.fillMaxSize(0.8f)
.background(color),
contentAlignment = Alignment.Center
) {
Text(
text = "公众号:安安安安卓 原创,禁抄袭",
style = TextStyle(color = Color.White, fontSize = 30.sp)
)
}
}
}
TargetBasedAnimation 可以控制动画的执行时间,还可以延迟一段时间再开启动画。
@Composable
fun animTargetBasedAnimation() {
var state by remember {
mutableStateOf(0)
}
val anim = remember {
TargetBasedAnimation(
animationSpec = tween(2000),
typeConverter = Float.VectorConverter,
initialValue = 100f,
targetValue = 300f
)
}
var playTime by remember { mutableStateOf(0L) }
var animationValue by remember {
mutableStateOf(0)
}
LaunchedEffect(state) {
val startTime = withFrameNanos { it }
println("进入协程:")
do {
playTime = withFrameNanos { it } - startTime
animationValue = anim.getValueFromNanos(playTime).toInt()
} while (!anim.isFinishedFromNanos(playTime))
}
Box(modifier = Modifier.fillMaxSize(1f),contentAlignment = Alignment.Center) {
Box(modifier = Modifier
.size(animationValue.dp)
.background(Color.Red,shape = RoundedCornerShape(animationValue/5))
.clickable {
state++
},contentAlignment = Alignment.Center) {
Text(text = animationValue.toString(),style = TextStyle(color = Color.White,fontSize = (animationValue/5).sp))
}
}
}
AnimationSpec 可以自定义动画的行为,效果类似于原生动画中的估值器。
@Composable
fun animSpring() {
val state = remember {
mutableStateOf(true)
}
var value = animateIntAsState(
targetValue = if (state.value) 300 else 100,
animationSpec = spring(
dampingRatio = Spring.DampingRatioHighBouncy,
stiffness = Spring.StiffnessVeryLow
)
)
Box(
Modifier
.fillMaxSize(1f)
.padding(start = 30.dp), contentAlignment = Alignment.CenterStart
) {
Box(
Modifier
.width(value.value.dp)
.height(80.dp)
.background(Color.Red, RoundedCornerShape(topEnd = 30.dp, bottomEnd = 30.dp))
.clickable {
state.value = !state.value
}, contentAlignment = Alignment.CenterStart
) {
Text(text = "哈哈哈", style = TextStyle(color = Color.White, fontSize = 20.sp))
}
}
}
@Composable
fun animTweenSpec() {
val state = remember {
mutableStateOf(true)
}
val value = animateIntAsState(
targetValue = if (state.value) 300 else 100,
animationSpec = tween(
durationMillis = 1500,
delayMillis = 200,
easing = LinearEasing
)
)
Box(
Modifier
.fillMaxSize(1f)
.padding(start = 50.dp), contentAlignment = Alignment.CenterStart
) {
Box(
Modifier
.width(value.value.dp)
.height(100.dp)
.background(Color.Red, RoundedCornerShape(topEnd = 30.dp, bottomEnd = 30.dp))
.clickable {
state.value = !state.value
}
) {
}
}
}
@Composable
fun animkeyframesSpec() {
var state by remember {
mutableStateOf(true)
}
val value by animateIntAsState(
targetValue = if (state) 300 else 100,
animationSpec = keyframes {
durationMillis = 2000
0 at 700 with LinearOutSlowInEasing
700 at 1400 with FastOutLinearInEasing
1400 at 2000
})
Box(Modifier.fillMaxSize(1f), contentAlignment = Alignment.CenterStart) {
Box(
Modifier
.width(value.dp)
.height(100.dp)
.background(Color.Red, RoundedCornerShape(topEnd = 30.dp, bottomEnd = 30.dp))
.clickable {
state = !state
}
) {
}
}
}
执行有限次数动画后自动停止
@Composable
fun animrepeatableSpec() {
var state by remember {
mutableStateOf(true)
}
val value by animateIntAsState(
targetValue = if (state) 300 else 100,
animationSpec = repeatable(
iterations = 5,//动画重复执行的次数,设置多少就执行多少次
animation = tween(durationMillis = 1000),
repeatMode = RepeatMode.Reverse
)
)
Box(
Modifier
.fillMaxSize(1f)
.padding(start = 30.dp), contentAlignment = Alignment.CenterStart) {
Box(
Modifier
.width(value.dp)
.height(100.dp)
.background(Color.Red, RoundedCornerShape(topEnd = 30.dp, bottomEnd = 30.dp))
.clickable {
state = !state
}
) {
}
}
}
动画会无限次的执行下去,直到视图被移除
@Composable
fun animinfiniteRepeatableSpec() {
var state by remember {
mutableStateOf(true)
}
val value by animateIntAsState(
targetValue = if (state) 300 else 100,
animationSpec = infiniteRepeatable(
animation = tween(durationMillis = 1000),
repeatMode = RepeatMode.Reverse
)
)
Box(
Modifier
.fillMaxSize(1f)
.padding(start = 30.dp), contentAlignment = Alignment.CenterStart) {
Box(
Modifier
.width(value.dp)
.height(100.dp)
.background(Color.Red, RoundedCornerShape(topEnd = 30.dp, bottomEnd = 30.dp))
.clickable {
state = !state
}
) {
Text(text = "公众号:安安安安卓 原创,禁转载")
}
}
}
Easing 类似于我们原生动画中的差值器
有以下几种选择:
这几种实现的效果和 android 原生实现的动画差值器差距很大,甚至看不出有啥效果,所以代码我就不放了。有清楚原因的读者可以联系我
大多数 Compose 动画 API 都支持将 Float、Color、Dp 以及其他基本数据类型作为开箱即用的动画值,但有时我们需要为其他数据类型(包括我们的自定义类型)添加动画效果
本例中实现颜色和大小的变换动画
代码中我们定义了一个 AnimSize 类,类中的第一个参数是颜色数据,第二个参数是尺寸数据。动画执行过程中会同事改变颜色和控件尺寸效果。
@Composable
fun animAnimationVector() {
var state by remember {
mutableStateOf(true)
}
val value by animateValueAsState(
targetValue = if (state) AnimSize(0xffff5500, 100f) else AnimSize(0xff00ff00, 300f),
typeConverter = TwoWayConverter(
convertToVector = {
// AnimationVector2D(target.color.toFloat(), target.size)
AnimationVector2D(it.color.toFloat(), it.size)
},
convertFromVector = {
AnimSize(it.v1.toLong(), it.v2)
}
)
)
println("颜色:${value.color}")
Box(modifier = Modifier.fillMaxSize(1f).padding(30.dp), contentAlignment = Alignment.Center) {
Box(
modifier = Modifier
.size(value.size.dp)
// .size(300.dp)
.background(Color(value.color), RoundedCornerShape(30.dp))
.clickable {
state = !state
}
) {
}
}
}
data class AnimSize(val color: Long, val size: Float)
缺点是执行颜色变化过程中有闪烁
高级动画一般指封装性较高的动画,使用较为简单,主要有以下三种:
因高级动画效果不明显,gif 很难展现出效果,所以这里不放代码和效果图了
AnimatedVisibility
animateContentSize
Crossfade
关注我的公众号 “安安安安卓” 学习更多知识