Flutter之進(jìn)度指示器 2025-09-17 周三

系統(tǒng)組件

  • LinearProgressIndicator 是 Flutter 中用于展示線性進(jìn)度的組件,適用于顯示操作的進(jìn)度狀態(tài)。

1. 不確定進(jìn)度(默認(rèn))

  • 不設(shè)置 value 屬性,組件會(huì)顯示循環(huán)動(dòng)畫
  • 適用于:網(wǎng)絡(luò)請(qǐng)求、數(shù)據(jù)加載等未知完成時(shí)間的場(chǎng)景
LinearProgressIndicator()
循環(huán)動(dòng)畫

2. 確定進(jìn)度

  • 設(shè)置 value 屬性(范圍 0.0-1.0),控制進(jìn)度顯示比例
  • 適用于:文件下載、任務(wù)進(jìn)度等可量化的場(chǎng)景
LinearProgressIndicator(
  value: 0.6, // 顯示60%進(jìn)度
)
固定進(jìn)度

3. 自定義樣式

  • backgroundColor:未填充部分的背景色
  • valueColor:進(jìn)度填充顏色(需用 AlwaysStoppedAnimation 包裹)

valueColor屬性優(yōu)先。也可以用color屬性,更簡(jiǎn)單

  • minHeight:進(jìn)度條高度(默認(rèn)較細(xì),可自定義)
  • borderRadius: 圓角
            LinearProgressIndicator(
              value: 0.6, // 顯示60%進(jìn)度
              backgroundColor: Colors.grey,
              color: Colors.red,
              minHeight: 8,
              borderRadius: BorderRadius.circular(4),
            ),
圓角等屬性

小結(jié)

  • 大多數(shù)情況,LinearProgressIndicator足夠用了,固定的指示器顏色,背景色,圓角等都是常用屬性。
  • valueColor用于顏色動(dòng)畫,使用場(chǎng)景不多,還是直接用color屬性更簡(jiǎn)單方便。
  • 設(shè)計(jì)稿中的漸變色沒有屬性可以設(shè)置,需要自定義實(shí)現(xiàn)。

自定義實(shí)現(xiàn)

  • 方案1:在一個(gè)Stack組件中放2個(gè)Container,一個(gè)表示背景,一個(gè)表示指示器,這是最容易想到的方法。
  • 方案2:通過FractionallySizedBox根據(jù)進(jìn)度值控制寬度;
            Container(
              width: 200,
              height: 100, // 父容器固定高度100
              color: Colors.grey[200],
              child: FractionallySizedBox(
                heightFactor: 0.8, // 高度為父容器的80%
                child: Container(color: Colors.green),
              ),
            )
  • 通過Container,就可以實(shí)現(xiàn)顏色漸變效果

動(dòng)畫

  • 大多數(shù)情況,不需要?jiǎng)赢?,能固定顯示進(jìn)度可以了;
  • 顏色動(dòng)畫用的場(chǎng)景不多,效果也不是很明顯;
  • 動(dòng)畫大多數(shù)是對(duì)于進(jìn)度參數(shù),也就是value參數(shù)。
  • 外部設(shè)置,比如0.6,就是value的最終狀態(tài);可以從0開始逐漸增加到value的值;這種變化比較明顯

組件封裝

  • 由于想要?jiǎng)赢嬚故緑alue值的變化,所以用StatefulWidget
  • 除了進(jìn)度展示,還有前后的文字說明,統(tǒng)一放在Row組件中


    組件部分
class ProcessWidget extends StatefulWidget {
  const ProcessWidget({
    super.key,
    required this.progress,
    this.margin,
    this.padding,
    this.height,
    this.backgroundColor,
    this.beginColor,
    this.endColor,
    this.milliseconds,
    this.showLabel = true,
    this.showProgress = true,
    this.labelText = "",
    this.labelStyle,
  });

  final double progress;
  final EdgeInsetsGeometry? margin;
  final EdgeInsetsGeometry? padding;
  final double? height;
  final Color? backgroundColor;
  final Color? beginColor;
  final Color? endColor;
  final int? milliseconds;
  final bool showLabel;
  final bool showProgress;
  final String labelText;
  final TextStyle? labelStyle;

  @override
  State<ProcessWidget> createState() => _ProcessWidgetState();
}

class _ProcessWidgetState extends State<ProcessWidget> with SingleTickerProviderStateMixin {
  late AnimationController _animationController;
  late Animation<double> _progressAnimation;

  @override
  void initState() {
    super.initState();

    // 1. 初始化動(dòng)畫控制器
    _animationController = AnimationController(
      vsync: this,
      duration: Duration(milliseconds: widget.milliseconds ?? 600), // 動(dòng)畫持續(xù)時(shí)間
    );

    // 2. 創(chuàng)建進(jìn)度動(dòng)畫(0.0 -> progress)
    _progressAnimation = Tween<double>(begin: 0.0, end: widget.progress).animate(
      CurvedAnimation(
        parent: _animationController,
        curve: Curves.easeInOut, // 動(dòng)畫曲線
      ),
    );

    // 4. 啟動(dòng)動(dòng)畫
    _animationController.forward();
  }

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

  @override
  Widget build(BuildContext context) {
    double height = widget.height ?? 8.r;
    double radius = height / 2;
    return Container(
      margin: widget.margin,
      padding: widget.padding,
      child: Row(
        children: [
          Visibility(
            visible: widget.showLabel,
            child: Container(
              margin: EdgeInsets.only(right: 8.r),
              constraints: BoxConstraints(minWidth: 60.r, maxWidth: 120.r),
              child: FittedBox(
                fit: BoxFit.scaleDown,
                child: Text(widget.labelText, style: widget.labelStyle ?? StyleUtils.ts_1f_12_500),
              ),
            ),
          ),
          Expanded(
            child: AnimatedBuilder(
              animation: _progressAnimation,
              builder: (context, child) {
                return Container(
                  height: height,
                  decoration: BoxDecoration(
                    color: widget.backgroundColor ?? ColorUtils.c_f8f8f8,
                    borderRadius: BorderRadius.circular(radius),
                  ),
                  child: FractionallySizedBox(
                    widthFactor: _progressAnimation.value,
                    alignment: Alignment.centerLeft,
                    child: Container(
                      height: height,
                      decoration: BoxDecoration(
                        borderRadius: BorderRadius.circular(radius),
                        gradient: LinearGradient(
                          colors: [widget.beginColor ?? ColorUtils.c_ffbc52, widget.endColor ?? ColorUtils.c_ff6f2a],
                          begin: Alignment.centerLeft,
                          end: Alignment.centerRight,
                        ),
                      ),
                    ),
                  ),
                );
              },
            ),
          ),
          Visibility(
            visible: widget.showProgress,
            child: Container(
              margin: EdgeInsets.only(left: 8.r),
              child: Text("${(widget.progress * 100).toStringAsFixed(0)}%", style: StyleUtils.ts_1f_13_500),
            ),
          ),
        ],
      ),
    );
  }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容