Created
October 3, 2021 23:03
-
-
Save darvld/eb3844474baf2f3fc6d3ab44a4b4b5f8 to your computer and use it in GitHub Desktop.
A circular reveal effect modifier for Jetpack Compose.
This file contains 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 cu.spin.catalog.ui.components | |
import android.annotation.SuppressLint | |
import androidx.compose.animation.core.animateFloat | |
import androidx.compose.animation.core.updateTransition | |
import androidx.compose.runtime.State | |
import androidx.compose.ui.Modifier | |
import androidx.compose.ui.composed | |
import androidx.compose.ui.draw.drawWithCache | |
import androidx.compose.ui.geometry.Offset | |
import androidx.compose.ui.geometry.Rect | |
import androidx.compose.ui.geometry.Size | |
import androidx.compose.ui.graphics.Path | |
import androidx.compose.ui.graphics.drawscope.clipPath | |
import androidx.compose.ui.platform.debugInspectorInfo | |
import kotlin.math.sqrt | |
/**A modifier that clips the composable content using an animated circle. The circle will | |
* expand/shrink with an animation whenever [visible] changes. | |
* | |
* For more fine-grained control over the transition, see this method's overload, which allows passing | |
* a [State] object to control the progress of the reveal animation. | |
* | |
* By default, the circle is centered in the content, but custom positions may be specified using | |
* [revealFrom]. Specified offsets should be between 0 (left/top) and 1 (right/bottom).*/ | |
@SuppressLint("UnnecessaryComposedModifier") | |
fun Modifier.circularReveal( | |
visible: Boolean, | |
revealFrom: Offset = Offset(0.5f, 0.5f), | |
): Modifier = composed( | |
factory = { | |
val factor = updateTransition(visible, label = "Visibility") | |
.animateFloat(label = "revealFactor") { if (it) 1f else 0f } | |
circularReveal(factor, revealFrom) | |
}, | |
inspectorInfo = debugInspectorInfo { | |
name = "circularReveal" | |
properties["visible"] = visible | |
properties["revealFrom"] = revealFrom | |
} | |
) | |
/**A modifier that clips the composable content using a circular shape. The radius of the circle | |
* will be determined by the [transitionProgress]. | |
* | |
* The values of the progress should be between 0 and 1. | |
* | |
* By default, the circle is centered in the content, but custom positions may be specified using | |
* [revealFrom]. Specified offsets should be between 0 (left/top) and 1 (right/bottom). | |
* */ | |
fun Modifier.circularReveal( | |
transitionProgress: State<Float>, | |
revealFrom: Offset = Offset(0.5f, 0.5f) | |
): Modifier { | |
return drawWithCache { | |
val path = Path() | |
val center = revealFrom.mapTo(size) | |
val radius = calculateRadius(revealFrom, size) | |
path.addOval(Rect(center, radius * transitionProgress.value)) | |
onDrawWithContent { | |
clipPath(path) { this@onDrawWithContent.drawContent() } | |
} | |
} | |
} | |
private fun Offset.mapTo(size: Size): Offset { | |
return Offset(x * size.width, y * size.height) | |
} | |
private fun calculateRadius(normalizedOrigin: Offset, size: Size) = with(normalizedOrigin) { | |
val x = (if (x > 0.5f) x else 1 - x) * size.width | |
val y = (if (y > 0.5f) y else 1 - y) * size.height | |
sqrt(x * x + y * y) | |
} |
Super useful, thanks!
Thanks, It's working like a charm!
@vrajendraBhavsar @Skaldebane @andraantariksa @darvld
Can any help me with, how to use it for a composable?
Here is a modification in case someone has to make the composable invisible when transitionProgress value equals to zero
fun Modifier.circularReveal(
transitionProgress: State<Float>,
revealFrom: Offset = Offset(0.5f, 0.5f)
): Modifier {
return if (transitionProgress.value == 0f) {
Modifier.layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
layout(placeable.width, placeable.height) {}
}
} else {
drawWithCache {
val path = Path()
val center = revealFrom.mapTo(size)
val radius = calculateRadius(revealFrom, size)
path.addOval(Rect(center, radius * transitionProgress.value))
onDrawWithContent {
clipPath(path) { this@onDrawWithContent.drawContent() }
}
}
}
}
Modifier.layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
layout(placeable.width, placeable.height) {}
}
thanks for sharing, I've also added a few modifications in case you want to reveal only a box instead of the entire thing.
here is also a preview example
https://gist.github.com/kibotu/996eab1b82237a94b2faedc5a90746e7
(check out the RevealTest)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks!