Qt 图形视图框架4-动画、碰撞检测和图形项组

发布于:2025-07-14 ⋅ 阅读:(19) ⋅ 点赞:(0)

1. 动画

在Qt图形视图框架中,实现动画效果有多种常用方法,下面介绍几种主要方式:

以下是在Qt 5.15.5 (MinGW环境)中实现图形视图框架动画的常用方法,代码已亲测可正常运行:

1.1. 使用 QObject 包装器

创建一个 QObject 派生类作为控制器,管理图形项的动画:

// itemcontroller.h
#include <QObject>
#include <QTimer>
#include "myitem.h"

class ItemController : public QObject
{
    Q_OBJECT
public:
    explicit ItemController(MyItem *item, QObject *parent = nullptr);
    
    void startRotation(int interval = 30, qreal speed = 60.0);
    void stopRotation();

private slots:
    void updateRotation();

private:
    MyItem *targetItem;
    QTimer *rotationTimer;
    QTime lastUpdateTime;
    qreal rotationSpeed;
};

// itemcontroller.cpp
#include "itemcontroller.h"

ItemController::ItemController(MyItem *item, QObject *parent)
    : QObject(parent),
      targetItem(item),
      rotationTimer(new QTimer(this)),
      rotationSpeed(60.0)
{
    connect(rotationTimer, &QTimer::timeout, this, &ItemController::updateRotation);
}

void ItemController::startRotation(int interval, qreal speed)
{
    rotationSpeed = speed;
    lastUpdateTime.start();
    rotationTimer->start(interval);
}

void ItemController::stopRotation()
{
    rotationTimer->stop();
}

void ItemController::updateRotation()
{
    if (!targetItem) return;
    
    int elapsed = lastUpdateTime.elapsed();
    lastUpdateTime.restart();
    
    qreal angleDelta = rotationSpeed * elapsed / 1000.0;
    qreal currentAngle = targetItem->rotation();
    currentAngle = fmod(currentAngle + angleDelta, 360.0);
    
    targetItem->setRotation(currentAngle);
}

1.2. 属性动画(QPropertyAnimation)

通过修改图形项的属性实现平滑过渡,需注册属性:

直接在myItem.h中定义QGraphicsObject子类MyGraphicsObject。不能在使用QGraphicsItem,因为QGraphicsItem 并不继承自 QObject,因此不能直接使用 QPropertyAnimation 动画系统,因为动画系统依赖于 QObject 的属性系统和信号槽机制。

#ifndef MYITEM_H
#define MYITEM_H

#include <QGraphicsItem>
#include <QObject>
#include <QPointF>
#include <QPainter>
#include <QPropertyAnimation>
#include <QGraphicsObject>
#include <QGraphicsObject>
#include <QPropertyAnimation>

class MyGraphicsObject : public QGraphicsObject {
    Q_OBJECT

    // 声明 position 属性
    Q_PROPERTY(QPointF position READ pos WRITE setPos)

public:
    MyGraphicsObject(QObject *parent = nullptr) : QGraphicsObject() {
        // 初始化动画
        posAnimation = new QPropertyAnimation(this, "position", this);
        posAnimation->setStartValue(QPointF(0, 0));
        posAnimation->setEndValue(QPointF(100, 100));
        posAnimation->setDuration(1000);
        posAnimation->start();
    }

    // 重写 boundingRect 和 paint 方法
    QRectF boundingRect() const override {
        return QRectF(-10, -10, 20, 20);
    }

    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override {
        painter->drawRect(boundingRect());
    }

    // 实现 position 属性的读写方法
    QPointF pos() const {
        return QGraphicsObject::pos();
    }

    void setPos(const QPointF &position) {
        QGraphicsObject::setPos(position);
    }

private:
    QPropertyAnimation *posAnimation;
};
#endif // MYITEM_H

1.3. 定时器动画(QTimer)

通过定时器定期更新图形项状态:

#ifndef MYITEM_H
#define MYITEM_H

#include <QGraphicsItem>
#include <QObject>
#include <QPointF>
#include <QPainter>
#include <QPropertyAnimation>
#include <QGraphicsObject>
#include <QGraphicsObject>
#include <QPropertyAnimation>
#include <QTimer>

class MyItem : public QGraphicsObject {
    Q_OBJECT

    // 声明 position 属性
    Q_PROPERTY(QPointF position READ pos WRITE setPos)

public:
    MyItem(QObject *parent = nullptr) : QGraphicsObject() {
        // ...已有初始化...
        currentAngle = 0;

        rotationTimer = new QTimer(this);
        connect(rotationTimer, &QTimer::timeout, this, &MyItem::updateRotation);
        rotationTimer->start(30); // 约30fps
    }

    // 重写 boundingRect 和 paint 方法
    QRectF boundingRect() const override {
        return QRectF(-10, -10, 20, 20);
    }

    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override {
        painter->drawRect(boundingRect());
    }

    // 实现 position 属性的读写方法
    QPointF pos() const {
        return QGraphicsObject::pos();
    }

    void setPos(const QPointF &position) {
        QGraphicsObject::setPos(position);
    }


private slots:
    void updateRotation(){
        currentAngle += 2;
        if (currentAngle >= 360) currentAngle = 0;
        setRotation(currentAngle);
    }

private:
    QTimer *rotationTimer;
    qreal currentAngle;
};
#endif // MYITEM_H

1.4. 场景推进动画(QGraphicsScene::advance)

通过重写advance()函数实现批量动画:

