QPainter - 使用一个时钟项目从头开始看QPainter

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();
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

turbolove

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

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

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

打赏作者

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

抵扣说明:

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

余额充值