Skip to content

Instantly share code, notes, and snippets.

@emenjivar
Last active May 25, 2025 22:17
Show Gist options
  • Save emenjivar/3c52e3e766428acf63b8581b4d4b5905 to your computer and use it in GitHub Desktop.
Save emenjivar/3c52e3e766428acf63b8581b4d4b5905 to your computer and use it in GitHub Desktop.
Simple parabolic animation
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)
@emenjivar
Copy link
Author

Screen_recording_20250525_161002.webm

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment