-
-
Save Bloody-Badboy/8eb407de4d10205affea75e2743567de to your computer and use it in GitHub Desktop.
Material Image Loading treatment for Android
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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.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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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())) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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 | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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 | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment