QT使用QGraphicsView绘图 重写QGraphicsObject类实现点在QPainterPath路径上移动动画效果

闲谈:眨眼间,2024年就过去了一半了,年前定下的计划一个都没完成,乘着有空,把之前学习的内容和示例先总结了。

导读

看wps的流程图,发现有一个连接线获取焦点会显示多个点按照箭头方向移动的功能效果,如:
请添加图片描述
我就在想这种效果如何通过QGraphicsSvgItem图元实现的:
一开始我是打算直接加载SVG文件,SVG文件中直接使用animateMotion属性就能实现这种效果,再通过QGraphicsSvgItem图元的setElementId属性实现动画的显示,
但是Qt的SVG处理模块是不支持animateMotion属性。不会运行动画。
Svg文件效果如:
请添加图片描述
Svg实际文件:

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<svg width="600" height="600" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="svg_0" viewBox="0 0 600 600" version="1.1"> 

<path fill="#FFFFFF" fill-opacity="0" stroke="#000000" stroke-opacity="1" stroke-width="1" d="M268.00 265.00 L264.00 423.00 L418.00 412.00 L462.00 216.00 C462.00 216.00 330.00 159.00 329.00 159.00 C328.00 159.00 119.00 180.00 119.00 180.00 C119.00 180.00 13.00 365.00 13.00 365.00 C13.00 365.00 86.00 537.00 87.00 537.00 C88.00 537.00 341.00 547.00 343.00 547.00 C345.00 547.00 512.00 546.00 512.00 546.00 C512.00 546.00 564.00 326.00 564.00 326.00 C564.00 326.00 547.00 136.00 547.00 136.00 C547.00 136.00 251.00 52.00 251.00 52.00 C251.00 52.00 251.00 52.00 251.00 52.00" id="motionPath" />

<circle r="5" fill="red">
    <animateMotion dur="15s" repeatCount="indefinite">
      <mpath href="#motionPath"/>
    </animateMotion>
  </circle>
</svg>

Qt中SVG动画效果无效,但是如果获取SVG文件在QGraphicsSvgItem图元绘制的轨迹,在添加一个点按照轨迹移动的动画效果,就能实现这个功能。
但是查看QGraphicsSvgItem图元源码发现,QGraphicsSvgItem图元都是通过QPainter直接绘制的,获取不到QPainterPath 轨迹,

SVG 转QPainterPath 路径

这涉及到SVG文件的解析,看了看QSvgRenderer实现的源码,有点打老壳;
最后通过Github找到一个Qt开发的基于C,C++的SVG编译器源码:
QtSVGEditor
https://github.com/SVGEditors/QtSVGEditor
这其中就是将SVG文件转成的QPainterPath类型绘制:
其他的SVG属性先不管,移植源码中的SVGElementPath.hSVGPathSeg.h文件实现对SVG的path 类型的D属性内容解析,获取到QPainterPath路径轨迹。

CSVGElementPath element;
 element.parseD(L"M268.00 265.00 L264.00 423.00 L418.00 412.00 L462.00 216.00 C462.00 216.00 330.00 159.00 329.00 159.00 C328.00 159.00 119.00 180.00 119.00 180.00 C119.00 180.00 13.00 365.00 13.00 365.00 C13.00 365.00 86.00 537.00 87.00 537.00 C88.00 537.00 341.00 547.00 343.00 547.00 C345.00 547.00 512.00 546.00 512.00 546.00 C512.00 546.00 564.00 326.00 564.00 326.00 C564.00 326.00 547.00 136.00 547.00 136.00 C547.00 136.00 251.00 52.00 251.00 52.00 C251.00 52.00 251.00 52.00 251.00 52.00");
QPainterPath m_Path=element.resetPath();

获取QPainterPath指定长度时的坐标。

之所以要转换成QPainterPath类,
就是为了能实时获取指定长度时对应在轨迹上的坐标,通过计算指定长度与实际长度的百分比,通过pointAtPercent方法获取到实际坐标;

QPointF QPainterPath::pointAtPercent(qreal t) const
/*
Returns the point at at the percentage t of the current path. The argument t has to be between 0 and 1.
Note that similarly to other percent methods, the percentage measurement is not linear with regards to the length, if curves are present in the path. When curves are present the percentage argument is mapped to the t parameter of the Bezier equations.
返回当前路径的百分比t处的点。参数t必须在0到1之间。
请注意,与其他百分比方法类似,
如果路径中存在曲线,则百分比测量与长度无关。
当曲线存在时,百分比参数被映射到Bezier方程的t参数。
*.

重写QGraphicsObject类 实现点图元

重写QGraphicsObject类 的
QPainterPath shape() const override
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override
方法,实现点图元;

QPainterPath TGraphicsPointItem::shape() const
{
    QPainterPath path;
    path.addRect(QRectF(-2,-2,selfRect.width(),selfRect.height()));
    return path;
}

void TGraphicsPointItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    QPen pen3(QColor("#DB5E5E"), 1);
    painter->setPen(pen3);
    painter->setBrush(QColor("#DB5E5E"));
    painter->drawEllipse(QRectF(-2,-2,selfRect.width(),selfRect.height()));
}

QPropertyAnimation 动画类的使用

参考:
QT中的动画类(QPropertyAnimation)
QT之QPropertyAnimation详细介绍
使用QPropertyAnimation 类,实时获取QPainterPath路径中对应的点坐标位置,实时设置点坐标 (setPos(坐标))
初始化示例:

  //! 运动轨迹点 长度
  qreal path_advance=0;
  //! 点坐标
  pos=m_Path.pointAtPercent(0);
  setPos(pos); 
  //! 周期时间,点从0到总长需要的时间
  qreal goThroughTime=15000;
  //! path_advance 元属性
  QPropertyAnimation* animation=new QPropertyAnimation(this,"path_advance");
  //! 业务值改变时,计算点坐标,移动点
  connect(animation,&QPropertyAnimation::valueChanged,this,[&](const QVariant &value){
      //! 计算百分比获取实时坐标
      pos=m_Path.pointAtPercent((path_advance/m_Path.length()));
      setPos(pos);
  });
  animation->setStartValue(0);
  //! 设置QPainterPath总长度
  animation->setEndValue(m_Path.length());
  //! 动画周期时间
  animation->setDuration(goThroughTime);
  //! -1 运行结束后重新开始
  animation->setLoopCount(-1);
  animation->start();

总结:
实际上整个动画流程也是读取SVG文件再转换成QPainterPath轨迹,重写QGraphicsObject类生成一个点图元,在添加一个路径元属性结合QPropertyAnimation 动画类实现这种效果;
在找到 QtSVGEditor 库的时候,我发现WPS整个数据库制作流程图模块,完全可以通过对SVG文件的解析和修改来实现。包括其中的各种动画效果也可以通过Qt的动画类,如QPropertyAnimation类等来实现,完成后甚至于能将制作的流程图导出为SVG文件 ,有兴趣的可以去看看。

功能实现:

示例图:

请添加图片描述

完整源码:

TGraphicsPointItem.h:
重写QGraphicsObject
传入SVG文件转成的QPainterPath 轨迹路径,
在通过QPropertyAnimation 控制QGraphicsObject 点图元的坐标移动


#include <QObject>
#include <QGraphicsObject>
#include <QGraphicsScene>

#include <QPainterPath>
#include <QPropertyAnimation>
#include <QPainter>

#include "tgraphicspointitem.h"

class TGraphicsPointItem :public QGraphicsObject
{
    Q_OBJECT
    Q_INTERFACES(QGraphicsItem)
    Q_PROPERTY(qreal path_advance READ get_path_advance WRITE set_path_advance)

public:
    TGraphicsPointItem(QPainterPath path,qreal time=5000,QGraphicsObject *parentItem = nullptr);

    enum { Type = UserType + 3 };
    int type() const override
    {return Type;}

    QRectF boundingRect() const override;
    QPainterPath shape() const override;
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;

    //! 设置轨迹路线 和 走完需要的时间
    void SetPainterPathByTime(QPainterPath path,qreal time);


    qreal get_path_advance()
    {
        return path_advance;
    }

public slots:
    void set_path_advance(qreal _path_advance)
    {
        path_advance=_path_advance;
    }

private:
    QRectF selfRect=QRectF(0,0,4,4);

    //! 运动轨迹点 长度
    qreal path_advance=0;
    //! 运动轨迹线
    QPainterPath m_Path;

    //! 所需时间
    qreal goThroughTime=0;


    //! 坐标点
    QPointF pos;

    //! 虚线动画
    QPropertyAnimation* animation;

};

TGraphicsPointItem.cpp:

#include "tgraphicspointitem.h"
#include <QDebug>

TGraphicsPointItem::TGraphicsPointItem(QPainterPath path,qreal time,QGraphicsObject *parentItem)
    :QGraphicsObject(parentItem)
{
    this->setAcceptHoverEvents(false);
    setFlag(QGraphicsItem::ItemIsMovable, false);
    setFlag(QGraphicsItem::ItemIsSelectable, false);
//    setFlag(QGraphicsItem::ItemIgnoresTransformations, true);
//    setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);

    SetPainterPathByTime(path, time);
    setParentItem(parentItem);
    setZValue(999);
    show();
}

QRectF TGraphicsPointItem::boundingRect() const
{
    return shape().boundingRect();
}


QPainterPath TGraphicsPointItem::shape() const
{
    QPainterPath path;
    path.addRect(QRectF(-2,-2,selfRect.width(),selfRect.height()));
    return path;
}

void TGraphicsPointItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    QPen pen3(QColor("#DB5E5E"), 1);
    painter->setPen(pen3);
    painter->setBrush(QColor("#DB5E5E"));
    painter->drawEllipse(QRectF(-2,-2,selfRect.width(),selfRect.height()));

}

void TGraphicsPointItem::SetPainterPathByTime(QPainterPath path,qreal time)
{
    m_Path=path;
    path_advance=0;
    pos=m_Path.pointAtPercent(0);
    qDebug()<<" [pos] -->"<<pos;
    setPos(pos);
    goThroughTime=time;

    animation=new QPropertyAnimation(this,"path_advance");
    connect(animation,&QPropertyAnimation::valueChanged,this,[&](const QVariant &value){
//        qDebug()<<" [valueChanged] -->"<<path_advance;
        pos=m_Path.pointAtPercent((path_advance/m_Path.length()));
        setPos(pos);
//        qDebug()<<" [pos] -->"<<pos;
//        this->update();
    });

    connect(animation,&QPropertyAnimation::finished,this,[&](){
//        qDebug()<<" [finished] -->";
//        this->parentItem()->scene()->removeItem((QGraphicsItem*)this);
    });

    animation->setStartValue(0);
    animation->setEndValue(m_Path.length());
    animation->setDuration(goThroughTime);
    animation->setLoopCount(-1);
    animation->start();
}

  • 26
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Qt中,QGraphicsView是用来显示和处理大型场景的视图。要实现鼠标绘图,可以通过以下步骤进行: 1. 创建QGraphicsView实例和一个场景(QGraphicsScene)对象,并将场景设置给视图。 2. 创建一个继承自QGraphicsItem的自定义图形项,用于表示绘图的形状。 3. 在自定义图形项中,重写mousePressEvent和mouseMoveEvent两个事件处理函数,用于捕捉鼠标按下和移动的事件。 4. 在mousePressEvent中,记录鼠标按下的位置,并创建一个新的图形项对象。 5. 在mouseMoveEvent中,根据鼠标移动的位置,更新图形项对象的形状。 6. 将新创建的图形项对象添加到场景中,并使用QGraphicsScene::addItem()函数进行添加。 7. 最后,将场景设置给QGraphicsView,并调用QGraphicsView::show()函数显示视图。 以下是一个简单示例代码: ```cpp #include <QtWidgets> class CustomGraphicsItem : public QGraphicsItem { public: CustomGraphicsItem() : QGraphicsItem() { setFlag(ItemIsMovable); setAcceptHoverEvents(true); } QRectF boundingRect() const override { return QRectF(0, 0, 100, 100); } void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override { painter->drawEllipse(boundingRect()); } void mousePressEvent(QGraphicsSceneMouseEvent* event) override { if (event->button() == Qt::LeftButton) { QPointF position = event->pos(); qDebug() << "Mouse press at:" << position; } } void mouseMoveEvent(QGraphicsSceneMouseEvent* event) override { setPos(event->pos()); } }; int main(int argc, char* argv[]) { QApplication app(argc, argv); QGraphicsScene scene; QGraphicsView view(&scene); CustomGraphicsItem* item = new CustomGraphicsItem(); scene.addItem(item); view.show(); return app.exec(); } ``` 这是一个简单的绘图程序,可以在视图中击鼠标左键,然后拖动鼠标绘制一个圆形图形项。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

得鹿梦鱼、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值