web-dev-qa-db-ja.com

不確定な進行のように、繰り返し動くアニメーションのグラデーションドローアブルを作成する方法は?

バックグラウンド

Androidには、不確定のときに特別なアニメーションを表示する標準のProgressBarがあります。利用可能な非常に多くの種類の進行状況ビューのライブラリもたくさんあります( here )。

問題

私が検索したすべての中で、非常に単純なことを行う方法を見つけることができません。

色Xから色Yへのグラデーションがあり、水平に表示され、X座標で移動して、Xの前の色が色Yになるようにします。

たとえば(単なるイラスト)、EdgeからEdgeへのblue <-> redのグラデーションがある場合、次のようになります。

enter image description here

私が試したこと

ここで提供されているいくつかのソリューションをStackOverflowで試しました:

悲しいことに、これらはすべてAndroidの標準のProgressBarビューに関するものです。つまり、ドローアブルのアニメーションを表示する方法が異なります。

Androidアーセナルのウェブサイトでも似たようなものを見つけようとしましたが、ニースのサイトはたくさんありますが、そのようなものは見つかりませんでした。

もちろん、自分で2つのビューをアニメーション化することもできます。それぞれに独自の勾配があります(一方が他方の反対です)が、もっと良い方法があると確信しています。

質問

Drawableまたはそのアニメーションを使用して、グラデーション(またはその他)をこのように移動させることは可能ですか(もちろん繰り返します)?

たぶんImageViewから拡張して、そこでドローアブルをアニメーション化するだけでしょうか?

繰り返しドローアブルに使用されるコンテナの量を設定することもできますか?つまり、上記の例では、青から赤になり、青が端になり、赤が中央になります。


編集:

OK、少し進歩しましたが、動きが大丈夫かどうかはわかりません。CPUが少しビジーな場合に備えて、速度が適切に一定していないと思います。フレームドロップは考慮されません。私がしたことは、次のように2つのGradientDrawableを隣同士に描画することです。

class HorizontalProgressView @JvmOverloads constructor(
        context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
    private val speedInPercentage = 1.5f
    private var xMovement: Float = 0.0f
    private val rightDrawable: GradientDrawable = GradientDrawable()
    private val leftDrawable: GradientDrawable = GradientDrawable()

    init {
        if (isInEditMode)
            setGradientColors(intArrayOf(Color.RED, Color.BLUE))
        rightDrawable.gradientType = GradientDrawable.LINEAR_GRADIENT;
        rightDrawable.orientation = GradientDrawable.Orientation.LEFT_RIGHT
        rightDrawable.shape = GradientDrawable.RECTANGLE;
        leftDrawable.gradientType = GradientDrawable.LINEAR_GRADIENT;
        leftDrawable.orientation = GradientDrawable.Orientation.RIGHT_LEFT
        leftDrawable.shape = GradientDrawable.RECTANGLE;
    }

    fun setGradientColors(colors: IntArray) {
        rightDrawable.colors = colors
        leftDrawable.colors = colors
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        val widthSize = View.MeasureSpec.getSize(widthMeasureSpec)
        val heightSize = View.MeasureSpec.getSize(heightMeasureSpec)
        rightDrawable.setBounds(0, 0, widthSize, heightSize)
        leftDrawable.setBounds(0, 0, widthSize, heightSize)
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        canvas.save()
        if (xMovement < width) {
            canvas.translate(xMovement, 0.0f)
            rightDrawable.draw(canvas)
            canvas.translate(-width.toFloat(), 0.0f)
            leftDrawable.draw(canvas)
        } else {
            //now the left one is actually on the right
            canvas.translate(xMovement - width, 0.0f)
            leftDrawable.draw(canvas)
            canvas.translate(-width.toFloat(), 0.0f)
            rightDrawable.draw(canvas)
        }
        canvas.restore()
        xMovement += speedInPercentage * width / 100.0f
        if (isInEditMode)
            return
        if (xMovement >= width * 2.0f)
            xMovement = 0.0f
        invalidate()
    }
}

使用法:

    horizontalProgressView.setGradientColors(intArrayOf(Color.RED, Color.BLUE))

そして結果(ループはうまくいきます、ビデオを編集するのは難しいです):

enter image description here

だから私の質問は、UIスレッドが少し忙しい場合でも、アニメーションがうまく機能することを確認するにはどうすればよいですか?

