效果图录屏
桌面时钟
画盘原理
通过效果视频可以看到一直在转动的是秒盘,其他盘如分盘只有在秒盘转慢一圈时才会转一下,那时盘只会在分盘转一圈时转一下,所以为了秒盘转动的流畅性,我把除秒盘之外的其他盘预先画在一个画布上(QPixmap),那秒盘转圈时我只用每次在预先画好其他盘的画布上画秒盘即可,当秒盘转一圈时,再更新这个底层的画布。
先来获取当前的系统时间,这个是每更新一次画面就会去获取一次这个时间。
QDateTime current_date_time = QDateTime::currentDateTime();
QString current_tim = current_date_time.toString("zzz");
QString current_date = current_date_time.toString("yyyy-MM-dd-ddd");
current_tim = current_date_time.toString("ss");
int sec = current_tim.toInt();
current_tim = current_date_time.toString("zzz");
int ms = current_tim.toInt();
预设一下码盘的内容:
QStringList second;
second << "零"<< "一"<< "二"<< "三"<< "四"<< "五"<< "六"<< "七"<< "八"<< "九" << "十"<< "十一"<< "十二"
<< "十三"<< "十四"<< "十五" << "十六" << "十七"<< "十八" << "十九" << "二十" << "二十一" << "二十二"
<< "二十三" << "二十四" << "二十五" << "二十六"<< "二十七"<< "二十八" << "二十九" << "三十"<< "三十一"
<< "三十二" << "三十三" << "三十四" << "三十五"<< "三十六" << "三十七"<< "三十八"<< "三十九"<< "四十"
<< "四十一" << "四十二"<< "四十三" << "四十四" << "四十五" << "四十六" << "四十七"<< "四十八" << "四十九"
<< "五十" << "五十一"<<"五十二"<< "五十三"<<"五十四"<<"五十五"<<"五十六"<< "五十七"<< "五十八"<< "五十九";
设置一下内容的字体格式:
QFont font("黑体", 9, QFont::Black, false);
font.setBold(true);
//设置字符间的间距
font.setLetterSpacing(QFont::AbsoluteSpacing, 5);
我们先创建一个底层的画布,这个画布会预先画好除秒盘之外的码盘:
QPixmap pix; //总画布
pix = QPixmap(UI_WIDTH, UI_HIGH);//这个是画布的长和宽
然后来在这个画布上画秒盘之外的盘,但是这写盘只会在分钟变化时重新画一次:
current_tim = current_date_time.toString("mm");
int min = current_tim.toInt();
/* 当分改变时才重新画底布 */
if (minute != min) {
//使用字体
QPixmap tmp_pix = QPixmap(UI_WIDTH, UI_HIGH); //临时画布,先在临时画布上画,防止页面卡顿
QPainter pain(&tmp_pix);
pain.setFont(font);
pain.setPen(Qt::blue);
minute = min;
for (uint8_t i = 0; i < second.count(); i++) {
pain.save();
pain.translate(min_x[i], min_y[i]);
pain.rotate(-i * 6);
pain.translate(-min_x[i], -min_y[i]);
if (i == 0) {
pain.setOpacity(1);
} else {
pain.setOpacity(0.5);
}
QString str = second[(min + i) % 60] + "分";
pain.drawText(min_x[i], min_y[i], str);
pain.restore();
}
pix = tmp_pix;//画完之后就把临时画布上的内容转移到底层画布
}
以上是除秒盘之外的更新方法和画法,具体实现我是分盘、时盘、日盘、月盘一起画的,这里代码太长,没有贴出来
我再来解释下画分盘代码是的min_x和min_y变量的由来,这个是把分盘的没一个成员放到合适的位置上组成一个圆形:
for (uint8_t i = 0; i < 60; i++) {
min_x[i] = cos((-i * 0.104719753)) * (UI_MID_WIDTH - ONE_CIRCLE_WIDTH * 2) + UI_MID_WIDTH;
min_y[i] = sin(-i * 0.104719753) * (UI_MID_HIGH - ONE_CIRCLE_WIDTH * 2) + UI_MID_HIGH;
}
UI_MID_WIDTH 和 UI_MID_HIGH 这两个宏定义是整个画布长宽的中点,组合起来就是整个画布的中心坐标
ONE_CIRCLE_WIDTH 是预留给每个盘的字体的宽度,你可以认为有一个内环和一个外环,这个内环和外环之间的距离就是这个宽度。
0.104719753这个是计算倾斜角度的,为了让每个成员在各自的位置上有一个倾斜角,使得每个成员都的位置都指向圆心。
时盘、日盘、月盘都会计算这个,就是为了能让每个盘放在合适的位置上。
那现在我们来画秒盘:
/* 画秒盘 */
QPixmap sec_pix = pix; //临时画布 将底层画布铺在临时画布上
QPainter pain_sec(&sec_pix);
pain_sec.setFont(font);
pain_sec.setPen(Qt::red);
for (uint8_t i = 0; i < second.count(); i++) {
//使用字体
int32_t x = 0;
int32_t y = 0;
x = cos((-i * 0.104719753) - (0.104719753 - ms * 0.000104719753)) * (UI_MID_WIDTH - ONE_CIRCLE_WIDTH) + UI_MID_WIDTH;//这里为了让旋转动画更顺滑 根据ms数据来进行调整角度
y = sin(-i * 0.104719753 - (0.104719753 - ms * 0.000104719753)) * (UI_MID_HIGH - ONE_CIRCLE_WIDTH) + UI_MID_HIGH;
pain_sec.save();
pain_sec.translate(x, y);
pain_sec.rotate((-i) * 6);//旋转角度
pain_sec.translate(-x, -y);//回到原点 这个是必要的 不然画笔会乱跑
QString str = second[(sec + i) % 60] + "秒";
float opacity = 0.3;
if (i == 0) {
opacity = ((ms * 0.001)) < 0.3 ? 0.3 : ((ms * 0.001));
pain_sec.setOpacity(opacity);
} else if (i == (second.count() - 1)) {
opacity = (1 - (ms * 0.001)) < 0.3 ? 0.3 : (1 - (ms * 0.001));
pain_sec.setOpacity(opacity);
} else {
pain_sec.setOpacity(0.3);
}
pain_sec.drawText(x, y, str);
pain_sec.restore();
}
然后将我们画的内容显示在窗口上
QSize picSize(window_width, window_high);
QPixmap tmp_sec_pix = sec_pix.scaled(picSize);
QPainter painter_play(this);
painter_play.drawPixmap(0, 0, tmp_sec_pix);
那如果想让这个盘动起来,就需要持续不断的刷新页面:
QTimer* pTimer = new QTimer(this);
pTimer->setTimerType(Qt::PreciseTimer);//精准定时
pTimer->start(50); /* 定时 50ms;每50ms刷新一次界面 */
connect(pTimer, SIGNAL(timeout()), this, SLOT(update()));
那有了显示的旋转在动的盘,那我们想把他放在合适的地方,缩放成合适的大小,甚至开机自启动,那该怎么做呢?
我们先把整个盘变透明并且能够缩放:
this->setAttribute(Qt::WA_TranslucentBackground, true);
this->setWindowFlags(Qt::FramelessWindowHint);
this->setWindowModality(Qt::WindowModal);
那再来移动,为了不影响使用人的正常办公,我使用鼠标中键来触发移动:
void my_time::mousePressEvent(QMouseEvent* event)
{
QMainWindow::mousePressEvent(event);
if (nullptr == event)
return;
if (event->button() == Qt::MidButton) {
isDragging = true;
mouse_startPoint = event->globalPos();
window_top_left_point = this->frameGeometry().topLeft();
}
}
void my_time::mouseMoveEvent(QMouseEvent* event)
{
QMainWindow::mouseMoveEvent(event);
if (nullptr == event) {
return;
}
if (isDragging) {
QPoint distence = event->globalPos() - mouse_startPoint;
this->move(window_top_left_point + distence);
}
}
void my_time::mouseReleaseEvent(QMouseEvent* event)
{
QMainWindow::mouseReleaseEvent(event);
if (nullptr == event)
return;
if (event->button() == Qt::LeftButton)
isDragging = false;
}
那再来个开机自启动,这个记得慎用,因为这个会放在电脑的自启动列表里,每次电脑开机时都会自动启动:
void my_time::setProcessAutoRun(const QString& appPath, bool flag)
{
QSettings settings(AUTO_RUN, QSettings::NativeFormat);
//以程序名称作为注册表中的键,根据键获取对应的值(程序路径)
QFileInfo fInfo(appPath);
QString name = fInfo.baseName(); //键-名称
//如果注册表中的路径和当前程序路径不一样,则表示没有设置自启动或本自启动程序已经更换了路径
QString oldPath = settings.value(name).toString(); //获取目前的值-绝对路劲
QString newPath = QDir::toNativeSeparators(appPath); //toNativeSeparators函数将"/"替换为"\"
if (flag) {
if (oldPath != newPath)
settings.setValue(name, newPath);
} else
settings.remove(name);
}
使用这个函数:
setProcessAutoRun(QApplication::applicationFilePath(), 1);
至此功能完善
源代码:https://download.csdn.net/download/SXD_SJJ/87625480