Qt:玩转QPainter后转之时钟(步骤详细、包含源码)

前言

简单了解了QPainter之后当然是要找两个例子练练手啦。

正文

先看效果图

在绘制之前我们要先构思好自己要绘制的对象可以分成几部分,比如我要绘制时钟的话,我可以分成:外边框(圆环或者圆),圆形背景,刻度,时间数字,秒针,分针,时针,中心点;
当然也可以不分这么细,或者分的更细,留下接口以实现更多的效果。

绘制外边框

cpp 复制代码
ClockPainter::ClockPainter(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::ClockPainter)
{
    ui->setupUi(this);
    // 外半径
    outRadius = 108;
    // 内半径
    inRadius = 100;
    crownColorStart = QColor(255,0,0);
    crownColorEnd = QColor(64,156,250);
}

void ClockPainter::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    // 设置图形和文本抗锯齿
    painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
    // 将绘制点移动到窗口中心
    painter.translate(width() / 2, height() / 2);
    drawCrown(&painter);
}

void ClockPainter::drawCrown(QPainter *painter)
{
    painter->save();
    // 接下来的绘制不用笔,如果没有填充的话将什么效果都没有
    painter->setPen(Qt::NoPen);
    // 渐变
    QLinearGradient gradient(-outRadius, -outRadius, outRadius, outRadius);
    gradient.setColorAt(0, crownColorStart);
    gradient.setColorAt(1, crownColorEnd);
    painter->setBrush(gradient);
    painter->drawEllipse(-outRadius, -outRadius, outRadius<<1, outRadius<<1);
    painter->restore();
}
效果

绘制背景

cpp 复制代码
ClockPainter::ClockPainter(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::ClockPainter)
{
    ui->setupUi(this);
    // 外半径
    outRadius = 108;
    // 内半径
    inRadius = 100;
    crownColorStart = QColor(255,0,0);
    crownColorEnd = QColor(64,156,250);
}


void ClockPainter::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    // 设置图形和文本抗锯齿
    painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
    // 将绘制点移动到窗口中心
    painter.translate(width() / 2, height() / 2);
    drawCrown(&painter);
    drawBg(&painter);
}

void ClockPainter::drawBg(QPainter *painter)
{
    painter->save();
    painter->setBrush(Qt::black);
    painter->drawEllipse(-inRadius, -inRadius, inRadius<<1, inRadius<<1);
    painter->restore();
}
效果

绘制刻度

cpp 复制代码
void ClockPainter::drawScale(QPainter *painter)
{
    painter->save();
    QPen pen;
    pen.setColor(Qt::white);

    for (int i = 0; i < 60; ++i) {
        // 长的和短的粗细不一样
        if (i % 5 == 0) {
            pen.setWidth(2);
            pen.setCapStyle(Qt::RoundCap);
            painter->setPen(pen);
            painter->drawLine(inRadius - 8, 0, inRadius, 0);
        } else {
            pen.setWidth(1);
            painter->setPen(pen);
            painter->drawLine(inRadius - 4, 0, inRadius, 0);
        }
        painter->rotate(6);
    }
    painter->restore();
}


void ClockPainter::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    // 设置图形和文本抗锯齿
    painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
    // 将绘制点移动到窗口中心
    painter.translate(width() / 2, height() / 2);
    drawCrown(&painter);
    drawBg(&painter);
    drawScale(&painter);
}
效果

绘制时间数字

cpp 复制代码
QRectF ClockPainter::textRectF(qreal radius, int pointSize, qreal angle)
{
    // 用于计算每个数字所在的矩形
    QRectF rectF;
    rectF.setX(radius*qCos(angle*M_PI/180.0) - pointSize*2);
    rectF.setY(radius*qSin(angle*M_PI/180.0) - pointSize/2.0);
    rectF.setWidth(pointSize*4);
    rectF.setHeight(pointSize*1.5);
    return rectF;
}

