Skip to content

Commit

Permalink
Migrate circularReavel animation using the new api updateTransition
Browse files Browse the repository at this point in the history
  • Loading branch information
skydoves committed Jan 31, 2021
1 parent b71c9e2 commit 9dbd494
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,40 +16,62 @@

package com.skydoves.landscapist

import androidx.compose.animation.core.Easing
import androidx.compose.animation.core.FloatPropKey
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.transitionDefinition
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.tween
import androidx.compose.animation.core.updateTransition
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.painter.Painter

/**
* CircularRevealedAnimation is an animation for animating a clipping circle to reveal an image.
* The animation has two states Loaded, Empty.
* circularReveal is an extension of the [Painter] for animating a clipping circle to reveal an image.
* The animation has two states [CircularRevealState.None], [CircularRevealState.Finished].
*
* @param imageBitmap an image bitmap for loading the content.
* @param durationMs milli-second times from start to finish animation.
*/
internal object CircularRevealedAnimation {
@Composable
internal fun Painter.circularReveal(
imageBitmap: ImageBitmap,
durationMs: Int
): Painter {
// Defines a transition of `CircularRevealState`, and updates the transition when the provided state changes.
val transitionState = remember { MutableTransitionState(CircularRevealState.None) }
transitionState.targetState = CircularRevealState.Finished

/** Common interface of the animation states. */
enum class State {
/** animation not started. */
None,
// Our actual transition, which reads our transitionState
val transition = updateTransition(transitionState)

/** animation finished. */
Finished
val radius: Float by transition.animateFloat(
transitionSpec = { tween(durationMillis = durationMs) }
) { state ->
when (state) {
CircularRevealState.None -> 0f
CircularRevealState.Finished -> 1f
}
}

val Radius = FloatPropKey()
return remember(this) {
CircularRevealedPainter(
imageBitmap = imageBitmap,
painter = this
)
}.also {
it.radius = radius
}
}

/** definitions of the specific animating values based on animation states. */
fun definition(durationMillis: Int, easing: Easing = LinearEasing) = transitionDefinition<State> {
state(State.None) {
this[Radius] = 0f
}
state(State.Finished) {
this[Radius] = 1f
}
/**
* CircularRevealState is state of transition for clipping circle to reveal an image
* depending on its state.
*/
internal enum class CircularRevealState {
/** animation is not started. */
None,

transition {
Radius using tween(durationMillis = durationMillis, easing = easing)
}
}
/** animation is finished. */
Finished
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,8 @@

package com.skydoves.landscapist

import androidx.compose.animation.asDisposableClock
import androidx.compose.animation.core.AnimationClockObservable
import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
Expand All @@ -32,7 +29,6 @@ import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.painter.ImagePainter
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.AmbientAnimationClock

/**
* CircularRevealedImage is an image composable for animating a clipping circle to reveal when loading an image.
Expand All @@ -49,7 +45,6 @@ import androidx.compose.ui.platform.AmbientAnimationClock
* @param colorFilter colorFilter to apply for the image when it is rendered onscreen.
* @param circularRevealedEnabled a conditional value for enabling or not the circular revealing animation.
* @param circularRevealedDuration milli-second times from start to finish animation.
* @param clock an interface allows AnimationClock to be subscribed and unsubscribed.
*/
@Composable
fun CircularRevealedImage(
Expand All @@ -62,20 +57,11 @@ fun CircularRevealedImage(
alpha: Float = DefaultAlpha,
colorFilter: ColorFilter? = null,
circularRevealedEnabled: Boolean = false,
circularRevealedDuration: Int = DefaultCircularRevealedDuration,
clock: AnimationClockObservable = AmbientAnimationClock.current.asDisposableClock(),
circularRevealedDuration: Int = DefaultCircularRevealedDuration
) {
val circularRevealedPainter = remember(imagePainter) {
CircularRevealedPainter(
bitmap,
imagePainter,
clock,
circularRevealedDuration
).also { it.start() }
}
Image(
painter = if (circularRevealedEnabled) {
circularRevealedPainter.getMainPainter()
imagePainter.circularReveal(bitmap, circularRevealedDuration)
} else {
imagePainter
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@ package com.skydoves.landscapist

import android.graphics.Matrix
import android.graphics.RectF
import androidx.compose.animation.core.AnimationClockObservable
import androidx.compose.animation.core.createAnimation
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.neverEqualPolicy
import androidx.compose.runtime.setValue
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
Expand All @@ -43,35 +42,13 @@ import androidx.core.util.Pools
*
* @param imageBitmap an image bitmap for loading for the content.
* @param painter an image painter to draw an [ImageBitmap] into the provided canvas.
* @param clock an interface allows AnimationClock to be subscribed and unsubscribed.
* @param durationMs milli-second times from start to finish animation.
*/
internal class CircularRevealedPainter(
private val imageBitmap: ImageBitmap,
private val painter: Painter,
clock: AnimationClockObservable,
durationMs: Int
private val painter: Painter
) : Painter() {

private var radius by mutableStateOf(0f)

private val circularAnimation =
CircularRevealedAnimation.definition(durationMs).createAnimation(clock)

var isFinished by mutableStateOf(false)
private set

init {
circularAnimation.onUpdate = {
radius = circularAnimation[CircularRevealedAnimation.Radius]
}

circularAnimation.onStateChangeFinished = { state ->
if (state == CircularRevealedAnimation.State.Finished) {
isFinished = true
}
}
}
var radius by mutableStateOf(0f, policy = neverEqualPolicy())

override fun DrawScope.onDraw() {
val paint = paintPool.acquire() ?: Paint()
Expand Down Expand Up @@ -127,22 +104,8 @@ internal class CircularRevealedPainter(
}
}

/**
* if the animation is ongoing, returns a [CircularRevealedPainter],
* if the animation is finished, returns a [painter].
*/
fun getMainPainter(): Painter {
return if (!isFinished) this
else this.painter
}

/** return the dimension size of the [ImageBitmap]'s intrinsic width and height. */
override val intrinsicSize: Size get() = painter.intrinsicSize

/** starts the circular revealed animation by transitioning to the Loaded state. */
fun start() {
circularAnimation.toState(CircularRevealedAnimation.State.Finished)
}
}

/** paint pool which caching and reusing [Paint] instances. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ fun Shimmer(
targetValue = 1f,
anim = infiniteRepeatable(
animation = tween(durationMillis = durationMillis, easing = LinearEasing)
),
)
)
}

Expand Down

0 comments on commit 9dbd494

Please sign in to comment.