Transition 初体验
先看下面这个代码 ,其实效果很简答 就是一个点击时间 触发一段动画而已
一边改size 一边改圆角大小
setContent {
var big by remember {
mutableStateOf(false)
}
var bigTransition = updateTransition(big, label = "big")
//animateDpAsState
val size by bigTransition.animateDp(label = "size") {
if (it) 100.dp else 50.dp
}
val corner by bigTransition.animateDp(label = "corner") {
if (it) 50.dp else 0.dp
}
Box(modifier = Modifier
.size(size)
.clip(RoundedCornerShape(corner))
.background(Color.Green)
.clickable {
big = !big
})
}
有人就要问了 你这个动画效果 我们用animateDpAsState 一样可以实现啊,代码还比你这个少一点,为啥要用这个?
我们可以看看 如果用animateDpAsState 来写 代码是啥一样的
setContent {
var big by remember {
mutableStateOf(false)
}
val size by animateDpAsState (
if (big) 100.dp else 50.dp
)
val corner by animateDpAsState (
if (big) 0.dp else 50.dp
)
Box(modifier = Modifier
.size(size)
.clip(RoundedCornerShape(corner))
.background(Color.Green)
.clickable {
big = !big
})
}
明显看的出来 animateDpAsState 写法反而更简单。
两者区别或者说为啥要推 Transition 这个东西?
首先第一, 当你想做多个动画的时候,比如上文中的例子,我要改变多个状态,animateDpAsState 这一个函数就代表了一个协程, 你有10个状态要改变 那就是10个, 但是如果用Transition 来管理,则不管你有多少个状态需要改变,则都只有一个协程,性能上 Transition 会有一点优势
第二 Transition 能够更好的预览效果
看下图 几个红框的地方 其实就是Transition 写法中的label, 这种预览方式可以很方便的让我们调试动画效果 这是animateDpAsState 没有的
第三 当你管理一组动画的时候,实际上 Transition 的写法 更好,这点纯属个人代码审美了
AnimatedVisibility
来看下这个动画,看名字也能知道 这应该是个控制可见性的动画
setContent {
Column() {
var show by remember {
mutableStateOf(true)
}
AnimatedVisibility(show) {
Box(
modifier = Modifier
.height(100.dp)
.width(100.dp)
.background(color = Color.Red)
)
}
Button(onClick = {
show = !show
},) {
Text(text = "change")
}
}
}
可以看下效果
你会发现,这个可见性动画有点神奇啊,他的表现形式 是垂直方向伸缩,跟我们的布局Column是一一对应的
来看下代码 就一目了然了
同样我们也看一下其他的实现
看到这 我们其实可以下一个结论 就是 AnimatedVisibility 本质上就是 Transition 动画
可以着重关注下 下面的 动画是怎么实现的?+ 号啥意思
主要就是这个sealed class了, 不知道sealed class的同学 可以自己搜下, 暂时理解成他就是一个interface即可
这里可以看下他的实现就只有一个impl而已
恩,实际上动画的表现形式就是在这个class中来决定了
fade 其实就是一个 淡入淡出的效果 可以看下参数类型
再看下这个slide 他其实就是定义你这个出入的效果 是从哪个位置延伸而来,属于是延伸动画
AnimatedVisibility(show, enter = slideIn(tween(2500)) {
IntOffset(-50,-50)
}) {
Box(
modifier = Modifier
.height(100.dp)
.width(100.dp)
.background(color = Color.Red)
)
}
这个lambda参数 就是定义 你是从哪个位置 伸展而来
可以看下效果
这里可能有人觉得 你这个效果太傻了,正常人不会写的这么丑 我想要的是 跟我的view 自己大小相关的
其实这个动画是有个默认值可以取到原始大小的
所以我们能直接取到view的宽高 那么一切就很和谐了
稍微改一下
IntOffset(-it.width,-it.height)
效果大家可以自己跑一下 看看,这里就不重复演示了
再来看一下 第三个参数ChangeSize
AnimatedVisibility(show, enter = expandIn(tween(2500), expandFrom = Alignment.TopStart,
initialSize = { IntSize(0, 0) })) {
Box(
modifier = Modifier
.height(100.dp)
.width(100.dp)
.background(color = Color.Red)
)
}
看下效果:
这里可能有人会问,这个效果和slidein 有点类似?有啥区别?
其实区别大了啊,这个changesize 本质上是 裁切变换的view的大小,你看这个change的位置就能看出来 他是逐步到下面的
而我们的slideIn,也就是slide动画 他的大小是一瞬间就固定到view的原始大小,然后view从一个位置 挪过来而已。
这俩本质上不是一个东西,只是视觉上的效果比较类似罢了
最后一个参数 Scale 挺简单的,大家自己写一个试试吧,注意Scale 也是不改变view大小的,改变的只是view绘制的那部分大小。
+ 号是怎么回事?
其实关键就是在plus函数的实现了, 我们可以简单的理解为 A+B 就等于 A.plus(B)
仔细看这个 plus函数的实现 其实就是 如果A有 那就用A的,如果A没有 就用B的
组合,注意是组合不是相加,然后 组合成一个新的TransitionData
CrossFade
setContent {
Column() {
var show by remember {
mutableStateOf(true)
}
Crossfade(show){
if (it) {
Box(
modifier = Modifier
.height(50.dp)
.width(50.dp)
.background(color = Color.Blue)
)
} else {
Box(
modifier = Modifier
.height(100.dp)
.width(100.dp)
.background(color = Color.Red)
)
}
}
Button(
onClick = {
show = !show
},
) {
Text(text = "change")
}
}
}
这个很简单,其实就是2个view的变化 变化的过程中有动画
参数和上一个小节里面几乎一摸一样,不过多介绍了
AnimatedContent
这个东西和crossfade 效果差不多 可以控制多个view展示的,只不过他控制的粒度更细致。
setContent {
Column() {
var show by remember {
mutableStateOf(true)
}
AnimatedContent(show) {
if (it) {
Box(
modifier = Modifier
.background(Color.Red)
.width(100.dp)
.height(100.dp)
)
} else {
Box(
modifier = Modifier
.background(Color.Green)
.width(50.dp)
.height(50.dp)
)
}
}
Button(
onClick = {
show = !show
},
) {
Text(text = "AnimatedContent change")
}
var show2 by remember {
mutableStateOf(true)
}
Crossfade(show2, modifier = Modifier.padding(top = 100.dp)) {
if (it) {
Box(
modifier = Modifier
.background(Color.Red)
.width(100.dp)
.height(100.dp)
)
} else {
Box(
modifier = Modifier
.background(Color.Green)
.width(50.dp)
.height(50.dp)
)
}
}
Button(
onClick = {
show2 = !show2
},
) {
Text(text = "Crossfade change2")
}
}
}
可以看下 animatedContent和crossfade的区别
-
crossfade 两个一起变化,进入和退出的动画是一起进行,而animatedContent则是有先后顺序 简单来说cf 是并行的,而animatecontent则是串行的
-
AnimateContent的 size变化是 动态的,逐渐变化的,而Crossfade 则是突兀的,瞬间变化的
大家可以多体会一下gif图 或者自己写个demo
可以看下代码 明显的deley时间和 fadeout的动画时间是一一对应的
可以看下这个关键参数 contenttransform
这个target和init 2个参数就是 进入和退出的效果,很简单, sizeTransform 是 大小的渐变效果 而 targetContentZIndex 则是 z轴的优先级,也就是 到底是你遮盖我还是我遮盖你
不过 一般我们写动画也不会写到这个targetContentZIndex
所以掌握了这些参数信息 你很容易就可以定制自己想要的动画效果了
比如下面这种写法,就是等一个动画结束了 另外一个动画才开始 很简单的写法
AnimatedContent(show, transitionSpec = { fadeIn(tween(3000)) with fadeOut(tween(3000,3000)) }) {
总结
整个Compose的动画 到这里就差不多结束了,整体感觉上写法比传统view 上 要简单不少,多写几个demo 试试不同参数的效果 也就能快速掌握了。另外推荐下扔物线大佬的compose课 讲的很好