void ClockPainter::drawScaleNum(QPainter *painter)
{
    painter->save();
    // 获取当前字体
    QFont font = painter->font();
    // 设置粗体
    font.setBold(true);
    painter->setFont(font);
    int pointSize = font.pointSize();
    painter->setPen(Qt::white);
    int nHour = 0;
    for (int i = 0; i < 12; ++i) {
      nHour = i + 3;
      if (nHour > 12)
        nHour -= 12;
      painter->drawText(textRectF(inRadius*0.8, pointSize, i * 30), Qt::AlignCenter, QString::number(nHour));
    }
    painter->restore();
}

void ClockPainter::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    // 设置图形和文本抗锯齿
    painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
    // 将绘制点移动到窗口中心
    painter.translate(width() / 2, height() / 2);
    drawCrown(&painter);
    drawBg(&painter);
    drawScale(&painter);
    drawScaleNum(&painter);
}
效果

绘制中心点

cpp 复制代码
void ClockPainter::drawCenterDot(QPainter *painter)
{
    painter->save();
    painter->setPen(Qt::NoPen);
    QLinearGradient gradient(-10, -10, 10, 10);
    gradient.setColorAt(0, Qt::gray);
    gradient.setColorAt(0.2,Qt::black);
    gradient.setColorAt(1, Qt::gray);

    painter->setBrush(gradient);
    painter->drawEllipse(-5,-5,10,10);
    painter->restore();
}

void ClockPainter::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    // 设置图形和文本抗锯齿
    painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
    // 将绘制点移动到窗口中心
    painter.translate(width() / 2, height() / 2);
    drawCrown(&painter);
    drawBg(&painter);
    drawScale(&painter);
    drawScaleNum(&painter);
    drawCenterDot(&painter);

}
效果

绘制秒针

cpp 复制代码
void ClockPainter::drawSec(QPainter *painter)
{
    painter->save();
    // 接下来的绘制不用笔,如果没有填充的话将什么效果都没有
    painter->setPen(Qt::NoPen);
    // 画一个四边形,两头大,两头小
    painter->setBrush(Qt::gray);
    QPolygon polygon;
    polygon<<QPoint(-3,-2)<<QPoint(70,-1)<<QPoint(70,1)<<QPoint(-3,2);
    painter->drawPolygon(polygon);
    painter->restore();
}

void ClockPainter::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    // 设置图形和文本抗锯齿
    painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
    // 将绘制点移动到窗口中心
    painter.translate(width() / 2, height() / 2);
    drawCrown(&painter);
    drawBg(&painter);
    drawScale(&painter);
    drawScaleNum(&painter);
    drawSec(&painter);
    drawCenterDot(&painter);

}
效果

绘制分针

cpp 复制代码
void ClockPainter::drawMin(QPainter *painter)
{
    painter->save();
    // 接下来的绘制不用笔,如果没有填充的话将什么效果都没有
    painter->setPen(Qt::NoPen);
    // 画一个四边形,两头大,两头小
    painter->setBrush(Qt::gray);
    QPolygon polygon;
    polygon<<QPoint(-6,-3)<<QPoint(63,-2)<<QPoint(63,2)<<QPoint(-6,3);
    painter->drawPolygon(polygon);
    painter->restore();
}

void ClockPainter::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    // 设置图形和文本抗锯齿
    painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
    // 将绘制点移动到窗口中心
    painter.translate(width() / 2, height() / 2);
    drawCrown(&painter);
    drawBg(&painter);
    drawScale(&painter);
    drawScaleNum(&painter);
    drawMin(&painter);
    drawSec(&painter);
    drawCenterDot(&painter);

}
效果

绘制时针

cpp 复制代码
void ClockPainter::drawHour(QPainter *painter)
{
    painter->save();
    // 接下来的绘制不用笔,如果没有填充的话将什么效果都没有
    painter->setPen(Qt::NoPen);
    // 画一个四边形,两头大,两头小
    painter->setBrush(Qt::gray);
    QPolygon polygon;
    polygon<<QPoint(-6,-4)<<QPoint(50,-3)<<QPoint(50,3)<<QPoint(-6,4);
    painter->drawPolygon(polygon);
    painter->restore();
}

