QPainter - 使用一个时钟项目从头开始看QPainter
之前一直在说绘制,但是没有从头详细的去了解绘制这块的写法,因此我们来使用一个时钟的项目来学习一下绘制
先上个图:
绘制的原理
clockpainter
#ifndef PAINTERTEST_CLOCKPAINTER_H #define PAINTERTEST_CLOCKPAINTER_H #include <QTimer> #include <QWidget> #include <QColor> class ClockPainter : public QWidget { Q_OBJECT public: explicit ClockPainter(QWidget *parent = nullptr); ~ClockPainter() override; protected: void paintEvent(QPaintEvent *event) override; private: }; #endif //PAINTERTEST_CLOCKPAINTER_H #include "clockpainter.h" #include <QPainter> ClockPainter::ClockPainter(QWidget *parent) : QWidget(parent) { } ClockPainter::~ClockPainter() { } void ClockPainter::paintEvent(QPaintEvent *event) { int width = this->width(); int height = this->height(); int side = qMin(width, height); QPainter painter(this); // 抗锯齿 painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing); // 将绘制原点移动到界面的中心点 painter.translate(width / 2, height / 2); // 用(sx, sy)对坐标系进行缩放。(计算的比例使得缩放在一个合适的范围) painter.scale(side / 200.0, side / 200.0); // 保存当前的painter状态(将状态推入堆栈)。save()之后必须跟着相应的restore();end()函数的作用是展开堆栈。 // save 和restore 是为了保证在绘制复杂图形的时候绘制的准确性的。因为需要经过多次的变换操作,为了多次变换间产生的影响,因此需要使用这两个函数 painter.save(); QPen pen; pen.setColor(Qt::red); pen.setWidth(5); painter.setPen(pen); // 顺时针旋转坐标系。给定的角度参数以度为单位。 painter.translate(50, 50); // 顺时针旋转坐标系。给定的角度参数以度为单位。 painter.rotate(90); // 绘制直线 painter.drawLine(QPoint(0, 0), QPoint(0, 50)); // 恢复当前painter状态(从堆栈中弹出已保存的状态)。 painter.restore(); QWidget::paintEvent(event); }
从上面的例子我们可以看到QT整体的坐标系是怎么样的, (0,0)在左上角,+x轴是向左,+y轴是向下。
我们可以通过变换去改变这些位置。这个需要对线性代数有基本的了解,具体的可以参考下面这篇:
QT+ OpenGL 变换_turbolove的博客-CSDN博客
你可以尝试修改对应的顺序看看效果
备注:painter操作的是坐标系,这个我自己看感觉出来的
按照上面的思路,我们加一个计时器,并且使得每秒旋转6度,这样的话,我们的这个线一分钟可以转一圈
绘制时分秒指针
我们需要删除现有的绘制函数,然后添加新的函数来绘制对应的指针
void ClockPainter::drawHands(QPainter &painter) { // 保存当前的painter状态(将状态推入堆栈)。save()之后必须跟着相应的restore();end()函数的作用是展开堆栈。 // save 和restore 是为了保证在绘制复杂图形的时候绘制的准确性的。因为需要经过多次的变换操作,为了多次变换间产生的影响,因此需要使用这两个函数 int secondHandR = radius_ - 20; int minuteHandR = radius_ - 40; int HourHandR = radius_ - 50; QTime current_time =QTime::currentTime(); int hour = current_time.hour(); //当前的小时 int minute = current_time.minute(); //当前的分 int second = current_time.second(); //当前的秒 QPolygon pts; painter.save(); pts.setPoints(4, -3, 0, 0, 5, 3, 0, 0, -HourHandR); painter.setBrush(Qt::red); painter.setPen(Qt::NoPen); painter.rotate(hour * 30 + float(minute / 60.0) * 30.0); painter.drawPolygon(pts); painter.restore(); painter.save(); pts.setPoints(4, -2, 0, 0, 7, 2, 0, 0, -minuteHandR); painter.setBrush(Qt::blue); painter.setPen(Qt::NoPen); painter.rotate(minute * 6 + float(second / 60.0) * 6.0); painter.drawPolygon(pts); painter.restore(); painter.save(); pts.setPoints(4, -1, 0, 0, 9, 1, 0, 0, -secondHandR); painter.setBrush(Qt::black); painter.setPen(Qt::NoPen); painter.rotate(second * 6); painter.drawPolygon(pts); painter.restore(); }
解释一下主要的函数
QPolygon pts; 定义的是多边形的数据结构;
pts.setPoints(4, -3, 0, 0, 5, 3, 0, 0, -HourHandR); 第一个是点的个数,后面依次是对应点的xy值,使用这个可以绘制出自己想要的指针样式;
painter.setBrush(Qt::blue); 这里是设置的填充色, setPen设置的是边框颜色。
painter.rotate 上面介绍过了,这里是获取的旋转角度。
绘制背景
这里本来应该先绘制背景的,我们调用的时候也是绘制的背景先。但是我在写代码的时候是先写的指针看效果,然后给对应的指针加背景的,我这里是按照这个顺序来写的博客。
我们新增函数来绘制背景
void ClockPainter::drawBackground(QPainter &painter) { // 3.绘制外部的黑色的圈 int r = radius_ * 0.9; painter.save(); painter.setPen(Qt::NoPen); painter.setBrush(Qt::black);//外围 painter.drawEllipse(-r, -r, r * 2, r * 2); painter.restore(); // 2.绘制外部的颜色 r = radius_*0.88; painter.save(); painter.setPen(Qt::NoPen); painter.setBrush(Qt::white);//外围 painter.drawEllipse(-r, -r, r * 2, r * 2); painter.restore(); // 1.绘制指针背景色 r = radius_*0.1; painter.save(); painter.setPen(Qt::NoPen); painter.setBrush(QColor(200, 200, 200));//外围 painter.drawEllipse(-r, -r, r * 2, r * 2); painter.restore(); }
这里我们绘制一个圆和2个圆环,圆是给指针中心做背景的,第一个圆环覆盖所有的外框,第二个圆环显示时间值,思路是这样,但是绘制的时候需要反着绘制,先绘制大的,然后使用小的覆盖大的
绘制刻度线
接下来我们来绘制刻度线,刻度线没啥,就直接循环绘制即可
void ClockPainter::drawScale(QPainter &painter) { double radius = radius_*0.88; painter.save(); int h = 0; for(int i = 0; i < 60; i++) { if(i % 5 == 0) { QPen pen; pen.setWidthF(1.5); painter.setPen(pen); painter.drawLine(0, - radius + 8, 0, -radius); QFontMetrics fm(painter.font()); QString text; h == 0? text = "12":text = QString::number(h); h++; int width = fm.width(text); int height = fm.height(); painter.drawText(-width / 2.0, - radius + 8 + height, text); } else { QPen pen; pen.setWidthF(0.8); painter.setPen(pen); painter.drawLine(0, - radius + 3, 0, -radius); } painter.rotate(6); } painter.restore(); }
我这个是效果简单的,但是万丈高楼平地起,如果你想写复杂的,需要先学会简单的。
完整代码
#ifndef PAINTERTEST_CLOCKPAINTER_H #define PAINTERTEST_CLOCKPAINTER_H #include <QTimer> #include <QWidget> #include <QColor> class ClockPainter : public QWidget { Q_OBJECT public: explicit ClockPainter(QWidget *parent = nullptr); ~ClockPainter() override; protected: void paintEvent(QPaintEvent *event) override; void drawHands(QPainter &painter); void drawBackground(QPainter &painter); void drawScale(QPainter &painter); private: int radius_{100}; QTimer timer_; }; #endif //PAINTERTEST_CLOCKPAINTER_H #include "clockpainter.h" #include <QPainter> #include <QTimer> #include <QTime> int degree = 0; ClockPainter::ClockPainter(QWidget *parent) : QWidget(parent) { connect(&timer_, &QTimer::timeout, [&](){ update(); }); timer_.start(1000); } ClockPainter::~ClockPainter() { } void ClockPainter::paintEvent(QPaintEvent *event) { int width = this->width(); int height = this->height(); int side = qMin(width, height); QPainter painter(this); // 抗锯齿 painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing); // 将绘制原点移动到界面的中心点 painter.translate(width / 2, height / 2); // 用(sx, sy)对坐标系进行缩放。(计算的比例使得缩放在一个合适的范围) painter.scale(side / 200.0, side / 200.0); drawBackground(painter); drawScale(painter); drawHands(painter); } void ClockPainter::drawHands(QPainter &painter) { // 保存当前的painter状态(将状态推入堆栈)。save()之后必须跟着相应的restore();end()函数的作用是展开堆栈。 // save 和restore 是为了保证在绘制复杂图形的时候绘制的准确性的。因为需要经过多次的变换操作,为了多次变换间产生的影响,因此需要使用这两个函数 int secondHandR = radius_ - 20; int minuteHandR = radius_ - 40; int HourHandR = radius_ - 50; QTime current_time =QTime::currentTime(); int hour = current_time.hour(); //当前的小时 int minute = current_time.minute(); //当前的分 int second = current_time.second(); //当前的秒 QPolygon pts; painter.save(); pts.setPoints(4, -3, 0, 0, 5, 3, 0, 0, -HourHandR); painter.setBrush(Qt::red); painter.setPen(Qt::NoPen); painter.rotate(hour * 30 + float(minute / 60.0) * 30.0); painter.drawPolygon(pts); painter.restore(); painter.save(); pts.setPoints(4, -2, 0, 0, 7, 2, 0, 0, -minuteHandR); painter.setBrush(Qt::blue); painter.setPen(Qt::NoPen); painter.rotate(minute * 6 + float(second / 60.0) * 6.0); painter.drawPolygon(pts); painter.restore(); painter.save(); pts.setPoints(4, -1, 0, 0, 9, 1, 0, 0, -secondHandR); painter.setBrush(Qt::black); painter.setPen(Qt::NoPen); painter.rotate(second * 6); painter.drawPolygon(pts); painter.restore(); } void ClockPainter::drawBackground(QPainter &painter) { // 这里我们绘制一个圆和2个圆环,圆是给指针中心做背景的,第一个圆环覆盖所有的外框,第二个圆环显示时间值,思路是这样,但是绘制的时候需要反着绘制,先绘制大的,然后使用小的覆盖大的 // 3.绘制外部的黑色的圈 int r = radius_ * 0.9; painter.save(); painter.setPen(Qt::NoPen); painter.setBrush(Qt::black);//外围 painter.drawEllipse(-r, -r, r * 2, r * 2); painter.restore(); // 2.绘制外部的颜色 r = radius_*0.88; painter.save(); painter.setPen(Qt::NoPen); painter.setBrush(Qt::white);//外围 painter.drawEllipse(-r, -r, r * 2, r * 2); painter.restore(); // 1.绘制指针背景色 r = radius_*0.1; painter.save(); painter.setPen(Qt::NoPen); painter.setBrush(QColor(200, 200, 200));//外围 painter.drawEllipse(-r, -r, r * 2, r * 2); painter.restore(); } void ClockPainter::drawScale(QPainter &painter) { double radius = radius_*0.88; painter.save(); int h = 0; for(int i = 0; i < 60; i++) { if(i % 5 == 0) { QPen pen; pen.setWidthF(1.5); painter.setPen(pen); painter.drawLine(0, - radius + 8, 0, -radius); QFontMetrics fm(painter.font()); QString text; h == 0? text = "12":text = QString::number(h); h++; int width = fm.width(text); int height = fm.height(); painter.drawText(-width / 2.0, - radius + 8 + height, text); } else { QPen pen; pen.setWidthF(0.8); painter.setPen(pen); painter.drawLine(0, - radius + 3, 0, -radius); } painter.rotate(6); } painter.restore(); }