通俗易懂玩Qt:时间滑动选择器实现(内附主要源码)

时间滑动选择器实现

组件说明:

  1. 本组件命名为时间滑动选择器,主要运用于 arm 平台下的触摸屏上,虽然 QT 自带有时间选择组件,但是对触摸屏的使用并不友好,为了提升项目界面的交互性,于是就有了时间滑动选择器,主要靠 QWidget 的 paintEvent 事件来完成的。
  2. 因为需求,本组件仅做了鼠标左键的拖滚事件,未做滑轮事件处理,有需要的可以自行添加。
  3. 代码非搬运,文章是原创。

时间滑动选择器的效果如下方 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滚动日期选择器》

学习分享,一起成长!以上为小编的经验分享,若存在不当之处,请批评指正!
欢迎加入学习交流群:嵌入式技术交流群

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

混子还在路上

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

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

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

打赏作者

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

抵扣说明:

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

余额充值