目录导读
- 
- 简述
 - 
- [使用 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是实现了边框的动态效果;
但是在QGraphicsView 中QGraphicsSvgItem 中是不会有动画效果的,包括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值来回的修改就可以了,
值得注意的是
- 通过研究 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);
}