void ClockPainter::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    // 设置图形和文本抗锯齿
    painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
    // 将绘制点移动到窗口中心
    painter.translate(width() / 2, height() / 2);
    drawCrown(&painter);
    drawBg(&painter);
    drawScale(&painter);
    drawScaleNum(&painter);
    drawHour(&painter);
    drawMin(&painter);
    drawSec(&painter);
    drawCenterDot(&painter);

}
效果

其实从话指针就很容易看出来,只要会画一个,其余的就改动一点,代码重复性极高

添加定时器使得指针动起来

cpp 复制代码
ClockPainter::ClockPainter(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::ClockPainter)
{
    ui->setupUi(this);
    // 外半径
    outRadius = 108;
    // 内半径
    inRadius = 100;
    crownColorStart = QColor(255,0,0);
    crownColorEnd = QColor(64,156,250);
    // 初始化时间
    QTime time = QTime::currentTime();
    hour = time.hour();
    min = time.minute();
    sec = time.second();
    // 初始化定时器
    timer = new QTimer(this);
    connect(timer, &QTimer::timeout, this, &ClockPainter::updateTime);
    // 设置时间间隔为1s
    timer->start(1000);

}
void ClockPainter::updateTime()
{
    QTime time = QTime::currentTime();
    hour = time.hour();
    // 12小时制
    if (hour > 12) {
        hour -= 12;
    }
    min = time.minute();
    sec = time.second();
    // 更新图像
    update();
}
//关键是要在相应的绘制指针的函数中调用rotate函数,使得绘制的图像旋转
效果

总体代码

这里我定义了指针的颜色,但是我并没有使用,是想留一个接口进行下一步开发。

头文件

cpp 复制代码
#ifndef CLOCKPAINTER_H
#define CLOCKPAINTER_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui { class ClockPainter; }
QT_END_NAMESPACE

class ClockPainter : public QWidget
{
    Q_OBJECT

public:
    ClockPainter(QWidget *parent = nullptr);
    ~ClockPainter();


protected:
    void paintEvent(QPaintEvent *);
    // 画外边框
    void drawCrown(QPainter *painter);
    // 画背景
    void drawBg(QPainter *painter);
    // 画刻度
    void drawScale(QPainter *painter);
    // 画刻度上的数字
    void drawScaleNum(QPainter *painter);
    // 时针
    void drawHour(QPainter *painter);
    // 分针
    void drawMin(QPainter *painter);
    // 秒针
    void drawSec(QPainter *painter);
    // 画中心点
    void drawCenterDot(QPainter *painter);
    // 处理数字位置
    QRectF textRectF(qreal radius, int pointSize, qreal angle);


private slots:
    void updateTime();
private:
    Ui::ClockPainter* ui;
    // 外边框半径
    int outRadius;
    // 内边框半径
    int inRadius;
    // 外边框渐变开始颜色
    QColor crownColorStart;
    // 外边框渐变结束颜色
    QColor crownColorEnd;
    // 背景色
    QColor background;
    // 时钟指针颜色
    QColor pointerHourColor;
    // 分钟指针颜色
    QColor pointerMinColor;
    // 秒钟指针颜色
    QColor pointerSecColor;
    // 定时器绘制
    QTimer *timer;
    // 时分秒
    int hour, min, sec;
};

#endif // CLOCKPAINTER_H

源文件

cpp 复制代码
#include "clockpainter.h"
#include "ui_clockpainter.h"
#include "qpainter.h"
#include "qpen.h"
#include "qcolor.h"
#include "qpolygon.h"
#include "qfont.h"
#include "qtimer.h"
#include "qmath.h"
#include "qrect.h"
#include "qtransform.h"
#include <QTime>
#include <QLinearGradient>
#include "qdebug.h"

ClockPainter::ClockPainter(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::ClockPainter)
{
    ui->setupUi(this);
    // 外半径
    outRadius = 108;
    // 内半径
    inRadius = 100;
    crownColorStart = QColor(255,0,0);
    crownColorEnd = QColor(64,156,250);
    // 初始化时间
    QTime time = QTime::currentTime();
    hour = time.hour();
    min = time.minute();
    sec = time.second();
    // 初始化定时器
    timer = new QTimer(this);
    connect(timer, &QTimer::timeout, this, &ClockPainter::updateTime);
    // 设置时间间隔为1s
    timer->start(1000);

}

ClockPainter::~ClockPainter()
{
    delete ui;
}

void ClockPainter::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    // 设置图形和文本抗锯齿
    painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
    // 将绘制点移动到窗口中心
    painter.translate(width() / 2, height() / 2);
    drawCrown(&painter);
    drawBg(&painter);
    drawScale(&painter);
    drawScaleNum(&painter);
    drawHour(&painter);
    drawMin(&painter);
    drawSec(&painter);
    drawCenterDot(&painter);

}

void ClockPainter::drawCrown(QPainter *painter)
{
    painter->save();
    // 接下来的绘制不用笔,如果没有填充的话将什么效果都没有
    painter->setPen(Qt::NoPen);
    // 渐变
    QLinearGradient gradient(-outRadius, -outRadius, outRadius, outRadius);
    gradient.setColorAt(0, crownColorStart);
    gradient.setColorAt(1, crownColorEnd);
    painter->setBrush(gradient);
    painter->drawEllipse(-outRadius, -outRadius, outRadius<<1, outRadius<<1);
    painter->restore();
}

void ClockPainter::drawBg(QPainter *painter)
{
    painter->save();
    painter->setBrush(Qt::black);
    painter->drawEllipse(-inRadius, -inRadius, inRadius<<1, inRadius<<1);
    painter->restore();
}

void ClockPainter::drawScale(QPainter *painter)
{
    painter->save();
    QPen pen;
    pen.setColor(Qt::white);

    for (int i = 0; i < 60; ++i) {
        // 长的和短的粗细不一样
        if (i % 5 == 0) {
            pen.setWidth(2);
            pen.setCapStyle(Qt::RoundCap);
            painter->setPen(pen);
            painter->drawLine(inRadius - 8, 0, inRadius, 0);
        } else {
            pen.setWidth(1);
            painter->setPen(pen);
            painter->drawLine(inRadius - 4, 0, inRadius, 0);
        }
        painter->rotate(6);
    }
    painter->restore();
}

void ClockPainter::drawScaleNum(QPainter *painter)
{
    painter->save();
    // 获取当前字体
    QFont font = painter->font();
    // 设置粗体
    font.setBold(true);
    painter->setFont(font);
    int pointSize = font.pointSize();
    painter->setPen(Qt::white);
    int nHour = 0;
    for (int i = 0; i < 12; ++i) {
      nHour = i + 3;
      if (nHour > 12)
        nHour -= 12;
      painter->drawText(textRectF(inRadius*0.8, pointSize, i * 30), Qt::AlignCenter, QString::number(nHour));
    }
    painter->restore();
}

void ClockPainter::drawHour(QPainter *painter)
{
    painter->save();
    // 接下来的绘制不用笔,如果没有填充的话将什么效果都没有
    painter->setPen(Qt::NoPen);
    // 画一个四边形,两头大,两头小
    painter->setBrush(Qt::gray);
    QPolygon polygon;
    polygon<<QPoint(-6,-4)<<QPoint(50,-3)<<QPoint(50,3)<<QPoint(-6,4);
    // 旋转,角度需要减去90,因为不减的话,是从12开始计算角度
    painter->rotate(30.0 * ((hour + min / 60.0)) - 90);
    painter->drawPolygon(polygon);
    painter->restore();
}

void ClockPainter::drawMin(QPainter *painter)
{
    painter->save();
    // 接下来的绘制不用笔,如果没有填充的话将什么效果都没有
    painter->setPen(Qt::NoPen);
    // 画一个四边形,两头大,两头小
    painter->setBrush(Qt::gray);
    QPolygon polygon;
    polygon<<QPoint(-6,-3)<<QPoint(63,-2)<<QPoint(63,2)<<QPoint(-6,3);
    // 旋转,角度需要减去90,因为不减的话,是从12开始计算角度
    painter->rotate(6.0 * (min + sec / 60.0) - 90);
    painter->drawPolygon(polygon);
    painter->restore();
}

void ClockPainter::drawSec(QPainter *painter)
{
    painter->save();
    // 接下来的绘制不用笔,如果没有填充的话将什么效果都没有
    painter->setPen(Qt::NoPen);
    // 画一个四边形,两头大,两头小
    painter->setBrush(Qt::gray);
    QPolygon polygon;
    polygon<<QPoint(-3,-2)<<QPoint(70,-1)<<QPoint(70,1)<<QPoint(-3,2);
    // 旋转,角度需要减去90,因为不减的话,是从12开始计算角度
    painter->rotate(6.0 * sec - 90);
    painter->drawPolygon(polygon);
    painter->restore();
}

void ClockPainter::drawCenterDot(QPainter *painter)
{
    painter->save();
    painter->setPen(Qt::NoPen);
    QLinearGradient gradient(-10, -10, 10, 10);
    gradient.setColorAt(0, Qt::gray);
    gradient.setColorAt(0.2,Qt::black);
    gradient.setColorAt(1, Qt::gray);

    painter->setBrush(gradient);
    painter->drawEllipse(-5,-5,10,10);
    painter->restore();
}

QRectF ClockPainter::textRectF(qreal radius, int pointSize, qreal angle)
{
    // 用于计算每个数字所在的矩形
    QRectF rectF;
    rectF.setX(radius*qCos(angle*M_PI/180.0) - pointSize*2);
    rectF.setY(radius*qSin(angle*M_PI/180.0) - pointSize/2.0);
    rectF.setWidth(pointSize*4);
    rectF.setHeight(pointSize*1.5);
    return rectF;
}

void ClockPainter::updateTime()
{
    QTime time = QTime::currentTime();
    hour = time.hour();
    // 12小时制
    if (hour > 12) {
        hour -= 12;
    }
    min = time.minute();
    sec = time.second();
    // 更新图像
    update();
}
相关推荐
尘浮生5 分钟前
Java项目实战II基于微信小程序的电影院买票选座系统(开发文档+数据库+源码)
java·开发语言·数据库·微信小程序·小程序·maven·intellij-idea
hopetomorrow19 分钟前
学习路之PHP--使用GROUP BY 发生错误 SELECT list is not in GROUP BY clause .......... 解决
开发语言·学习·php
小牛itbull29 分钟前
ReactPress vs VuePress vs WordPress
开发语言·javascript·reactpress
请叫我欧皇i37 分钟前
html本地离线引入vant和vue2(详细步骤)
开发语言·前端·javascript
闲暇部落40 分钟前
‌Kotlin中的?.和!!主要区别
android·开发语言·kotlin
GIS瞧葩菜1 小时前
局部修改3dtiles子模型的位置。
开发语言·javascript·ecmascript
chnming19871 小时前
STL关联式容器之set
开发语言·c++
熬夜学编程的小王1 小时前
【C++篇】深度解析 C++ List 容器:底层设计与实现揭秘
开发语言·数据结构·c++·stl·list
GIS 数据栈1 小时前
每日一书 《基于ArcGIS的Python编程秘笈》
开发语言·python·arcgis
Mr.131 小时前
什么是 C++ 中的初始化列表?它的作用是什么?初始化列表和在构造函数体内赋值有什么区别?
开发语言·c++