Qt5 图形与动画详细讲解

个人博客:blogs.wurp.top

一、Qt5 图形系统基础

Qt 的图形系统是建立在 Qt GUI 模块之上的,它提供了一套跨平台的抽象,使得开发者可以用同一套代码在不同的操作系统(Windows, macOS, Linux, Android, iOS等)上渲染图形和UI。

1. 核心概念:QPaintDevice 和 QPainter

这是 Qt 绘图最基础、最核心的哲学:在任何绘制设备上,使用画师作画

  • QPaintDevice (绘制设备):可以看作是一张画布。所有能在上面绘图的类都继承自它。常见的绘制设备包括:

    • QWidget: 所有窗口部件的基类。
    • QPixmap: 优化用于在屏幕上显示的图像。
    • QImage: 优化用于图像读写和像素级操作。
    • QOpenGLPaintDevice: 用于在OpenGL上下文中绘制。
    • QPrinter: 用于打印。
  • QPainter (画师):负责在所有 QPaintDevice 上执行实际的绘制操作。你可以把它想象成一个拿着各种工具的画家,它提供了大量高度优化的函数来绘制各种图形。

    • 绘制基本图元:点、线、矩形、椭圆、多边形、弧、弦、饼图等。
    • 绘制文字:使用 drawText()
    • 绘制图像:使用 drawPixmap()drawImage()
    • 路径(QPainterPath):用于绘制更复杂的形状,由基本的图元组合而成。

一个简单的示例:在QWidget上绘制

// 在自定义Widget的paintEvent中
void MyWidget::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event);

    QPainter painter(this); // 创建画师,指定“this”(QWidget)为绘制设备

    // 设置画笔(用于轮廓)和画刷(用于填充)
    QPen pen(Qt::blue, 2); // 蓝色,2像素宽
    QBrush brush(Qt::red); // 红色填充

    painter.setPen(pen);
    painter.setBrush(brush);

    // 画一个矩形
    painter.drawRect(10, 10, 100, 100);

    // 画一条线
    painter.drawLine(120, 10, 220, 110);

    // 画文字
    painter.drawText(130, 130, tr("Hello, Qt!"));
}

关键点:所有自定义绘制都必须在 paintEvent() 事件处理函数中进行。当窗口需要重绘(如首次显示、被其他窗口遮挡后重现、调整大小等)时,Qt会自动调用此函数。

2. 坐标系系统

Qt的默认坐标系原点 (0, 0)绘制设备的左上角。X轴向右为正,Y轴向下为正
QPainter 支持坐标变换(平移、旋转、缩放、错切),允许你在一个变换后的坐标系中轻松绘制。

void MyWidget::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    painter.setBrush(Qt::lightGray);

    // 保存当前坐标系状态
    painter.save();

    // 平移坐标系原点至Widget中心
    painter.translate(width() / 2, height() / 2);

    // 旋转坐标系45度
    painter.rotate(45);

    // 此时画的矩形会在Widget中心,并且是旋转45度的
    painter.drawRect(-50, -50, 100, 100); // 以新的原点(0,0)为中心画矩形

    // 恢复之前保存的坐标系状态
    painter.restore();

    // 这个矩形不受上面变换影响,仍在左上角
    painter.drawRect(10, 10, 50, 50);
}

3. 双缓冲(Double Buffering)

Qt 默认提供了自动的双缓冲机制。这意味着所有的绘制操作首先在一个离屏的像素图(QPixmap)上进行,绘制完成后,这个像素图会被一次性复制到屏幕上。这有效地消除了闪烁现象,提供了平滑的绘制体验。你通常不需要手动处理它,但了解其原理很重要。


二、Graphics View Framework(图形视图框架)

对于需要管理大量(成千上万)、可交互的独立图形项(Item)的复杂场景,使用原始的 QPainterQWidget 上直接绘制会非常低效且难以管理。这时就该 Graphics View 出场了。

它是一个基于Item-View模型的框架,包含三个核心类:

  1. QGraphicsScene (场景)

    • 是一个容器,管理所有的图形项(QGraphicsItem)。
    • 负责管理所有Item的状态(如选择、焦点),以及传播事件(如鼠标、键盘事件)给Item。
    • 本身不可见,只是一个逻辑上的容器。
  2. QGraphicsItem (图形项)

    • 是场景中的单个图形对象,例如椭圆、矩形、文本,甚至是自定义图形。
    • 可以有父子关系、支持平移/旋转/缩放、处理鼠标键盘事件、获取焦点、碰撞检测等。
    • 常用的内置Item有 QGraphicsEllipseItem, QGraphicsRectItem, QGraphicsTextItem 等。
    • 你可以通过继承 QGraphicsItem 来创建自己的自定义Item。
  3. QGraphicsView (视图)

    • 是一个可视化的窗口部件,用于显示场景的内容。
    • 一个场景可以被多个视图观察,每个视图可以有自己的变换(如缩放、旋转),实现“一张地图,多个小窗口”的效果。
    • 提供滚动条、坐标映射、缩放、旋转等 viewing 功能。

工作流程:创建 Scene -> 创建 Item 并添加到 Scene -> 创建 View 并设置 Scene

一个简单示例:

// 创建场景和视图
QGraphicsScene *scene = new QGraphicsScene;
QGraphicsView *view = new QGraphicsView(scene);

