Qt ScrollBar 实现滚动屏时间选择器

一直觉得Qt中的时间选择的控件不能够满足一些特定的需求,比如说通过鼠标滚动或者拖动(手机中时间选择器)实现时间的改变,最近研究了下怎么实现这个需求。

首先我们先看下实现效果,毕竟展示效果具有很强的吸引力:
在这里插入图片描述

做这个最主要的难点是怎么控制鼠标的偏移量,其他的都比较直接,实现paintEvent函数将信息绘画在界面上就行了。

我们定义了一个类去实现,这样以后就能够直接拿来用了,我们先看下类头文件的定义
包含头文件

#include <QMouseEvent>
#include <QColor>
#include <QPainter>
#include <QTime>
#include<QDate>
class ScrollBar : public QWidget
{
    Q_OBJECT
public:
	/**定义类型的目的是滚动屏有垂直和水平之分,为了实现两种模式**/
    enum ScrollType{
      VERTICAL = 1,
      HORIZONTAL
    };
	/**这个只是界面的颜色显示**/
    enum ColorType{
        BACKHROUND = 1,
        LINE,
        CURRENTTEXT,
        DISABLETEXT
    };
public:
    explicit ScrollBar(QWidget *parent = 0, ScrollType nType = VERTICAL);
    ~ScrollBar();
public:
	/**
	 * @brief      设置界面显示的颜色
     *
     * @param[in]  color		颜色      
     * @param[in]  ColorType    颜色的类型,区分颜色的    
	**/
    void setColor(QColor &pColor, ColorType nType = CURRENTTEXT);
	
	/**
	 * @brief      获取当前值
     *
     * @return     int
	 * @retval     当前在界面中间的值
	**/
    inline int getValue() { return m_nCurrentValue;}
	
	/**
	 * @brief      设置当前值
     *
	 * @param[in]  int	当前值
	**/
    inline void setValue(int nValue) { m_nCurrentValue = nValue;}
	
	/**
	 * @brief      设置步长
     *
	 * @param[in]  int	步长 界面相邻显示的数值间隔 默认为 1 
	**/
    inline void setStep(int nStep) { m_nStep = nStep;}
	
	/**
	 * @brief      设置界面显示的个数
     *
	 * @param[in]  int	页面中显示多少个数字
	**/
    inline void setDevice(int nDevice) {m_nDevice = nDevice;}
	
	/**
	 * @brief      设置滚动屏的类型 默认为垂直
     *
	 * @param[in]  ScrollType	滚动屏的类型
	**/
	inline void setScrollType(ScrollType nType) {m_nType = nType;}
	
	/**
	 * @brief      设置滚动范围
     *
	 * @param[in]  int	最小值
	 * @param[in]  int	最大值
	**/
	inline void setRang(int nMin, int nMax)
	{
		m_nMin = nMin;
		m_nMax = nMax;
		m_nCurrentValue = m_nCurrentValue > m_nMax ? m_nMax : m_nCurrentValue;
    	m_nCurrentValue = m_nCurrentValue < m_nMin ? m_nMin : m_nCurrentValue;
	}
/**主要是鼠标事件的重实现,其实我们用的最多的应该是滚轮,因此也实现轮子事件**/
protected:
    void wheelEvent(QWheelEvent* event);
    void mousePressEvent(QMouseEvent* event);
    void mouseMoveEvent(QMouseEvent* event);
    void mouseReleaseEvent(QMouseEvent* event);
    void paintEvent(QPaintEvent* event);
	
private:
	/**
	 * @brief      绘制背景
     *
	 * @param[in]  QPainter	QPainter指针
	**/
    void paintBackground(QPainter* pPainter);
	
	/**
	 * @brief      绘制线条
     *
	 * @param[in]  QPainter	QPainter指针
	**/
    void paintLine(QPainter* pPainter);
	
