You need to enable JavaScript to run this app. 如何使用Flutter CustomPainter绘制平滑曲线折线图?
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Flutter CustomPainter绘制平滑曲线折线图?

你现有代码的效果不符合预期有两个核心原因:

  1. 二次贝塞尔曲线的控制点采用固定偏移15的逻辑,无法适配不同坐标点间距、不同数值落差的场景,过渡效果生硬
  2. quadraticBezierTo方法传入的控制点y坐标没有减去yHeight,坐标计算逻辑错误,导致曲线轨迹完全偏离预期

参考效果图:
参考效果图
实际运行效果图:
实际效果图

可行实现方案

以下是两种可落地的平滑折线实现方案:

方案1:三次贝塞尔曲线动态计算控制点(fl_chart内部采用的实现逻辑)

通过计算相邻点的中点作为控制点,适配不同的坐标场景,可自由调整平滑度,和你给出的参考效果完全匹配。
修改后的完整代码如下:

import 'package:flutter/material.dart';

class DrawLines extends StatefulWidget {
  // 改为传入所有坐标点数组,适配多段折线
  final List<CoordValues> allCoordinates;
  final double yHeight;
  // 平滑系数,0~1之间,越大曲线越弯曲
  final double smoothness;

  DrawLines({
    required this.allCoordinates, 
    required this.yHeight,
    this.smoothness = 0.4
  });

  @override
  _DrawLinesState createState() => _DrawLinesState();
}

class _DrawLinesState extends State<DrawLines> {
  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      foregroundPainter: LinePainter(
        allCoordinates: widget.allCoordinates, 
        yHeight: widget.yHeight,
        smoothness: widget.smoothness
      ),
    );
  }
}

class CoordValues {
  double x;
  double y;

  CoordValues({required this.x, required this.y});
}

class LinePainter extends CustomPainter {
  List<CoordValues> allCoordinates;
  double yHeight;
  double smoothness;

  LinePainter({
    required this.allCoordinates, 
    required this.yHeight,
    required this.smoothness
  });

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = const Color(0xFF295AE3)
      ..strokeCap = StrokeCap.round
      ..style = PaintingStyle.stroke
      ..strokeWidth = 2;

    Path path = Path();
    if (allCoordinates.isEmpty) return;
    
    // 移动到第一个点
    path.moveTo(
      allCoordinates.first.x, 
      yHeight - allCoordinates.first.y
    );

    for (int i = 1; i < allCoordinates.length; i++) {
      final prevPoint = allCoordinates[i-1];
      final currentPoint = allCoordinates[i];
      
      double x0 = prevPoint.x;
      double y0 = yHeight - prevPoint.y;
      double x1 = currentPoint.x;
      double y1 = yHeight - currentPoint.y;
      
      // 动态计算三次贝塞尔曲线控制点
      double controlX1 = x0 + (x1 - x0) * smoothness;
      double controlY1 = y0;
      double controlX2 = x1 - (x1 - x0) * smoothness;
      double controlY2 = y1;
      
      // 绘制平滑曲线段
      path.cubicTo(controlX1, controlY1, controlX2, controlY2, x1, y1);
    }

    canvas.drawPath(path, paint);
  }

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

使用说明

  • 直接传入所有需要绘制的坐标点数组即可生成连续平滑折线
  • 调整smoothness参数即可修改曲线弯曲程度,推荐取值范围为0.3~0.5,和参考效果图效果一致。

方案2:使用Catmull-Rom样条曲线实现

如果你需要更贴合原始坐标点的平滑效果,可以使用Catmull-Rom样条曲线算法,该算法生成的曲线会经过所有给定的坐标点,不会出现曲线偏移原始点位的问题。
核心路径生成逻辑如下:

// 传入原始坐标点和y轴高度直接生成平滑路径
Path generateCatmullRomPath(List<CoordValues> points, double yHeight) {
  Path path = Path();
  if (points.length < 2) return path;
  
  // 转换为适配坐标系的偏移点
  List<Offset> convertedPoints = points.map((p) => Offset(p.x, yHeight - p.y)).toList();
  
  path.moveTo(convertedPoints.first.dx, convertedPoints.first.dy);
  for (int i = 0; i < convertedPoints.length - 1; i++) {
    final p0 = i == 0 ? convertedPoints[0] : convertedPoints[i-1];
    final p1 = convertedPoints[i];
    final p2 = convertedPoints[i+1];
    final p3 = i == convertedPoints.length -2 ? convertedPoints[i+1] : convertedPoints[i+2];
    
    // 计算Catmull-Rom控制点
    final cp1x = p1.dx + (p2.dx - p0.dx) / 6;
    final cp1y = p1.dy + (p2.dy - p0.dy) / 6;
    final cp2x = p2.dx - (p3.dx - p1.dx) / 6;
    final cp2y = p2.dy - (p3.dy - p1.dy) / 6;
    
    path.cubicTo(cp1x, cp1y, cp2x, cp2y, p2.dx, p2.dy);
  }
  return path;
}

内容的提问来源于stack exchange,提问作者João Pedro

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.16 12:06:08