// myitem.h
// myitem.h
#include <QGraphicsItem>
#include <QPainter>
#include <QTime>

class MyItem : public QGraphicsItem
{
public:
    MyItem();
    QRectF boundingRect() const override;
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;

    void setRotationSpeed(qreal speed);

protected:
    void advance(int phase) override;

private:
    qreal rotationSpeed;
    QTime lastUpdateTime;
};


// myitem.cpp


// myitem.cpp
#include "myitem.h"

QRectF MyItem::boundingRect() const
{
    return QRectF(-20, -10, 40, 20); // 设置边界矩形,可根据实际图形调整
}

void MyItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    Q_UNUSED(option)
    Q_UNUSED(widget)
    painter->setBrush(Qt::red); // 设置填充颜色为红色
    painter->drawEllipse(-20, -10, 40, 20); // 绘制圆形
}

MyItem::MyItem() : rotationSpeed(0)
{
    lastUpdateTime.start();
}

void MyItem::setRotationSpeed(qreal speed)
{
    rotationSpeed = speed;
    lastUpdateTime.restart();
}

void MyItem::advance(int phase)
{
    if (!phase) return; // phase=0是预计算阶段

    if (rotationSpeed != 0) {
        int elapsed = lastUpdateTime.elapsed();
        lastUpdateTime.restart();

        qreal angleDelta = rotationSpeed * elapsed / 1000.0;
        setRotation(rotation() + angleDelta);
    }
}

// main.cpp中添加定时器触发场景更新

#include <QApplication>
#include <QTimer>
#include "myitem.h"
#include "myview.h"

int main(int argc,char*argv[])
{
    QApplication app(argc,argv);
    QGraphicsScene scene;
    scene.setSceneRect(-200,-150,400,300);

    MyItem *item =new MyItem;
    item->setPos(QPointF(0, 0));
    scene.addItem(item);

    MyView view;
    view.setScene(&scene);
    view.show();

    // 方法1: 使用控制器
    ItemController controller(item);
    controller.startRotation(30, 90.0); // 90度/秒
    
    // 方法2: 使用属性动画
    // item->startRotationAnimation(5000); // 5秒转一圈
    
    // 方法3: 使用advance()
    // item->setRotationSpeed(60.0); // 60度/秒

    // 启动场景更新定时器
    QTimer timer;
    QObject::connect(&timer, &QTimer::timeout, &scene, &QGraphicsScene::advance);
    timer.start(16); // 约60FPS

    return  app.exec();
}

效果:
在这里插入图片描述
在这里插入图片描述

2. 碰撞检测

图形视图框架提供了两种碰撞检测方法:

2.1. 方法一:使用 QGraphicsItem::shape()

  • 实现方式
    • 重新实现 QGraphicsItem::shape() 函数,返回图形项的准确形状。
    • 使用默认的 collidesWithItem() 函数,通过形状交集判断是否碰撞。
  • 缺点
    • 形状复杂时,计算耗时。
  • 默认行为
    • 未实现 shape() 时,默认调用 boundingRect() 返回简单矩形。

2.2. 方法二:自定义碰撞算法

  • 实现方式
    • 重新实现 collidesWithItem() 函数,提供自定义的碰撞检测逻辑。

2.3. 相关函数

函数 说明
collidesWithItem() 判断与指定图形项是否碰撞
collidesWithPath() 判断与指定路径是否碰撞
collidingItems() 获取所有碰撞的图形项列表

2.4. Qt::ItemSelectionMode 参数

控制图形项选取模式,共有4种值:

常量 描述
Qt::ContainsItemShape 仅选取形状完全包含在选择区域中的图形项
Qt::IntersectsItemShape(默认) 选取形状完全包含或与区域边界相交的图形项
Qt::ContainsItemBoundingRect 仅选取边界矩形完全包含在选择区域中的图形项
Qt::IntersectsItemBoundingRect 选取边界矩形完全包含或与区域边界相交的图形项

2.5. 示例代码

myitem.h

public:
    QPainterPath shape();

myitem.cpp

QPainterPath MyItem::shape() {
    // 简单返回矩形形状
    QPainterPath path;
    path.addRect(boundingRect());
    return path;
}

paint() 函数修改

if (hasFocus() || !collidingItems().isEmpty()) {
    // 碰撞时轮廓线变为白色
}

3. 图形项组(QGraphicsItemGroup)

3.1. 功能

  • 将多个图形项组合为一个独立图形项。
  • 所有子项平等,可拖动任意子项一起移动。
  • 不同于父图形项,子项无层级关系。

3.2. 创建方式

3.2.1. 手动创建
QGraphicsItemGroup *group = new QGraphicsItemGroup;
scene->addItem(group);

QGraphicsRectItem *rect1 = new QGraphicsRectItem(0, 0, 50, 50);
QGraphicsRectItem *rect2 = new QGraphicsRectItem(50, 50, 50, 50);
group->addToGroup(rect1);
group->addToGroup(rect2);
3.2.2. 场景直接创建
QGraphicsItemGroup *group = scene->createItemGroup(scene->selectedItems());

3.3. 交互操作

  • 选择:需设置 ItemIsSelectable 标志。
    item->setFlag(QGraphicsItem::ItemIsSelectable);
    
  • 橡皮筋选择
    view->setDragMode(QGraphicsView::RubberBandDrag);
    

3.4. 移除与销毁

  • 移除子项
    group->removeFromGroup(item);
    
  • 销毁组(不移除子项):
    scene->destroyItemGroup(group);
    
    子项会移动到父组或场景中。

网站公告

今日签到

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