目录导读
- 简述
- 使用 QTimer 实现 QGraphicsSvgItem 边框动画效果
简述
在了解学习WPS的流程图的时候,发现它这个选择图元有个动态边框效果,而且连接线还会根据线生成点从头移动到尾的动画。像这种:
在QML中实现这种动画属性很简单,现成的动画属性,但是在QGraphicsView中实现这种效果就值得思考一下,
在QT中SVG的动画属性只支持animateTransform元素,其他动画元素是不支持。即使我把QT版本升级成 6.7.0版本 也不支持,
例如 animate 动画元素:
SVG文件:
<?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是实现了边框的动态效果;
但是在QGraphicsView中QGraphicsSvgItem中是不会有动画效果的,包括QSvgWidget等其他Svg相关的控件也没有,只能尝试其他办法。。
使用QT的 QTimer 类 实现 QGraphicsSvgItem 边框动画,
通过研究上面的SVG可以发现,边框的动画效果实际是stroke-dashoffset 属性的变动,也可以通过
QPen 类的 setDashOffset(qreal offset) 变动,
所以在
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
绘制图元时,只要使用 QTimer 周期性将画笔的DashOffset值来回的修改就可以了,
值得注意的是
- 通过研究 QGraphicsSvgItem类源码中的 paint 事件发现 QGraphicsItem自带的选中显示边框效果中的边框是外扩了一定像素,
参考源码:
//! 这是 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 penconst QColor fgcolor = option->palette.windowText().color();const QColor bgcolor( // ensure good contrast against fgcolorfgcolor.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;
只需要以下内容:
void TGraphicsSvgItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{if (!renderer()->isValid())return;if (elementId().isEmpty())renderer()->render(painter, boundingRect());elserenderer()->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)的大小递增递减才能看出完整效果。
timer = new QTimer();QObject::connect(timer, &QTimer::timeout,[&](){dashoffset-=10;if(dashoffset<=0){dashoffset=100;}this->update();});
实际效果:
完整代码:
TGraphicsSvgItem.h
#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
#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());elserenderer()->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);
}