时间滑动选择器实现
组件说明:
- 本组件命名为时间滑动选择器,主要运用于 arm 平台下的触摸屏上,虽然 QT 自带有时间选择组件,但是对触摸屏的使用并不友好,为了提升项目界面的交互性,于是就有了时间滑动选择器,主要靠 QWidget 的 paintEvent 事件来完成的。
- 因为需求,本组件仅做了鼠标左键的拖滚事件,未做滑轮事件处理,有需要的可以自行添加。
- 代码非搬运,文章是原创。
时间滑动选择器的效果如下方 GIF 图片所示:
废话不多说,直接上代码:
//slidingbox.h
#ifndef SLIDINGBOX_H
#define SLIDINGBOX_H
#include <QWidget>
class QPropertyAnimation;
class SlidingBox : public QWidget {
Q_OBJECT
Q_PROPERTY(int offset READ getOffset WRITE setOffset)
public:
explicit SlidingBox(QWidget *parent = nullptr);
void setRange(int min, int max);
void setCurrentVal(int val);
int curentVal();
signals:
void valueChanged(int val);
protected:
void paintEvent(QPaintEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
private:
int source_y,
offset_y,
max_val,
min_val,
current_val,
val_len;
bool is_press;
QPropertyAnimation *prop_amt;
void drawText(QPainter *paint, QString text, int offset);
void setOffset(int val);
int getOffset();
};
#endif // SLIDINGBOX_H
//slidingbox.cpp
#include "slidingbox.h"
#include <QMouseEvent>
#include <QPainter>
#include <QPropertyAnimation>
#define WID_UNIT 20
#define FONT_SIZE 27
#define HIGT_UNIT 40
SlidingBox::SlidingBox(QWidget *parent) :
QWidget(parent),
source_y(0),
offset_y(0),
max_val(0),
min_val(0),
current_val(0),
val_len(2),
is_press(false) {
prop_amt = new QPropertyAnimation(this, "offset");
prop_amt->setDuration(300);
prop_amt->setEasingCurve(QEasingCurve::OutQuad);
setMinimumWidth(val_len * WID_UNIT);
}
void SlidingBox::setRange(int min, int max) {
min_val = min;
max_val = max;
current_val = current_val > max ? max : current_val;
current_val = current_val < min ? min : current_val;
val_len = 0;
while(max > 0) {
max /= 10;
val_len ++;
}
setMinimumWidth(val_len * WID_UNIT);
update();//repaint();
}
void SlidingBox::setCurrentVal(int val) {
current_val = val;
current_val = current_val > max_val ? max_val : current_val;
current_val = current_val < min_val ? min_val : current_val;
update();//repaint();
}
int SlidingBox::curentVal() {
return current_val;
}
void SlidingBox::paintEvent(QPaintEvent *e) {
Q_UNUSED(e);
QPainter paint(this);
paint.setRenderHint(QPainter::Antialiasing, true);
if(current_val - min_val >= 2) {
drawText(&paint, QString::number(current_val - 2), offset_y + HIGT_UNIT * 0);
}
if(current_val > min_val) {
drawText(&paint, QString::number(current_val - 1), offset_y + HIGT_UNIT * 1);
}
drawText(&paint, QString::number(current_val + 0), offset_y + HIGT_UNIT * 2);
if(current_val < max_val) {
drawText(&paint, QString::number(current_val + 1), offset_y + HIGT_UNIT * 3);
}
if(max_val - current_val >= 2) {
drawText(&paint, QString::number(current_val + 2), offset_y + HIGT_UNIT * 4);
}
paint.setPen(QColor(121, 121, 121, 232));
paint.drawLine(QLineF(0, HIGT_UNIT * 2, val_len * WID_UNIT, HIGT_UNIT * 2));
paint.drawLine(QLineF(0, HIGT_UNIT * 3, val_len * WID_UNIT, HIGT_UNIT * 3));
}
void SlidingBox::mouseMoveEvent(QMouseEvent *e) {
// 判别鼠标左键是否被按下
if(is_press) {
// 禁止越界
if((current_val == max_val && e->y() <= source_y) ||
(current_val == min_val && e->y() >= source_y)) { return; }
// 更新鼠标 y 坐标的偏移
offset_y = e->y() - source_y;
// 下滑降值
if(offset_y > HIGT_UNIT) {
source_y += HIGT_UNIT;
offset_y %= HIGT_UNIT;
current_val --;
repaint();
return;
}
// 上滑增值
if(offset_y < -HIGT_UNIT) {
source_y -= HIGT_UNIT;
offset_y %= HIGT_UNIT;
current_val ++;
repaint();
return;
}
update();//repaint();
}
}
void SlidingBox::mousePressEvent(QMouseEvent *e) {
// 判别鼠标左键
if(e->button() == Qt::LeftButton) {
//
prop_amt->stop();
// 记录鼠标起始 y 坐标
source_y = e->y();
// 按下鼠标左键
is_press = true;
}
}
void SlidingBox::mouseReleaseEvent(QMouseEvent *e) {
// 判别鼠标左键
if(e->button() == Qt::LeftButton) {
if(is_press) {
if(offset_y > HIGT_UNIT / 2) {
current_val - 1 < min_val ? prop_amt->setStartValue(offset_y) :
prop_amt->setStartValue(HIGT_UNIT / 2 - offset_y);
prop_amt->setEndValue(0);
current_val = current_val - 1 < min_val ? min_val : current_val - 1;
}
else if(offset_y > -HIGT_UNIT / 2) {
prop_amt->setStartValue(offset_y);
prop_amt->setEndValue(0);
}
else {
current_val + 1 > max_val ? prop_amt->setStartValue(offset_y) :
prop_amt->setStartValue(- offset_y - HIGT_UNIT / 2);
prop_amt->setEndValue(0);
current_val = current_val + 1 > max_val ? max_val : current_val + 1;
}
emit valueChanged(current_val);
prop_amt->start();
}
// 释放鼠标左键
is_press = false;
}
}
void SlidingBox::drawText(QPainter *paint, QString text, int offset) {
int size = FONT_SIZE - qAbs(offset - HIGT_UNIT * 2) / 10,
transparent = 255 - 255 * qAbs(offset - HIGT_UNIT * 2) * 2 / HIGT_UNIT / 5;
transparent = transparent < 0 ? 0 : transparent;
QFont font;
font.setPixelSize(size);
paint->setFont(font);
paint->setPen(QColor(0, 0, 0, transparent));
while(text.length() < val_len) {
text = QString("0%1").arg(text);
}
paint->drawText(QRectF(0, offset, val_len * WID_UNIT, HIGT_UNIT), Qt::AlignCenter, text);
}
void SlidingBox::setOffset(int val) {
offset_y = val;
repaint();
}
int SlidingBox::getOffset() {
return offset_y;
}
//timeselector.h
#ifndef TIMESELECTOR_H
#define TIMESELECTOR_H
#include <QWidget>
class SlidingBox;
class TimeSelector : public QWidget {
Q_OBJECT
public:
explicit TimeSelector(QWidget *parent = nullptr);
~TimeSelector();
void setTime(int y, int m, int d, int h = 0, int min = 0, int s = 0);
void setTime(QString time);
void setTime(QDateTime dt);
QString getTime();
signals:
void timeNew(QString time);
private:
QList<SlidingBox *> sdbl;
QStringList sdb_strl;
QList<int> li_sdb_grange,
li_md;
void resetFebDays(int year, int month);
SlidingBox *sdb(QString sdb_str);
void showCurrentTime();
};
#endif // TIMESELECTOR_H
//timeselector.cpp
#include "timeselector.h"
#include "slidingbox.h"
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QLabel>
#include <QPushButton>
#include <QDateTime>
TimeSelector::TimeSelector(QWidget *parent) :
QWidget(parent) {
sdb_strl << "年" << "月" << "日" << "时" << "分" << "秒";
li_sdb_grange << 2000 << 2500 << 1 << 12 << 1 << 31 << 0 << 23 << 0 << 59 << 0 << 59;
li_md << 31 << 28 << 31 << 30 << 31 << 30 << 31 << 31 << 30 << 31 << 30 << 31;
QHBoxLayout *hup_layout = new QHBoxLayout;
hup_layout->setSpacing(5);
hup_layout->setContentsMargins(0, 0, 0, 0);
hup_layout->addStretch();
for(int i = 0; i < sdb_strl.length(); i ++) {
SlidingBox *sdb = new SlidingBox;
sdb->setRange(li_sdb_grange.at(i * 2), li_sdb_grange.at(i * 2 + 1));
hup_layout->addWidget(sdb);
sdbl << sdb;
QLabel *l_tip = new QLabel(sdb_strl.at(i));
l_tip->setStyleSheet("font-size: 23px;");
hup_layout->addWidget(l_tip);
}
hup_layout->addStretch();
QWidget *w_up = new QWidget;
w_up->setLayout(hup_layout);
w_up->setStyleSheet("min-height: 200px;max-height: 200px;");
QHBoxLayout *hdown_layout = new QHBoxLayout;
hdown_layout->setSpacing(5);
hdown_layout->setContentsMargins(0, 0, 0, 0);
QPushButton *btn_now = new QPushButton("现在");
btn_now->setStyleSheet("min-height: 34px;max-height: 34px;font-size: 19px;");
hdown_layout->addWidget(btn_now);
hdown_layout->addStretch();
QPushButton *btn_cancel = new QPushButton("取消");
btn_cancel->setStyleSheet("min-height: 34px;max-height: 34px;font-size: 19px;");
hdown_layout->addWidget(btn_cancel);
QPushButton *btn_sure = new QPushButton("确定");
btn_sure->setStyleSheet("min-height: 34px;max-height: 34px;font-size: 19px;");
hdown_layout->addWidget(btn_sure);
QWidget *w_down = new QWidget;
w_down->setLayout(hdown_layout);
QVBoxLayout *v_layout = new QVBoxLayout;
v_layout->setSpacing(0);
v_layout->addWidget(w_up);
v_layout->addWidget(w_down);
setLayout(v_layout);
setMinimumSize(500, 280);
setMaximumSize(500, 280);
setWindowFlags(Qt::FramelessWindowHint);
setStyleSheet("background-color: #fff;");
resetFebDays(li_sdb_grange.at(sdb_strl.indexOf("年") * 2),
li_sdb_grange.at(sdb_strl.indexOf("月") * 2));
connect(sdb("年"), &SlidingBox::valueChanged, [=](int year) {
resetFebDays(year, sdb("月")->curentVal());
});
connect(sdb("月"), &SlidingBox::valueChanged, [=](int month) {
sdb("日")->setRange(1, li_md.at(month - 1));
});
connect(btn_now, &QPushButton::clicked, [=] {
showCurrentTime();
});
connect(btn_cancel, &QPushButton::clicked, [=] {
close();
});
connect(btn_sure, &QPushButton::clicked, [=] {
emit timeNew(getTime());
close();
});
setTime(2024, 8, 1, 15, 4, 32);
// setTime("2024-08-01 20:47:15");
}
TimeSelector::~TimeSelector() {
}
void TimeSelector::setTime(int y, int m, int d, int h, int min, int s) {
sdb("年")->setCurrentVal(y);
sdb("月")->setCurrentVal(m);
sdb("日")->setCurrentVal(d);
sdb("时")->setCurrentVal(h);
sdb("分")->setCurrentVal(min);
sdb("秒")->setCurrentVal(s);
}
void TimeSelector::setTime(QString time) {
QRegExp regexp("^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$");
if(regexp.exactMatch(time)) {
QDateTime dt = QDateTime::fromString(time, "yyyy-MM-dd hh:mm:ss");
setTime(dt.date().year(), dt.date().month(), dt.date().day(),
dt.time().hour(), dt.time().minute(), dt.time().second());
}
}
void TimeSelector::setTime(QDateTime dt) {
if(!dt.isValid()) {
setTime(dt.date().year(), dt.date().month(), dt.date().day(),
dt.time().hour(), dt.time().minute(), dt.time().second());
}
}
QString TimeSelector::getTime() {
QString y = QString::number(sdb("年")->curentVal()),
m = sdb("月")->curentVal() < 10 ? "0" + QString::number(sdb("月")->curentVal()) :
QString::number(sdb("月")->curentVal()),
d = sdb("日")->curentVal() < 10 ? "0" + QString::number(sdb("日")->curentVal()) :
QString::number(sdb("日")->curentVal()),
h = sdb("时")->curentVal() < 10 ? "0" + QString::number(sdb("时")->curentVal()) :
QString::number(sdb("时")->curentVal()),
min = sdb("分")->curentVal() < 10 ? "0" + QString::number(sdb("分")->curentVal()) :
QString::number(sdb("分")->curentVal()),
s = sdb("秒")->curentVal() < 10 ? "0" + QString::number(sdb("秒")->curentVal()) :
QString::number(sdb("秒")->curentVal());
return QString("%1-%2-%3 %4:%5:%6").arg(y, m, d, h, min, s);
}
void TimeSelector::resetFebDays(int year, int month) {
if(month == 2) {
sdb("日")->setRange(1, ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) ? 29 : 28);
}
else {
li_md.replace(1, ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) ? 29 : 28);
}
}
SlidingBox *TimeSelector::sdb(QString sdb_str) {
return sdbl.at(sdb_strl.indexOf(sdb_str));
}
void TimeSelector::showCurrentTime() {
QDateTime dt = QDateTime::currentDateTime();
setTime(dt.date().year(), dt.date().month(), dt.date().day(),
dt.time().hour(), dt.time().minute(), dt.time().second());
}
使用参考:
TimeSelector *tsor = new TimeSelector;
tsor->show();
connect(tsor, &TimeSelector::timeNew, [=](QString time) {
//
});
代码注释就不想加了,太麻烦了,确实想学习逻辑的话,可以参考下面的文章,我也是参考别人写的来实现的,逻辑大致相同,只是代码实现不一样。
借鉴文章:
《QT自定义控件–滑动选择器》
《QT日期时间选择器自定义滚动选择》
《Qt滚动日期选择器》
学习分享,一起成长!以上为小编的经验分享,若存在不当之处,请批评指正!
欢迎加入学习交流群:嵌入式技术交流群