	/**
	 * @brief      绘制字体
     *
	 * @param[in]  QPainter	QPainter指针
	 * @param[in]  int	需要绘制的数值
	 * @param[in]  int	绘制的偏移量 相对于中心便宜多少量绘制数值
	 * @param[in]  int	字体的大小
	**/
    void paintText(QPainter* pPainter, int nValue, int nOffSet, int nFontSize);
signals:
	/**
	 * @brief      当前值改变时发给上层的信号
     *
	 * @param[in]  int	当前数值
	 * @param[in]  QWidget* 该滚动条的父类Widget,目的是为了能够准确的找到是属于哪个对象
	**/
    void signal_currentValueChange(int nValue, QWidget* pWidget);
private:
    int m_nCurrentValue;  	
    int m_nOffSet;        	//偏离值
    int m_nMax;           	//滚动的最大值
    int m_nMin;           	//滚动的最小值
    int m_nMousePos;		//鼠标点击的位置
    int m_nDevice;      	//显示的数量
    int m_nStep;        	//滚动的步长
    ScrollType  m_nType;    //垂直还是水平
    QColor  m_cBackground;	//背景颜色
    QColor  m_cCurrentText;	//当前值颜色
    QColor  m_cDisableText;	//其他字体颜色
    QColor  m_cLine;		//线条颜色
};

下面是该类的构造函数

ScrollBar::ScrollBar(QWidget *parent, ScrollType nType) : QWidget(parent), m_nType(nType)
    , m_nCurrentValue(0)
    , m_nOffSet(0)
    , m_nMax(0)
    , m_nMin(0)
    , m_nMousePos(0)
    , m_nDevice(5)
    , m_nStep(1)
{
    this->setFixedSize(parent->size());
}

ScrollBar::~ScrollBar()
{

}

设置界面颜色,这个可以由个人喜好决定

void ScrollBar::setColor(QColor &pColor, ColorType nType)
{
    switch (nType)
    {
    case ...:
    {
        ** = pColor;
        break;
    }
	...
    default:
        break;
    }
}

实现界面数值变换的方式有两种,一种是鼠标的滚轮滚动,另一种是鼠标按下并且拖动,两种方式实现的效果是相同的,目的都是为了得到偏移量,但就省力来说,滚动滚轮方便的岂是一星半点,那我们就先看看滚轮的实现。

void ScrollBar::wheelEvent(QWheelEvent *event)
{
	/**滚动的角度,*8就是鼠标滚动的距离**/
    int nDegrees = event->delta() / 8;
	/**滚动的步数,*15就是鼠标滚动的角度**/
    int nSteps = nDegrees / 15;
    int nTarget = m_nType == ScrollType::VERTICAL ? this->height() : this->width();
    m_nOffSet = nTarget / m_nDevice * nSteps;
    update();
}

没错,你没有看错,就是这个简单的几行代码就能够得到鼠标的偏移量。m_nOffSet有正有负,正负表示偏移的方向。

鼠标滚轮的实现已经看完了,我们再看看鼠标拖动的方式。

首先鼠标在按下的时候我们要记录点的坐标值,因为相对于垂直滚动来说,鼠标左边的x值无意义,同理,对水平滚动y值也无意义,因此我们通过滚动条的类型选择对我们有用的数值

void ScrollBar::mousePressEvent(QMouseEvent *event)
{
    m_nMousePos = m_nType == ScrollType::VERTICAL ? event->pos().y() : event->pos().x();
    update();
}

接下来就是鼠标的移动事件,在移动的过程中我们要时刻计算偏移量,以达到界面不间断的目的。

void ScrollBar::mouseMoveEvent(QMouseEvent *event)
{
    int nMouserPos = m_nType == ScrollType::VERTICAL ? event->pos().y() : event->pos().x();
    /**判断当前值的大小,如果为范围的极限值则返回**/
    if(m_nCurrentValue == m_nMin && nMouserPos >= m_nMousePos ||
       m_nCurrentValue == m_nMax && nMouserPos <= m_nMousePos)
    {
        return;
    }
    int nTarget = m_nType == ScrollType::VERTICAL ? this->height() : this->width();
    int nOffSet = nMouserPos - m_nMousePos;

	/**判断鼠标移动的距离是否大于最小偏移量 如果大于偏移量 则将偏移量置位最小偏移量 目的是避免界面出现跨越显示**/
	if(nOffSet > (nTarget / m_nDevice))	/**(nTarget / m_nDevice) 为一次偏移的最小值 也就是一个字体的显示的大小边界值**/
    {
        nOffSet = nTarget / m_nDevice;
    }
    else if(nOffSet < -nTarget / m_nDevice)
    {
        nOffSet = -nTarget / m_nDevice;
    }
	
	/**nOffSet的正负代表便宜的方向**/
    m_nOffSet = nOffSet;
    update();
}

