Skip to content

Instantly share code, notes, and snippets.

@ElianFabian
Created October 11, 2023 20:26
Show Gist options
  • Save ElianFabian/7e17497437d5918285d274cdb0b658f5 to your computer and use it in GitHub Desktop.
Save ElianFabian/7e17497437d5918285d274cdb0b658f5 to your computer and use it in GitHub Desktop.
Several flow operators to animate kotlin flows in Android.
@file:Suppress("NOTHING_TO_INLINE")
import android.animation.ArgbEvaluator
import android.animation.TimeInterpolator
import android.animation.TypeEvaluator
import android.animation.ValueAnimator
import android.view.animation.LinearInterpolator
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlin.math.abs
@JvmName("animateInt")
fun Flow<Int>.animate(
durationInMillis: Long,
interpolator: TimeInterpolator = LinearInterpolator(),
): Flow<Int> = callbackFlow {
var previousValue: Int? = null
val animator = ValueAnimator().apply {
this.interpolator = interpolator
this.duration = durationInMillis
}
val updateListener = ValueAnimator.AnimatorUpdateListener { animation ->
trySend(animation.animatedValue as Int)
}
animator.addUpdateListener(updateListener)
collect { newValue ->
if (previousValue == null) {
send(newValue)
}
else if (animator.isRunning) {
val currentAnimatedValue = animator.animatedValue as Int
animator.setIntValues(currentAnimatedValue, newValue)
animator.start()
}
else {
animator.setIntValues(previousValue!!, newValue)
animator.start()
}
previousValue = newValue
}
awaitClose {
animator.removeUpdateListener(updateListener)
animator.cancel()
}
}.distinctUntilChanged()
@JvmName("animateInt")
fun Flow<Int>.animate(
speed: Float,
interpolator: TimeInterpolator = LinearInterpolator(),
): Flow<Int> = callbackFlow {
var previousValue: Int? = null
val animator = ValueAnimator().apply {
this.interpolator = interpolator
}
val updateListener = ValueAnimator.AnimatorUpdateListener { animation ->
trySend(animation.animatedValue as Int)
}
animator.addUpdateListener(updateListener)
collect { newValue ->
if (previousValue == null) {
send(newValue)
}
else if (animator.isRunning) {
val currentAnimatedValue = animator.animatedValue as Int
val distance = abs(newValue - currentAnimatedValue)
val durationInMillis = (distance / speed).toLong()
animator.apply {
setIntValues(currentAnimatedValue, newValue)
this.duration = durationInMillis
animator.start()
}
}
else {
animator.apply {
val duration = if (previousValue != null) {
val distance = abs(newValue - previousValue!!)
(distance / speed).toLong()
}
else 0L
setIntValues(previousValue!!, newValue)
this.duration = duration
animator.start()
}
}
previousValue = newValue
}
awaitClose {
animator.removeUpdateListener(updateListener)
animator.cancel()
}
}.distinctUntilChanged()
@JvmName("animateFloat")
fun Flow<Float>.animate(
durationInMillis: Long,
interpolator: TimeInterpolator = LinearInterpolator(),
): Flow<Float> = callbackFlow {
var previousValue: Float? = null
val animator = ValueAnimator().apply {
this.interpolator = interpolator
this.duration = durationInMillis
}
val updateListener = ValueAnimator.AnimatorUpdateListener { animation ->
trySend(animation.animatedValue as Float)
}
animator.addUpdateListener(updateListener)
collect { newValue ->
if (previousValue == null) {
send(newValue)
}
else if (animator.isRunning) {
val currentAnimatedValue = animator.animatedValue as Float
animator.setFloatValues(currentAnimatedValue, newValue)
animator.start()
}
else {
animator.setFloatValues(previousValue!!, newValue)
animator.start()
}
previousValue = newValue
}
awaitClose {
animator.removeUpdateListener(updateListener)
animator.cancel()
}
}.distinctUntilChanged()
@JvmName("animateFloat")
fun Flow<Float>.animate(
speed: Float,
interpolator: TimeInterpolator = LinearInterpolator(),
): Flow<Float> = callbackFlow {
var previousValue: Float? = null
val animator = ValueAnimator().apply {
this.interpolator = interpolator
}
val updateListener = ValueAnimator.AnimatorUpdateListener { animation ->
trySend(animation.animatedValue as Float)
}
animator.addUpdateListener(updateListener)
collect { newValue ->
if (previousValue == null) {
send(newValue)
}
else if (animator.isRunning) {
val currentAnimatedValue = animator.animatedValue as Float
val distance = abs(newValue - currentAnimatedValue)
val durationInMillis = (distance / speed).toLong()
animator.apply {
setFloatValues(currentAnimatedValue, newValue)
this.duration = durationInMillis
animator.start()
}
}
else {
animator.apply {
val duration = if (previousValue != null) {
val distance = abs(newValue - previousValue!!)
(distance / speed).toLong()
}
else 0L
setFloatValues(previousValue!!, newValue)
this.duration = duration
animator.start()
}
}
previousValue = newValue
}
awaitClose {
animator.removeUpdateListener(updateListener)
animator.cancel()
}
}.distinctUntilChanged()
private val argbEvaluator = ArgbEvaluator()
fun Flow<Int>.animateArgb(
durationInMillis: Long,
interpolator: TimeInterpolator = LinearInterpolator(),
): Flow<Int> = callbackFlow {
var previousValue: Int? = null
val animator = ValueAnimator().apply {
this.interpolator = interpolator
this.duration = durationInMillis
setEvaluator(argbEvaluator)
}
val updateListener = ValueAnimator.AnimatorUpdateListener { animation ->
trySend(animation.animatedValue as Int)
}
animator.addUpdateListener(updateListener)
collect { newValue ->
if (previousValue == null) {
send(newValue)
}
else if (animator.isRunning) {
val currentAnimatedValue = animator.animatedValue as Int
animator.setIntValues(currentAnimatedValue, newValue)
animator.start()
}
else {
animator.setIntValues(previousValue!!, newValue)
animator.start()
}
previousValue = newValue
}
awaitClose {
animator.removeUpdateListener(updateListener)
animator.cancel()
}
}.distinctUntilChanged()
@Suppress("UNCHECKED_CAST")
fun <T : Any> Flow<T>.animate(
durationInMillis: Long,
evaluator: TypeEvaluator<T>,
interpolator: TimeInterpolator = LinearInterpolator(),
): Flow<T> = callbackFlow {
var previousValue: T? = null
val animator = ValueAnimator().apply {
this.interpolator = interpolator
this.duration = durationInMillis
}
val updateListener = ValueAnimator.AnimatorUpdateListener { animation ->
trySend(animation.animatedValue as T)
}
animator.addUpdateListener(updateListener)
collect { newValue ->
if (previousValue == null) {
send(newValue)
}
else if (animator.isRunning) {
val currentAnimatedValue = animator.animatedValue as T
animator.apply {
setObjectValues(currentAnimatedValue, newValue)
setEvaluator(evaluator)
start()
}
}
else {
animator.apply {
setObjectValues(previousValue!!, newValue)
setEvaluator(evaluator)
start()
}
}
previousValue = newValue
}
awaitClose {
animator.removeUpdateListener(updateListener)
animator.cancel()
}
}.distinctUntilChanged()
@Suppress("UNCHECKED_CAST")
fun <T : Any> Flow<T>.animate(
speed: Float,
evaluator: TypeEvaluator<T>,
getDistance: (currentValue: T, newValue: T) -> Float,
interpolator: TimeInterpolator = LinearInterpolator(),
): Flow<T> = callbackFlow {
var previousValue: T? = null
val animator = ValueAnimator().apply {
this.interpolator = interpolator
}
val updateListener = ValueAnimator.AnimatorUpdateListener { animation ->
trySend(animation.animatedValue as T)
}
animator.addUpdateListener(updateListener)
collect { newValue ->
if (previousValue == null) {
send(newValue)
}
else if (animator.isRunning) {
val currentAnimatedValue = animator.animatedValue as T
val distance = getDistance(currentAnimatedValue, newValue)
val durationInMillis = (distance / speed).toLong()
animator.apply {
setObjectValues(currentAnimatedValue, newValue)
setEvaluator(evaluator)
this.duration = durationInMillis
start()
}
}
else {
animator.apply {
val duration = if (previousValue != null) {
val distance = getDistance(previousValue!!, newValue)
(distance / speed).toLong()
}
else 0L
setObjectValues(previousValue!!, newValue)
setEvaluator(evaluator)
this.duration = duration
animator.start()
}
}
previousValue = newValue
}
awaitClose {
animator.removeUpdateListener(updateListener)
animator.cancel()
}
}.distinctUntilChanged()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment