You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter技术问询:如何通过手势动态绘制Bezier curve?

实现跨Android/Ubuntu平台的拖拽式贝塞尔曲线绘制功能

Android 平台实现方案

核心逻辑

通过自定义View,监听触摸事件识别控制点的选中与拖拽操作,实时更新曲线参数并触发重绘。

关键步骤与代码示例

  1. 定义控制点数据结构
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;
  1. 重写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);
    }
}
  1. 处理触摸事件实现拖拽
@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);
}
  1. 初始化默认控制点
    在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实时绘制贝塞尔曲线。

关键步骤与代码示例

  1. 定义控制点数据结构
#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);
};
  1. 实现绘制逻辑
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);
    }
}
  1. 处理鼠标事件
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);
}
  1. 初始化默认控制点
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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 04:25:22