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);