I have a function that creates a gradient across the background of my MainActivity
. The gradient is created programmatically as a GradientDrawable
. Certain interactions in the App change the color of the gradient.
The gradient and colors are properly shown, and they do change accordingly, however, I would like a transition effect on the color changes. Right now, the colors transition instantly with no crossfade.
fun createGradient() {
gd.shape = GradientDrawable.RECTANGLE
mainActivityID.background = gd
gd.gradientType = GradientDrawable.LINEAR_GRADIENT
gd.orientation = (GradientDrawable.Orientation.BL_TR)
}
Based on interactions in the App, I use a when
statement to change the colors of the GradientDrawable
fun changeGradient(){
//val td = gd as TransitionDrawable
//td.isCrossFadeEnabled = true
//td.startTransition(1500)
when(calories){
in 0..50 -> gd.colors = intArrayOf(
ContextCompat.getColor(this, R.color.dBlue),
ContextCompat.getColor(this, R.color.lBlue)
)
in 51..100 -> gd.colors = intArrayOf(
ContextCompat.getColor(this, R.color.dRed),
ContextCompat.getColor(this, R.color.lRed)
)
// etc...
}
}
I tried casting the GradientDrawable as a TransitionDrawable (as seen in the commented code above), but that crashes with:
java.lang.ClassCastException: android.graphics.drawable.GradientDrawable cannot be cast to android.graphics.drawable.TransitionDrawable
How do I add a 1500 millisecond transition between the GradientDrawable color changes?
Progress crashes with java.lang.NegativeArraySizeException: -16777216
when(calories){
in 0..50 -> animateGradient(gd, IntArray(Color.BLACK), IntArray(Color.CYAN))
in 51..100 -> animateGradient(gd, IntArray(Color.RED), IntArray(Color.BLUE))
in 101..150 -> animateGradient(gd, IntArray(Color.GREEN), IntArray(Color.YELLOW))
// etc... about 15 more statements.
}
I don't think gradient drawable has built in transition methods, but You can run your own value animator to perform color transition.
var anim : Animator? = null
fun animateGradient(gd: GradientDrawable, from : IntArray, to: IntArray){
require(from.size == to.size)
anim?.cancel()
val arraySize = from.size
val props = Array<PropertyValuesHolder>(arraySize){
PropertyValuesHolder.ofObject(it.toString(), ArgbEvaluator(), from[it], to[it])
}
val anim = ValueAnimator.ofPropertyValuesHolder(*props)
anim.addUpdateListener {
gd.colors = IntArray(arraySize){i ->
it.getAnimatedValue(i.toString()) as Int
}
}
anim.duration = 1500
anim.start()
this.anim = anim
}
Edit: modified method so it that tracks current color itself, reduced number of arguments.
var anim : Animator? = null
// variable that tracks current color - must be initialized with default gradient colors
var currentGradient = intArrayOf(
ContextCompat.getColor(this, R.color.dBlue),
ContextCompat.getColor(this, R.color.lBlue)
)
fun animateGradient(targetColors: IntArray){
val from = currentGradient
require(from.size == targetColors.size)
anim?.cancel()
val arraySize = from.size
val props = Array<PropertyValuesHolder>(arraySize){
PropertyValuesHolder.ofObject(it.toString(), ArgbEvaluator(), from[it], targetColors[it])
}
val anim = ValueAnimator.ofPropertyValuesHolder(*props)
anim.addUpdateListener {valueAnim ->
IntArray(arraySize){i ->
valueAnim.getAnimatedValue(i.toString()) as Int
}.let{
currentGradient = it
gd.colors = it
}
}
anim.duration = 1500
anim.start()
this.anim = anim
}
Call site:
when(calories){
in 0..50 -> animateGradient(intArrayOf(
ContextCompat.getColor(this, R.color.dBlue),
ContextCompat.getColor(this, R.color.lBlue)
))
in 51..100 -> animateGradient(intArrayOf(
ContextCompat.getColor(this, R.color.dRed),
ContextCompat.getColor(this, R.color.lRed)
))
// etc...
}