重写QTreeWidget,为其添加平滑滚动效果。

 

实现思路

通过定时器溢出,令滚轮事件微微滞后达到平滑效果。

定时器溢出是需要时间的,无法立马处理完所有的滚轮事件,所以干脆自己复制一个滚轮事件lastWheelEvent,然后计算每一次滚动需要移动的距离和步数,将这两个参数绑定在一起放入队列中。定时器每次溢出时就将所有未处理完的事件对应的距离累加得到totalDelta,每个未处理事件的步数-1,将 totalDelta 和 lastWheelEvent 作为参数传入QWeelEvent的构造函数,构建出真正需要的滚轮事件e并将其发送到app的事件处理队列中,发生滚动。

 

//**********************************************************h****************************************************************

class SmoothScroll : public QTreeWidget
{
    Q_OBJECT;
public:
    SmoothScroll(QWidget* parent = NULL);
    
    enum SmoothMode
    {
        NO_SMOOTH,
        CONSTANT,
        LINEAR,
        QUADRATIC,
        COSINE
    };

    SmoothMode smoothMode();
    void       setSmoothMode(SmoothMode mode);

    int  fps();
    void setFps(int fps);

    // value in millisecond
    int  duration();
    void setDuration(int mesc);

    double acceration();
    void   setAcceration(double acceleration);

    double smallStepRatio();
    void   setSmallStepRatio(double smallStepRatio);

    double bigStepRatio();
    void   setBigStepRatio(double bigStepRatio);

    Qt::Modifier smallStepModifier();
    void         setSmallStepModifier(Qt::Modifier smallStepModifier);

    Qt::Modifier bigStepModifier();
    void         setbigStepModifier(Qt::Modifier bigStepModifier);

public slots:
    void slotSmoothMove();

protected:
    void wheelEvent(QWheelEvent *e);

private:
    double subDelta(double delta, int stepsLeft);

    QTimer      *smoothMoveTimer;
    QWheelEvent *lastWheelEvent;

    int        m_fps;
    int        m_duration;
    SmoothMode m_smoothMode;

    double       m_acceleration;
    double       m_smallStepRatio;
    double       m_bigStepRatio;
    Qt::Modifier m_smallStepModifier;
    Qt::Modifier m_bigStepModifier;

    int stepsTotal;
    QList< QPair<double, int> > stepsLeftQueue;
};

 

//*****************************************************************cpp*********************************************************

SmoothScroll::SmoothScroll(QWidget* parent /*= NULL*/):QTreeWidget(parent)
{
    lastWheelEvent = 0;
    smoothMoveTimer = new QTimer(this);
    connect(smoothMoveTimer, SIGNAL(timeout()), this, SLOT(slotSmoothMove()));

    m_fps = 60;
    m_duration = 400;
    m_smoothMode = COSINE;
    m_acceleration = 2.5;

    m_smallStepModifier = Qt::SHIFT;
    m_smallStepRatio = 1.0 / 5.0;
    m_bigStepModifier = Qt::ALT;
    m_bigStepRatio = 16.0;

    verticalScrollBar()->setContextMenuPolicy(Qt::NoContextMenu);
    horizontalScrollBar()->setContextMenuPolicy(Qt::NoContextMenu);
}

void SmoothScroll::wheelEvent(QWheelEvent *e)
{

    //int value = verticalScrollBar()->sliderPosition();
    //emit wheelScrollChange(value);

    //QTreeWidget::wheelEvent(e);

    if (m_smoothMode == NO_SMOOTH) 
    {
        QTreeWidget::wheelEvent(e);
        return;
    }

    static QQueue<qint64> scrollStamps;
    qint64 now = QDateTime::currentDateTime().toMSecsSinceEpoch();
    scrollStamps.enqueue(now);
    while (now - scrollStamps.front() > 500)
        scrollStamps.dequeue();
    double accerationRatio = qMin(scrollStamps.size() / 15.0, 1.0);

    if (!lastWheelEvent)
        lastWheelEvent = new QWheelEvent(*e);
    else
        *lastWheelEvent = *e;

    stepsTotal = m_fps * m_duration / 1000;
    double multiplier = 1.0;
    if (QApplication::keyboardModifiers() & smallStepModifier())
        multiplier *= smallStepRatio();
    if (QApplication::keyboardModifiers() & bigStepModifier())
        multiplier *= bigStepRatio();
    double delta = e->delta() * multiplier;
    if (acceration() > 0)
        delta += delta * acceration() * accerationRatio;

    stepsLeftQueue.push_back(qMakePair(delta, stepsTotal));
    smoothMoveTimer->start(1000 / m_fps);
}