invalidateだけでは信頼できる方法とは思えないだけです。それ以上チェックすべきだと思います。多分それは代わりにいくつかのアニメーションAPIを使用することができます。

23

ここでKotlinに「pskink」という回答を入れることにしました(Origin here )。ここに書いたのは、他の解決策が機能しなかった、または私が尋ねたものの代わりに回避策だったからです。

class ScrollingGradient(private val pixelsPerSecond: Float) : Drawable(), Animatable, TimeAnimator.TimeListener {
    private val Paint = Paint()
    private var x: Float = 0.toFloat()
    private val animator = TimeAnimator()

    init {
        animator.setTimeListener(this)
    }

    override fun onBoundsChange(bounds: Rect) {
        Paint.shader = LinearGradient(0f, 0f, bounds.width().toFloat(), 0f, Color.WHITE, Color.BLUE, Shader.TileMode.MIRROR)
    }

    override fun draw(canvas: Canvas) {
        canvas.clipRect(bounds)
        canvas.translate(x, 0f)
        canvas.drawPaint(Paint)
    }

    override fun setAlpha(alpha: Int) {}

    override fun setColorFilter(colorFilter: ColorFilter?) {}

    override fun getOpacity(): Int = PixelFormat.TRANSLUCENT

    override fun start() {
        animator.start()
    }

    override fun stop() {
        animator.cancel()
    }

    override fun isRunning(): Boolean = animator.isRunning

    override fun onTimeUpdate(animation: TimeAnimator, totalTime: Long, deltaTime: Long) {
        x = pixelsPerSecond * totalTime / 1000
        invalidateSelf()
    }
}

使用法:

MainActivity.kt

    val px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200f, resources.getDisplayMetrics())
    progress.indeterminateDrawable = ScrollingGradient(px)

activity_main.xml

<LinearLayout
    xmlns:Android="http://schemas.Android.com/apk/res/Android" xmlns:app="http://schemas.Android.com/apk/res-auto"
    xmlns:tools="http://schemas.Android.com/tools" Android:layout_width="match_parent"
    Android:layout_height="match_parent" Android:gravity="center" Android:orientation="vertical"
    tools:context=".MainActivity">

    <ProgressBar
        Android:id="@+id/progress" style="?android:attr/progressBarStyleHorizontal" Android:layout_width="200dp"
        Android:layout_height="20dp" Android:indeterminate="true"/>
</LinearLayout>
4

私のソリューションの背後にあるアイデアは比較的単純です。2つの子ビュー(開始-終了グラデーションと終了-開始グラデーション)があるFrameLayoutを表示し、ValueAnimatorを使用して子ビューをアニメーション化します ' translationX属性。カスタム描画を行っておらず、フレームワークが提供するアニメーションユーティリティを使用しているので、アニメーションのパフォーマンスについて心配する必要はありません。

これをすべて管理するためのカスタムFrameLayoutサブクラスを作成しました。次のように、レイアウトにビューのインスタンスを追加するだけです。

_<com.example.MyHorizontalProgress
    Android:layout_width="match_parent"
    Android:layout_height="6dp"
    app:animationDuration="2000"
    app:gradientStartColor="#000"
    app:gradientEndColor="#fff"/>
_

グラデーションカラーとアニメーションの速度は、XMLから直接カスタマイズできます。

コード

最初に、カスタム属性を_res/values/attrs.xml_で定義する必要があります。

_<declare-styleable name="MyHorizontalProgress">
    <attr name="animationDuration" format="integer"/>
    <attr name="gradientStartColor" format="color"/>
    <attr name="gradientEndColor" format="color"/>
</declare-styleable>
_

そして、2つのアニメーションビューを膨らませるレイアウトリソースファイルがあります。

_<merge xmlns:Android="http://schemas.Android.com/apk/res/Android">

    <View
        Android:id="@+id/one"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent"/>

    <View
        Android:id="@+id/two"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent"/>

</merge>
_

そしてここにJavaがあります:

