Skip to content

Instantly share code, notes, and snippets.

@Popalay
Created February 5, 2018 09:54
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Popalay/92a66904ccb321de52153b7e3b662197 to your computer and use it in GitHub Desktop.
Save Popalay/92a66904ccb321de52153b7e3b662197 to your computer and use it in GitHub Desktop.
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.annotation.TargetApi
import android.graphics.Outline
import android.graphics.Rect
import android.os.Build
import android.transition.Transition
import android.transition.TransitionValues
import android.view.View
import android.view.ViewAnimationUtils
import android.view.ViewGroup
import android.view.ViewOutlineProvider
import android.view.animation.LinearInterpolator
import kotlin.math.roundToInt
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
class CircularReveal : Transition() {
companion object {
private const val DEFAULT_DURATION = 200L
private const val BOUNDS = "BOUNDS"
private val TRANSITION_PROPERTIES = arrayOf(BOUNDS)
}
init {
duration = DEFAULT_DURATION
interpolator = LinearInterpolator()
}
override fun getTransitionProperties() = TRANSITION_PROPERTIES
override fun captureStartValues(transitionValues: TransitionValues) {
captureValues(transitionValues)
}
override fun captureEndValues(transitionValues: TransitionValues) {
captureValues(transitionValues)
}
override fun createAnimator(
sceneRoot: ViewGroup,
startValues: TransitionValues?,
endValues: TransitionValues?
): Animator? {
if (startValues == null || endValues == null) return null
val startBounds = startValues.values[BOUNDS] as Rect
val endBounds = endValues.values[BOUNDS] as Rect
val isReveal = endBounds.width() > startBounds.width()
val view = endValues.view
val translationX = startBounds.centerX() - endBounds.centerX()
val translationY = startBounds.centerY() - endBounds.centerY()
if (isReveal) {
view.translationX = translationX.toFloat()
view.translationY = translationY.toFloat()
}
val translate = ObjectAnimator.ofFloat(
view,
View.TRANSLATION_X,
View.TRANSLATION_Y,
if (isReveal) pathMotion.getPath(translationX.toFloat(), translationY.toFloat(), 0f, 0f)
else pathMotion.getPath(0f, 0f, (-translationX).toFloat(), (-translationY).toFloat())
)
val startScale = startBounds.width() / endBounds.width().toFloat()
val endScale = endBounds.width() / startBounds.width().toFloat()
val resize = AnimatorSet().apply {
if (isReveal) {
play(ObjectAnimator.ofFloat(view, View.SCALE_X, startScale, 1F))
.with(ObjectAnimator.ofFloat(view, View.SCALE_Y, startScale, 1F))
} else {
play(ObjectAnimator.ofFloat(view, View.SCALE_X, endScale))
.with(ObjectAnimator.ofFloat(view, View.SCALE_Y, endScale))
}
}
val circularReveal: Animator = if (isReveal) {
ViewAnimationUtils.createCircularReveal(
view,
view.width / 2,
view.height / 2,
startBounds.width() / 2F * endScale,
Math.hypot(endBounds.width().toDouble(), endBounds.height().toDouble()).toFloat()
)
} else {
view.layout(startBounds.left, startBounds.top, startBounds.right, startBounds.bottom)
ViewAnimationUtils.createCircularReveal(
view,
view.width / 2,
view.height / 2,
Math.hypot(startBounds.width().toDouble(), startBounds.height().toDouble()).toFloat(),
endBounds.width() / 2F * startScale
).apply {
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
view.outlineProvider = object : ViewOutlineProvider() {
override fun getOutline(view: View, outline: Outline) {
val left = ((view.width - endBounds.width() * startScale) / 2).roundToInt()
val top = ((view.height - endBounds.height() * startScale) / 2).roundToInt()
val right = left + Math.min(view.width, view.height)
val bottom = top + Math.min(view.width, view.height)
outline.setOval(left, top, right, bottom)
view.clipToOutline = true
}
}
}
})
}
}
val transition = AnimatorSet().apply {
playTogether(translate, circularReveal, resize)
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
view.overlay.clear()
}
})
}
return NoPauseAnimator(transition)
}
private fun captureValues(transitionValues: TransitionValues) {
val view = transitionValues.view
if (view == null || view.width <= 0 || view.height <= 0) return
transitionValues.values[BOUNDS] = Rect(view.left, view.top, view.right, view.bottom)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment