Jetpack Compose 从入门到入门
Posted 唯鹿
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Jetpack Compose 从入门到入门相关的知识,希望对你有一定的参考价值。
本篇进入Compose 动画部分。
1.动画预览
在本系列第一篇中我们提到过,@Preview
可以帮我们实现UI的预览功能,简单的交互和播放动画。
在android Studio Bumblebee(大黄蜂)中你可以开启动画的预览,但是只支持少部分API。
前几天Android Studio 稳定版更新到了Chipmunk(花栗鼠),开始支持 animatedVisibility
的动画预览,这里也建议你将 Compose 升至 1.1.0 或更高版本,可以体验更完整的内容。
提示:本篇使用Compose
版本为1.1.0(对应 Kotlin版本为 1.6.10)。会涉及一些实验性API,可能后面会有变动甚至取消。
简单说一先动画预览的功能。当检测到预览的ui中有支持预览的动画时,会出现一个 图标。点击这个图标后,就可以看到具体每个动画的运动曲线,我们可以在面板上拖动、快进或放慢动画。逐帧预览过渡效果。
举个小例子,我在页面上放置一个OutlinedTextField
输入框,我们看一下实际的动画预览效果。
是不是很强大,很便捷。好了,下面正式开始动画API部分了。
2. 高级别API
高级别api就是对基础api的封装,便于我们更好的使用。
1. AnimatedVisibility(实验性)
看名字就知道,它是一个显示隐藏的动画。
@ExperimentalAnimationApi
@Composable
fun <T> Transition<T>.AnimatedVisibility(
visible: (T) -> Boolean,
modifier: Modifier = Modifier,
enter: EnterTransition = fadeIn() + expandIn(),
exit: ExitTransition = shrinkOut() + fadeOut(),
content: @Composable() AnimatedVisibilityScope.() -> Unit
) = AnimatedEnterExitImpl(this, visible, modifier, enter, exit, content)
visible
: 显示还是隐藏,显示执行enter,隐藏执行exit。enter
:显示动画,默认是从左上角开始水平和垂直方向同时展开时淡入。目前EnterTransition
有四种类型:
- fade:
fadeIn
- scale:
scaleIn
- slide:
slideIn
,slideInHorizontally
,slideInVertically
- expand:
expandIn
,expandHorizontally
,expandVertically
exit
:消失动画,默认是收缩(与显示相反)时淡出。ExitTransition
如下:
- fade:
fadeOut
- scale:
scaleOut
- slide:
slideOut
,slideOutHorizontally
,slideOutVertically
- shrink:
shrinkOut
,shrinkHorizontally
,shrinkVertically
多个动画效果我们可以使用 +
运算符进行组合,使用起来很方便。具体这些动画的效果我们可以参考官方文档的示例动图:EnterTransition、ExitTransition 示例
需要注意的是,Row
和Column
下的Scope有对应的AnimatedVisibility
拓展方法,所以默认动画略有不同。以Row
举例:
@Composable
fun RowScope.AnimatedVisibility(
visible: Boolean,
modifier: Modifier = Modifier,
enter: EnterTransition = fadeIn() + expandHorizontally(),
exit: ExitTransition = fadeOut() + shrinkHorizontally(),
label: String = "AnimatedVisibility",
content: @Composable() AnimatedVisibilityScope.() -> Unit
)
Row
中的显示隐藏动画是expandHorizontally
和shrinkHorizontally
,所以是水平方向从左到右展开时淡入,从右到左收缩时淡出。Column
就是垂直方向过渡。这点我们在使用时需要注意。
AnimatedVisibility
还提供了传入 MutableTransitionState
的变体方法。该属性有助于观察动画状态。官方demo:
// Create a MutableTransitionState<Boolean> for the AnimatedVisibility.
val state = remember
MutableTransitionState(false).apply
// Start the animation immediately.
targetState = true
Column
AnimatedVisibility(visibleState = state)
Text(text = "Hello, world!")
// Use the MutableTransitionState to know the current animation state
// of the AnimatedVisibility.
Text(
text = when
state.isIdle && state.currentState -> "Visible"
!state.isIdle && state.currentState -> "Disappearing"
state.isIdle && !state.currentState -> "Invisible"
else -> "Appearing"
)
Button(
onClick =
state.targetState = !state.targetState
)
Text("Change")
效果图:
为子项添加进入和退出动画效果
AnimatedVisibility
中的内容可以使用animateEnterExit
修饰符为每个子项指定不同的动画行为。我稍微修改了一下官方的demo:
val visible = remember false
Column(modifier = Modifier.fillMaxWidth())
AnimatedVisibility(
visible = visible,
enter = fadeIn(),
exit = fadeOut()
)
Box(Modifier.fillMaxSize().background(Color.DarkGray))
Box(
Modifier
.align(Alignment.Center).padding(bottom = 150.dp)
.animateEnterExit(
enter = slideInVertically(),
exit = slideOutVertically()
)
.sizeIn(minWidth = 150.dp, minHeight = 150.dp)
.background(Color.Red)
)
Box(
Modifier
.align(Alignment.Center).padding(top = 150.dp)
.animateEnterExit(
enter = slideInHorizontally(),
exit = slideOutHorizontally()
)
.sizeIn(minWidth = 150.dp, minHeight = 150.dp)
.background(Color.Blue)
)
页面整体是灰色背景的,其中有一红一蓝两个方块居中放置。灰色背景是淡入淡出,红蓝方块是滑动效果。效果如下:
如果你不需要动画效果,可以设置EnterTransition.None
和 ExitTransition.None
。
添加自定义动画
通过AnimatedVisibility
的transition
属性访问底层Transition
实例,就可以添加自定义动画,这些动画将与AnimatedVisibility
的进入和退出动画同时运行。
AnimatedVisibility(
visible = visible,
enter = fadeIn(),
exit = fadeOut()
)
val background by transition.animateColor state ->
if (state == EnterExitState.Visible) Color.Blue else Color.Red
Box(modifier = Modifier.size(128.dp).background(background))
效果如下:
关于Transition
,我们后面说低级动画API时会说明。
2. AnimatedContent(实验性)
AnimatedContent
会在内容目标状态发生变化时,为内容添加动画效果。
@ExperimentalAnimationApi
@Composable
fun <S> AnimatedContent(
targetState: S,
modifier: Modifier = Modifier,
transitionSpec: AnimatedContentScope<S>.() -> ContentTransform =
fadeIn(animationSpec = tween(220, delayMillis = 90)) +
scaleIn(initialScale = 0.92f, animationSpec = tween(220, delayMillis = 90)) with
fadeOut(animationSpec = tween(90))
,
contentAlignment: Alignment = Alignment.TopStart,
content: @Composable() AnimatedVisibilityScope.(targetState: S) -> Unit
) ...
根据源码我们可以看到默认动画效果是淡出后同时放大淡入。
targetState
:目标状态,当它变化时会触发动画。transitionSpec
:过渡的动画效果。我们可以利用AnimatedContentScope
中的initialState
和targetState
等属性,来自定义动画效果。
下面看一个数字变换的例子:
var count by remember mutableStateOf(0)
Column(modifier = Modifier.fillMaxWidth().padding(10.dp))
AnimatedContent(
targetState = count,
transitionSpec =
// 将当前的数字与之前的数字进行比较。
if (targetState > initialState)
// 如果目标数字较大,它会向上滑动并淡入,而初始(较小)数字则向上滑动并淡入。
slideInVertically height -> height + fadeIn() with
slideOutVertically height -> -height + fadeOut()
else
// 如果目标数字较小,则它会下滑并淡入,而初始数字则下滑并淡入。
slideInVertically height -> -height + fadeIn() with
slideOutVertically height -> height + fadeOut()
.using(
// 禁用剪切,因为要显示在边界之外。
SizeTransform(clip = false)
)
) targetCount ->
Text(text = "$targetCount", fontSize = 33.sp)
Button(onClick = count++ )
Text("Add")
效果如下:
代码中使用using
传入一个SizeTransform
,禁止将内容裁剪为组件大小。同时我们可以使用SizeTransform
中的initialSize
, targetSize
属性来定义过渡中的大小变化。
3. animateContentSize
fun Modifier.animateContentSize(
animationSpec: FiniteAnimationSpec<IntSize> = spring(),
finishedListener: ((initialValue: IntSize, targetValue: IntSize) -> Unit)? = null
): Modifier
animateContentSize
是Modifier
的一个扩展方法,可以在内容大小发生变化时添加动画过渡效果。此方法使用简单,这里就不过多的说明了。
4. Crossfade
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun <T> Crossfade(
targetState: T,
modifier: Modifier = Modifier,
animationSpec: FiniteAnimationSpec<Float> = tween(),
content: @Composable (T) -> Unit
)
Crossfade
在布局切换时添加淡入淡出动画。用法与AnimatedContent
类似。
3. 低级别动画API
1. animateXXXAsState
animateXXXAsState
方法是 Compose
中最简单的动画API,用于为单个值添加动画效果。您只需提供结束值(或目标值),该 API 就会从当前值开始向指定值播放动画。
Compose
为 Float、Color、Dp、Size、Offset、Rect、Int、IntOffset 和 IntSize 提供开箱即用的 animate*AsState
方法。通过为接受通用类型的 animateValueAsState
提供 TwoWayConverter
,您可以轻松添加对其他数据类型的支持。
例如animateIntAsState
源码如下:
@Composable
fun animateIntAsState(
targetValue: Int,
animationSpec: AnimationSpec<Int> = intDefaultSpring,
finishedListener: ((Int) -> Unit)? = null
): State<Int>
return animateValueAsState(
targetValue, Int.VectorConverter, animationSpec, finishedListener = finishedListener
)
private val intDefaultSpring = spring(visibilityThreshold = Int.VisibilityThreshold)
默认的动画效果都是spring
,它是一种弹簧(弹性)动画。
@Stable
fun <T> spring(
dampingRatio: Float = Spring.DampingRatioNoBouncy,
stiffness: Float = Spring.StiffnessMedium,
visibilityThreshold: T? = null
)
不过dampingRatio
默认值是1f,stiffness
默认是1500f,所以实际并无明显的弹性和摆动。
2. updateTransition
animateXXXAsState
适合单个属性变化的动画,如果是同时执行多个动画,可以使用updateTransition
。
@Composable
fun <T> updateTransition(
targetState: T,
label: String? = null
): Transition<T>
val transition = remember Transition(targetState, label = label)
transition.animateTo(targetState)
DisposableEffect(transition)
onDispose
transition.onTransitionEnd()
return transition
targetState
:目标状态,变化时会触发动画。label
:动画预览时的动画名称,用于区分动画。
下面直接照搬官方Demo,说明一下如何使用updateTransition
类似。
// 定义枚举类型
enum class BoxState
Collapsed,
Expanded
// 定义保存动画值的对象
private class TransitionData(
color: State<Color>,
size: State<Dp>
)
val color by color
val size by size
页面上创建一个Box
,它的大小背景色来自动画值,点击按钮更新状态。
@Composable
private fun updateTransitionDemo()
Column(modifier = Modifier.fillMaxWidth().padding(10.dp))
var currentState by remember mutableStateOf(BoxState.Collapsed)
val transitionData = updateTransitionData(currentState)
Box(
modifier = Modifier
.background(transitionData.color)
.size(transitionData.size)
)
Button(onClick =
currentState = if (currentState == BoxState.Collapsed)
BoxState.Expanded
else
BoxState.Collapsed
)
Text(text = "Update")
这里核心是updateTransitionData
方法:
@Composable
private fun updateTransitionData(boxState: BoxState): TransitionData
val transition = updateTransition(boxState)
val color = transition.animateColor(label = "color") state ->
when (state)
BoxState.Collapsed -> Color.Gray
BoxState.Expanded -> Color.Red
val size = transition.animateDp(label = "dp") state ->
when (state)
BoxState.Collapsed -> 64.dp
BoxState.Expanded -> 128.dp
return remember(transition) TransitionData(color, size)
updateTransition
可创建并记住Transition
的实例,并更新其状态。通过transition可以使用某个animateXXX
扩展方法来定义此过渡效果中的子动画。为每个状态指定目标值。(Transition
同时也有AnimatedVisibility
与 AnimatedContent
的拓展方法。)
具体实现的效果就是展开时128dp的红色方块,收起就是64dp的灰色方块。具体效果如下:
3. rememberInfiniteTransition
用来创建一个无限循环的动画。
比如我们创建一个红绿色过渡的无限循环动画,代码如下:
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().background(color))
篇幅有限,我们下一篇再介绍Animatable
与自定义动画部分。
参考
以上是关于Jetpack Compose 从入门到入门的主要内容,如果未能解决你的问题,请参考以下文章