自定义分段进度条Flutter实现

652 阅读1分钟

前言

原生项目应业务要求现在要用Flutter实现,项目中自定义的控件需要用flutter重绘,如图:步阶进度条。

效果图

微信截图_20221022211604.png

思路很简单,底层绘制灰色矩形,根据步阶总数计算出每段宽度绘制渐变矩形,最后再在每段末尾的位置绘制上分割线。 Flutter的绘制部分需要继承CusomPainter并实现paint(Canvas canvas, Size size)和shouldRepaint(WindSpeedPainter oldDelegate),使用Canvas、Paint绘制具体图形,最后通过CustomPaint展示。

代码

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

///静音运转组件
class MuteWorkSlider extends StatefulWidget {
  const MuteWorkSlider({
    Key? key,
    required this.width,
    required this.height,
    required this.initialValue,
    this.minValue = 1,
    this.maxValue = 6,
    required this.onChanged,
    this.dividerWidth = 1,
  }) : super(key: key);

  final int initialValue; //初始值
  final int minValue; //最小值
  final int maxValue; //最大值
  final double width; //控件总体宽度
  final double height; //控件总体高度
  final ValueChanged<int> onChanged;
  final double dividerWidth; //分割线宽度

  @override
  State<MuteWorkSlider> createState() => _WindSpeedSliderState();
}

class _WindSpeedSliderState extends State<MuteWorkSlider> {
  late int divisions = widget.maxValue; //共有多少个值
  late int value = 0; //内部使用的数值 从0开始
  late int resultValue; //最终输出的数值

  @override
  void initState() {
    super.initState();
    value = widget.initialValue - 1;
  }

  @override
  Widget build(BuildContext context) {
    void dealTouch(dynamic e) {
      final newValue =
          (e.localPosition.dx / (widget.width / divisions)).floor();
      if (value != newValue && newValue >= 0 && newValue < divisions) {
        value = newValue;
        resultValue = value + 1;
        setState(() {});
      }
    }

    return ClipRRect(
      borderRadius: BorderRadius.all(Radius.circular(widget.height / 2)),
      child: Container(
        color: const Color(0xFFDDDDDD),
        width: widget.width,
        height: widget.height,
        child: LayoutBuilder(
          builder: (context, constraints) {
            final rect = Rect.fromLTRB(
              0,
              0,
              widget.width,
              widget.height,
            );
            return GestureDetector(
              child: CustomPaint(
                painter: WindSpeedPainter(
                  divisions: divisions,
                  value: value,
                  rect: rect,
                  dividerWidth: widget.dividerWidth,
                ),
              ),
              onPanStart: (e) {
                dealTouch(e);
              },
              onPanUpdate: (e) {
                dealTouch(e);
              },
              onPanEnd: (e) {
                widget.onChanged(resultValue);
              },
            );
          },
        ),
      ),
    );
  }
}

class WindSpeedPainter extends CustomPainter {
  WindSpeedPainter({
    required this.divisions,
    required this.value,
    required this.rect,
    required this.dividerWidth,
  });

  final int divisions;
  final int value;
  final Rect rect;
  final double dividerWidth;

  @override
  void paint(Canvas canvas, Size size) {
    // 每个进度的宽度
    final itemWidth = rect.right / divisions;
    //当前步阶对应的宽度
    final progressWidth = (value + 1) * itemWidth;

    //渐变色
    Gradient gradient = const LinearGradient(
        colors: [Color(0xFF91C1FE), Color(0xFF495FE9)],
        begin: Alignment.centerLeft,
        end: Alignment.centerRight);

    //画进度
    canvas.drawRect(
        Rect.fromLTRB(rect.left, rect.top, progressWidth, rect.bottom),
        Paint()
          ..style = PaintingStyle.fill
          ..shader = gradient.createShader(rect)
          ..isAntiAlias = true
          ..strokeCap = StrokeCap.butt);

    //画分割线
    for (var i = 1; i < divisions; i++) {
      final dividerX = i * itemWidth;
      canvas.drawLine(
        Offset(dividerX, rect.top),
        Offset(dividerX, rect.bottom),
        Paint()
          ..style = PaintingStyle.stroke
          ..color = const Color(0xFFFFFFFF).withOpacity(0.5)
          ..strokeWidth = dividerWidth
          ..strokeCap = StrokeCap.round,
      );
    }
  }

  @override
  bool shouldRepaint(WindSpeedPainter oldDelegate) =>
      divisions != oldDelegate.divisions || value != oldDelegate.value;
}