目录导读
- 简述
- 使用 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 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;
只需要以下内容:
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)的大小递增递减才能看出完整效果。
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());
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);
}