// 创建一些图形项并添加到场景
QGraphicsRectItem *rect = scene->addRect(0, 0, 100, 100);
rect->setPos(50, 50);
rect->setBrush(Qt::blue);
// 让矩形可移动、可选中
rect->setFlag(QGraphicsItem::ItemIsMovable);
rect->setFlag(QGraphicsItem::ItemIsSelectable);

QGraphicsEllipseItem *ellipse = scene->addEllipse(0, 0, 100, 100);
ellipse->setPos(150, 150);
ellipse->setBrush(Qt::red);

// 设置视图的变换(缩放0.8倍)
view->scale(0.8, 0.8);

view->show(); // 显示视图窗口

Graphics View 框架非常强大,内置了对Item动画的支持,这为我们接下来要讲的动画框架提供了完美的舞台。


三、Qt5 动画框架

Qt动画框架的目标是让属性(不仅仅是图形属性,可以是任何QVariant支持的属性)平滑地变化。其核心是 QAbstractAnimation 和它的子类。

1. 属性动画(QPropertyAnimation)

这是最常用、最直接的动画类。它通过在一段时间内连续修改一个对象的某个属性来实现动画效果。

核心概念

  • 目标对象(Target Object):拥有该属性的QObject。
  • 属性名(Property Name):需要动画的属性,必须是该对象的Qt属性(用Q_PROPERTY声明或有对应的setter/getter)。例如 "pos", "geometry", "opacity",也可以是自定义属性如 "radius"
  • 持续时间(Duration):动画持续的毫秒数。
  • 起始值(Start Value)结束值(End Value):属性变化的范围。
  • 缓和曲线(Easing Curve):控制属性值随时间变化的插值算法,例如 QEasingCurve::InOutQuad 可以实现慢入慢出的效果。

一个移动QWidget的示例:

QPushButton *button = new QPushButton("Animate Me", this);
button->setGeometry(10, 10, 100, 30);

// 1. 创建属性动画
QPropertyAnimation *animation = new QPropertyAnimation(button, "pos");
// 2. 设置持续时间和值范围
animation->setDuration(2000); // 2秒
animation->setStartValue(QPoint(10, 10));
animation->setEndValue(QPoint(200, 200));
// 3. 设置缓和曲线
animation->setEasingCurve(QEasingCurve::InOutBounce);
// 4. 启动动画
animation->start();

2. 在Graphics View中使用动画

QGraphicsItem 不是 QObject 的子类,所以不能直接用 QPropertyAnimation。有两种主要方法:

  1. 使用QGraphicsItemAnimation (已过时,不推荐):一个旧的辅助类,功能有限。

  2. 继承QObject和QGraphicsItem (推荐)

    • 让你的自定义Item同时继承 QObjectQGraphicsItem(注意Qt的多继承)。
    • 使用 Q_PROPERTY 声明你想要做动画的属性(如 rotation)。
    • 然后就可以像操作普通QObject一样,使用 QPropertyAnimation 来动画这个属性。
// 头文件
class MyAnimatedItem : public QObject, public QGraphicsRectItem
{
    Q_OBJECT
    Q_PROPERTY(qreal rotation READ rotation WRITE setRotation) // 暴露rotation属性
public:
    MyAnimatedItem(qreal x, qreal y, qreal w, qreal h) : QGraphicsRectItem(x, y, w, h) {}
    // ... 可以使用QPropertyAnimation动画rotation了
};

// 使用
MyAnimatedItem *item = new MyAnimatedItem(0, 0, 50, 50);
scene->addItem(item);

QPropertyAnimation *anim = new QPropertyAnimation(item, "rotation");
anim->setDuration(1000);
anim->setStartValue(0);
anim->setEndValue(360);
anim->start();

3. 动画组(Group Animation)

用于同时控制多个动画。

  • QParallelAnimationGroup (并行动画组):组内的所有动画同时开始,同时结束(以最长的为准)。
  • QSequentialAnimationGroup (串行动画组):组内的动画一个接一个地按顺序执行。
QPropertyAnimation *anim1 = new QPropertyAnimation(button1, "pos");
QPropertyAnimation *anim2 = new QPropertyAnimation(button2, "pos");
// ... 设置anim1和anim2的参数

QParallelAnimationGroup *group = new QParallelAnimationGroup;
group->addAnimation(anim1);
group->addAnimation(anim2);

group->start(); // 两个按钮会同时移动

4. 状态机框架(State Machine Framework)

对于更复杂的、由事件驱动的交互和动画,Qt提供了状态机框架。你可以定义不同的状态(QState),每个状态可以设置对象的属性(如位置、大小),状态之间的切换(QTransition)可以触发QPropertyAnimation,从而实现平滑的状态过渡动画。这对于实现复杂的UI流程非常有用。


四、总结与选择指南

需求场景推荐技术理由
简单的自定义绘制(如绘制图表、背景)QPainter + paintEvent()轻量级,直接控制,无需额外开销。
少量、简单的UI元素动画(如移动按钮、淡入淡出)QPropertyAnimation简单易用,直接操作QWidget属性。
大量、复杂的图形项(如CAD绘图、流程图、游戏场景)Graphics View Framework高效管理大量Item,内置碰撞检测、坐标映射等。
复杂图形项的动画Graphics View + (QObject继承) + QPropertyAnimation结合了两者的优势,是标准做法。
复杂的、有状态的交互流程状态机框架 + 动画清晰管理状态和转换,逻辑分明。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

谱写秋天

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值