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
相关产品推荐
相关产品推荐

