QT实现QGraphicsView绘图 重写QGraphicsSvgItem类实现边框动画

目录导读

    • 简述
      • [使用 QTimer 实现 QGraphicsSvgItem 边框动画效果](#使用 QTimer 实现 QGraphicsSvgItem 边框动画效果)

简述

在了解学习WPS的流程图的时候,发现它这个选择图元有个动态边框效果,而且连接线还会根据线生成点从头移动到尾的动画。像这种:

在QML中实现这种动画属性很简单,现成的动画属性,但是在QGraphicsView 中实现这种效果就值得思考一下,

在QT中SVG的动画属性只支持animateTransform 元素,其他动画元素是不支持。即使我把QT版本升级成 6.7.0版本 也不支持,

例如 animate 动画元素:

SVG文件:

xml 复制代码
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">

<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink/" baseProfile="tiny" version="1.2">
  <title>Spheres</title>
  <path id="dashedSquare" d="M10,10 L190,10 L190,190 L10,190 Z M10,10" stroke="black" stroke-width="2" fill="none" stroke-dasharray="10,5" stroke-dashoffset="0" stroke-linecap="round">
    <animate  id="dashAnimate" attributeName="stroke-dashoffset" from="200" to="0" dur="2s" begin="0s"  repeatCount="indefinite" />
    <animate  id="resetAnimate" attributeName="stroke-dashoffset" from="0" to="200" dur="2s"  begin="dashAnimate.end" fill="freeze" />
</path>
</svg>

这个SVG是实现了边框的动态效果;

但是在QGraphicsViewQGraphicsSvgItem 中是不会有动画效果的,包括QSvgWidget等其他Svg相关的控件也没有,只能尝试其他办法。。

使用 QTimer 实现 QGraphicsSvgItem 边框动画效果

使用QT的 QTimer 类 实现 QGraphicsSvgItem 边框动画,

通过研究上面的SVG可以发现,边框的动画效果实际是stroke-dashoffset 属性的变动,也可以通过
QPen 类的 setDashOffset(qreal offset) 变动,

所以在
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;

绘制图元时,只要使用 QTimer 周期性将画笔的DashOffset值来回的修改就可以了,

值得注意的是

  1. 通过研究 QGraphicsSvgItem 类源码中的 paint 事件发现 QGraphicsItem 自带的选中显示边框效果中的边框是外扩了一定像素,
    参考源码:
cpp 复制代码
//! 这是 QGraphicsItem 自带选中边框效果的绘制边框
//! 源码参考路径:  D:\Qt\Qt5.13.1\5.13.1\Src\qtsvg\src\svg\qgraphicssvgitem.cpp
//! \internal
//! Highlights \a item as selected.
//! NOTE: This function is a duplicate of qt_graphicsItem_highlightSelected() in qgraphicsitem.cpp!

static void qt_graphicsItem_highlightSelected(
    QGraphicsItem *item, QPainter *painter, const QStyleOptionGraphicsItem *option)
{
    const QRectF murect = painter->transform().mapRect(QRectF(0, 0, 1, 1));
    if (qFuzzyIsNull(qMax(murect.width(), murect.height())))
        return;

    const QRectF mbrect = painter->transform().mapRect(item->boundingRect());
    if (qMin(mbrect.width(), mbrect.height()) < qreal(1.0))
        return;

    qreal itemPenWidth;
    switch (item->type()) {
        case QGraphicsEllipseItem::Type:
            itemPenWidth = static_cast<QGraphicsEllipseItem *>(item)->pen().widthF();
            break;
        case QGraphicsPathItem::Type:
            itemPenWidth = static_cast<QGraphicsPathItem *>(item)->pen().widthF();
            break;
        case QGraphicsPolygonItem::Type:
            itemPenWidth = static_cast<QGraphicsPolygonItem *>(item)->pen().widthF();
            break;
        case QGraphicsRectItem::Type:
            itemPenWidth = static_cast<QGraphicsRectItem *>(item)->pen().widthF();
            break;
        case QGraphicsSimpleTextItem::Type:
            itemPenWidth = static_cast<QGraphicsSimpleTextItem *>(item)->pen().widthF();
            break;
        case QGraphicsLineItem::Type:
            itemPenWidth = static_cast<QGraphicsLineItem *>(item)->pen().widthF();
            break;
        default:
            itemPenWidth = 1.0;
    }
    const qreal pad = itemPenWidth / 2;

    const qreal penWidth = 0; // cosmetic pen

    const QColor fgcolor = option->palette.windowText().color();
    const QColor bgcolor( // ensure good contrast against fgcolor
        fgcolor.red()   > 127 ? 0 : 255,
        fgcolor.green() > 127 ? 0 : 255,
        fgcolor.blue()  > 127 ? 0 : 255);

    painter->setPen(QPen(bgcolor, penWidth, Qt::SolidLine));
    painter->setBrush(Qt::NoBrush);
    painter->drawRect(item->boundingRect().adjusted(pad, pad, -pad, -pad));

    painter->setPen(QPen(option->palette.windowText(), 0, Qt::DashLine));
    painter->setBrush(Qt::NoBrush);
    painter->drawRect(item->boundingRect().adjusted(pad, pad, -pad, -pad));
}

所以在重写
void QGraphicsSvgItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;

事件的时候,不能继承原有paint;

只需要以下内容:

cpp 复制代码
void TGraphicsSvgItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
   if (!renderer()->isValid())
         return;
     if (elementId().isEmpty())
         renderer()->render(painter, boundingRect());
     else
         renderer()->render(painter, elementId(), boundingRect());
         
	 if(isSelected())
	  {
	       QPen pen2(QColor("#067BEF"), 2);
	       pen2.setDashPattern(QVector<qreal>{2,2}); // 设置虚线模式
	       pen2.setDashOffset(dashoffset);
	       painter->setPen(pen2);
	       painter->drawRect(shape().boundingRect()); // 绘制正方形
	   }
}