鼠标被松开之后,会存在几种比较极端的情况,最后一次偏移量和显示界面大小一半的比较(大于、等于、小于)。出现这种情况的只有最后一次偏移,因为在鼠标移动的过程中已经计算了所有的偏移并显示在界面上了。

void ScrollBar::mouseReleaseEvent(QMouseEvent *event)
{
    int nTarget = m_nType == ScrollType::VERTICAL ? this->height() : this->width();
    int nOffSet = m_nOffSet;
    /**计算鼠标的偏移量,根据显示字体的控件大小的一半来确定该偏移到那个值(正负表示偏移的方向)**/
    int nJudge = nOffSet < 0 ? -(nTarget / (m_nDevice * 2)) : nTarget / (m_nDevice * 2);
    if(nOffSet < 0)
    {
        if(nOffSet < nJudge)
        {
            m_nOffSet = 0;
            goto UPDATE;
        }
        m_nOffSet = -nTarget / m_nDevice;
        goto UPDATE;
    }
    if (nOffSet < nJudge)
    {
        m_nOffSet = 0;
        goto UPDATE;
    }
    m_nOffSet = nTarget / m_nDevice;

UPDATE:
    update();
}

现在偏移量的两种计算都已经介绍完了,接下来就是界面的绘画了。毕竟对大多数程序员来说,查找资料的过程中总是会先看看技术实现的效果能否满足自己的需求,再决定代码需不需要研究。

void ScrollBar::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
    if(m_nMin == m_nMax)
    {
        return;
    }
	
	/**在绘制界面之前我们需要通过偏移量来计算当前值,毕竟我们的目的是得到值,而不是看着界面做沉思状**/
    int nTarget = m_nType == ScrollType::VERTICAL ? this->height() : this->width();
    int nOffSet = m_nOffSet;
    if(nOffSet >= (nTarget / m_nDevice) && m_nCurrentValue > m_nMin)
    {
        m_nMousePos += nTarget / m_nDevice;
        nOffSet -= nTarget / m_nDevice;
        this->setValue(m_nCurrentValue - m_nStep);
        goto PAINTE;
    }
    else if(nOffSet <= -nTarget / m_nDevice && m_nCurrentValue < m_nMax)
    {
        m_nMousePos -= nTarget / m_nDevice;
        nOffSet += nTarget / m_nDevice;
        this->setValue(m_nCurrentValue + m_nStep);
    }

/**当前值设置完成后,进入界面的绘制**/
PAINTE:
    if(getValue() == m_nMax || getValue() == m_nMin)
    {
        nOffSet = 0;
    }
    m_nOffSet = nOffSet;
	
	/**首先绘制背景**/
    paintBackground(&painter);
	/**绘制线条 具体的绘制方法后面再说**/
    paintLine(&painter);
    int nFontSize = 14;	/**绘制的字体大小,后面会介绍自东获取字体大小的方法,我在这边定义是由于现实的界面比较大,通过自动获取的字体大小,界面会比较难看**/
	/**绘制当前字体**/
    paintText(&painter, m_nCurrentValue, nOffSet, nFontSize);
	/**绘制两边的字体**/
    for (int nIndex = 1; nIndex <= m_nDevice / 2; ++nIndex)
    {
        nFontSize -= 2;
        if (m_nCurrentValue - m_nStep * nIndex >= m_nMin)
        {
			/**两边字体的偏移量是通过距离计算的**/
            paintText(&painter, m_nCurrentValue - m_nStep * nIndex, nOffSet - nTarget / m_nDevice * nIndex, nFontSize);
        }

        if (m_nCurrentValue + m_nStep * nIndex <= m_nMax)
        {
            paintText(&painter, m_nCurrentValue + m_nStep * nIndex, nOffSet + nTarget / m_nDevice * nIndex, nFontSize);
        }
    }
    /**将父窗口发送目的是能够准确的找到是属于哪个父类(假设界面有多个)**/
    emit signal_currentValueChange(getValue(), this->parentWidget());
}