int SmoothScroll::fps()
{
    return m_fps;
}

void SmoothScroll::setFps(int fps)
{
    m_fps = fps;
}

int SmoothScroll::duration()
{
    return m_duration;
}

void SmoothScroll::setDuration(int mesc)
{
    m_duration = mesc;
}

SmoothScroll::SmoothMode SmoothScroll::smoothMode()
{
    return m_smoothMode;
}

void SmoothScroll::setSmoothMode(CMyTreeWidget::SmoothMode mode)
{
    m_smoothMode = mode;
}

double SmoothScroll::acceration()
{
    return m_acceleration;
}

void SmoothScroll::setAcceration(double acceleration)
{
    m_acceleration = acceleration;
}

double SmoothScroll::smallStepRatio()
{
    return m_smallStepRatio;
}

void SmoothScroll::setSmallStepRatio(double smallStepRatio)
{
    m_smallStepRatio = smallStepRatio;
}

double SmoothScroll::bigStepRatio()
{
    return m_bigStepRatio;
}

void SmoothScroll::setBigStepRatio(double bigStepRatio)
{
    m_bigStepRatio = bigStepRatio;
}

Qt::Modifier SmoothScroll::smallStepModifier()
{
    return m_smallStepModifier;
}

void SmoothScroll::setSmallStepModifier(
    Qt::Modifier smallStepModifier)
{
    m_smallStepModifier = smallStepModifier;
}

Qt::Modifier SmoothScroll::bigStepModifier()
{
    return m_bigStepModifier;
}

void SmoothScroll::setbigStepModifier(
    Qt::Modifier bigStepModifier)
{
    m_bigStepModifier = bigStepModifier;
}


void SmoothScroll::slotSmoothMove()
{
    double totalDelta = 0;

    for (QList< QPair<double, int> >::Iterator it = stepsLeftQueue.begin();
        it != stepsLeftQueue.end(); ++it)
    {
        totalDelta += subDelta(it->first, it->second);
        --(it->second);
    }
    while (!stepsLeftQueue.empty() && stepsLeftQueue.begin()->second == 0)
        stepsLeftQueue.pop_front();

    Qt::Orientation orientation = lastWheelEvent->orientation();
    // By default, when you press ALT, QT will scroll horizontally.  But if we
    // have defined the use of ALT key, we ignore this setting since horizontal
    // scroll is not so useful in okular
    if ((bigStepModifier() & Qt::ALT) || (smallStepModifier() & Qt::ALT))
        orientation = Qt::Vertical;

    QWheelEvent e(
        lastWheelEvent->pos(),
        lastWheelEvent->globalPos(),
        qRound(totalDelta),
        lastWheelEvent->buttons(),
        0,
        orientation
        );
    if (e.orientation() == Qt::Horizontal)
        QApplication::sendEvent(horizontalScrollBar(), &e);
    else
        QApplication::sendEvent(verticalScrollBar(), &e);

    if (stepsLeftQueue.empty()) {
        smoothMoveTimer->stop();
    }
}

double SmoothScroll::subDelta(double delta, int stepsLeft)
{
    Q_ASSERT(m_smoothMode != NO_SMOOTH);

    double m = stepsTotal / 2.0;
    double x = abs(stepsTotal - stepsLeft - m);

    // some mathmatical integral result.
    switch (m_smoothMode) {
    case NO_SMOOTH:
        return 0;
        break;

    case CONSTANT:
        return double(delta) / stepsTotal;
        break;

    case LINEAR:
        return 2.0*delta / stepsTotal * (m - x) / m;
        break;

    case QUADRATIC:
        return 3.0 / 4.0 / m * (1.0 - x*x / m / m) * delta;
        break;

    case COSINE:
        return (cos(x * M_PI / m) + 1.0) / (2.0*m) * delta;
        break;
    }

    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值