如何动画渐变?
How to animate gradient?
如何制作从颜色#1 到颜色#2 的渐变动画?类似于
我打算将它用作单位的健康条(因此,它将是从绿色开始到红色结束的有限动画)
在谷歌搜索时,我找到了 2 种方法来实现 android:use ShaderFactory 或使用 new Shader(new LinearGradient())
扩展视图。两个答案都做同样的事情——在每个 View.onDraw(Canvas canvas)
方法的调用中调用 new Shader()
。如果这种动画渐变的数量超过 ~3 个,它真的很昂贵。
所以我换了一种方式。我避免使用单个预先计算的 LinearGradient
每隔 onDraw()
调用 new
。这就是它的样子(gif,所以动画衰减):
诀窍是创建 LinearGradient
,它是 View.getWidth()
的 colorsCount
倍。之后,您可以在绘制渐变时使用 canvas.translate()
来更改其颜色,因此 new
根本不会调用 onDraw()
。
要创建渐变,您需要当前的宽度和高度。我在 onSizeChanged()
中做到了。我也在这里设置了Shader
。
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
width = getWidth();
height = getHeight();
LinearGradient gradient = new LinearGradient(
0, height / 2, width * colors.length - 1, height / 2,
colors, null, Shader.TileMode.REPEAT);
fillPaint.setShader(gradient);
shapePath = getParallelogrammPath(width, height, sidesGap);
shapeBorderPath = getParallelogrammPath(width, height, sidesGap);
}
我用路径是因为平行四边形视图,你可以随便用。在实现绘图时,您应该注意两件事:您需要 translate()
当前偏移量 canvas
和 offset()
您的填充形状:
@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
}
您还应该使用 canvas.save()
& canvas.restore()
。会把canvas的内矩阵保存到栈中,并对应恢复。
因此,您最后需要做的是制作动画 gradientOffset
。你可以使用任何你想要的东西,比如 ObjectAnimator (Property Animation). I used TimeAnimator,因为我需要直接控制 updateTick
和开始偏移。这是我的实现(有点困难和苛刻):
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();
}
您可以找到完整的 View
代码 here
我找到了不同的、更短的方法。只需使用 GradientDrawable
的 setColors (int[] colors)
方法并随着时间的推移对其进行动画处理。
这是一个如何操作的示例:
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()
结果:
刚刚重写了@Krzysztof Misztal 对 java
的回答:
public static void startAnimation(final int view, final Activity activity) {
final int start = Color.parseColor("#FDB72B");
final int mid = Color.parseColor("#88FDB72B");
final int end = Color.TRANSPARENT;
final ArgbEvaluator evaluator = new ArgbEvaluator();
View preloader = activity.findViewById(R.id.gradientPreloaderView);
preloader.setVisibility(View.VISIBLE);
final GradientDrawable gradient = (GradientDrawable) preloader.getBackground();
ValueAnimator animator = TimeAnimator.ofFloat(0.0f, 1.0f);
animator.setDuration(500);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setRepeatMode(ValueAnimator.REVERSE);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
Float fraction = valueAnimator.getAnimatedFraction();
int newStrat = (int) evaluator.evaluate(fraction, start, end);
int newMid = (int) evaluator.evaluate(fraction, mid, start);
int newEnd = (int) evaluator.evaluate(fraction, end, mid);
int[] newArray = {newStrat, newMid, newEnd};
gradient.setColors(newArray);
}
});
animator.start();
}
public static void stopAnimation(final int view, final Activity activity){
ObjectAnimator.ofFloat(activity.findViewById(view), "alpha", 0f).setDuration(125).start();
}
其中 view
是具有渐变背景的简单 View
:
//gradient_preloader
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:startColor="#FDB72B"
android:endColor="#00000000"
android:angle="0"/>
</shape>
观点:
<View
android:id="@+id/gradientPreloaderView"
android:layout_width="match_parent"
android:visibility="gone"
android:layout_height="@dimen/basic_8_dp"
android:background="@drawable/gradient_preloader" />
希望我的回答对您有所帮助
最新的备选答案。
class GradientAnimationDrawable(
start: Int = Color.rgb(0, 143, 209),
center: Int = Color.rgb(1, 106, 154),
end: Int = Color.rgb(28, 179, 249),
frameDuration: Int = 3000,
enterFadeDuration: Int = 0,
exitFadeDuration: Int = 3000
) : AnimationDrawable() {
private val gradientStart = GradientDrawable(GradientDrawable.Orientation.RIGHT_LEFT, intArrayOf(start, center, end))
.apply {
shape = GradientDrawable.RECTANGLE
gradientType = GradientDrawable.LINEAR_GRADIENT
}
private val gradientCenter = GradientDrawable(GradientDrawable.Orientation.RIGHT_LEFT, intArrayOf(center, end, start))
.apply {
shape = GradientDrawable.RECTANGLE
gradientType = GradientDrawable.LINEAR_GRADIENT
}
private val gradientEnd = GradientDrawable(GradientDrawable.Orientation.RIGHT_LEFT, intArrayOf(end, start, center))
.apply {
shape = GradientDrawable.RECTANGLE
gradientType = GradientDrawable.LINEAR_GRADIENT
}
init {
addFrame(gradientStart, frameDuration)
addFrame(gradientCenter, frameDuration)
addFrame(gradientEnd, frameDuration)
setEnterFadeDuration(enterFadeDuration)
setExitFadeDuration(exitFadeDuration)
isOneShot = false
}
}
如何制作从颜色#1 到颜色#2 的渐变动画?类似于
我打算将它用作单位的健康条(因此,它将是从绿色开始到红色结束的有限动画)
在谷歌搜索时,我找到了 2 种方法来实现 android:use ShaderFactory 或使用 new Shader(new LinearGradient())
扩展视图。两个答案都做同样的事情——在每个 View.onDraw(Canvas canvas)
方法的调用中调用 new Shader()
。如果这种动画渐变的数量超过 ~3 个,它真的很昂贵。
所以我换了一种方式。我避免使用单个预先计算的 LinearGradient
每隔 onDraw()
调用 new
。这就是它的样子(gif,所以动画衰减):
诀窍是创建 LinearGradient
,它是 View.getWidth()
的 colorsCount
倍。之后,您可以在绘制渐变时使用 canvas.translate()
来更改其颜色,因此 new
根本不会调用 onDraw()
。
要创建渐变,您需要当前的宽度和高度。我在 onSizeChanged()
中做到了。我也在这里设置了Shader
。
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
width = getWidth();
height = getHeight();
LinearGradient gradient = new LinearGradient(
0, height / 2, width * colors.length - 1, height / 2,
colors, null, Shader.TileMode.REPEAT);
fillPaint.setShader(gradient);
shapePath = getParallelogrammPath(width, height, sidesGap);
shapeBorderPath = getParallelogrammPath(width, height, sidesGap);
}
我用路径是因为平行四边形视图,你可以随便用。在实现绘图时,您应该注意两件事:您需要 translate()
当前偏移量 canvas
和 offset()
您的填充形状:
@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
}
您还应该使用 canvas.save()
& canvas.restore()
。会把canvas的内矩阵保存到栈中,并对应恢复。
因此,您最后需要做的是制作动画 gradientOffset
。你可以使用任何你想要的东西,比如 ObjectAnimator (Property Animation). I used TimeAnimator,因为我需要直接控制 updateTick
和开始偏移。这是我的实现(有点困难和苛刻):
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();
}
您可以找到完整的 View
代码 here
我找到了不同的、更短的方法。只需使用 GradientDrawable
的 setColors (int[] colors)
方法并随着时间的推移对其进行动画处理。
这是一个如何操作的示例:
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()
结果:
刚刚重写了@Krzysztof Misztal 对 java
的回答:
public static void startAnimation(final int view, final Activity activity) {
final int start = Color.parseColor("#FDB72B");
final int mid = Color.parseColor("#88FDB72B");
final int end = Color.TRANSPARENT;
final ArgbEvaluator evaluator = new ArgbEvaluator();
View preloader = activity.findViewById(R.id.gradientPreloaderView);
preloader.setVisibility(View.VISIBLE);
final GradientDrawable gradient = (GradientDrawable) preloader.getBackground();
ValueAnimator animator = TimeAnimator.ofFloat(0.0f, 1.0f);
animator.setDuration(500);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setRepeatMode(ValueAnimator.REVERSE);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
Float fraction = valueAnimator.getAnimatedFraction();
int newStrat = (int) evaluator.evaluate(fraction, start, end);
int newMid = (int) evaluator.evaluate(fraction, mid, start);
int newEnd = (int) evaluator.evaluate(fraction, end, mid);
int[] newArray = {newStrat, newMid, newEnd};
gradient.setColors(newArray);
}
});
animator.start();
}
public static void stopAnimation(final int view, final Activity activity){
ObjectAnimator.ofFloat(activity.findViewById(view), "alpha", 0f).setDuration(125).start();
}
其中 view
是具有渐变背景的简单 View
:
//gradient_preloader
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:startColor="#FDB72B"
android:endColor="#00000000"
android:angle="0"/>
</shape>
观点:
<View
android:id="@+id/gradientPreloaderView"
android:layout_width="match_parent"
android:visibility="gone"
android:layout_height="@dimen/basic_8_dp"
android:background="@drawable/gradient_preloader" />
希望我的回答对您有所帮助
最新的备选答案。
class GradientAnimationDrawable(
start: Int = Color.rgb(0, 143, 209),
center: Int = Color.rgb(1, 106, 154),
end: Int = Color.rgb(28, 179, 249),
frameDuration: Int = 3000,
enterFadeDuration: Int = 0,
exitFadeDuration: Int = 3000
) : AnimationDrawable() {
private val gradientStart = GradientDrawable(GradientDrawable.Orientation.RIGHT_LEFT, intArrayOf(start, center, end))
.apply {
shape = GradientDrawable.RECTANGLE
gradientType = GradientDrawable.LINEAR_GRADIENT
}
private val gradientCenter = GradientDrawable(GradientDrawable.Orientation.RIGHT_LEFT, intArrayOf(center, end, start))
.apply {
shape = GradientDrawable.RECTANGLE
gradientType = GradientDrawable.LINEAR_GRADIENT
}
private val gradientEnd = GradientDrawable(GradientDrawable.Orientation.RIGHT_LEFT, intArrayOf(end, start, center))
.apply {
shape = GradientDrawable.RECTANGLE
gradientType = GradientDrawable.LINEAR_GRADIENT
}
init {
addFrame(gradientStart, frameDuration)
addFrame(gradientCenter, frameDuration)
addFrame(gradientEnd, frameDuration)
setEnterFadeDuration(enterFadeDuration)
setExitFadeDuration(exitFadeDuration)
isOneShot = false
}
}