Compose 动画 (三) : AnimatedVisibility 从入门到深入,实现组件的隐藏和显示

  • Post author:
  • Post category:其他




1. AnimatedVisibility 是什么


AnimatedVisibility



Android Compose

中的一个高级别动画

API



可以实现

Compose组件

的显示和隐藏,并且可以指定显示/隐藏时候的动画效果。(

EnterTransition/ExitTransition

)



animateXxxAsState



animateContentSize



Crossfade



AnimatedContent

这几个API一起,都是

Compose

的高级别动画API,是比较易用的。



2. AnimatedVisibility 的基础使用

@Composable
fun AnimatedVisibilityPage() {
    Column(horizontalAlignment = Alignment.CenterHorizontally) {
        var visible by remember {
            mutableStateOf(true)
        }
        AnimatedVisibility(visible = visible) {
	        MyImage()
	    }
        Spacer(modifier = Modifier.height(10.dp))
        Button(onClick = { visible = !visible }) {
            Text(text = "显示/隐藏")
        }
    }
}

@Composable
private fun MyImage() {
    Image(
        painter = painterResource(id = R.mipmap.photot1),
        modifier = Modifier.width(300.dp),
        contentDescription = null
    )
}

看上去是不是很简单,只需要在

Image

外层包上

AnimatedVisibility

就可以了,显示效果如下

在这里插入图片描述

我们点进

AnimatedVisibility

的源码,可以看到如下代码

@Composable
fun ColumnScope.AnimatedVisibility(
    visible: Boolean,
    modifier: Modifier = Modifier,
    enter: EnterTransition = fadeIn() + expandVertically(),
    exit: ExitTransition = fadeOut() + shrinkVertically(),
    label: String = "AnimatedVisibility",
    content: @Composable AnimatedVisibilityScope.() -> Unit
){
    val transition = updateTransition(visible, label)
    AnimatedEnterExitImpl(transition, { it }, modifier, enter, exit, content)
}

可以发现其内部调用了

updateTransition

,该函数内部会返回

Transition

对象。

@Composable
fun <T> updateTransition(
    targetState: T,
    label: String? = null
): Transition<T> {
	//...省略...
}


Transition

可管理一个或多个动画作为其子项,并在多个状态之间同时运行这些动画。

这个我们后续文章会讲到,现在先知道有这样一个概念就好。



3. 入场和出场动画


enter



exit

,用来配置

入场/出场

时候的动画效果。

默认的入场效果是

fadeIn() + expandVertically()


默认的出场效果是

fadeOut() + shrinkVertically()



1.

EnterTransition



ExitTransition

支持的动画


enter

的参数类型是

EnterTransition

,支持这些动画

  • 淡入 :

    fade: fadeIn
  • 缩放 :

    scale: scaleIn
  • 滑动 :

    slide: slideIn, slideInHorizontally, slideInVertically
  • 展开 :

    expand: expandIn, expandHorizontally, expandVertically


exit

的参数类型是

EnterTransition

,支持这些动画

  • 淡出 :

    fade: fadeOut
  • 缩放 :

    scale: scaleOut
  • 滑动 :

    slide: slideOut, slideOutHorizontally, slideOutVertically
  • 收缩 :

    shrink: shrinkOut, shrinkHorizontally, shrinkVertically

可以看到

EnterTransition



ExitTransition

支持的动画只有

expand



shrink

命名上有区别,

其他都是对应的

fadeIn



fadeOut



scaleIn



scaleOut



slideIn



slideOut




expand



shrink

命名上做区分,是因为

expand

就是

展开

的意思,而

shrink



收缩

的意思,它们其实就是相对应的。

具体

EnterTransition



ExitTransition

的动画效果,可以看我的这篇文章 :

Compose AnimatedVisibility 各种入场和出场动画效果



3.2

EnterTransition



ExitTransition

的源码

再来看下源码,入场动画

EnterTransition

和出场动画

ExitTransition

内部都有

TransitionData

变量

sealed class EnterTransition {
    internal abstract val data: TransitionData
    //...
}
sealed class ExitTransition {
    internal abstract val data: TransitionData
    //...
}


TransitionData

即是可配置的动画参数,分别对应

fade



slide



expand/shrink



scale

internal data class TransitionData(
    val fade: Fade? = null,
    val slide: Slide? = null,
    val changeSize: ChangeSize? = null,
    val scale: Scale? = null
)

我们可以发现 EnterTransition 和 ExitTransition 是 sealed class,密封类

其子类可以出现在定义 sealed class 的不同文件中,但不允许出现在与不同的 module 中,且需要保证 package 一致

这样既可以避免 sealed class 文件过于庞大,又可以确保第三方库无法扩展你定义的 sealed class,达到限制类的扩展目的



3.3

+

号的作用


AnimatedVisibility

源码的部分,入场(

enter

)和出场(

exit

)的配置,使用了

+

,这个加号是用来做什么的呢 ?

首先,

+

号是

Kotlin

的一个特性 :

重载运算符

我们点击这个

+

号,就可以跳转到它的源码

@Stable
operator fun plus(enter: EnterTransition): EnterTransition {
        return EnterTransitionImpl(
            TransitionData(
                fade = data.fade ?: enter.data.fade,
                slide = data.slide ?: enter.data.slide,
                changeSize = data.changeSize ?: enter.data.changeSize,
                scale = data.scale ?: enter.data.scale
            )
        )
    }

如果

data

不为空,就用

data

的值,否则用

enter/exit

的。

这里的

data

就是

EnterTransition



ExitTransition

中的那个变量

internal abstract val data: TransitionData

(见 3.1

EnterTransition



ExitTransition

支持的动画部分)

所以

fadeIn() + expandVertically()



fadeIn()

会赋值给

TransitionData



fade



expandVertically

会赋值给

changeSize

即 :

合并各个动画的效果



3.3.1 两个相同的动画会有什么效果

如果是两个相同的动画,比如

fadeIn(initialAlpha = 0.3f) + fadeIn(initialAlpha = 0.5f)


根据源码中的这个规则

如果data不为空,就用data的值,否则用enter/exit的。

可知 :

两个

fade

,会重叠了,导致后面那部分不会生效,等同于

fadeIn(initialAlpha = 0.3f)



+

号左边优先级高



3.4 多种动画效果结合

我们来尝试下多种动画效果,使用

+

号合并之后的效果

val density = LocalDensity.current
AnimatedVisibility(visible = visible,
    enter = slideInVertically {
        //从顶部-200dp的位置开始滑入
        with(density) { -200.dp.roundToPx() }
    } + expandHorizontally(
        //展开位置
        expandFrom = Alignment.End
    ) + fadeIn(
        //从初始透明度0.3f开始淡入
        initialAlpha = 0.3f
    ),
    exit = slideOutHorizontally() + shrinkHorizontally() + fadeOut()
) {
    MyImage()
}

效果如下所示

在这里插入图片描述



4. 不同的作用域



4.1 AnimatedVisibility的作用域


AnimatedVisibility

有好几种作用域,区别在于在这几种布局中,默认的

入场动画



出场动画

是不同的


Column

默认的出入场动画具有

垂直展开

,而

Row

的出入场动画具有

横向展开



Transition



无前缀

的出入场动画是

展开/收缩

Column

fun ColumnScope.AnimatedVisibility(
    visible: Boolean,
    modifier: Modifier = Modifier,
    enter: EnterTransition = fadeIn() + expandVertically(),
    exit: ExitTransition = fadeOut() + shrinkVertically(),
    label: String = "AnimatedVisibility",
    content: @Composable AnimatedVisibilityScope.() -> Unit
)

Row

fun RowScope.AnimatedVisibility(
    visible: Boolean,
    modifier: Modifier = Modifier,
    enter: EnterTransition = fadeIn() + expandHorizontally(),
    exit: ExitTransition = fadeOut() + shrinkHorizontally(),
    label: String = "AnimatedVisibility",
    content: @Composable() AnimatedVisibilityScope.() -> Unit
)

Transition

@ExperimentalAnimationApi //实验的动画API
@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)

无前缀

fun AnimatedVisibility(
    visible: Boolean,
    modifier: Modifier = Modifier,
    enter: EnterTransition = fadeIn() + expandIn(),
    exit: ExitTransition = shrinkOut() + fadeOut(),
    label: String = "AnimatedVisibility",
    content: @Composable() AnimatedVisibilityScope.() -> Unit
)



4.2 使用不了

AnimatedVisibility

如果我们在

Column

里面有个

Box



Box

里面又有

AnimatedVisibility

,会发现

AnimatedVisibility

会报错

在这里插入图片描述

我们把鼠标移到这个报红的地方,可以看到如下的提示

在这里插入图片描述

这边提示

'fun ColumnScope.AnimatedVisibility(visible: Boolean, modifier: Modifier = ..., enter: EnterTransition = ..., exit: ExitTransition = ..., label: String = ..., content: AnimatedVisibilityScope.() -> Unit): Unit' can't be called in this context by implicit receiver. Use the explicit one if necessary

注意最后一句

也就是说,不能使用隐式调用,而必须得用显式的。因为

AnimatedVisibility

有好几种作用域,

ColumnScope

和全局的作用域,

IDE

不知道该引用哪个了。

我们可以显示添加

this@Column.

,这样,就会引用

Column

的那个

AnimatedVisibility



在这里插入图片描述

当然也可以包装一层

Compose

函数,使用全局作用域的

AnimatedVisibility


在这里插入图片描述

这两种都是可行的



5. Compose 动画系列

Compose 动画系列,后续持续更新,可以先关注


Compose 动画 (一) : animateXxxAsState 实现放大/缩小/渐变等效果



Compose 动画 (二) : 为什么animateDpAsState要用val ? MutableState和State有什么区别 ?



Compose 动画 (三) : AnimatedVisibility 从入门到深入



Compose 动画 (四) : AnimatedVisibility 各种入场和出场动画效果



版权声明:本文为EthanCo原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。