Android如何创建类不确定进度条的重复动画移动渐变Drawable
解决移动渐变进度条的流畅性问题
你说得对,直接在onDraw里靠invalidate()驱动动画的方式确实不够可靠——当UI线程被其他任务(比如布局计算、触摸事件处理)占用时,invalidate()的调用会被推迟,导致动画卡顿、速度不稳定。改用Android官方的**动画API(ValueAnimator)**是更优的选择,它基于时间轴驱动,会和系统的帧同步机制(Choreographer)配合,即使丢帧也能保证动画进度和时间匹配,不会出现速度变慢的情况。
优化后的实现方案
我们可以用ValueAnimator来控制渐变的偏移比例,监听动画的进度更新来调整绘制偏移量,再触发重绘。这样动画的速度完全由时间控制,不受UI线程繁忙程度的影响:
class SmoothHorizontalProgressView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : View(context, attrs, defStyleAttr) { private val loopDurationSeconds = 2.5f // 整个循环的时长(秒),控制动画快慢 private var offsetRatio: Float = 0f // 偏移比例,从0到2(对应0到2倍视图宽度) private val rightDrawable: GradientDrawable = GradientDrawable().apply { gradientType = GradientDrawable.LINEAR_GRADIENT orientation = GradientDrawable.Orientation.LEFT_RIGHT shape = GradientDrawable.RECTANGLE } private val leftDrawable: GradientDrawable = GradientDrawable().apply { gradientType = GradientDrawable.LINEAR_GRADIENT orientation = GradientDrawable.Orientation.RIGHT_LEFT shape = GradientDrawable.RECTANGLE } private val animator: ValueAnimator by lazy { ValueAnimator.ofFloat(0f, 2f).apply { duration = (loopDurationSeconds * 1000).toLong() repeatCount = ValueAnimator.INFINITE repeatMode = ValueAnimator.RESTART interpolator = LinearInterpolator() // 保证渐变匀速移动 addUpdateListener { animation -> offsetRatio = animation.animatedValue as Float invalidate() // 仅在动画进度更新时触发重绘 } } } init { if (isInEditMode) setGradientColors(intArrayOf(Color.RED, Color.BLUE)) } fun setGradientColors(colors: IntArray) { rightDrawable.colors = colors leftDrawable.colors = colors invalidate() } override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) val widthSize = MeasureSpec.getSize(widthMeasureSpec) val heightSize = MeasureSpec.getSize(heightMeasureSpec) rightDrawable.setBounds(0, 0, widthSize, heightSize) leftDrawable.setBounds(0, 0, widthSize, heightSize) } override fun onAttachedToWindow() { super.onAttachedToWindow() animator.start() // 视图附着到窗口时启动动画 } override fun onDetachedFromWindow() { super.onDetachedFromWindow() animator.cancel() // 视图脱离窗口时停止动画,避免内存泄漏 } override fun onDraw(canvas: Canvas) { super.onDraw(canvas) canvas.save() val width = width.toFloat() val currentOffset = offsetRatio * width when { currentOffset < width -> { // 右渐变在当前偏移位置,左渐变在其左侧(偏移-宽度) canvas.translate(currentOffset, 0f) rightDrawable.draw(canvas) canvas.translate(-width, 0f) leftDrawable.draw(canvas) } else -> { // 偏移超过宽度后,左渐变在当前偏移-宽度的位置,右渐变在其左侧 canvas.translate(currentOffset - width, 0f) leftDrawable.draw(canvas) canvas.translate(-width, 0f) rightDrawable.draw(canvas) } } canvas.restore() } }
关键改进点说明
用ValueAnimator驱动动画:
- 动画基于时间轴运行,时长固定(
loopDurationSeconds控制整个循环的时间),即使UI线程繁忙,系统也会自动调整进度,保证动画完成时间一致。 - 使用
LinearInterpolator确保渐变匀速移动,避免速度忽快忽慢。
- 动画基于时间轴运行,时长固定(
生命周期管理:
- 在
onAttachedToWindow启动动画,onDetachedFromWindow停止动画,避免视图销毁后动画继续运行导致内存泄漏。
- 在
更清晰的偏移逻辑:
- 用
offsetRatio(0到2)代替原来的xMovement,计算更直观,也更容易和动画API配合。
- 用
使用方式
和原来的用法一致,设置渐变颜色即可:
smoothHorizontalProgressView.setGradientColors(intArrayOf(Color.RED, Color.BLUE))
额外建议
如果你需要支持暂停/继续动画,可以添加对应的方法:
fun pauseAnimation() { if (animator.isRunning) animator.pause() } fun resumeAnimation() { if (animator.isPaused) animator.resume() }
内容的提问来源于stack exchange,提问作者android developer
相关产品推荐
相关产品推荐

