【问题标题】:Scaling Button Animation in Jetpack Compose在 Jetpack Compose 中缩放按钮动画
【发布时间】:2025-12-14 20:30:01
【问题描述】:

我想用 Jetpack Compose 从 AirBnB 应用程序中构建这个很棒的按钮动画

不幸的是,Animation/Transition API 最近发生了变化,几乎没有相关文档。有人可以帮我找到正确的方法来实现这个按钮按下动画吗?

编辑 根据@Amirhosein 的回答,我开发了一个按钮,看起来几乎与 Airbnb 示例一模一样

代码:

@Composable
fun AnimatedButton() {
    val boxHeight = animatedFloat(initVal = 50f)
    val relBoxWidth = animatedFloat(initVal = 1.0f)
    val fontSize = animatedFloat(initVal = 16f)

    fun animateDimensions() {
        boxHeight.animateTo(45f)
        relBoxWidth.animateTo(0.95f)
       // fontSize.animateTo(14f)
    }

    fun reverseAnimation() {
        boxHeight.animateTo(50f)
        relBoxWidth.animateTo(1.0f)
        //fontSize.animateTo(16f)
    }

        Box(
        modifier = Modifier
            .height(boxHeight.value.dp)
            .fillMaxWidth(fraction = relBoxWidth.value)

            .clip(RoundedCornerShape(8.dp))
            .background(Color.Black)
            .clickable { }
            .pressIndicatorGestureFilter(
                onStart = {
                    animateDimensions()
                },
                onStop = {
                    reverseAnimation()
                },
                onCancel = {
                    reverseAnimation()
                }
            ),
        contentAlignment = Alignment.Center
    ) {
        Text(text = "Explore Airbnb", fontSize = fontSize.value.sp, color = Color.White)
    }
}

视频:

不幸的是,我无法弄清楚如何正确地为文本设置动画,因为它目前看起来很糟糕

【问题讨论】:

    标签: android android-jetpack-compose


    【解决方案1】:

    你在寻找这样的东西吗?

    @Composable
    fun AnimatedButton() {
        val selected = remember { mutableStateOf(false) }
        val scale = animateFloatAsState(if (selected.value) 2f else 1f)
    
        Column(
            Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Button(
                onClick = {  },
                modifier = Modifier
                    .scale(scale.value)
                    .height(40.dp)
                    .width(200.dp)
                    .pointerInteropFilter {
                        when (it.action) {
                            MotionEvent.ACTION_DOWN -> {
                                selected.value = true }
    
                            MotionEvent.ACTION_UP  -> {
                               selected.value = false }
                        }
                        true
                    }
            ) {
                Text(text = "Explore Airbnb", fontSize = 15.sp, color = Color.White)
            }
        }
    }
    

    【讨论】:

      【解决方案2】:

      使用pressIndicatorGestureFilter 来实现此行为。

      这是我的解决方法:

      @Preview
      @Composable
      fun MyFancyButton() {
      val boxHeight = animatedFloat(initVal = 60f)
      val boxWidth = animatedFloat(initVal = 200f)
          Box(modifier = Modifier
              .height(boxHeight.value.dp)
              .width(boxWidth.value.dp)
              .clip(RoundedCornerShape(4.dp))
              .background(Color.Black)
              .clickable { }
              .pressIndicatorGestureFilter(
                  onStart = {
                      boxHeight.animateTo(55f)
                      boxWidth.animateTo(180f)
                  },
                  onStop = {
                      boxHeight.animateTo(60f)
                      boxWidth.animateTo(200f)
                  },
                  onCancel = {
                      boxHeight.animateTo(60f)
                      boxWidth.animateTo(200f)
                  }
              ), contentAlignment = Alignment.Center) {
                 Text(text = "Utforska Airbnb", color = Color.White)
           }
      }
      

      默认的jetpack compose Button 在其onClick 事件中使用点击手势,而pressIndicatorGestureFilter 不接收点击。这就是我创建这个自定义按钮的原因

      【讨论】:

      • 好方法!你知道我们如何为整个表面(包括字体大小)设置动画,而不仅仅是盒子的宽度和高度吗? (查看我更新的问题)
      • 动画文本字体大小看起来很简单。 目前看起来很糟糕是什么意思?
      • pressIndicatorGestureFilter 导入是什么?
      【解决方案3】:

      使用1.0.0(使用1.0.0-beta08 测试),您可以使用Modifier.pointerInput 来检测tapGesture。
      定义一个枚举:

      enum class ComponentState { Pressed, Released }
      

      然后:

      var toState by remember { mutableStateOf(ComponentState.Released) }
      val modifier = Modifier.pointerInput(Unit) {
          detectTapGestures(
              onPress = {
                  toState = ComponentState.Pressed
                  tryAwaitRelease()
                  toState = ComponentState.Released
              }
      
          )
      }
       // Defines a transition of `ComponentState`, and updates the transition when the provided [targetState] changes
      val transition: Transition<ComponentState> = updateTransition(targetState = toState, label = "")
      
      // Defines a float animation to scale x,y
      val scalex: Float by transition.animateFloat(
          transitionSpec = { spring(stiffness = 50f) }, label = ""
      ) { state ->
          if (state == ComponentState.Pressed) 1.25f else 1f
      }
      val scaley: Float by transition.animateFloat(
          transitionSpec = { spring(stiffness = 50f) }, label = ""
      ) { state ->
          if (state == ComponentState.Pressed) 1.05f else 1f
      }
      

      应用modifier 并使用Modifier.graphicsLayer 来更改文本尺寸。

      Box(
          modifier
              .padding(16.dp)
              .width((100 * scalex).dp)
              .height((50 * scaley).dp)
              .background(Color.Black, shape = RoundedCornerShape(8.dp)),
          contentAlignment = Alignment.Center) {
      
              Text("BUTTON", color = Color.White,
                  modifier = Modifier.graphicsLayer{
                      scaleX = scalex;
                      scaleY = scaley
                  })
      
      }
      

      【讨论】:

        【解决方案4】:

        如果你想为按钮设置不同类型的动画,比如缩放、旋转和许多不同类型的动画,那么你可以在 jetpack compose 中使用这个库。 Check Here

        【讨论】: