flutter如何实现自定义尺子功能

在Flutter中如何实现一个自定义的尺子功能?需要支持滑动测量、刻度自定义以及数值实时显示。目前尝试过使用ListView和CustomPaint,但滑动流畅度和精度不够理想。想请教是否有更好的实现方案或推荐的第三方库?最好能提供核心代码示例或实现思路。

2 回复

在Flutter中实现自定义尺子功能,可以通过CustomPainter绘制刻度线和数字。以下是核心步骤:

  1. 创建CustomPainter子类,在paint方法中使用Canvas绘制:

    • 用drawLine画刻度线(主/副刻度)
    • 用drawParagraph画数字标签
  2. 刻度计算:

    double interval = 10; // 刻度间隔
    for(double i=minValue; i<=maxValue; i+=interval){
      double position = (i - minValue) * pixelPerUnit;
      // 绘制刻度线
    }
    
  3. 添加手势交互:

    GestureDetector(
      onPanUpdate: (details) {
        setState(() {
          // 更新选中位置
        });
      }
    )
    
  4. 可封装为StatefulWidget,支持自定义:

    • 刻度范围
    • 刻度密度
    • 颜色样式
    • 回调选中值

可通过Stack组合其他控件实现游标效果,使用Transform实现横向/竖向切换。

更多关于flutter如何实现自定义尺子功能的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


在Flutter中实现自定义尺子功能可以通过CustomPainter绘制,以下是核心实现步骤:

1. 创建尺子绘制类

class RulerPainter extends CustomPainter {
  final double startValue;
  final double endValue;
  final double currentValue;
  final bool isHorizontal;

  RulerPainter({
    required this.startValue,
    required this.endValue,
    required this.currentValue,
    this.isHorizontal = true,
  });

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.grey
      ..strokeWidth = 1.0;

    // 绘制主尺子线
    if (isHorizontal) {
      canvas.drawLine(Offset(0, size.height), Offset(size.width, size.height), paint);
    } else {
      canvas.drawLine(Offset(0, 0), Offset(0, size.height), paint);
    }

    // 绘制刻度
    final totalUnits = endValue - startValue;
    final pixelsPerUnit = isHorizontal ? size.width / totalUnits : size.height / totalUnits;

    for (double i = startValue; i <= endValue; i++) {
      final position = (i - startValue) * pixelsPerUnit;
      
      // 主刻度(每10个单位)
      if (i % 10 == 0) {
        final lineLength = isHorizontal ? 20.0 : 20.0;
        if (isHorizontal) {
          canvas.drawLine(
            Offset(position, size.height),
            Offset(position, size.height - lineLength),
            paint,
          );
          // 绘制数值
          _drawText(canvas, i.toStringAsFixed(0), Offset(position, size.height - 25));
        } else {
          canvas.drawLine(
            Offset(0, position),
            Offset(lineLength, position),
            paint,
          );
          _drawText(canvas, i.toStringAsFixed(0), Offset(25, position));
        }
      }
      // 次刻度(每1个单位)
      else if (i % 1 == 0) {
        final lineLength = isHorizontal ? 10.0 : 10.0;
        if (isHorizontal) {
          canvas.drawLine(
            Offset(position, size.height),
            Offset(position, size.height - lineLength),
            paint,
          );
        } else {
          canvas.drawLine(
            Offset(0, position),
            Offset(lineLength, position),
            paint,
          );
        }
      }
    }

    // 绘制当前值指示器
    final indicatorPaint = Paint()
      ..color = Colors.red
      ..strokeWidth = 2.0;
    
    final currentPosition = (currentValue - startValue) * pixelsPerUnit;
    
    if (isHorizontal) {
      canvas.drawLine(
        Offset(currentPosition, 0),
        Offset(currentPosition, size.height),
        indicatorPaint,
      );
    } else {
      canvas.drawLine(
        Offset(0, currentPosition),
        Offset(size.width, currentPosition),
        indicatorPaint,
      );
    }
  }

  void _drawText(Canvas canvas, String text, Offset offset) {
    final textPainter = TextPainter(
      text: TextSpan(
        text: text,
        style: const TextStyle(color: Colors.black, fontSize: 12),
      ),
      textDirection: TextDirection.ltr,
    );
    textPainter.layout();
    textPainter.paint(canvas, offset);
  }

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

2. 创建可交互的尺子组件

class CustomRuler extends StatefulWidget {
  final double startValue;
  final double endValue;
  final bool isHorizontal;
  final ValueChanged<double>? onValueChanged;

  const CustomRuler({
    super.key,
    this.startValue = 0,
    this.endValue = 100,
    this.isHorizontal = true,
    this.onValueChanged,
  });

  @override
  State<CustomRuler> createState() => _CustomRulerState();
}

class _CustomRulerState extends State<CustomRuler> {
  double _currentValue = 0;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onPanUpdate: (details) {
        setState(() {
          if (widget.isHorizontal) {
            _currentValue = _calculateValue(details.localPosition.dx);
          } else {
            _currentValue = _calculateValue(details.localPosition.dy);
          }
        });
        widget.onValueChanged?.call(_currentValue);
      },
      child: Container(
        width: widget.isHorizontal ? 300 : 50,
        height: widget.isHorizontal ? 50 : 300,
        decoration: BoxDecoration(
          color: Colors.white,
          border: Border.all(color: Colors.grey),
        ),
        child: CustomPaint(
          painter: RulerPainter(
            startValue: widget.startValue,
            endValue: widget.endValue,
            currentValue: _currentValue,
            isHorizontal: widget.isHorizontal,
          ),
        ),
      ),
    );
  }

  double _calculateValue(double position) {
    final totalPixels = widget.isHorizontal 
        ? 300 
        : 300; // 容器尺寸
    final totalUnits = widget.endValue - widget.startValue;
    final value = (position / totalPixels) * totalUnits + widget.startValue;
    return value.clamp(widget.startValue, widget.endValue);
  }
}

3. 使用示例

class RulerExample extends StatefulWidget {
  @override
  State<RulerExample> createState() => _RulerExampleState();
}

class _RulerExampleState extends State<RulerExample> {
  double currentValue = 50;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('当前值: ${currentValue.toStringAsFixed(1)}'),
        const SizedBox(height: 20),
        CustomRuler(
          startValue: 0,
          endValue: 100,
          onValueChanged: (value) {
            setState(() {
              currentValue = value;
            });
          },
        ),
        const SizedBox(height: 20),
        // 垂直尺子
        CustomRuler(
          startValue: 0,
          endValue: 200,
          isHorizontal: false,
          onValueChanged: (value) {
            setState(() {
              currentValue = value;
            });
          },
        ),
      ],
    );
  }
}

主要特性

  • 支持水平和垂直方向
  • 可自定义数值范围
  • 触摸交互选择数值
  • 主刻度和次刻度显示
  • 当前值指示器

你可以根据需要调整刻度样式、颜色、尺寸等参数来满足具体的设计需求。

回到顶部