Skip to content

Instantly share code, notes, and snippets.

@Humayung
Last active December 15, 2023 15:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Humayung/d5d2ffaab31428556c77b5908363073a to your computer and use it in GitHub Desktop.
Save Humayung/d5d2ffaab31428556c77b5908363073a to your computer and use it in GitHub Desktop.
Tilt + Press click effect modifier Jetpack Compose
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