web-dev-qa-db-ja.com

グラデーションをアニメーション化する方法は?

色#1から色#2へのグラデーションをアニメーション化する方法は?に似たもの

enter image description here

ユニットのヘルスバーとして使用する予定です(つまり、緑で始まり赤で終わるfinitアニメーションになります)

12
Nexen

グーグルしながら、Androidでそれを行う2つの方法を見つけました: ShaderFactoryを使用 またはnew Shader(new LinearGradient())を使用してViewを拡張します。どちらの答えも同じです-new Shader()メソッドの呼び出しごとにView.onDraw(Canvas canvas)を呼び出します。そのようなアニメーション化されたグラデーションの数が3を超える場合、それは本当に高価です。

だから私はそれを別の方法でやった。事前に計算された単一のnewを使用して、onDraw()ごとにLinearGradientを呼び出すことを避けました。それはそれがどのように見えるかです(gif、したがってアニメーションは崩壊しました):

enter image description here

秘訣は、View.getWidth()LinearGradient倍のcolorsCountを作成することです。その後、グラデーションを描画しながらcanvas.translate()を使用して色を変更できるため、onDraw()newが呼び出されることはありません。

グラデーションを作成するには、現在の幅と高さが必要です。 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);
}
_

平行四辺形のビューのためにパスを使用します。好きなものを使用できます。描画を実装するときは、2つのことに注意する必要があります。現在のオフセットで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()を使用する必要があります。キャンバスの内部マトリックスを保存してスタックし、それに応じて復元します。

したがって、最後に行う必要があるのは、gradientOffsetをアニメーション化することです。 ObjectAnimator(Property Animation) のように好きなものをすべて使用できます。 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コード ここ

28
Nexen

私は別の、はるかに短いアプローチを考え出しました。 GradientDrawablesetColors (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()

そして結果:

enter image description here

9

@KrzysztofMisztalの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" />

私の答えが役立つことを願っています

4
Csabi

最新の代替回答。

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
  }

}

0
MrOnyszko