一、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)的复杂场景,使用原始的 QPainter
在 QWidget
上直接绘制会非常低效且难以管理。这时就该 Graphics View 出场了。
它是一个基于Item-View模型的框架,包含三个核心类:
-
QGraphicsScene (场景):
- 是一个容器,管理所有的图形项(
QGraphicsItem
)。 - 负责管理所有Item的状态(如选择、焦点),以及传播事件(如鼠标、键盘事件)给Item。
- 本身不可见,只是一个逻辑上的容器。
- 是一个容器,管理所有的图形项(
-
QGraphicsItem (图形项):
- 是场景中的单个图形对象,例如椭圆、矩形、文本,甚至是自定义图形。
- 可以有父子关系、支持平移/旋转/缩放、处理鼠标键盘事件、获取焦点、碰撞检测等。
- 常用的内置Item有
QGraphicsEllipseItem
,QGraphicsRectItem
,QGraphicsTextItem
等。 - 你可以通过继承
QGraphicsItem
来创建自己的自定义Item。
-
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
。有两种主要方法:
-
使用QGraphicsItemAnimation (已过时,不推荐):一个旧的辅助类,功能有限。
-
继承QObject和QGraphicsItem (推荐):
- 让你的自定义Item同时继承
QObject
和QGraphicsItem
(注意Qt的多继承)。 - 使用
Q_PROPERTY
声明你想要做动画的属性(如rotation
)。 - 然后就可以像操作普通QObject一样,使用
QPropertyAnimation
来动画这个属性。
- 让你的自定义Item同时继承
// 头文件
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 | 结合了两者的优势,是标准做法。 |
复杂的、有状态的交互流程 | 状态机框架 + 动画 | 清晰管理状态和转换,逻辑分明。 |