While googling it, I found 2 ways to do it for android: use ShaderFactory or extends View, using new Shader(new LinearGradient()). Both answers do the same - calling new Shader() every View.onDraw(Canvas canvas) method's call. Its really expensive if number of such animated gradients more than ~3.
So I did it another way. I avoided calling new every onDraw(), using single precalculated LinearGradient. That's how it is look like (gif, so animation decayed) :
The trick is to create LinearGradient which is colorsCount times larger than View.getWidth(). After that you can use canvas.translate(), while drawing gradient, to change its color, so no new calls in onDraw() at all.
To create gradient, you need current width and height. I did it in onSizeChanged(). Also I set Shader here too.
I use path because of parallelogramy views, you can use whatever you want. When implementing drawing, you should notice 2 things: you need to translate() whole canvas on current offset and offset() your filling shape:
@Override
protected void onDraw(Canvas canvas) {
canvas.save();
canvas.translate(-gradientOffset, 0);
shapePath.offset(gradientOffset, 0f, tempPath);
canvas.drawPath(tempPath, fillPaint);
canvas.restore();
canvas.drawPath(shapeBorderPath, borderPaint);
super.onDraw(canvas); // my View is FrameLayout, so need to call it after
}
Also you should use canvas.save() & canvas.restore(). It will save inner matrix of canvas to stack and restore it correspondingly.
So the last what you need to do is to animate gradientOffset. You can use everything you want like ObjectAnimator (Property Animation). I used TimeAnimator, because I needed to control updateTick and starting offset directly. Here is my realisation (a bit difficult and harsh):
static public final int LIFETIME_DEAFULT = 2300;
private long lifetime = LIFETIME_DEAFULT, updateTickMs = 25, timeElapsed = 0;
private long accumulatorMs = 0;
private float gradientOffset = 0f;
public void startGradientAnimation() {
stopGradientAnimation();
resolveTimeElapsed();
final float gradientOffsetCoef = (float) (updateTickMs) / lifetime;
final int colorsCount = this.colors.length - 1;
gradientAnimation.setTimeListener(new TimeAnimator.TimeListener() {
@Override
public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
final long gradientWidth = width * colorsCount;
if (totalTime > (lifetime - timeElapsed)) {
animation.cancel();
gradientOffset = gradientWidth;
invalidate();
} else {
accumulatorMs += deltaTime;
final long gradientOffsetsCount = accumulatorMs / updateTickMs;
gradientOffset += (gradientOffsetsCount * gradientWidth) * gradientOffsetCoef;
accumulatorMs %= updateTickMs;
boolean gradientOffsetChanged = (gradientOffsetsCount > 0) ? true : false;
if (gradientOffsetChanged) {
invalidate();
}
}
}
});
gradientAnimation.start();
}
I've figured out different, way shorter approach. Simply use setColors (int[] colors) method of GradientDrawable and animate it over time.
This is an example how to do it:
val start = Color.DKGRAY
val mid = Color.MAGENTA
val end = Color.BLUE
//content.background is set as a GradientDrawable in layout xml file
val gradient = content.background as GradientDrawable
val evaluator = ArgbEvaluator()
val animator = TimeAnimator.ofFloat(0.0f, 1.0f)
animator.duration = 1500
animator.repeatCount = ValueAnimator.INFINITE
animator.repeatMode = ValueAnimator.REVERSE
animator.addUpdateListener {
val fraction = it.animatedFraction
val newStart = evaluator.evaluate(fraction, start, end) as Int
val newMid = evaluator.evaluate(fraction, mid, start) as Int
val newEnd = evaluator.evaluate(fraction, end, mid) as Int
gradient.colors = intArrayOf(newStart, newMid, newEnd)
}
animator.start()
just rewrited @Krzysztof Misztal's answer to
java
:where the
view
is a simpleView
with gradient background:the view :
hope my answer will help
While googling it, I found 2 ways to do it for android: use ShaderFactory or extends View, using
new Shader(new LinearGradient())
. Both answers do the same - callingnew Shader()
everyView.onDraw(Canvas canvas)
method's call. Its really expensive if number of such animated gradients more than ~3.So I did it another way. I avoided calling
new
everyonDraw()
, using single precalculatedLinearGradient
. That's how it is look like (gif, so animation decayed) :The trick is to create
LinearGradient
which iscolorsCount
times larger thanView.getWidth()
. After that you can usecanvas.translate()
, while drawing gradient, to change its color, so nonew
calls inonDraw()
at all.To create gradient, you need current width and height. I did it in
onSizeChanged()
. Also I setShader
here too.I use path because of parallelogramy views, you can use whatever you want. When implementing drawing, you should notice 2 things: you need to
translate()
wholecanvas
on current offset andoffset()
your filling shape:Also you should use
canvas.save()
&canvas.restore()
. It will save inner matrix of canvas to stack and restore it correspondingly.So the last what you need to do is to animate
gradientOffset
. You can use everything you want like ObjectAnimator (Property Animation). I used TimeAnimator, because I needed to controlupdateTick
and starting offset directly. Here is my realisation (a bit difficult and harsh):The full
View
code you can find hereI've figured out different, way shorter approach. Simply use
setColors (int[] colors)
method ofGradientDrawable
and animate it over time.This is an example how to do it:
And the outcome: