简体   繁体   English

Jetpack Compose 中的缩放按钮 Animation

[英]Scaling Button Animation in Jetpack Compose

I want to build this awesome button animation pressed from the AirBnB App with Jetpack Compose我想使用 Jetpack Compose 从 AirBnB 应用程序中按下这个很棒的按钮 animation1个

Unfortunately, the Animation/Transition API was changed recently and there's almost no documentation for it.不幸的是,Animation/Transition API 最近发生了变化,几乎没有相关文档。 Can someone help me get the right approach to implement this button press animation?有人可以帮我找到正确的方法来实现这个按钮按下 animation 吗?

Edit Based on @Amirhosein answer I have developed a button that looks almost exactly like the Airbnb example编辑基于@Amirhosein 的回答,我开发了一个看起来几乎与 Airbnb 示例完全一样的按钮

Code:代码:

@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)
    }
}

Video:视频:

2个

Unfortunately, I cannot figure out how to animate the text correctly as It looks very bad currently不幸的是,我不知道如何正确地为文本设置动画,因为它目前看起来很糟糕

Are you looking for something like this?你在寻找这样的东西吗?

@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)
        }
    }
}

Use pressIndicatorGestureFilter to achieve this behavior.使用pressIndicatorGestureFilter来实现此行为。

Here is my workaround:这是我的解决方法:

@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)
     }
}

The default jetpack compose Button consumes tap gestures in its onClick event and pressIndicatorGestureFilter doesn't receive taps.默认的 jetpack compose Button在其onClick事件中使用点击手势,并且pressIndicatorGestureFilter不接收点击。 That's why I created this custom button这就是我创建这个自定义按钮的原因

With 1.0.0 (tested with 1.0.0-beta08 ) you can use the Modifier.pointerInput to detect the tapGesture.使用1.0.0 (使用1.0.0-beta08测试),您可以使用Modifier.pointerInput来检测 tapGesture。
Define an enum:定义一个枚举:

enum class ComponentState { Pressed, Released }

Then:然后:

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
}

Apply the modifier and use the Modifier.graphicsLayer to change also the text dimension.应用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
            })

}

在此处输入图像描述

Here's the implementation I used in my project.这是我在项目中使用的实现。 Seems most concise to me.对我来说似乎最简洁。

val interactionSource = remember { MutableInteractionSource() }
val isPressed by interactionSource.collectIsPressedAsState()
val sizeScale by animateFloatAsState(if (isPressed) 0.5f else 1f)

Button(
    onClick = { },
    modifier = Modifier
        .wrapContentSize()
        .graphicsLayer(
            scaleX = sizeScale,
            scaleY = sizeScale
        ),
    interactionSource = interactionSource
) { Text(text = "Open the reward") }

Here is the ScalingButton , the onClick callback is fired when users click the button and state is reset when users move their finger out of the button area after pressing the button and not releasing it.这是ScalingButton ,当用户单击按钮时会触发onClick回调,当用户在按下按钮后将手指移出按钮区域而不释放它时,state 会重置。 I'm using Modifier.pointerInput function to detect user inputs:我正在使用Modifier.pointerInput function 来检测用户输入:

@Composable
fun ScalingButton(onClick: () -> Unit, content: @Composable RowScope.() -> Unit) {
    var selected by remember { mutableStateOf(false) }
    val scale by animateFloatAsState(if (selected) 0.7f else 1f)

    Button(
        onClick = onClick,
        modifier = Modifier
            .scale(scale)
            .pointerInput(Unit) {
                while (true) {
                    awaitPointerEventScope {
                        awaitFirstDown(false)
                        selected = true
                        waitForUpOrCancellation()
                        selected = false
                    }
                }
            }
    ) {
        content()
    }
}

OR或者

Another approach without using an infinite loop:另一种不使用无限循环的方法:

@Composable
fun ScalingButton(onClick: () -> Unit, content: @Composable RowScope.() -> Unit) {
    var selected by remember { mutableStateOf(false) }
    val scale by animateFloatAsState(if (selected) 0.75f else 1f)

    Button(
        onClick = onClick,
        modifier = Modifier
            .scale(scale)
            .pointerInput(selected) {
                awaitPointerEventScope {
                    selected = if (selected) {
                        waitForUpOrCancellation()
                        false
                    } else {
                        awaitFirstDown(false)
                        true
                    }
                }
            }
    ) {
        content()
    }
}

I've the perfect solution:我有完美的解决方案:


    fun AnimatedButton() {
        val selected = remember { mutableStateOf(false) }
        val scale = animateFloatAsState(if (selected.value) 0.96f else 1f)
        val fade = animateFloatAsState(if (selected.value) 0.60f else 1f)
        Button(
            onClick = {  },
            modifier = Modifier
                .scale(scale.value)
                .alpha(fade.value)
                .height(50.dp)
                .pointerInteropFilter {
                    when (it.action) {
                        MotionEvent.ACTION_DOWN -> {
                            selected.value = true
                        }
    
                        MotionEvent.ACTION_UP -> {
                            selected.value = false
                        }
                    }
                    true
                },
            shape = RoundedCornerShape(50)
    
        ) {
            Text(text = "Explore Airbnb", Modifier.padding(horizontal= 24.dp))
        }
    }

State normal: State normal State正常: State正常

State pressed: State pressed State 按下: State 按下

Record: Animation recorded记录: Animation记录

If you want to animated button with different types of animation like scaling, rotating and many different kind of animation then you can use this library in jetpack compose.如果你想用不同类型的 animation 动画按钮,比如缩放、旋转和许多不同类型的 animation,那么你可以在 jetpack compose 中使用这个库。 Check Here 在这里检查

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM