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

发布于:2024-07-07 ⋅ 阅读:(61) ⋅ 点赞:(0)

闲谈:眨眼间,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();
}


网站公告

今日签到

点亮在社区的每一天
去签到