如何创建一个重复的动画移动渐变drawable,就像一个不确定的进展?

and*_*per 22 animation android drawable android-progressbar

背景

Android有一个标准的ProgressBar,在不确定时会有一个特殊的动画.还有很多可用的进度视图库(这里).

问题

在我搜索过的所有内容中,我找不到办法做一件非常简单的事情:

具有从颜色X到颜色Y的渐变,水平显示,并在X坐标中移动,以便X之前的颜色将变为颜色Y.

例如(只是一个插图),如果我有一个蓝色< - >红色的渐变,从边缘到边缘,它将如此:

在此输入图像描述

我试过的

我在StackOverflow上尝试了一些解决方案:

但遗憾的是,它们都是关于Android的标准ProgressBar视图,这意味着它有一种不同的方式来显示drawable的动画.

我也试过在Android Arsenal网站上找到类似的东西,但即使有很多不错的东西,我找不到这样的东西.

当然,我可以自己动画2个视图,每个视图都有自己的渐变(一个与另一个相反),但我确信有更好的方法.

这个问题

有可能使用Drawable或它的动画,这使得渐变(或其他任何东西)以这种方式移动(当然重复)?

也许只是从ImageView扩展并在那里制作drawable动画?

是否也可以设置容器将用于重复可绘制的多少?我的意思是,在上面的例子中,它可能是从蓝色到红色,所以蓝色将在边缘,红色将在中间.


编辑:

好的,我已经取得了一些进展,但是我不确定这个动作是否正常,而且我认为它的速度不一致,以防万一CPU有点忙,因为它不考虑帧丢失.我所做的是将2个GradientDrawables一个接一个地绘制,如下:

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()
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

    horizontalProgressView.setGradientColors(intArrayOf(Color.RED, Color.BLUE))
Run Code Online (Sandbox Code Playgroud)

结果(它确实循环好,很难编辑视频):

在此输入图像描述

所以现在我的问题是,即使UI线程有点忙,我该怎样做才能确保它的动画效果很好?

只是invalidate单凭这对我来说似乎不是一种可靠的方式.我认为它应该检查更多.也许它可以使用一些动画API而不是内插器.

and*_*per 5

我决定在 Kotlin 中放置“pskink”答案(起源在这里)。我写在这里只是因为其他解决方案要么不起作用,要么是解决方法而不是我问的问题。

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()
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

主活动.kt

    val px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200f, resources.getDisplayMetrics())
    progress.indeterminateDrawable = ScrollingGradient(px)
Run Code Online (Sandbox Code Playgroud)

活动_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>
Run Code Online (Sandbox Code Playgroud)