Flutter 实现渐变弧形进度条

在Flutter开发中,构建一个具有视觉吸引力的、反映进度的圆形弧形进度条是一个常见需求。本文将详细介绍如何使用Flutter和Dart语言实现这一功能。最终效果如图:

首先,我们需要导入必要的包和库,比如dart:mathpackage:flutter/material.dart。这些库为绘制和样式提供基础支持。

接下来,创建一个ArcProgressPainter类,它继承自CustomPainter。这个类的核心是paint方法,用于绘制进度条。我们使用Canvas对象和Size对象来确定绘制区域,并利用数学运算确定圆心、半径等参数。

此外,文章还将展示如何使用线性渐变(LinearGradient)来美化进度条,以及如何计算角度和绘制圆弧。这包括如何根据进度动态变化圆弧的颜色和位置。

最后,我们将创建一个ArcProgressBar组件,它包装了CustomPaint,并使用上面定义的ArcProgressPainter来实现视觉效果。

整个过程不仅涉及基础的Flutter绘图技术,还包含一些高级的定制化元素,如颜色计算和动态布局调整。通过本文,读者可以学习如何灵活运用Flutter框架的绘图能力,为自己的应用程序添加独特且富有表现力的UI组件。

完整代码如下:

dart 复制代码
import 'dart:math' as math;
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:otterlife/component/theme/extension.dart';

class ArcProgressPainter extends CustomPainter {
  final double progress;
  final Color backgroundColor;
  final double strokeWidth;
  final TextStyle textStyle;

  ArcProgressPainter({
    required this.progress,
    required this.backgroundColor,
    required this.strokeWidth,
    required this.textStyle,
  });

  @override
  void paint(Canvas canvas, Size size) {
    final gradientColors = [const Color(0xFFFFC75A), const Color(0xFF6DAFF9), const Color(0xFF31A7AE)];
    final gradient = LinearGradient(
      begin: Alignment.centerLeft,
      end: Alignment.centerRight,
      colors: gradientColors,
    );

    Offset center = Offset(size.width / 2, size.height / 2);
    double radius = math.min(size.width / 2, size.height / 2);

    Rect rect = Rect.fromCircle(center: center, radius: radius).inflate(-strokeWidth / 2);

    double degreesToRadians(num deg) => deg * (math.pi / 180.0);
    double startAngle = degreesToRadians(90 + 40);
    double sweepAngle = degreesToRadians(360 - 80);

    for (double i = 0; i < sweepAngle; i += 0.01) {
      double angle = startAngle + i;
      double colorPosition = i / sweepAngle;
      Color color = _calculateGradientColor(gradientColors, colorPosition);

      Paint segmentPaint = Paint()
        ..color = color
        ..strokeWidth = strokeWidth
        ..strokeCap = StrokeCap.round
        ..style = PaintingStyle.stroke;

      canvas.drawArc(
        rect,
        angle,
        0.01, // 绘制小段的角度
        false,
        segmentPaint,
      );
    }

    double sliderAngle = startAngle + progress * sweepAngle;
    Offset sliderPosition = Offset(
      center.dx + (radius - strokeWidth / 2) * cos(sliderAngle),
      center.dy + (radius - strokeWidth / 2) * sin(sliderAngle),
    );

    double sliderRadius = 28 / 2;
    Paint sliderPaint = Paint()..color = _calculateSliderColor(progress); // Assuming you have this method

    canvas.drawCircle(sliderPosition, sliderRadius, sliderPaint);

    Paint whiteCenterPaint = Paint()..color = Colors.white;
    canvas.drawCircle(sliderPosition, 16 / 2, whiteCenterPaint);
  }

  Color _calculateGradientColor(List<Color> colors, double position) {
    int index = (position * (colors.length - 1)).floor();
    double localPosition = (position * (colors.length - 1)) - index;
    return Color.lerp(colors[index], colors[index + 1], localPosition) ?? colors.last;
  }

  Color _calculateSliderColor(double progress) {
    final colors = [const Color(0xFFFFC75A), const Color(0xFF6DAFF9), const Color(0xFF31A7AE)];

    progress = progress.clamp(0.0, 1.0);

    double colorPosition = progress * (colors.length - 1);
    int index = colorPosition.floor();
    int nextIndex = (index + 1).clamp(0, colors.length - 1);

    double t = colorPosition - index;

    return Color.lerp(colors[index], colors[nextIndex], t) ?? colors.first;
  }

  double convertRadiusToSigma(double radius) {
    return radius * 0.57735 + 0.5;
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}

class ArcProgressBar extends StatelessWidget {
  final double progress;
  final double strokeWidth;

  const ArcProgressBar({
    super.key,
    required this.progress,
    this.strokeWidth = 16,
  });

  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      painter: ArcProgressPainter(
        progress: progress,
        backgroundColor: Colors.red,
        strokeWidth: strokeWidth,
        textStyle: Colors.red,
      ),
    );
  }
}
相关推荐
coder_pig2 小时前
🤡 公司Android老项目升级踩坑小记
android·flutter·gradle
死就死在补习班3 小时前
Android系统源码分析Input - InputReader读取事件
android
死就死在补习班3 小时前
Android系统源码分析Input - InputChannel通信
android
死就死在补习班3 小时前
Android系统源码分析Input - 设备添加流程
android
死就死在补习班3 小时前
Android系统源码分析Input - 启动流程
android
tom4i4 小时前
Launcher3 to Launchpad 01 布局修改
android
雨白4 小时前
OkHttpClient 核心配置详解
android·okhttp
淡淡的香烟4 小时前
Android auncher3实现简单的负一屏功能
android
RabbitYao5 小时前
Android 项目 通过 AndroidStringsTool 更新多语言词条
android·python
RabbitYao5 小时前
使用 Gemini 及 Python 更新 Android 多语言 Excel 文件
android·python