Skip to content

Instantly share code, notes, and snippets.

@chrisbanes
Last active March 7, 2024 11:13
Show Gist options
  • Select an option

  • Save chrisbanes/b0db5e852035ab8c2a49803bac526019 to your computer and use it in GitHub Desktop.

Select an option

Save chrisbanes/b0db5e852035ab8c2a49803bac526019 to your computer and use it in GitHub Desktop.

Revisions

  1. chrisbanes revised this gist Jun 27, 2018. 1 changed file with 0 additions and 2 deletions.
    2 changes: 0 additions & 2 deletions Animators.kt
    Original file line number Diff line number Diff line change
    @@ -14,8 +14,6 @@
    * limitations under the License.
    */

    package app.tivi.ui.animations

    import android.animation.Animator
    import android.animation.AnimatorSet
    import android.animation.ObjectAnimator
  2. chrisbanes created this gist Jun 27, 2018.
    57 changes: 57 additions & 0 deletions Animators.kt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,57 @@
    /*
    * Copyright 2018 Google, Inc.
    *
    * Licensed under the Apache License, Version 2.0 (the "License");
    * you may not use this file except in compliance with the License.
    * You may obtain a copy of the License at
    *
    * http://www.apache.org/licenses/LICENSE-2.0
    *
    * Unless required by applicable law or agreed to in writing, software
    * distributed under the License is distributed on an "AS IS" BASIS,
    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    * See the License for the specific language governing permissions and
    * limitations under the License.
    */

    package app.tivi.ui.animations

    import android.animation.Animator
    import android.animation.AnimatorSet
    import android.animation.ObjectAnimator
    import android.graphics.ColorMatrixColorFilter
    import android.graphics.drawable.Drawable
    import android.support.v4.view.animation.FastOutSlowInInterpolator
    import android.view.View
    import androidx.core.animation.doOnEnd
    import kotlin.math.roundToLong

    private val fastOutSlowInInterpolator = FastOutSlowInInterpolator()

    fun saturateDrawableAnimator(current: Drawable, view: View): Animator {
    view.setHasTransientState(true)
    val cm = ImageLoadingColorMatrix()

    val duration = 1500L

    val satAnim = ObjectAnimator.ofFloat(cm, ImageLoadingColorMatrix.PROP_SATURATION, 0f, 1f)
    satAnim.duration = duration
    satAnim.interpolator = fastOutSlowInInterpolator
    satAnim.addUpdateListener { current.colorFilter = ColorMatrixColorFilter(cm) }

    val alphaAnim = ObjectAnimator.ofFloat(cm, ImageLoadingColorMatrix.PROP_ALPHA, 0f, 1f)
    alphaAnim.duration = duration / 2
    alphaAnim.interpolator = fastOutSlowInInterpolator

    val darkenAnim = ObjectAnimator.ofFloat(cm, ImageLoadingColorMatrix.PROP_DARKEN, 0f, 1f)
    darkenAnim.duration = (duration * 0.75f).roundToLong()
    darkenAnim.interpolator = fastOutSlowInInterpolator

    val set = AnimatorSet()
    set.playTogether(satAnim, alphaAnim, darkenAnim)
    set.doOnEnd {
    current.clearColorFilter()
    view.setHasTransientState(false)
    }
    return set
    }
    52 changes: 52 additions & 0 deletions GlideExtensions.kt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,52 @@
    /*
    * Copyright 2018 Google, Inc.
    *
    * Licensed under the Apache License, Version 2.0 (the "License");
    * you may not use this file except in compliance with the License.
    * You may obtain a copy of the License at
    *
    * http://www.apache.org/licenses/LICENSE-2.0
    *
    * Unless required by applicable law or agreed to in writing, software
    * distributed under the License is distributed on an "AS IS" BASIS,
    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    * See the License for the specific language governing permissions and
    * limitations under the License.
    */

    import android.graphics.drawable.Drawable
    import com.bumptech.glide.load.DataSource
    import com.bumptech.glide.request.transition.NoTransition
    import com.bumptech.glide.request.transition.Transition
    import com.bumptech.glide.request.transition.TransitionFactory

    class SaturationTransitionFactory : TransitionFactory<Drawable> {
    override fun build(dataSource: DataSource, isFirstResource: Boolean): Transition<Drawable> {
    return if (isFirstResource && dataSource != DataSource.MEMORY_CACHE) {
    // Only start the transition if this is not a recent load. We approximate that by
    // checking if the image is from the memory cache
    SaturationTransition()
    } else {
    NoTransition<Drawable>()
    }
    }
    }

    internal class SaturationTransition : Transition<Drawable> {
    override fun transition(current: Drawable, adapter: Transition.ViewAdapter): Boolean {
    saturateDrawableAnimator(current, adapter.view).also {
    it.start()
    }
    // We want Glide to still set the drawable
    return false
    }
    }

    @GlideExtension
    object GlideExtensions {
    @JvmStatic
    @GlideType(Drawable::class)
    fun saturateOnLoad(requestBuilder: RequestBuilder<Drawable>) {
    requestBuilder.transition(DrawableTransitionOptions.with(SaturationTransitionFactory()))
    }
    }
    98 changes: 98 additions & 0 deletions ImageLoadingColorMatrix.kt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,98 @@
    /*
    * Copyright 2018 Google, Inc.
    *
    * Licensed under the Apache License, Version 2.0 (the "License");
    * you may not use this file except in compliance with the License.
    * You may obtain a copy of the License at
    *
    * http://www.apache.org/licenses/LICENSE-2.0
    *
    * Unless required by applicable law or agreed to in writing, software
    * distributed under the License is distributed on an "AS IS" BASIS,
    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    * See the License for the specific language governing permissions and
    * limitations under the License.
    */

    import android.graphics.ColorMatrix

    /**
    * An extension to [ColorMatrix] which implements the Material image loading pattern
    */
    class ImageLoadingColorMatrix : ColorMatrix() {
    private val elements = FloatArray(20)

    var saturationFraction = 1f
    set(value) {
    System.arraycopy(array, 0, elements, 0, 20)

    // Taken from ColorMatrix.setSaturation. We can't use that though since it resets the matrix
    // before applying the values
    val invSat = 1 - value
    val r = 0.213f * invSat
    val g = 0.715f * invSat
    val b = 0.072f * invSat

    elements[0] = r + value
    elements[1] = g
    elements[2] = b
    elements[5] = r
    elements[6] = g + value
    elements[7] = b
    elements[10] = r
    elements[11] = g
    elements[12] = b + value

    set(elements)
    }

    var alphaFraction = 1f
    set(value) {
    System.arraycopy(array, 0, elements, 0, 20)
    elements[18] = value
    set(elements)
    }

    var darkenFraction = 1f
    set(value) {
    System.arraycopy(array, 0, elements, 0, 20)

    // We substract to make the picture look darker, it will automatically clamp
    val darkening = (1 - value) * MAX_DARKEN_PERCENTAGE * 255
    elements[4] = -darkening
    elements[9] = -darkening
    elements[14] = -darkening

    set(elements)
    }

    companion object {
    private val saturationFloatProp = object : FloatProp<ImageLoadingColorMatrix>("saturation") {
    override operator fun get(o: ImageLoadingColorMatrix): Float = o.saturationFraction
    override operator fun set(o: ImageLoadingColorMatrix, value: Float) {
    o.saturationFraction = value
    }
    }

    private val alphaFloatProp = object : FloatProp<ImageLoadingColorMatrix>("alpha") {
    override operator fun get(o: ImageLoadingColorMatrix): Float = o.alphaFraction
    override operator fun set(o: ImageLoadingColorMatrix, value: Float) {
    o.alphaFraction = value
    }
    }

    private val darkenFloatProp = object : FloatProp<ImageLoadingColorMatrix>("darken") {
    override operator fun get(o: ImageLoadingColorMatrix): Float = o.darkenFraction
    override operator fun set(o: ImageLoadingColorMatrix, value: Float) {
    o.darkenFraction = value
    }
    }

    val PROP_SATURATION = createFloatProperty(saturationFloatProp)
    val PROP_ALPHA = createFloatProperty(alphaFloatProp)
    val PROP_DARKEN = createFloatProperty(darkenFloatProp)

    // This means that we darken the image by 20%
    private const val MAX_DARKEN_PERCENTAGE = 0.20f
    }
    }
    47 changes: 47 additions & 0 deletions Properties.kt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,47 @@
    /*
    * Copyright 2018 Google, Inc.
    *
    * Licensed under the Apache License, Version 2.0 (the "License");
    * you may not use this file except in compliance with the License.
    * You may obtain a copy of the License at
    *
    * http://www.apache.org/licenses/LICENSE-2.0
    *
    * Unless required by applicable law or agreed to in writing, software
    * distributed under the License is distributed on an "AS IS" BASIS,
    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    * See the License for the specific language governing permissions and
    * limitations under the License.
    */

    import android.os.Build
    import android.util.FloatProperty
    import android.util.Property

    /**
    * A delegate for creating a [Property] of `float` type
    */
    abstract class FloatProp<T>(val name: String) {
    abstract operator fun set(o: T, value: Float)
    abstract operator fun get(o: T): Float
    }

    fun <T> createFloatProperty(impl: FloatProp<T>): Property<T, Float> {
    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
    object : FloatProperty<T>(impl.name) {
    override fun get(o: T): Float = impl[o]

    override fun setValue(o: T, value: Float) {
    impl[o] = value
    }
    }
    } else {
    object : Property<T, Float>(Float::class.java, impl.name) {
    override fun get(o: T): Float = impl[o]

    override fun set(o: T, value: Float) {
    impl[o] = value
    }
    }
    }
    }