Last active
May 25, 2025 22:17
-
-
Save emenjivar/3c52e3e766428acf63b8581b4d4b5905 to your computer and use it in GitHub Desktop.
Simple parabolic animation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.emenjivar.testanimatedcurves | |
import android.os.Bundle | |
import androidx.activity.ComponentActivity | |
import androidx.activity.compose.setContent | |
import androidx.activity.enableEdgeToEdge | |
import androidx.compose.animation.core.EaseInOutBack | |
import androidx.compose.animation.core.animateFloatAsState | |
import androidx.compose.animation.core.tween | |
import androidx.compose.foundation.background | |
import androidx.compose.foundation.clickable | |
import androidx.compose.foundation.layout.Arrangement | |
import androidx.compose.foundation.layout.Box | |
import androidx.compose.foundation.layout.Column | |
import androidx.compose.foundation.layout.fillMaxSize | |
import androidx.compose.foundation.layout.size | |
import androidx.compose.runtime.getValue | |
import androidx.compose.runtime.mutableStateOf | |
import androidx.compose.runtime.remember | |
import androidx.compose.runtime.setValue | |
import androidx.compose.ui.Alignment | |
import androidx.compose.ui.Modifier | |
import androidx.compose.ui.graphics.Color | |
import androidx.compose.ui.graphics.TransformOrigin | |
import androidx.compose.ui.graphics.graphicsLayer | |
import androidx.compose.ui.unit.dp | |
import androidx.compose.ui.util.lerp | |
import com.emenjivar.testanimatedcurves.ui.theme.TestAnimatedCurvesTheme | |
class MainActivity : ComponentActivity() { | |
override fun onCreate(savedInstanceState: Bundle?) { | |
super.onCreate(savedInstanceState) | |
enableEdgeToEdge() | |
setContent { | |
TestAnimatedCurvesTheme { | |
var triggerAnimation by remember { mutableStateOf(false) } | |
val animation = animateFloatAsState( | |
targetValue = if (triggerAnimation) { | |
1f | |
} else { | |
0f | |
}, | |
animationSpec = tween(durationMillis = 1000, easing = EaseInOutBack), | |
label = "animation progress" | |
) | |
Column( | |
modifier = Modifier.fillMaxSize(), | |
horizontalAlignment = Alignment.CenterHorizontally, | |
verticalArrangement = Arrangement.Center | |
) { | |
Box( | |
modifier = Modifier | |
.graphicsLayer { | |
// Represents a non-linear animation fraction (0f to 1f, then back to 0f) | |
val progress = normalizedParabola(animation.value) | |
val rotation = lerp( | |
start = 0f, | |
stop = ROTATION_DEGREES, | |
fraction = progress | |
) | |
val scale = lerp( | |
start = 1f, | |
stop = SCALE_TO, | |
fraction = progress | |
) | |
val alpha = lerp( | |
start = 1f, | |
stop = ALPHA_PEAK, | |
fraction = progress | |
) | |
rotationZ = rotation | |
scaleX = scale | |
scaleY = scale | |
this.alpha = alpha | |
this.transformOrigin = TransformOrigin(0f, 1f) | |
} | |
.size(RECTANGLE_SIZE) | |
.background(Color.Blue) | |
.clickable { | |
triggerAnimation = !triggerAnimation | |
} | |
) | |
} | |
} | |
} | |
} | |
} | |
private val RECTANGLE_SIZE = 70.dp | |
private const val ROTATION_DEGREES = 8f | |
private const val SCALE_TO = 0.8f | |
private const val ALPHA_PEAK = 0.6f | |
/** | |
* Generates a normalized parabolic curve value. | |
* This function created a parabola passing through (0,0), (0, 0.5) and (1,0). | |
* | |
* @param x A float value representing the normalized progress from 0f to 1f. | |
* @return The corresponding parabolic curve value. | |
* | |
* @see <a href="https://www.desmos.com/calculator/vparvkrezm">Visualize the curve</a> | |
*/ | |
fun normalizedParabola(x: Float) = -4 * x * (x - 1) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Screen_recording_20250525_161002.webm