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

Flutter如何实现带摆动伸缩效果的线性进度指示器?

实现方案

原方案失效的核心原因:Flutter内置的LinearProgressIndicator逻辑固定,进度条起点永远对齐容器左边缘,value参数仅控制填充终点的位置,哪怕开启动画反向重复,也只能实现从左到右填满再缩回的效果,无法做到滑块整体左右摆动+运动过程中自身长度动态伸缩的效果。

要实现目标效果,只需要通过两个联动动画分别控制滑块的水平位置和自身宽度即可,不需要依赖第三方组件,完整实现代码如下:

import 'package:flutter/material.dart';

class StretchSwingProgressBar extends StatefulWidget {
  const StretchSwingProgressBar({
    super.key,
    this.height = 4,
    this.backgroundColor = const Color(0xFFE0E0E0),
    this.foregroundColor = Colors.blue,
    this.duration = const Duration(milliseconds: 1500),
  });

  final double height;
  final Color backgroundColor;
  final Color foregroundColor;
  final Duration duration;

  @override
  State<StretchSwingProgressBar> createState() => _StretchSwingProgressBarState();
}

class _StretchSwingProgressBarState extends State<StretchSwingProgressBar>
    with TickerProviderStateMixin {
  late final AnimationController _controller;
  // 控制滑块水平位置:0对应最左侧,1对应最右侧
  late final Animation<double> _positionAnimation;
  // 控制滑块宽度:以进度条总宽度为基准,最小占比15%,最大占比40%
  late final Animation<double> _widthAnimation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this, duration: widget.duration)
      ..repeat(reverse: true);

    // 位置动画用线性插值,保证滑块匀速移动
    _positionAnimation = Tween(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(parent: _controller, curve: Curves.linear),
    );

    // 宽度动画用分阶段插值:滑块移动到中间位置时宽度最大,到达两端时宽度最小,模拟拉伸收缩效果
    _widthAnimation = TweenSequence<double>([
      TweenSequenceItem(tween: Tween(begin: 0.15, end: 0.4), weight: 25),
      TweenSequenceItem(tween: Tween(begin: 0.4, end: 0.15), weight: 25),
      TweenSequenceItem(tween: Tween(begin: 0.15, end: 0.4), weight: 25),
      TweenSequenceItem(tween: Tween(begin: 0.4, end: 0.15), weight: 25),
    ]).animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut));
  }

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

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        final totalWidth = constraints.maxWidth;
        return SizedBox(
          height: widget.height,
          child: ColoredBox(
            color: widget.backgroundColor,
            child: Stack(
              children: [
                AnimatedBuilder(
                  animation: _controller,
                  builder: (context, child) {
                    final currentWidth = totalWidth * _widthAnimation.value;
                    // 计算滑块左侧偏移量,保证滑块全程不会超出进度条边界
                    final maxLeftOffset = totalWidth - currentWidth;
                    final currentLeft = maxLeftOffset * _positionAnimation.value;
                    return Positioned(
                      left: currentLeft,
                      width: currentWidth,
                      height: widget.height,
                      child: DecoratedBox(
                        decoration: BoxDecoration(
                          color: widget.foregroundColor,
                          borderRadius: BorderRadius.circular(widget.height / 2),
                        ),
                      ),
                    );
                  },
                ),
              ],
            ),
          ),
        );
      },
    );
  }
}

使用方法

直接替换原代码中的LinearProgressIndicator即可,示例如下:

Padding(
  padding: const EdgeInsets.all(20.0),
  child: StretchSwingProgressBar(
    height: 6,
    foregroundColor: Colors.blueAccent,
    backgroundColor: Colors.grey.shade200,
    duration: const Duration(seconds: 2),
  ),
)

参数调整说明

  • 可通过height属性自定义进度条粗细
  • 可通过backgroundColor、foregroundColor分别自定义进度条轨道色、滑块颜色
  • 可通过duration属性调整滑块来回摆动一次的总时长
  • 如需调整拉伸幅度,直接修改_widthAnimation中Tween的起止数值即可,数值差越大拉伸效果越明显

内容的提问来源于stack exchange,提问作者Ng Lok Chun

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 03:33:33