You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现Linear Progress Indicator的同款动画效果?

实现自定义流动渐变线性进度条动画

从你提供的动画效果来看,核心是进度条填充区域内的渐变色彩循环流动,而非Shimmer那种整体扫过的闪烁效果,所以需要自定义动画逻辑来实现。以下分Flutter和Android原生两种场景给出实现方案:


Flutter 实现方案

通过Stack叠加基础进度条与动态渐变层,配合动画控制器控制渐变的水平偏移,实现流动效果:

import 'package:flutter/material.dart';

class FlowProgressIndicator extends StatefulWidget {
  final double value; // 进度值(0-1)
  final Color baseColor;
  final Color highlightColor;

  const FlowProgressIndicator({
    super.key,
    required this.value,
    this.baseColor = const Color(0xFFE0E0E0),
    this.highlightColor = const Color(0xFF4CAF50),
  });

  @override
  State<FlowProgressIndicator> createState() => _FlowProgressIndicatorState();
}

class _FlowProgressIndicatorState extends State<FlowProgressIndicator> with SingleTickerProviderStateMixin {
  late AnimationController _animController;
  late Animation<double> _offsetAnim;

  @override
  void initState() {
    super.initState();
    // 创建循环动画控制器,周期1秒
    _animController = AnimationController(
      duration: const Duration(seconds: 1),
      vsync: this,
    )..repeat();
    // 控制渐变偏移的动画值(0→1)
    _offsetAnim = Tween<double>(begin: 0.0, end: 1.0).animate(_animController);
  }

  @override
  void dispose() {
    _animController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _offsetAnim,
      builder: (context, child) {
        return Stack(
          children: [
            // 基础灰色进度条(背景+已填充部分)
            LinearProgressIndicator(
              value: widget.value,
              backgroundColor: widget.baseColor,
              valueColor: AlwaysStoppedAnimation(widget.baseColor),
            ),
            // 流动渐变层:仅显示在进度范围内
            ClipRect(
              child: Align(
                alignment: Alignment.centerLeft,
                widthFactor: widget.value,
                child: Container(
                  height: 4, // 与进度条高度保持一致
                  decoration: BoxDecoration(
                    gradient: LinearGradient(
                      colors: [widget.baseColor, widget.highlightColor, widget.baseColor],
                      // 通过动画值控制渐变的起始/结束位置,实现流动
                      begin: Alignment(_offsetAnim.value * 2 - 1, 0),
                      end: Alignment(_offsetAnim.value * 2 + 1, 0),
                    ),
                  ),
                ),
              ),
            ),
          ],
        );
      },
    );
  }
}

// 使用示例
class DemoPage extends StatelessWidget {
  const DemoPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("流动进度条示例")),
      body: const Center(
        child: Padding(
          padding: EdgeInsets.symmetric(horizontal: 20),
          child: FlowProgressIndicator(value: 0.6),
        ),
      ),
    );
  }
}

逻辑说明

  1. 用AnimationController创建循环动画,控制渐变的水平偏移量
  2. 通过ClipRect和Align限制渐变层仅显示在进度条已填充的范围内
  3. 渐变采用「基础色→高亮色→基础色」的过渡,随着偏移量变化形成流动效果

Android 原生实现方案

通过自定义View,结合ValueAnimator动态修改渐变的位置参数,实现流动效果:

import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.View
import androidx.core.content.ContextCompat

class FlowProgressView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    private var progress = 0.6f // 进度值(0-1)
    private val baseColor = ContextCompat.getColor(context, R.color.grey)
    private val highlightColor = ContextCompat.getColor(context, R.color.green)
    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
    private lateinit var gradient: LinearGradient
    private val animator = ValueAnimator.ofFloat(0f, 2f).apply {
        duration = 1000
        repeatCount = ValueAnimator.INFINITE
        addUpdateListener {
            // 动态更新渐变的颜色位置,实现流动
            val offset = it.animatedValue as Float
            gradient.locations = floatArrayOf(offset - 1f, offset - 0.5f, offset)
            invalidate()
        }
        start()
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        // 初始化线性渐变
        gradient = LinearGradient(
            0f, 0f, w.toFloat(), 0f,
            intArrayOf(baseColor, highlightColor, baseColor),
            floatArrayOf(0f, 0.5f, 1f),
            Shader.TileMode.CLAMP
        )
    }

    fun setProgress(progress: Float) {
        this.progress = progress.coerceIn(0f, 1f)
        invalidate()
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        val viewWidth = width.toFloat()
        val viewHeight = height.toFloat()
        val radius = viewHeight / 2

        // 绘制背景进度条
        paint.color = baseColor
        paint.shader = null
        canvas.drawRoundRect(RectF(0f, 0f, viewWidth, viewHeight), radius, radius, paint)

        // 绘制流动渐变进度层
        paint.shader = gradient
        val progressWidth = viewWidth * progress
        canvas.drawRoundRect(
            RectF(0f, 0f, progressWidth, viewHeight),
            radius, radius,
            paint
        )
    }

    override fun onDetachedFromWindow() {
        super.onDetachedFromWindow()
        animator.cancel() // 页面销毁时停止动画,避免内存泄漏
    }
}

逻辑说明

  1. 在onSizeChanged中初始化线性渐变,确保渐变宽度与View一致
  2. 用ValueAnimator循环更新渐变的locations数组,让渐变色彩沿水平方向流动
  3. 绘制时先画灰色背景,再在进度范围内绘制流动渐变层

内容的提问来源于stack exchange,提问作者Farid Ahmadov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 00:54:54