From a83dd51f545dda905194ea84caed6e2906b0f06f Mon Sep 17 00:00:00 2001 From: AllanWang Date: Sun, 9 Sep 2018 13:06:49 -0400 Subject: Add initial progress animator change --- .../kotlin/ca/allanwang/kau/ui/ProgressAnimator.kt | 167 +++++++++++++++------ docs/Migration.md | 3 + sample/src/main/res/xml/kau_changelog.xml | 2 - 3 files changed, 120 insertions(+), 52 deletions(-) diff --git a/core/src/main/kotlin/ca/allanwang/kau/ui/ProgressAnimator.kt b/core/src/main/kotlin/ca/allanwang/kau/ui/ProgressAnimator.kt index e37e59f..39e8657 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/ui/ProgressAnimator.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/ui/ProgressAnimator.kt @@ -3,7 +3,7 @@ package ca.allanwang.kau.ui import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator -import android.view.animation.Interpolator +import ca.allanwang.kau.kotlin.kauRemoveIf /** * Created by Allan Wang on 2017-11-10. @@ -13,77 +13,144 @@ import android.view.animation.Interpolator * This differs in that everything can be done with simple listeners, which will be bundled * and added to the backing [ValueAnimator] */ -class ProgressAnimator private constructor(private vararg val values: Float) { +class ProgressAnimator private constructor() : ValueAnimator() { companion object { - inline fun ofFloat(crossinline builder: ProgressAnimator.() -> Unit) = ofFloat(0f, 1f) { builder() } - fun ofFloat(vararg values: Float, builder: ProgressAnimator.() -> Unit) = ProgressAnimator(*values).apply { - builder() - build() + fun ofFloat(builder: ProgressAnimator.() -> Unit): ProgressAnimator = ProgressAnimator().apply { + setFloatValues(0f, 1f) + addUpdateListener { apply(it.animatedValue as Float) } + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationStart(animation: Animator?, isReverse: Boolean) { + isCancelled = false + startActions.runAll() + } + + override fun onAnimationCancel(animation: Animator?) { + isCancelled = true + cancelActions.runAll() + } + + override fun onAnimationEnd(animation: Animator?) { + endActions.runAll() + isCancelled = false + } + }) + apply(builder) + } + + /** + * Gets output of a linear function starting at [start] when [progress] is 0 and [end] when [progress] is 1 at point [progress]. + */ + fun progress(start: Float, end: Float, progress: Float): Float = start + (end - start) * progress + + fun progress(start: Float, end: Float, progress: Float, min: Float, max: Float): Float = when { + min == max -> throw IllegalArgumentException("Progress range cannot be 0 (min == max == $min") + progress <= min -> start + progress >= max -> end + else -> { + val trueProgress = (progress - min) / (max - min) + start + (end - start) * trueProgress + } } + } - private val animators: MutableList<(Float) -> Unit> = mutableListOf() - private val startActions: MutableList<() -> Unit> = mutableListOf() - private val endActions: MutableList<() -> Unit> = mutableListOf() + private val animators: MutableList = mutableListOf() + private val startActions: MutableList = mutableListOf() + private val cancelActions: MutableList = mutableListOf() + private val endActions: MutableList = mutableListOf() + var isCancelled: Boolean = false + private set - var duration: Long = -1L - var interpolator: Interpolator? = null /** - * Add more changes to the [ValueAnimator] before running + * Converts an action to a disposable action */ - var extraConfigs: ValueAnimator.() -> Unit = {} + private fun ProgressAction.asDisposable(): ProgressDisposableAction = { this(it); false } + + private fun ProgressRunnable.asDisposable(): ProgressDisposableRunnable = { this(); false } /** - * Range animator. Multiples the range by the current float progress before emission + * If [condition] applies, run the animator. + * @return [condition] */ - fun withAnimator(from: Float, to: Float, animator: (Float) -> Unit) = animators.add { - val range = to - from - animator(range * it + from) + private fun ProgressAction.runIf(condition: Boolean, progress: Float): Boolean { + if (condition) this(progress) + return condition } - /** - * Standard animator. Emits progress value as is - */ - fun withAnimator(animator: (Float) -> Unit) = animators.add(animator) + private fun MutableList.runAll() = kauRemoveIf { it() } - /** - * Start action to be called once when the animator first begins - */ - fun withStartAction(action: () -> Unit) = startActions.add(action) + internal fun apply(progress: Float) { + animators.kauRemoveIf { it(progress) } + } + + fun withAnimator(action: ProgressAction) = + withDisposableAnimator(action.asDisposable()) /** - * End action to be called once when the animator ends + * Range animator. Multiples the range by the current float progress before emission */ - fun withEndAction(action: () -> Unit) = endActions.add(action) - - fun build() { - ValueAnimator.ofFloat(*values).apply { - if (this@ProgressAnimator.duration > 0L) - duration = this@ProgressAnimator.duration - if (this@ProgressAnimator.interpolator != null) - interpolator = this@ProgressAnimator.interpolator - addUpdateListener { - val progress = it.animatedValue as Float - animators.forEach { it(progress) } + fun withAnimator(from: Float, to: Float, action: ProgressAction) = + withDisposableAnimator(from, to, action.asDisposable()) + + fun withDisposableAnimator(action: ProgressDisposableAction) = animators.add(action) + + fun withDisposableAnimator(from: Float, to: Float, action: ProgressDisposableAction) { + if (to != from) { + animators.add { + action(progress(from, to, it)) } - addListener(object : AnimatorListenerAdapter() { - override fun onAnimationStart(animation: Animator?) { - startActions.forEach { it() } - } + } + } - override fun onAnimationEnd(animation: Animator?) { - endActions.forEach { it() } + fun withRangeAnimator(min: Float, max: Float, start: Float, end: Float, progress: Float, action: ProgressAction) { + if (min >= max) { + throw IllegalArgumentException("Range animator must have min < max; currently min=$min, max=$max") + } + withDisposableAnimator { + when { + it > max -> true + it < min -> false + else -> { + action(progress(start, end, progress, min, max)) + false + } } + } + } - override fun onAnimationCancel(animation: Animator?) { - endActions.forEach { it() } - } - }) - extraConfigs() - start() + fun withPointAnimator(point: Float, action: ProgressAction) { + animators.add { + action.runIf(it >= point, it) } } -} \ No newline at end of file + + fun withDelayedStartAction(skipCount: Int, action: ProgressAction) { + var count = 0 + animators.add { + action.runIf(count++ >= skipCount, it) + } + } + + /** + * Start action to be called once when the animator first begins + */ + fun withStartAction(action: ProgressRunnable) = withDisposableStartAction(action.asDisposable()) + + fun withDisposableStartAction(action: ProgressDisposableRunnable) = startActions.add(action) + + fun withCancelAction(action:ProgressRunnable) = withDisposableCancelAction(action.asDisposable()) + + fun withDisposableCancelAction(action: ProgressDisposableRunnable) = cancelActions.add(action) + + fun withEndAction(action: ProgressRunnable) = withDisposableEndAction(action.asDisposable()) + + fun withDisposableEndAction(action: ProgressDisposableRunnable) = endActions.add(action) +} + +private typealias ProgressAction = (Float) -> Unit +private typealias ProgressDisposableAction = (Float) -> Boolean +private typealias ProgressRunnable = () -> Unit +private typealias ProgressDisposableRunnable = () -> Boolean \ No newline at end of file diff --git a/docs/Migration.md b/docs/Migration.md index 721cd0d..c9e39b8 100644 --- a/docs/Migration.md +++ b/docs/Migration.md @@ -8,6 +8,9 @@ Along with the update to support Android Studio 3.1, a lot of changes have occur * Resource ids can be negatives due to the bit overflow. Instead, `INVALID_ID` has been introduced to signify an unset or invalid id. Methods such as `Context.string(id, fallback)` now check against `INVALID_ID` through equality rather than using an inequality to address this. +* Kotterknife has been deprecated in favour of `kotlin-android-extensions`. See [official docs](https://kotlinlang.org/docs/tutorials/android-plugin.html#view-binding). +* `ProgressAnimator` has been completely rewritten to be an extension of `ValueAnimator`. +This for the most part is not a breaking change, apart from the fact that creating an animator will not start it immediately. # v3.6.0 diff --git a/sample/src/main/res/xml/kau_changelog.xml b/sample/src/main/res/xml/kau_changelog.xml index a5d0ea9..e5ddad5 100644 --- a/sample/src/main/res/xml/kau_changelog.xml +++ b/sample/src/main/res/xml/kau_changelog.xml @@ -12,8 +12,6 @@ - - -- cgit v1.2.3