如何使用Flutter CustomPainter绘制平滑曲线折线图?
你现有代码的效果不符合预期有两个核心原因:
- 二次贝塞尔曲线的控制点采用固定偏移15的逻辑,无法适配不同坐标点间距、不同数值落差的场景,过渡效果生硬
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
相关产品推荐
相关产品推荐

