Flutter技术问询:如何通过手势动态绘制Bezier curve?
实现跨Android/Ubuntu平台的拖拽式贝塞尔曲线绘制功能
Android 平台实现方案
核心逻辑
通过自定义View,监听触摸事件识别控制点的选中与拖拽操作,实时更新曲线参数并触发重绘。
关键步骤与代码示例
- 定义控制点数据结构
private static class Point { float x, y; boolean isSelected; Point(float x, float y) { this.x = x; this.y = y; } } // 三阶贝塞尔需要4个点:起始点、两个控制点、结束点 private List<Point> bezierPoints = new ArrayList<>(); private Point selectedPoint = null; private final float POINT_RADIUS = 20f;
- 重写
onDraw绘制曲线与控制点
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); // 绘制贝塞尔曲线 if (bezierPoints.size() == 4) { Path path = new Path(); Point start = bezierPoints.get(0); Point ctrl1 = bezierPoints.get(1); Point ctrl2 = bezierPoints.get(2); Point end = bezierPoints.get(3); path.moveTo(start.x, start.y); path.cubicTo(ctrl1.x, ctrl1.y, ctrl2.x, ctrl2.y, end.x, end.y); paint.setColor(Color.BLACK); paint.setStrokeWidth(5f); paint.setStyle(Paint.Style.STROKE); canvas.drawPath(path, paint); } // 绘制可交互的控制点 paint.setStyle(Paint.Style.FILL); for (Point p : bezierPoints) { paint.setColor(p.isSelected ? Color.RED : Color.BLUE); canvas.drawCircle(p.x, p.y, POINT_RADIUS, paint); } }
- 处理触摸事件实现拖拽
@Override public boolean onTouchEvent(MotionEvent event) { float touchX = event.getX(); float touchY = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: // 判断点击是否命中控制点 for (Point p : bezierPoints) { if (Math.abs(touchX - p.x) <= POINT_RADIUS && Math.abs(touchY - p.y) <= POINT_RADIUS) { selectedPoint = p; p.isSelected = true; invalidate(); return true; } } break; case MotionEvent.ACTION_MOVE: if (selectedPoint != null) { selectedPoint.x = touchX; selectedPoint.y = touchY; invalidate(); } break; case MotionEvent.ACTION_UP: if (selectedPoint != null) { selectedPoint.isSelected = false; selectedPoint = null; invalidate(); } break; } return super.onTouchEvent(event); }
- 初始化默认控制点
在View的onSizeChanged方法中设置初始位置:
@Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); bezierPoints.clear(); bezierPoints.add(new Point(w/4f, h/2f)); bezierPoints.add(new Point(w/4f, h/4f)); bezierPoints.add(new Point(3*w/4f, 3*h/4f)); bezierPoints.add(new Point(3*w/4f, h/2f)); }
Ubuntu 平台实现方案(基于Qt Widgets)
核心逻辑
自定义QWidget,监听鼠标事件实现控制点的选中与拖拽,通过QPainter实时绘制贝塞尔曲线。
关键步骤与代码示例
- 定义控制点数据结构
#include <QWidget> #include <QPointF> #include <QVector> class BezierWidget : public QWidget { Q_OBJECT private: QVector<QPointF> bezierPoints; QPointF* selectedPoint = nullptr; const qreal POINT_RADIUS = 20; protected: void paintEvent(QPaintEvent* event) override; void mousePressEvent(QMouseEvent* event) override; void mouseMoveEvent(QMouseEvent* event) override; void mouseReleaseEvent(QMouseEvent* event) override; public: BezierWidget(QWidget* parent = nullptr); };
- 实现绘制逻辑
void BezierWidget::paintEvent(QPaintEvent* event) { Q_UNUSED(event); QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); // 绘制贝塞尔曲线 if (bezierPoints.size() == 4) { QPainterPath path; path.moveTo(bezierPoints[0]); path.cubicTo(bezierPoints[1], bezierPoints[2], bezierPoints[3]); painter.setPen(QPen(Qt::black, 5)); painter.drawPath(path); } // 绘制可交互的控制点 for (auto& p : bezierPoints) { painter.setBrush(&p == selectedPoint ? Qt::red : Qt::blue); painter.drawEllipse(p, POINT_RADIUS, POINT_RADIUS); } }
- 处理鼠标事件
void BezierWidget::mousePressEvent(QMouseEvent* event) { QPointF pos = event->pos(); for (auto& p : bezierPoints) { if (QRectF(p.x() - POINT_RADIUS, p.y() - POINT_RADIUS, 2*POINT_RADIUS, 2*POINT_RADIUS).contains(pos)) { selectedPoint = &p; update(); return; } } QWidget::mousePressEvent(event); } void BezierWidget::mouseMoveEvent(QMouseEvent* event) { if (selectedPoint) { *selectedPoint = event->pos(); update(); } QWidget::mouseMoveEvent(event); } void BezierWidget::mouseReleaseEvent(QMouseEvent* event) { selectedPoint = nullptr; update(); QWidget::mouseReleaseEvent(event); }
- 初始化默认控制点
BezierWidget::BezierWidget(QWidget* parent) : QWidget(parent) { setFixedSize(800, 600); bezierPoints << QPointF(width()/4, height()/2) << QPointF(width()/4, height()/4) << QPointF(3*width()/4, 3*height()/4) << QPointF(3*width()/4, height()/2); }
补充说明
- 若Ubuntu平台使用QML开发,可通过
Canvas组件结合触摸事件实现相同逻辑,核心思路一致:维护控制点数组,实时更新路径并绘制。 - 如需二阶贝塞尔曲线,只需将控制点数量调整为3个,使用
quadTo(Android)或quadTo(Qt)方法绘制。
内容的提问来源于stack exchange,提问作者Đức Hải Trần
相关产品推荐
相关产品推荐