背景和线条的绘制比较简单,就不多做解释,值得注意的地方是线条的绘制个数和坐标的变化

void ScrollBar::paintBackground(QPainter *pPainter)
{
    pPainter->save();
    pPainter->setPen(Qt::NoPen);
    pPainter->setBrush(m_cBackground);
    pPainter->drawRect(rect());
    pPainter->restore();
}

画线条

void ScrollBar::paintLine(QPainter *pPainter)
{
    int nWidth = this->width();
    int nHeight = this->height();

    pPainter->save();
    pPainter->setBrush(Qt::NoBrush);

    QPen pen = pPainter->pen();
    pen.setWidth(1);
    pen.setColor(m_cLine);
    pen.setCapStyle(Qt::RoundCap);
    pPainter->setPen(pen);

	/**绘制线条需要指定线条的起始坐标, 对于不同类型的滚动屏,坐标也有不同的数值**/

	for(int nIndex = 2; nIndex <= 3; nIndex++)
    {
		/**对于垂直滚动屏来说,线条的Y值是不变的,同理对于水平的滚动屏来说,线条的X值是不变的**/
		int nPosX = m_nType == ScrollType::VERTICAL ? 0 : nWidth / 5 * nIndex;
        int nPosY = m_nType == ScrollType::VERTICAL ? nHeight / 5 * nIndex : 0;
		int nEndPosX = m_nType == ScrollType::VERTICAL ? nHeight : nPosX;
        int nEndPosY = m_nType == ScrollType::VERTICAL ? nPosY : nHeight;

        pPainter->drawLine(nPosX, nPosY, nEndPosX,  nEndPosY);
    }
    pPainter->restore();
}

接下来是绘制数值,数值的绘制分为当前值和两边的临界值,有不同的显示规则

void ScrollBar::paintText(QPainter *pPainter, int nValue, int nOffSet, int nFontSize)
{
    pPainter->save();

    int nWidth = this->width();
    int nHeight = this->height();
	/**下面注释掉的两行是通过整个界面的长(高)来控制字体的大小**/
//    int nTarget = m_nType == ScrollType::VERTICAL ? this->height() : this->width();
//    font.setPixelSize((nTarget - qAbs(nOffSet)) / m_nDevice);
	QFont font = QFont("Helvetica", 5);
    font.setPixelSize(nFontSize);
    QColor nColor = nOffSet == 0 ? m_cCurrentText : m_cDisableText;
    QPen pen = pPainter->pen();
    pen.setColor(nColor);
    pPainter->setPen(pen);
    pPainter->setBrush(Qt::NoBrush);
    pPainter->setFont(font);

    if(m_nType == ScrollType::HORIZONTAL)
    {
        int textWidth = pPainter->fontMetrics().width(nValue);
        int initX = nWidth / 2 + nOffSet - textWidth / 2;
        pPainter->drawText(QRect(initX, 0, 15, nHeight), Qt::AlignCenter, QString::number(nValue));
        //pPainter->drawText(QRect(initX, 0, textWidth, nHeight), Qt::AlignCenter, QString::number(nValue));
        /**有个小问题,当这儿使用下面的语句时,左边的0和1就会不显示,暂时未纠结其具体原因**/
        goto NEXT;
    }

    int textHeight = pPainter->fontMetrics().height();
    int initY = nHeight / 2 + nOffSet - textHeight / 2;
    pPainter->drawText(QRect(0, initY, nWidth, textHeight), Qt::AlignCenter, QString::number(nValue));

NEXT:
    pPainter->restore();
}

