(QT-UI)九、实现时间轴前后拖动效果

 本系列预计实现

①刻度上方文字显示,

②时间轴拖动效果,

③时间轴刻度缩放,

④时间轴和其他控件联动显示,

⑤鼠标放置到时间轴,显示具体时间。

完整代码可见GitHub - 754816/QT_TimeLine: qt时间轴实现效果

1、基础思路

当按下鼠标时,时间轴的会随着鼠标的移动,而水平跟随移动等量像素位置,视觉上看起来,时间轴被拖动了一样,实际上则是时间轴上每根竖线,都根据鼠标移动的位置,而进行了偏移,所以在计算竖线的x坐标时,需要加上偏移量

2、先添加鼠标事件,获取到鼠标移动位置

这里运用了鼠标的按下,移动,释放三个事件

在MyTimeLine.hpp中添加上三个虚函数

    //MyTimeLine.hpp
    void mousePressEvent(QMouseEvent *event);
    void mouseMoveEvent(QMouseEvent *event);
    void mouseReleaseEvent(QMouseEvent *event);

然后在cpp中实现,设定当点击的为鼠标左键时,才继续处理

if (event->button() == Qt::LeftButton) {
    //do something
}

3、继续拆解鼠标移动逻辑

鼠标的移动可以看成两次鼠标位置的差异,在处理中,可以选择在mousePressEvent函数中获取一次初始鼠标位置(m_BeginX),然后在mouseMoveEvent中反复获取鼠标位置(CurMoveX),在mouseReleaseEvent中记录下本次总共的移动位置(m_totalX)。由于拖动是一个持续的过程,所以会不断的触发mouseMoveEvent函数,所以需要计算mouseMoveEvent中每次鼠标位置相对于开始时的偏移量(m_MoveX)

当然也有不断的获取mouseMoveEvent中,然后每次记录鼠标移动差异的方法,只是并不直观,并且每次都只大约移动了1个像素,就会触发一次mouseMoveEvent,这种像素差距较小的情况,并不利于时间轴的计算,容易导致拖动距离过长时,产生绘制误差。

那么当第一次拖动时,按照CurMoveX绘制即可,拖动完成后得到一个m_totalX,但是由于存在多次的拖动,第二次绘制时,则是本次移动的而加上之前累计移动的像素,得到综合的时间轴偏移量。所以m_MoveX实际上等于m_totalX + curMoveX

void MyTimeLine::mousePressEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton) {
        //记录鼠标的按下位置,作为初始位置,存储到全局变量m_BeginX中
        m_BeginX = event->pos().x();            
    }
}
void MyTimeLine::mouseMoveEvent(QMouseEvent *event)
{
    if (event->buttons() & Qt::LeftButton) {
        //获取鼠标的本次移动位置
        int curMoveX = event->pos().x() - m_BeginX;    
        m_MoveX = m_totalX + curMoveX;
        update();
    }
}
void MyTimeLine::mouseReleaseEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton) {
        //统计本次一共移动了多少位置,并且将其累计起来
        m_totalX = m_totalX +  event->pos().x() - m_BeginX;    
        m_MoveX = m_totalX;
    }
}

4、在计算位置中,每次都添加上偏移

设定时间轴的中间为当前时间,当鼠标向左拖动时,时间轴也跟着向左移动,计算的像素为负,当前时间实际为增加,鼠标向右拖,时间为减小,计算的像素为正。

那么计算时,带入鼠标的偏移。

    //添加长竖线, 需要添加13根长竖线
    for(int i = 0; i <= 12; i++)
    {   //第i根长竖线w = i*单个块长度
        int LongW = i * m_blockWidth + mouseMoveX;
        lines.push_back(QLine(LongW, TimeLineHeight / 2, LongW, TimeLineHeight));
    }