2.不使用QPropertyAnimation类而是用QTimer 类实现DashOffset值周期性变化,

QPropertyAnimation类 变化的差值是线性的,看不出边框虚线效果。

需要设置按指定步长(10)的大小递增递减才能看出完整效果。

cpp 复制代码
timer = new QTimer();
    QObject::connect(timer, &QTimer::timeout,[&](){
        dashoffset-=10;
        if(dashoffset<=0)
        {
            dashoffset=100;
        }
        this->update();
    });

实际效果:

完整代码:

TGraphicsSvgItem.h

cpp 复制代码
#ifndef TGRAPHICSSVGITEM_H
#define TGRAPHICSSVGITEM_H

#include <QGraphicsItem>
#include <QGraphicsSvgItem>
#include <QTimer>
#include <QPainter>
#include <QSvgRenderer>

#include <QPropertyAnimation>
#include <QParallelAnimationGroup>
#include <QSequentialAnimationGroup>
#include "ParseSvg/lib_csvgelementpath.h"
#include "tgraphicspointitem.h"


//! D:\Qt\Qt5.13.1\5.13.1\Src\qtsvg\src\svg\qgraphicssvgitem.cpp
//!
class TGraphicsSvgItem:public QGraphicsSvgItem
{
    Q_OBJECT
public:
    TGraphicsSvgItem(QGraphicsItem *parentItem = nullptr);
    TGraphicsSvgItem(const QString &fileName, QGraphicsItem *parentItem = nullptr);

public slots:

    //! 初始化计时器
    void Init_timer();
protected:
//    QRectF boundingRect() const override;
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;

    QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;
private:

    qreal dashoffset=100;
   
    //! 虚边框线
    QTimer* timer;
};

#endif // TGRAPHICSSVGITEM_H

TGraphicsSvgItem.cpp

cpp 复制代码
#include "tgraphicssvgitem.h"
#include <QDebug>
#include <QPropertyAnimation>
#include <QTimeLine>

TGraphicsSvgItem::TGraphicsSvgItem(QGraphicsItem *parentItem)
    :QGraphicsSvgItem(parentItem)
{
    this->setAcceptHoverEvents(true);
    Init_timer();
}

TGraphicsSvgItem::TGraphicsSvgItem(const QString &fileName, QGraphicsItem *parentItem)
    :QGraphicsSvgItem(fileName,parentItem)
{
    this->setAcceptHoverEvents(true);
    Init_timer();


}

void TGraphicsSvgItem::Init_timer()
{

    this->setFlag(TGraphicsSvgItem::ItemIsMovable, true);
    this->setFlag(TGraphicsSvgItem::ItemIsSelectable, true);
    this->setFlag(TGraphicsSvgItem::ItemClipsToShape,true);
    timer = new QTimer();
    QObject::connect(timer, &QTimer::timeout,[&](){
        dashoffset-=10;
        if(dashoffset<=0)
        {
            dashoffset=100;
        }
        this->update();
    });
}

//QRectF TGraphicsSvgItem::boundingRect() const {
//    return shape().boundingRect();
//    return this->renderer()->boundsOnElement("shadow");
//}

void TGraphicsSvgItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
        if (!renderer()->isValid())
            return;

        if (elementId().isEmpty())
            renderer()->render(painter, boundingRect());
        else
            renderer()->render(painter, elementId(), boundingRect());


        if(isSelected())
        {
            QPen pen2(QColor("#067BEF"), 2);
            pen2.setDashPattern(QVector<qreal>{2,2}); // 设置虚线模式
            pen2.setDashOffset(dashoffset);
            painter->setPen(pen2);
            painter->drawRect(shape().boundingRect()); // 绘制正方形
        }

}

QVariant TGraphicsSvgItem::itemChange(GraphicsItemChange change, const QVariant &value)
{
    if(change==QGraphicsSvgItem::ItemSelectedChange)
    {
        if(value.toUInt()==1)
        {
            dashoffset=100;
            if(timer!=nullptr && timer!=NULL)
                timer->start(100);
        }
        else
        {
            dashoffset=100;
            if(timer!=nullptr && timer!=NULL)
                timer->stop();
        }
    }
    return QGraphicsSvgItem::itemChange(change,value);
}
相关推荐
「QT(C++)开发工程师」6 小时前
【qt版本概述】
开发语言·qt
一路冰雨9 小时前
Qt打开文件对话框选择文件之后弹出两次
开发语言·qt
老赵的博客10 小时前
QT 自定义界面布局要诀
开发语言·qt
码码哈哈0.010 小时前
VSCode 2022 离线安装插件QT VSTOOl报错此扩展不能安装在任何当前安装的产品上。
ide·vscode·qt
feiyangqingyun14 小时前
Qt/C++离线地图的加载和交互/可以离线使用/百度和天地图离线/支持手机上运行
c++·qt·qt天地图·qt离线地图·qt地图导航
gz94561 天前
windows下,用CMake编译qt项目,出现错误By not providing “FindQt5.cmake“...
开发语言·qt
「QT(C++)开发工程师」1 天前
Ubuntu 26.04 LTS 大升级:Qt 6 成为未来新引擎
qt
兆。1 天前
python实战案例----使用 PyQt5 构建简单的 HTTP 接口测试工具
爬虫·python·qt
喝哈喝哈1 天前
pycharm中配置pyqt5
python·qt·pycharm
Qt云程序员1 天前
Qt、C++实现五子棋人机对战与本地双人对战(高难度AI,极少代码)
c++·人工智能·qt