_public class MyHorizontalProgress extends FrameLayout {

    private static final int DEFAULT_ANIMATION_DURATION = 2000;
    private static final int DEFAULT_START_COLOR = Color.RED;
    private static final int DEFAULT_END_COLOR = Color.BLUE;

    private final View one;
    private final View two;

    private int animationDuration;
    private int startColor;
    private int endColor;

    private int laidOutWidth;

    public MyHorizontalProgress(Context context, AttributeSet attrs) {
        super(context, attrs);

        inflate(context, R.layout.my_horizontal_progress, this);
        readAttributes(attrs);

        one = findViewById(R.id.one);
        two = findViewById(R.id.two);

        ViewCompat.setBackground(one, new GradientDrawable(LEFT_RIGHT, new int[]{ startColor, endColor }));
        ViewCompat.setBackground(two, new GradientDrawable(LEFT_RIGHT, new int[]{ endColor, startColor }));

        getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

            @Override
            public void onGlobalLayout() {
                laidOutWidth = MyHorizontalProgress.this.getWidth();

                ValueAnimator animator = ValueAnimator.ofInt(0, 2 * laidOutWidth);
                animator.setInterpolator(new LinearInterpolator());
                animator.setRepeatCount(ValueAnimator.INFINITE);
                animator.setRepeatMode(ValueAnimator.RESTART);
                animator.setDuration(animationDuration);
                animator.addUpdateListener(updateListener);
                animator.start();

                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                    getViewTreeObserver().removeOnGlobalLayoutListener(this);
                }
                else {
                    getViewTreeObserver().removeGlobalOnLayoutListener(this);
                }
            }
        });
    }

    private void readAttributes(AttributeSet attrs) {
        TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.MyHorizontalProgress);
        animationDuration = a.getInt(R.styleable.MyHorizontalProgress_animationDuration, DEFAULT_ANIMATION_DURATION);
        startColor = a.getColor(R.styleable.MyHorizontalProgress_gradientStartColor, DEFAULT_START_COLOR);
        endColor = a.getColor(R.styleable.MyHorizontalProgress_gradientEndColor, DEFAULT_END_COLOR);
        a.recycle();
    }

    private ValueAnimator.AnimatorUpdateListener updateListener = new ValueAnimator.AnimatorUpdateListener() {

        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {
            int offset = (int) valueAnimator.getAnimatedValue();
            one.setTranslationX(calculateOneTranslationX(laidOutWidth, offset));
            two.setTranslationX(calculateTwoTranslationX(laidOutWidth, offset));
        }
    };

    private int calculateOneTranslationX(int width, int offset) {
        return (-1 * width) + offset;
    }

    private int calculateTwoTranslationX(int width, int offset) {
        if (offset <= width) {
            return offset;
        }
        else {
            return (-2 * width) + offset;
        }
    }
}
_

Javaの動作は非常に簡単です。ここでは、何が行われているのかを段階的に説明します。

  • レイアウトリソースを膨らませ、アニメーション化する2つの子をFrameLayoutに追加します
  • AttributeSetからアニメーションの継続時間と色の値を読み取ります
  • onetwoの子ビューを見つけます(あまりクリエイティブな名前ではありません、わかっています)
  • 子ビューごとにGradientDrawableを作成し、背景として適用します
  • OnGlobalLayoutListenerを使用してアニメーションを設定します

OnGlobalLayoutListenerを使用すると、プログレスバーの幅の実際の値が確実に取得され、レイアウトされるまでアニメーションが開始されなくなります。

アニメーションもかなりシンプルです。 _0_と_2 * width_の間の値を出力する、無限に繰り返すValueAnimatorを設定します。 「更新」イベントごとに、生成された「更新」値から計算された値を使用して、子ビューでupdateListenersetTranslationX()を呼び出します。

以上です!上記のいずれかが不明確である場合はお知らせください。喜んでお手伝いいたします。

4
Ben P.
final View bar = view.findViewById(R.id.progress);
final GradientDrawable background = new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, new int[]{Color.BLUE, Color.RED, Color.BLUE, Color.RED});
bar.setBackground(background);
bar.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
    @Override
    public void onLayoutChange(final View v, final int left, final int top, final int right, final int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
        background.setBounds(-2 * v.getWidth(), 0, v.getWidth(), v.getHeight());
        ValueAnimator animation = ValueAnimator.ofInt(0, 2 * v.getWidth());
        animation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                background.setBounds(-2 * v.getWidth() + (int) animation.getAnimatedValue(), 0, v.getWidth() + (int) animation.getAnimatedValue(), v.getHeight());
            }
        });
        animation.setRepeatMode(ValueAnimator.RESTART);
        animation.setInterpolator(new LinearInterpolator());
        animation.setRepeatCount(ValueAnimator.INFINITE);
        animation.setDuration(3000);
        animation.start();
    }
});

これはテスト用のビューです:

<FrameLayout
    xmlns:Android="http://schemas.Android.com/apk/res/Android"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    Android:layout_gravity="center" >

    <View
        Android:id="@+id/progress"
        Android:layout_width="match_parent"
        Android:layout_height="40dp"/>

</FrameLayout>
0
artkoenig

一部の人を助けるかもしれない 'Android開発者のコ​​ードを少し変更しました。

アニメーションが適切にサイズ変更されていないようだったので、これを修正し、アニメーションの速度を少し設定しやすく(ピクセルベースではなく秒単位)、initコードを再配置して、アクティビティのコードなしでレイアウトxmlに直接埋め込めるようにしました。

ScrollingProgressBar.kt

package com.test

import Android.content.Context
import Android.util.AttributeSet
import Android.widget.ProgressBar
import Android.animation.TimeAnimator
import Android.graphics.*
import Android.graphics.drawable.Animatable
import Android.graphics.drawable.Drawable

class ScrollingGradient : Drawable(), Animatable, TimeAnimator.TimeListener {

    private val Paint = Paint()
    private var x: Float = 0.toFloat()
    private val animator = TimeAnimator()
    private var pixelsPerSecond: Float = 0f
    private val animationTime: Int = 2

    init {
        animator.setTimeListener(this)
    }

    override fun onBoundsChange(bounds: Rect) {
        Paint.shader = LinearGradient(0f, 0f, bounds.width().toFloat(), 0f, Color.parseColor("#00D3D3D3"), Color.parseColor("#CCD3D3D3"), Shader.TileMode.MIRROR)
        pixelsPerSecond = ((bounds.right - bounds.left) / animationTime).toFloat()
    }

    override fun draw(canvas: Canvas) {
        canvas.clipRect(bounds)
        canvas.translate(x, 0f)
        canvas.drawPaint(Paint)
    }

    override fun setAlpha(alpha: Int) {}

    override fun setColorFilter(colorFilter: ColorFilter?) {}

    override fun getOpacity(): Int = PixelFormat.TRANSLUCENT

    override fun start() {
        animator.start()
    }

    override fun stop() {
        animator.cancel()
    }

    override fun isRunning(): Boolean = animator.isRunning

    override fun onTimeUpdate(animation: TimeAnimator, totalTime: Long, deltaTime: Long) {
        x = pixelsPerSecond * totalTime / 1000
        invalidateSelf()
    }
}

class ScrollingProgressBar : ProgressBar {

    constructor(context: Context) : super(context)

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

    constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle)

    init {
        this.indeterminateDrawable = ScrollingGradient()
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        this.indeterminateDrawable.setBounds(this.left, this.top, this.right, this.bottom)
    }
}

レイアウトxml(com.test.ScrollingProgressBarを上記のコードの場所に置き換えます)

<com.test.ScrollingProgressBar
        Android:id="@+id/progressBar1"
        Android:background="#464646"
        style="?android:attr/progressBarStyleHorizontal"
        Android:layout_width="match_parent"
        Android:layout_height="80dp"
        Android:gravity="center"
        Android:indeterminateOnly="true"/>
0
Twisted89

プログレスバーとして表示するために必要な色を定義するさまざまなドローアブルがある場合、それを実現できます。

AnimationDrawable animation_listを使用します

 <animation-list Android:id="@+id/selected" Android:oneshot="false">
    <item Android:drawable="@drawable/color1" Android:duration="50" />
    <item Android:drawable="@drawable/color2" Android:duration="50" />
    <item Android:drawable="@drawable/color3" Android:duration="50" />
    <item Android:drawable="@drawable/color4" Android:duration="50" />
    -----
    -----
 </animation-list>

そして、Activity/xmlで、これをプログレスバーのバックグラウンドリソースとして設定します。

その後、次のようにします

// Get the background, which has been compiled to an AnimationDrawable object.
 AnimationDrawable frameAnimation = (AnimationDrawable)prgressBar.getBackground();

 // Start the animation (looped playback by default).
 frameAnimation.start();

青から赤および赤から青のグラデーション効果をそれぞれカバーするようにそれぞれのドローアブルを取得する場合、アニメーションリストでcolor1、color2などとして言及する必要がある画像

このアプローチは、複数の静止画像でGIF画像を作成する方法に似ています。

0