结合上一节初始时间的偏移,时间轴拖动后是有两个偏移量的,把鼠标的偏移量定为 t1,把初始时间的偏移量定为 t2, 把综合偏移量定为 moveRatio,每小时间隔像素为m_blockWidth,那么最终时间轴则是偏移了 moveRatio * m_blockWidth 个像素。

    double moveRatio = CalMoveRatio(m_MoveX, m_dateTime, m_blockWidth);
    int MovePixMod = (int)moveRatio * m_blockWidth;

    //添加长竖线, 为了展示效果合理性,需要添加13+2根长竖线,+2表示余出左右两边,以应对拖动
    for(int i = -1; i <= TimeLineStyle::TimeCount + 1; i++)
    {   //第i根长竖线w = 左边Margin + i*单个块长度 - 像素偏移量
        int LongW = LeftMargin + i * m_blockWidth - MovePixMod;
        lines.push_back(QLine(LongW, TimeLineHeight / 2, LongW, TimeLineHeight));
    }
double MyTimeLine::CalMoveRatio(int movepix, QDateTime datetime, double moveRatio)
{
    int interval = 3600;
    //t1:鼠标累计的偏移像素; t2:非整点时间的偏移位置
    double t1, t2;
    if(0 == moveRatio) t1 = 0;
    else t1 = (double)movepix / moveRatio;
    int ss = 0;
    if(_OneHour == interval)
    {
        ss = datetime.time().minute() * 60 + datetime.time().second();
    }
    t2 = (double)ss / (double)interval;
    //t > 0,表示实际时间增大,鼠标是执行了向左拖动,t < 0,时间减小,鼠标向右拖
    double t = t2 - t1;
    return t;
}

以上是拖动的原理,但是在实际应用中,存在着时间轴拖动的距离比较长,导致拖动的区域出现了一片空白的情况,这里就需要补充绘制空白的区域,经过观察可以看出,其实时间轴的竖线都是重复的块,所以补充绘制块即可,后面再单独计算文字信息。

15:00:00-16:00:00是一个块,16:00:00-17:00:00是重复的块,17:00:00-18:00:00是重复的块,它们的竖线绘制都是一样的,只是文字不一样。

所以上面计算偏移量的实际,实际上只需要对结果取模,然后将除法运算结果带入到时间文字信息中

 int MovePixMod = (int)((double)moveRatio * (double)m_blockWidth) % m_blockWidth;

5、计算文字信息

首先从中间的时间开始,中间假设为12:34:56,左边长竖线有7个,最近的为12:00:00,可以计算出最左边的则为06:00:00,右边的长竖线有6个,最右边为18:00:00。计算时,先求出最左边的时间,然后后面的按照间隔,每次加1小时,就得到刻度时间。

QString MyTimeLine::CalText(const int index, const QDateTime midDateTime, const int moveRatio)
{
    QTime time(0,0,0);
    //STEP 1: 将时间按照间隔进行取模处理,例如间隔为1h,12:34:56处理后为12:00:00
    QDateTime IntegerTime = midDateTime;
    IntegerTime.setTime(QTime(midDateTime.time().hour(), 0, 0));
    
    //STEP 2: 根据中间的时间和Interval,计算出最左边的时间
    QDateTime LeftTime = DateTimeUtil::sub(IntegerTime, 6 * 3600);

    //STEP 3: 根据偏移量,更新最左边的时间
    QDateTime MoveLeftTime = DateTimeUtil::sub(LeftTime, -1 * moveRatio * 3600);

    //STEP 4: 根据index,计算i个间隔后的时间
    time = DateTimeUtil::sub(MoveLeftTime, -1 * index * m_IntervalType).time();
    return time.toString("hh:mm:ss");
}

由于Qt没有合适的时间减法,所以可以将QDateTime转为时间戳,计算后,再由时间戳转为QDateTime。这里给出封装一层的时间减法函数,注意当拖动的时间,大于了当前时间时,则这个不再适用。

QDateTime DateTimeUtil::sub(const QDateTime& time1, const QDateTime& time2)
{
     qint64 t = time1.toSecsSinceEpoch() - time2.toSecsSinceEpoch();
     if(t < 0) t = 0;
     QDateTime dt = QDateTime::fromSecsSinceEpoch(t);
     return dt;
}
QDateTime DateTimeUtil::sub(const QDateTime& time1, const qint64& seconds)
{
    qint64 t = time1.toSecsSinceEpoch() - seconds;
    if(t < 0) t = 0;
    QDateTime dt = QDateTime::fromSecsSinceEpoch(t);
    return dt;
}

  • 11
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值