Last active
December 15, 2023 15:04
-
-
Save Humayung/d5d2ffaab31428556c77b5908363073a to your computer and use it in GitHub Desktop.
Tilt + Press click effect modifier Jetpack Compose
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 id.co.components | |
import androidx.annotation.FloatRange | |
import androidx.compose.animation.core.Animatable | |
import androidx.compose.animation.core.AnimationSpec | |
import androidx.compose.animation.core.EaseInBounce | |
import androidx.compose.animation.core.EaseOutElastic | |
import androidx.compose.animation.core.FiniteAnimationSpec | |
import androidx.compose.foundation.clickable | |
import androidx.compose.foundation.interaction.MutableInteractionSource | |
import androidx.compose.material3.Text | |
import androidx.compose.runtime.Composable | |
import androidx.compose.runtime.remember | |
import androidx.compose.ui.Alignment | |
import androidx.compose.ui.Modifier | |
import androidx.compose.ui.draw.clip | |
import androidx.compose.ui.unit.dp | |
import androidx.compose.animation.core.animateFloatAsState | |
import androidx.compose.animation.core.tween | |
import androidx.compose.foundation.Image | |
import androidx.compose.foundation.interaction.Interaction | |
import androidx.compose.foundation.interaction.InteractionSource | |
import androidx.compose.foundation.interaction.PressInteraction | |
import androidx.compose.foundation.interaction.collectIsPressedAsState | |
import androidx.compose.foundation.layout.Box | |
import androidx.compose.foundation.layout.Column | |
import androidx.compose.foundation.layout.Spacer | |
import androidx.compose.foundation.layout.aspectRatio | |
import androidx.compose.foundation.layout.fillMaxSize | |
import androidx.compose.foundation.layout.fillMaxWidth | |
import androidx.compose.foundation.layout.height | |
import androidx.compose.foundation.shape.RoundedCornerShape | |
import androidx.compose.runtime.LaunchedEffect | |
import androidx.compose.runtime.getValue | |
import androidx.compose.runtime.mutableFloatStateOf | |
import androidx.compose.runtime.mutableStateOf | |
import androidx.compose.runtime.setValue | |
import androidx.compose.ui.composed | |
import androidx.compose.ui.geometry.Offset | |
import androidx.compose.ui.graphics.TransformOrigin | |
import androidx.compose.ui.graphics.graphicsLayer | |
import androidx.compose.ui.layout.ContentScale | |
import androidx.compose.ui.layout.onSizeChanged | |
import androidx.compose.ui.res.painterResource | |
import androidx.compose.ui.tooling.preview.Preview | |
import androidx.compose.ui.unit.IntSize | |
import androidx.compose.ui.unit.sp | |
import kotlinx.coroutines.flow.filter | |
import kotlinx.coroutines.flow.filterIsInstance | |
import kotlinx.coroutines.flow.launchIn | |
import kotlinx.coroutines.flow.onEach | |
fun Modifier.tilt( | |
clickableInteractionSource: InteractionSource, | |
magnitude: Float = 20f, | |
origin: TransformOrigin = TransformOrigin.Center, | |
animationSpec: AnimationSpec<Float> = tween() | |
) = composed { | |
var targetRotationX by remember { mutableFloatStateOf(0f) } | |
var targetRotationY by remember { mutableFloatStateOf(0f) } | |
val rotationX by animateFloatAsState(targetRotationX, animationSpec, label = "rotX") | |
val rotationY by animateFloatAsState(targetRotationY, animationSpec, label = "rotY") | |
var size by remember { mutableStateOf(IntSize.Zero) } | |
LaunchedEffect(Unit) { | |
clickableInteractionSource.interactions | |
.collect { | |
if (it !is PressInteraction.Press) return@collect run { | |
targetRotationX = 0f | |
targetRotationY = 0f | |
} | |
val pivotX = size.width * origin.pivotFractionX | |
val pivotY = size.height * origin.pivotFractionY | |
val transform = Offset( | |
it.pressPosition.x - pivotX, | |
it.pressPosition.y - pivotY | |
) | |
targetRotationY = | |
(transform.x / size.width * origin.pivotFractionX) * magnitude | |
targetRotationX = | |
(transform.y / size.height * origin.pivotFractionY) * -magnitude | |
} | |
} | |
Modifier | |
.onSizeChanged { | |
size = it | |
} | |
.graphicsLayer { | |
this.rotationX = rotationX | |
this.rotationY = rotationY | |
} | |
} | |
fun Modifier.press( | |
clickableInteractionSource: InteractionSource, | |
@FloatRange(from = 0.0, to = 1.0) fractionDepth: Float = 0.05f, | |
animationSpec: AnimationSpec<Float> = tween() | |
) = composed { | |
val pressed by clickableInteractionSource.collectIsPressedAsState() | |
val buttonScale by animateFloatAsState( | |
targetValue = if (pressed) (1 - fractionDepth) else 1f, | |
label = "button scale", | |
animationSpec = animationSpec | |
) | |
Modifier.graphicsLayer { | |
scaleY = buttonScale | |
scaleX = buttonScale | |
} | |
} | |
@Composable | |
fun TiltComponent() { | |
val interactionSource = remember { MutableInteractionSource() } | |
Column(horizontalAlignment = Alignment.CenterHorizontally, | |
modifier = Modifier | |
.tilt(interactionSource, magnitude = 20f) | |
.press(interactionSource, fractionDepth = 0.03f) | |
.clickable( | |
// IMPORTANT!!!!!!!!!!!!!!!!!!!!!!!! | |
interactionSource = interactionSource, | |
indication = null /* LocalIndication.current */ | |
) { | |
// perform onclick | |
}) { | |
Text( | |
text = "Tilt Effect Compose", | |
fontSize = 32.sp, | |
modifier = Modifier | |
.clip(RoundedCornerShape(10.dp)) | |
) | |
Spacer(modifier = Modifier.height(8.dp)) | |
Image( | |
modifier = Modifier | |
.fillMaxWidth(0.5f) | |
.clip(RoundedCornerShape(10.dp)) | |
.aspectRatio(9 / 16f), | |
painter = painterResource(id = R.drawable.colorednettle8224980), | |
contentDescription = "", | |
contentScale = ContentScale.Crop, | |
) | |
} | |
} | |
@Preview | |
@Composable | |
fun TiltComponentPreview() { | |
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { | |
TiltComponent() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment