艺术吃瓜 發表於 2023-8-24 15:24:00

Android开发 Jetpack Compose 动画

<p>                          本文来自博客园,作者:观心静&nbsp;,转载请注明原文链接:https://www.cnblogs.com/guanxinjing/p/17617292.html</p>
<div>                           本文版权归作者和博客园共有,欢迎转载,但必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利。</div>
<h1><span style="color: rgba(22, 145, 121, 1)">前言</span></h1>
<p><span style="color: rgba(0, 0, 0, 1)">  此篇博客讲解Jetpack Compose的动画实现。Compose的动画分两种:</span></p>
<ul>
<li><span style="color: rgba(0, 0, 0, 1)">一种是可以简单快捷使用的Animatable、AnimatedVisibility 、AnimatedContent动画,他们已经将使用进行的简单的封装。这其中AnimatedVisibility(动画控制显示与隐藏) 和 AnimatedContent(动画控制内容切换)已经封装成了容器组件</span></li>
<li><span style="color: rgba(0, 0, 0, 1)">另一种是稍微复杂的animateFloatAsState系列的属性动画,这个更底层可以完成更复杂组合的动画。</span></li>
</ul>
<p><span style="color: rgba(0, 0, 0, 1)">  此外除了上面的动画函数,你还需要了解插值器函数,他们会控制动画的执行中的效果或者持续时间,他们一共有以下几种:</span></p>
<ul>
<li><span style="color: rgba(0, 0, 0, 1)">tween:补间插值器,这个插值器可以让你设置动画的持续时间,这是最常用的插值器</span></li>
<li><span style="color: rgba(0, 0, 0, 1)">spring:&nbsp; 弹跳插值器,spring可以实现类似弹簧或者弹跳的动画效果,它不可以设置动画时间,但是可以设置阻尼值以调整回弹效果</span></li>
<li><span style="color: rgba(0, 0, 0, 1)">keyframes:关键帧插值器,关键帧动画可以逐帧设置当前动画的轨迹 </span></li>
<li><span style="color: rgba(0, 0, 0, 1)">snap:提前动画,直接不执行动画过程,直接到达动画结果</span></li>
</ul>
<p><span style="color: rgba(0, 0, 0, 1)">  官网文档:https://developer.android.google.cn/jetpack/compose/animation?hl=zh-cn</span></p>
<h1><span style="color: rgba(22, 145, 121, 1)">Animatable与tween插值器</span></h1>
<p>下面通过Animatable与tween插值器,实现了一个颜色渐变的动画效果。</p>
<p>效果图<br><img src="https://img2023.cnblogs.com/blog/1497956/202308/1497956-20230824163723459-865374799.gif" alt="" height="256" width="360"></p>
<p id="1692865430057">代码,代码中使用了tween插值器,此外easing动画数值曲线还有以下选择:</p>
<ul>
<li>FastOutSlowInEasing 快出慢进</li>
<li>FastOutLinearInEasing 先快出,后匀速线性进</li>
<li>LinearOutSlowInEasing 先匀速线性出,后快进</li>
<li>LinearEasing 匀速线性</li>
</ul>
<pre class="highlighter-hljs" data-dark-theme="true"><code>@Composable
fun ColorAnimation() {
    val color = remember { Animatable(Color.Gray) }
    LaunchedEffect(true) {
      color.animateTo(Color.Green,
            //delayMillis = 动画开始延迟时间 , durationMillis = 动画持续时间 , easing = 动画数值曲线
            animationSpec = tween(durationMillis = 3000, delayMillis = 1000, easing = LinearEasing)
      )
    }
    Box(modifier = Modifier.fillMaxSize()) {
      Surface(
            color = color.value,
            modifier = Modifier
                .size(100.dp)
                .align(Alignment.Center)
      ){}
    }
}</code></pre>
<h1><span style="color: rgba(22, 145, 121, 1)">spring弹跳插值器</span></h1>
<p>下面通过Animatable与spring插值器,实现了一个掉落动画。</p>
<p><strong><span style="color: rgba(186, 55, 42, 1)">请注意!下面的代码中,设置动画数值的函数是graphicsLayer。</span> </strong>这里不建议直接使用offset或者scale等等这些函数来说实现动画(可以在graphicsLayer里调用offset),因为直接offset会引起父类容器的重组,导致整个页面都在重组刷新,有些时候还会引起移动时抖动或者拖影的现象。 这里建议使用graphicsLayer会将重组范围限制到当前组件里,避免父类与其他组件都连带重组。</p>
<p>效果图</p>
<p><img src="https://img2023.cnblogs.com/blog/1497956/202308/1497956-20230824175520654-1574149219.gif" alt="" height="615" width="504"></p>
<p id="1692870921594"></p>
<p id="1692867711176">代码</p>
<pre class="highlighter-hljs" data-dark-theme="true"><code>@Composable
fun FallOffAnimation() {
    val yAnimation1 = remember { Animatable(0f) }
    val yAnimation2 = remember { Animatable(0f) }
    val yAnimation3 = remember { Animatable(0f) }
    val yAnimation4 = remember { Animatable(0f) }
    LaunchedEffect(true) {
      delay(2000)
      yAnimation1.animateTo(
            200f,
            //dampingRatio = 阻尼比 , stiffness = 刚度
            //DampingRatioHighBouncy = 阻尼比高弹性, StiffnessHigh = 刚度高
            animationSpec = spring(dampingRatio = Spring.DampingRatioHighBouncy, stiffness = Spring.StiffnessHigh)
      )

      yAnimation2.animateTo(
            200f,
            //DampingRatioHighBouncy = 阻尼比高弹性, StiffnessMediumLow = 刚度中低
            animationSpec = spring(dampingRatio = Spring.DampingRatioHighBouncy, stiffness = Spring.StiffnessMediumLow)
      )

      yAnimation3.animateTo(
            200f,
            //DampingRatioMediumBouncy = 阻尼比中弹性, StiffnessMedium = 刚度中
            animationSpec = spring(dampingRatio = Spring.DampingRatioMediumBouncy, stiffness = Spring.StiffnessMediumLow)
      )

      //DampingRatioNoBouncy = 阻尼比无弹性, StiffnessVeryLow = 刚度非常低
      yAnimation4.animateTo(
            200f,
            animationSpec = spring(dampingRatio = Spring.DampingRatioNoBouncy, stiffness = Spring.StiffnessVeryLow)
      )
    }
    Row(modifier = Modifier.fillMaxSize().padding(100.dp)) {
      Surface(
            color = Color.Green,
            modifier = Modifier
                .padding(horizontal = 30.dp)
                .size(40.dp)
                .graphicsLayer {
                  translationY = yAnimation1.value
                }
      ) {}

      Surface(
            color = Color.Green,
            modifier = Modifier
                .padding(horizontal = 30.dp)
                .size(40.dp)
                .graphicsLayer {
                  translationY = yAnimation2.value
                }
      ) {}

      Surface(
            color = Color.Green,
            modifier = Modifier
                .padding(horizontal = 30.dp)
                .size(40.dp)
                .graphicsLayer {
                  translationY = yAnimation3.value
                }
      ) {}

      Surface(
            color = Color.Green,
            modifier = Modifier
                .padding(horizontal = 30.dp)
                .size(40.dp)
                .graphicsLayer {
                  translationY = yAnimation4.value
                }
      ) {}
    }
}</code></pre>
<h1><span style="color: rgba(22, 145, 121, 1)">keyframes插值器</span></h1>
<p>下面通过Animatable与keyframes插值器,通过自定义设置动画帧的速度,实现了一个先快中间慢最后快的掉落动画。</p>
<p>效果图</p>
<p><img src="https://img2023.cnblogs.com/blog/1497956/202308/1497956-20230824183452127-1725867474.gif" alt="" height="615" width="146"></p>
<p id="1692873253582"></p>
<p id="1692873213457">代码</p>
<pre class="highlighter-hljs" data-dark-theme="true"><code>@Composable
fun FallOffAnimation() {
    val yAnimation1 = remember { Animatable(0f) }
    LaunchedEffect(true) {
      delay(2000)
      yAnimation1.animateTo(
            300f,
            animationSpec = keyframes {
                //动画持续时间
                durationMillis = 3000
                /*
                  简单说明下面代码意思:
                  下面代码实现了先以100毫秒从0f移动到100f的位置,然后以2800毫秒从100f移动到200f的位置,再接着以100毫秒从200f移动到最后的300f位置
               */
                0f at 0 with FastOutLinearInEasing //从0f位置0毫秒开始以FastOutLinearInEasing数值曲线运行到下一个阶段
                100f at 100 with LinearEasing //经过了100毫秒运动到100f此位置,现在从100f位置以LinearEasing数值曲线运行到下一个阶段
                200f at 2900 with FastOutLinearInEasing //经过了2800毫秒运动到200f此位置,现在从200f位置以LinearEasing数值曲线运行到300f结束
            }
      )
    }
    Row(modifier = Modifier
      .fillMaxSize()
      .padding(100.dp)) {
      Surface(
            color = Color.Green,
            modifier = Modifier
                .padding(horizontal = 30.dp)
                .size(40.dp)
                .graphicsLayer {
                  translationY = yAnimation1.value
                }
      ) {}
    }
}</code></pre>
<h1><span style="color: rgba(22, 145, 121, 1)">动画的重复</span></h1>
<p><span style="color: rgba(0, 0, 0, 1)">repeatable:可重复</span></p>
<p><span style="color: rgba(0, 0, 0, 1)">infiniteRepeatable:无限重复</span></p>
<p><span style="color: rgba(0, 0, 0, 1)">效果图</span></p>
<p><span style="color: rgba(0, 0, 0, 1)"><img src="https://img2023.cnblogs.com/blog/1497956/202308/1497956-20230825120016102-1088424296.gif" alt="" height="137" width="364"></span></p>
<p id="1692936017039">代码</p>
<pre class="highlighter-hljs" data-dark-theme="true"><code>@Composable
fun RotationAnimation() {
    val zRotationAnimation1 = remember { Animatable(0f) }
    val zRotationAnimation2 = remember { Animatable(0f) }
    //repeatable 设置重复次数
    LaunchedEffect(true) {
      zRotationAnimation1.animateTo(
            360f,
            //iterations 重复次数
            animationSpec = repeatable(iterations = 2, animation = tween(durationMillis = 1000))
      )
    }
    //infiniteRepeatable 无限重复
    LaunchedEffect(true) {
      zRotationAnimation2.animateTo(
            360f,
            animationSpec = infiniteRepeatable(animation = tween(durationMillis = 1000))
      )
    }
    Row(
      modifier = Modifier
            .fillMaxSize()
            .padding(100.dp)
    ) {
      Surface(
            color = Color.Green,
            modifier = Modifier
                .padding(horizontal = 30.dp)
                .size(40.dp)
                .graphicsLayer {
                  rotationZ = zRotationAnimation1.value
                }
      ) {}
      Surface(
            color = Color.Green,
            modifier = Modifier
                .padding(horizontal = 30.dp)
                .size(40.dp)
                .graphicsLayer {
                  rotationZ = zRotationAnimation2.value
                }
      ) {}
    }
}</code></pre>
<h1><span style="color: rgba(22, 145, 121, 1)">AnimatedVisibility 隐藏显示动画</span></h1>
<h2><span style="color: rgba(35, 111, 161, 1)">默认效果</span></h2>
<p><span style="color: rgba(0, 0, 0, 1)">效果图</span></p>
<p><span style="color: rgba(0, 0, 0, 1)"><img src="https://img2023.cnblogs.com/blog/1497956/202308/1497956-20230809164814619-305662434.gif" alt="" height="728" width="413"></span></p>
<p id="1691570895696">代码</p>
<pre class="highlighter-hljs" data-dark-theme="true"><code>@Composable
fun APage() {
    val imageVisible = remember {
      mutableStateOf(true)
    }
    //这边用一个协程,以1500毫秒反复显示与隐藏
    LaunchedEffect(true){
      for (i in 0..10){
            delay(1500)
            imageVisible.value = !imageVisible.value
      }
    }
    Box(modifier = Modifier.fillMaxSize()) {
      AnimatedVisibility(visible = imageVisible.value, modifier = Modifier.align(Alignment.Center)) {
            Image(
                painter = painterResource(id = R.mipmap.ic_fruit_apple),
                contentDescription = null,
                modifier = Modifier
                  .size(100.dp)
            )
      }
    }
}</code></pre>
<h2><span style="color: rgba(35, 111, 161, 1)">淡入淡出效果 fadeIn与fadeOut</span></h2>
<p><span style="color: rgba(0, 0, 0, 1)"><span style="color: rgba(35, 111, 161, 1)">效果图</span></span></p>
<p><span style="color: rgba(35, 111, 161, 1)"><img src="https://img2023.cnblogs.com/blog/1497956/202308/1497956-20230810153025510-743896869.gif" alt="" height="138" width="185"></span></p>
<p id="1691650190875">代码</p>
<pre class="highlighter-hljs" data-dark-theme="true"><code>@Composable
fun APage() {
    val imageVisible = remember {
      mutableStateOf(true)
    }
    //这边用一个协程,以1500毫秒反复显示与隐藏
    LaunchedEffect(true) {
      for (i in 0..50) {
            delay(1500)
            imageVisible.value = !imageVisible.value
      }
    }
    Box(
      modifier = Modifier.fillMaxSize()
    ) {
      //&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;淡入淡出效果 fadeIn与fadeOut
      AnimatedVisibility(
            modifier = Modifier.align(Alignment.Center),
            visible = imageVisible.value,
            enter = fadeIn(
                initialAlpha = 0f
            ),
            exit = fadeOut(targetAlpha = 0f)
      ) {
            Image(
                painter = painterResource(id = R.mipmap.ic_fruit_watermelon),
                contentDescription = null,
                modifier = Modifier
                  .size(100.dp)
            )
      }
    }
}</code></pre>
<h2><span style="color: rgba(35, 111, 161, 1)">滑入滑出 slideIn与slideOut</span></h2>
<p><span style="color: rgba(0, 0, 0, 1)">效果图</span></p>
<p><span style="color: rgba(35, 111, 161, 1)"><img src="https://img2023.cnblogs.com/blog/1497956/202308/1497956-20230810154008888-977440664.gif" alt="" height="185" width="216"></span></p>
<p id="1691653209638">代码</p>
<pre class="highlighter-hljs" data-dark-theme="true"><code>@Composable
fun APage() {
    val imageVisible = remember {
      mutableStateOf(true)
    }
    //这边用一个协程,以1500毫秒反复显示与隐藏
    LaunchedEffect(true) {
      for (i in 0..50) {
            delay(1500)
            imageVisible.value = !imageVisible.value
      }
    }
    Box(
      modifier = Modifier.fillMaxSize()
    ) {
      //&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;滑入滑出 slideIn与slideOut
      AnimatedVisibility(
            modifier = Modifier.align(Alignment.Center),
            visible = imageVisible.value,
            enter = slideIn { IntOffset(-100, -100) },
            exit = slideOut { IntOffset(100, 100) }
      ) {
            Image(
                painter = painterResource(id = R.mipmap.ic_fruit_watermelon),
                contentDescription = null,
                modifier = Modifier
                  .size(100.dp)
            )
      }
    }
}</code></pre>
<h2><span style="background-color: rgba(255, 255, 255, 1); color: rgba(35, 111, 161, 1)">水平滑入与水平滑出 slideInHorizontally与slideOutHorizontally</span></h2>
<p><span style="background-color: rgba(255, 255, 255, 1)">效果图</span></p>
<p><img src="https://img2023.cnblogs.com/blog/1497956/202308/1497956-20230810154742370-653856240.gif" alt="" height="185" width="216"></p>
<p id="1691653663283">代码</p>
<pre class="highlighter-hljs" data-dark-theme="true"><code>@Composable
fun APage() {
    val imageVisible = remember {
      mutableStateOf(true)
    }
    //这边用一个协程,以1500毫秒反复显示与隐藏
    LaunchedEffect(true) {
      for (i in 0..50) {
            delay(1500)
            imageVisible.value = !imageVisible.value
      }
    }
    Box(
      modifier = Modifier.fillMaxSize()
    ) {
      //&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;水平滑入与水平滑出 slideInHorizontally与slideOutHorizontally
      AnimatedVisibility(
            modifier = Modifier.align(Alignment.Center),
            visible = imageVisible.value,
            enter = slideInHorizontally(initialOffsetX = {fullWidth-&gt;
                return@slideInHorizontally -(fullWidth/2)
            }),
            exit = slideOutHorizontally(targetOffsetX = {fullWidth-&gt;
                return@slideOutHorizontally fullWidth/2
            })
      ) {
            Image(
                painter = painterResource(id = R.mipmap.ic_fruit_watermelon),
                contentDescription = null,
                modifier = Modifier
                  .size(100.dp)
            )
      }
    }
}</code></pre>
<h2><span style="color: rgba(35, 111, 161, 1)">垂直滑入与垂直滑出 slideInVertically与slideOutVertically</span></h2>
<p>效果图</p>
<p><img src="https://img2023.cnblogs.com/blog/1497956/202308/1497956-20230810155956790-1605414109.gif" alt="" height="200" width="216"></p>
<p>代码</p>
<pre class="highlighter-hljs" data-dark-theme="true"><code>@Composable
fun APage() {
    val imageVisible = remember {
      mutableStateOf(true)
    }
    //这边用一个协程,以1500毫秒反复显示与隐藏
    LaunchedEffect(true) {
      for (i in 0..50) {
            delay(1500)
            imageVisible.value = !imageVisible.value
      }
    }
    Box(
      modifier = Modifier.fillMaxSize()
    ) {
      //&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;垂直滑入与垂直滑出 slideInVertically与slideOutVertically
      AnimatedVisibility(
            modifier = Modifier.align(Alignment.Center),
            visible = imageVisible.value,
            enter = slideInVertically(initialOffsetY = {fullHeight-&gt;
                return@slideInVertically -(fullHeight/2)
            }),
            exit = slideOutVertically(targetOffsetY = {fullHeight-&gt;
                return@slideOutVertically fullHeight/2
            })
      ) {
            Image(
                painter = painterResource(id = R.mipmap.ic_fruit_watermelon),
                contentDescription = null,
                modifier = Modifier
                  .size(100.dp)
            )
      }
    }
}</code></pre>
<h2><span style="color: rgba(35, 111, 161, 1)">缩放进入与缩放退出 scaleIn与scaleOut</span></h2>
<p><span style="color: rgba(35, 111, 161, 1)">效果图</span></p>
<p><span style="color: rgba(35, 111, 161, 1)"><img src="https://img2023.cnblogs.com/blog/1497956/202308/1497956-20230810165513063-1771028049.gif" alt="" height="200" width="216"></span></p>
<p id="1691657713730">代码</p>
<pre class="highlighter-hljs" data-dark-theme="true"><code>@OptIn(ExperimentalAnimationApi::class)
@Composable
fun APage() {
    val imageVisible = remember {
      mutableStateOf(true)
    }
    //这边用一个协程,以1500毫秒反复显示与隐藏
    LaunchedEffect(true) {
      for (i in 0..50) {
            delay(1500)
            imageVisible.value = !imageVisible.value
      }
    }
    Box(
      modifier = Modifier.fillMaxSize()
    ) {
      //&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;缩放进入与缩放退出 scaleIn与scaleOut
      AnimatedVisibility(
            modifier = Modifier.align(Alignment.Center),
            visible = imageVisible.value,
            enter = scaleIn(animationSpec = spring(),initialScale = 0.1f, transformOrigin = TransformOrigin.Center),
            exit = scaleOut(animationSpec = spring(),targetScale = 0.1f, transformOrigin = TransformOrigin.Center)
      ) {
            Image(
                painter = painterResource(id = R.mipmap.ic_fruit_watermelon),
                contentDescription = null,
                modifier = Modifier
                  .size(100.dp)
            )
      }
    }
}</code></pre>
<h2><span style="color: rgba(35, 111, 161, 1)">扩展与收缩 expandIn与shrinkOut</span></h2>
<p><span style="color: rgba(0, 0, 0, 1)">效果图</span></p>
<p><span style="color: rgba(35, 111, 161, 1)"><img src="https://img2023.cnblogs.com/blog/1497956/202308/1497956-20230810170500786-2145406295.gif" alt="" height="200" width="216"></span></p>
<p id="1691658301465">代码</p>
<pre class="highlighter-hljs" data-dark-theme="true"><code>@Composable
fun APage() {
    val imageVisible = remember {
      mutableStateOf(true)
    }
    //这边用一个协程,以1500毫秒反复显示与隐藏
    LaunchedEffect(true) {
      for (i in 0..50) {
            delay(1500)
            imageVisible.value = !imageVisible.value
      }
    }
    Box(
      modifier = Modifier.fillMaxSize()
    ) {
      //&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;扩展与收缩 expandIn与shrinkOut
      AnimatedVisibility(
            modifier = Modifier.align(Alignment.Center),
            visible = imageVisible.value,
            enter = expandIn(),
            exit = shrinkOut()
      ) {
            Image(
                painter = painterResource(id = R.mipmap.ic_fruit_watermelon),
                contentDescription = null,
                modifier = Modifier
                  .size(100.dp)
            )
      }
    }
}</code></pre>
<h2><span style="color: rgba(35, 111, 161, 1)">水平扩展与水平收缩 expandHorizontally与shrinkHorizontally</span></h2>
<p><span style="color: rgba(0, 0, 0, 1)">效果图</span></p>
<p><span style="color: rgba(35, 111, 161, 1)"><img src="https://img2023.cnblogs.com/blog/1497956/202308/1497956-20230810174232010-142753408.gif" alt="" height="200" width="216"></span></p>
<p id="1691660553060">代码</p>
<pre class="highlighter-hljs" data-dark-theme="true"><code>@Composable
fun APage() {
    val imageVisible = remember {
      mutableStateOf(true)
    }
    //这边用一个协程,以1500毫秒反复显示与隐藏
    LaunchedEffect(true) {
      for (i in 0..50) {
            delay(1500)
            imageVisible.value = !imageVisible.value
      }
    }
    Box(
      modifier = Modifier.fillMaxSize()
    ) {
      //&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;水平扩展与水平收缩 expandHorizontally与shrinkHorizontally
      AnimatedVisibility(
            modifier = Modifier.align(Alignment.Center),
            visible = imageVisible.value,
            enter = expandHorizontally(),
            exit = shrinkHorizontally()
      ) {
            Image(
                painter = painterResource(id = R.mipmap.ic_fruit_watermelon),
                contentDescription = null,
                modifier = Modifier
                  .size(100.dp)
            )
      }
    }
}</code></pre>
<h2><span style="color: rgba(35, 111, 161, 1)">垂直扩展与垂直收缩 expandVertically与shrinkVertically</span></h2>
<p>效果图</p>
<p><img src="https://img2023.cnblogs.com/blog/1497956/202308/1497956-20230810174426354-785718588.gif" alt="" height="200" width="216"></p>
<p id="1691660667027">代码</p>
<pre class="highlighter-hljs" data-dark-theme="true"><code>@Composable
fun APage() {
    val imageVisible = remember {
      mutableStateOf(true)
    }
    //这边用一个协程,以1500毫秒反复显示与隐藏
    LaunchedEffect(true) {
      for (i in 0..50) {
            delay(1500)
            imageVisible.value = !imageVisible.value
      }
    }
    Box(
      modifier = Modifier.fillMaxSize()
    ) {
      //&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;垂直扩展与垂直收缩 expandVertically与shrinkVertically
      AnimatedVisibility(
            modifier = Modifier.align(Alignment.Center),
            visible = imageVisible.value,
            enter = expandVertically(),
            exit = shrinkVertically()
      ) {
            Image(
                painter = painterResource(id = R.mipmap.ic_fruit_watermelon),
                contentDescription = null,
                modifier = Modifier
                  .size(100.dp)
            )
      }
    }
}</code></pre>
<h1><span style="color: rgba(22, 145, 121, 1)">AnimatedContent 内容切换动画</span></h1>
<p><span style="color: rgba(0, 0, 0, 1)">请注意,AnimatedContent还是实验性API,此外AnimatedContent内容切换动画 &nbsp;与 上面的AnimatedVisibility 隐藏显示动画在使用上非常相似,这里稍微举例几个切换效果,其他效果的实现可以参考上面的AnimatedVisibility例子</span></p>
<h2><span style="color: rgba(0, 0, 0, 1)">垂直滑动切换动画</span></h2>
<p><span style="color: rgba(0, 0, 0, 1)">效果图</span></p>
<p><span style="color: rgba(0, 0, 0, 1)"><img src="https://img2023.cnblogs.com/blog/1497956/202308/1497956-20230825160517536-340038328.gif" alt="" height="234" width="276"></span></p>
<p id="1692950718264">代码</p>
<p><strong>代码中的<span style="color: rgba(186, 55, 42, 1)">with</span>连接方法非常重要,它连接了动画的进入与退出效果</strong></p>
<pre class="highlighter-hljs" data-dark-theme="true"><code>@OptIn(ExperimentalAnimationApi::class)
@Composable
fun VerticallyAnimatedContent() {
    val data = remember { mutableStateOf(0) }
    Box(
      modifier = Modifier
            .fillMaxSize()
    ) {
      AnimatedContent(targetState = data.value, transitionSpec = {
            slideInVertically { fullHeight -&gt; return@slideInVertically -fullHeight } with
                  slideOutVertically { fullHeight -&gt; return@slideOutVertically fullHeight   }
      }, modifier = Modifier.align(Alignment.Center)) {
            Text(text = "${data.value}", fontSize = 100.sp, modifier = Modifier.clickable {
                data.value++
            })
      }
    }
}</code></pre>
<h2>横向滑动+淡入淡出的组合切换动画</h2>
<p>效果图</p>
<p><img src="https://img2023.cnblogs.com/blog/1497956/202308/1497956-20230825162224863-182800143.gif" alt="" height="234" width="276"></p>
<p id="1692951745660">代码</p>
<p>请注意下面的写法 slideInHorizontally + fadeIn with slideOutHorizontally + fadeOut</p>
<pre class="highlighter-hljs" data-dark-theme="true"><code>@OptIn(ExperimentalAnimationApi::class)
@Composable
fun HorizontallyAnimatedContent() {
    val data = remember { mutableStateOf(0) }
    Box(
      modifier = Modifier
            .fillMaxSize()
    ) {
      AnimatedContent(targetState = data.value, transitionSpec = {
            slideInHorizontally(
                initialOffsetX = { fullWidth -&gt;
                  return@slideInHorizontally -fullWidth
                }) + fadeIn(initialAlpha = 0f, animationSpec = tween(1000)) with
                  slideOutHorizontally(targetOffsetX = { fullWidth -&gt; return@slideOutHorizontally fullWidth }) + fadeOut(
                targetAlpha = 0f,
                animationSpec = tween(1000)
            )
      }, modifier = Modifier.align(Alignment.Center)) {
            Text(text = "${data.value}", fontSize = 100.sp, modifier = Modifier.clickable {
                data.value++
            })
      }
    }
}</code></pre>
<h1><span style="color: rgba(22, 145, 121, 1)">属性动画</span></h1>
<p><span style="color: rgba(0, 0, 0, 1)">一共有如下类型值的属性动画函数,他们的使用都是类似的。此外这些属性动画函数还需要配合插值器使用。</span></p>
<ul>
<li>animateValueAsState : 其他的animateXxxAsState内部都是调用的这个</li>
<li>animateRectAsState : 参数是传的一个Rect对象,Rect(left,top,right,bottom)</li>
<li>animateIntAsState : 参数传的是Int</li>
<li>animateDpAsState : 参数传的是Dp</li>
<li>animateFloatAsState : 参数传的是Float</li>
<li>animateColorAsState : 参数传的是Color</li>
<li>animateOffsetAsState : 参数传的是Offset,Offset(x,y),x和y是Float类型</li>
<li>animateIntOffsetAsState : 参数传的是IntOffset,IntOffset(x,y),x和y是Int类型</li>
<li>animateSizeAsState : 参数传的是Size,Size(width,height),width和height是Float类型</li>
<li>animateIntSizeAsState : 参数传的是IntSize,IntSize(width,height),width和height是Int类型</li>
</ul>
<h2><span style="color: rgba(35, 111, 161, 1)">animateFloatAsState与spring插值器</span></h2>
<p>上面的属性动画函数使用都是类似的,这里以animateFloatAsState与spring插值器来举例</p>
<h3><span style="color: rgba(35, 111, 161, 1)">y轴移动动画</span></h3>
<p><span style="color: rgba(0, 0, 0, 1)">效果图</span></p>
<p><span style="color: rgba(35, 111, 161, 1)"><img src="https://img2023.cnblogs.com/blog/1497956/202308/1497956-20230811094504197-290428817.gif" alt="" height="732" width="411"></span></p>
<p id="1691718305030">代码</p>
<pre class="highlighter-hljs" data-dark-theme="true"><code>@Composable
fun APage() {
    val startAnim = remember {
      mutableStateOf(false)
    }
    //创建y轴的动画数值
    val yAnimValue = animateDpAsState(
      //targetValue为动画的目标数值
      targetValue = if (startAnim.value) 200.dp else 0.dp,
      //animationSpec动画的可选插值器,目前的结尾弹跳效果就是spring这个插值器实现的
      animationSpec = spring(
            dampingRatio = Spring.DampingRatioHighBouncy,
            stiffness = Spring.StiffnessMedium
      )
    )

    Box(
      modifier = Modifier.fillMaxSize()
    ) {
      Surface(color = Color.Gray, modifier = Modifier
            .graphicsLayer {
                translationY = yAnimValue.value.toPx()
            }
            .size(50.dp)
            .align(Alignment.Center)
            .clickable {
                //点击启动动画
                startAnim.value = !startAnim.value
            }) {

      }
    }
}</code></pre>
<h2><span style="color: rgba(35, 111, 161, 1)">tween插值器</span></h2>
<p>tween插值器可以设置动画时间</p>
<p>效果图</p>
<p><img src="https://img2023.cnblogs.com/blog/1497956/202308/1497956-20230824152250203-973875647.gif" alt="" height="371" width="417"></p>
<p id="1692861771003"></p>
<p>代码</p>
<pre class="highlighter-hljs" data-dark-theme="true"><code>@Composable
fun Animation() {
    val position = remember {
      mutableStateOf(Offset(0f, 0f))
    }
    val duration = remember {
      mutableStateOf(3000)
    }
    //动画数值
    val animValue = animateOffsetAsState(
      targetValue = position.value,
      animationSpec = tween(durationMillis = duration.value, easing = LinearEasing)
    )
    //移动位置集合
    val starList = listOf(
      //第一个参数是坐标,第二个参数延迟时间
      Offset(-100f, -100f) to 1000,//左上
      Offset(-100f, 100f) to 1000,//左下
      Offset(100f, 100f) to 1000,//右下
      Offset(100f, -100f) to 1000,//右上
    )

    LaunchedEffect(true) {
      delay(2000)
      for (item in starList) {
            position.value = item.first
            duration.value = item.second
            delay(duration.value.toLong())
      }
    }
    Box(modifier = Modifier.fillMaxSize()) {
      Image(
            painter = painterResource(id = R.mipmap.ic_red_dot),
            contentDescription = null,
            modifier = Modifier
                .graphicsLayer {
                  translationX = animValue.value.x
                  translationY = animValue.value.y
                }
                .align(Alignment.Center)
      )
    }
}</code></pre>
<h2><span style="color: rgba(35, 111, 161, 1)">在Canvas里的使用例子</span></h2>
<p>效果图</p>
<p><img src="https://img2023.cnblogs.com/blog/1497956/202308/1497956-20230821140550247-1268261284.gif" alt=""></p>
<p id="1692597965114">代码</p>
<pre class="highlighter-hljs" data-dark-theme="true"><code>
@Composable
fun AnimationBg() {
    //-400.dp 到 -1500.dp 动画范围值
    val yPosition = remember {
      mutableStateOf(-400.dp)
    }
    //y轴动画数值
    val yAnimValue = animateDpAsState(
      targetValue = yPosition.value,
      animationSpec = tween(durationMillis = 15_000, easing = LinearEasing)
    )
    val rotate = remember {
      mutableStateOf(0f)
    }
    //旋转动画,请注意这里使用的是animateFloatAsState
    val rotateAnimValue = animateFloatAsState(
      targetValue = rotate.value,
      animationSpec = tween(durationMillis = 5000, easing = LinearEasing)
    )
    LaunchedEffect(true) {
      var isFront = true
      val angleList = listOf&lt;Float&gt;(0f, 45f, 90f, 135f)
      var count = 0
      while (isActive) {
            isFront = !isFront
            if (isFront) {
                yPosition.value = -400.dp
            } else {
                yPosition.value = -1500.dp
            }
            delay(15_000)
            count++
            if (count &gt; 2){
                count = 0
                rotate.value = angleList.random()
            }
      }
    }
    Canvas(
      modifier = Modifier
            .fillMaxSize()
            .rotate(rotateAnimValue.value)
    ) {
      val itemHeight = size.height / 30
      for (index in 0..200) {
            if (index % 2 == 0) {
                //黑色
                drawLine(
                  color = Color.Black,
                  start = Offset(-500f, itemHeight * index + yAnimValue.value.toPx()),
                  end = Offset(size.width + 500f, itemHeight * index + yAnimValue.value.toPx()),
                  strokeWidth = itemHeight
                )
            } else {
                //白色
                drawLine(
                  color = Color.White,
                  start = Offset(-500f, itemHeight * index + yAnimValue.value.toPx()),
                  end = Offset(size.width + 500f, itemHeight * index + yAnimValue.value.toPx()),
                  strokeWidth = itemHeight
                )
            }
      }
    }
}</code></pre>
<p>&nbsp;</p>
<p>end</p>

</div>
<div id="MySignature" role="contentinfo">
    <div style="text-align: center">
    <p style="color:orange;font-size:16px;" >本文来自博客园,作者:观心静 ,转载请注明原文链接:https://www.cnblogs.com/guanxinjing/p/17617292.html </p>
    <div style="color:orange;font-size:16px;">本文版权归作者和博客园共有,欢迎转载,但必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利。 </div>
</div><br><br>
来源:https://www.cnblogs.com/guanxinjing/p/17617292.html
頁: [1]
查看完整版本: Android开发 Jetpack Compose 动画