上面已经将全部的流程介绍完了,接下来我们介绍下测试用例:
我们这边只做一个界面的说明,其他的照搬就行

	QColor dColorCurrentText("#43464B");
    QColor dColorLine("#0092FF");
    QColor dColorDisableText("#999999");
    QColor dColorBackground("#FFFFFF");

    ScrollBar* pWidgetHour = new ScrollBar(ui->widget);
    pWidgetHour->setRang(0, 23);
    pWidgetHour->setValue(time.hour());
    pWidgetHour->setColor(dColorCurrentText);
    pWidgetHour->setColor(dColorLine, ScrollBar::ColorType::LINE);
    pWidgetHour->setColor(dColorDisableText, ScrollBar::ColorType::DISABLETEXT);
    pWidgetHour->setColor(dColorBackground, ScrollBar::ColorType::BACKHROUND);
    connect(pWidgetHour, SIGNAL(signal_currentValueChange(int,QWidget*)), this, SLOT(slot_timeChange(int,QWidget*)));

    ScrollBar* pWidgeMin = new ScrollBar(ui->widget_2, ScrollBar::ScrollType::HORIZONTAL);
    pWidgeMin->setRang(0, 60);
    pWidgeMin->setValue(time.minute());
    pWidgeMin->setColor(dColorCurrentText);
    pWidgeMin->setColor(dColorLine, ScrollBar::ColorType::LINE);
    pWidgeMin->setColor(dColorDisableText, ScrollBar::ColorType::DISABLETEXT);
    pWidgeMin->setColor(dColorBackground, ScrollBar::ColorType::BACKHROUND);
    connect(pWidgeMin, SIGNAL(signal_currentValueChange(int,QWidget*)), this, SLOT(slot_timeChange(int,QWidget*)));
	

/**接收信号的槽函数**/
void DateTime:: slot_timeChange(int, QWidget* pWidget)
{
	int nHour = pWidget == ui->widget ? nValue : ui->label->text().toInt();
    int nMin = pWidget == ui->widget_2 ? nValue : ui->label_2->text().toInt();
    ui->label->setText(QString::number(nHour));
    ui->label_2->setText(QString::number(nMin));
}

测试用例

  • 13
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 14
    评论
el-scrollbar 是 element-ui 中的一个组件,可以用于实现在一个容器内部的无限滚动效果。实现自动滚动可以通过设置 el-scrollbar 的属性和使用 JavaScript 的 DOM 操作来完成。 首先在需要实现滚动效果的容器上添加 el-scrollbar,例如: ```html <el-scrollbar style="height: 300px;"> <ul> <li>...</li> <li>...</li> <li>...</li> ... </ul> </el-scrollbar> ``` 其中 ul 列表是需要滚动的内容。接下来,可以使用 CSS 的 overflow 属性来隐藏容器内多余的内容,只显示 el-scrollbar 内部的内容: ```css ul { overflow: hidden; padding: 0; margin: 0; list-style: none; } ``` 为了实现自动滚动,可以使用 JavaScript 来控制内容滚动的位置,以及设置一个定时器来定期滚动内容。 ```js // 获取 el-scrollbar 中的 ul 列表 const ul = document.querySelector('.el-scrollbar ul'); // 每秒滚动50个像素 const scrollSpeed = 50; function scrollStep() { ul.scrollTop += scrollSpeed; // 如果已经滚动到底部,回到顶部 if (ul.scrollTop + ul.offsetHeight >= ul.scrollHeight) { ul.scrollTop = 0; } } // 每隔一定的时间调用一次 scrollStep setInterval(scrollStep, 1000 / 60); ``` 上述代码中通过获取 el-scrollbar 中的 ul 列表,然后每次将 scrollTop 属性递增 scrollSpeed 的值来实现自动滚动。如果滚动到了内容的底部,则将 scrollTop 重置为0,实现循环滚动的效果。 综上所述,通过使用 el-scrollbar 组件以及 CSS 和 JavaScript 的操作,可以实现容器内部自动滚动的功能。
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值