用Qt实现一个动态缩放的滚动条

      • 用Qt实现一个动态缩放的滚动条
        • 1. QScrollArea与QScrollBar的关系
        • 2. 理解QWidget::sizeHint()
        • 3. 布局变化通知
        • 部分代码:

用Qt实现一个动态缩放的滚动条

很早的时候做一个Qt项目,需求是实现一个滚动条动态变粗变细的效果。当时由于对Qt了解不多,就拒绝了。

最近忙完工作,突然想起这个需求,花点时间实现了一下,确实不难。以下是实际效果图(使用style sheet调整过样式,包括初始宽度):
用Qt实现一个动态缩放的滚动条_第1张图片
示例代码已上传github

技术总结
实际上手实现的话,主要需要理解以下几点:

  1. QScrollArea与QScrollBar的关系
  2. 理解QWidget::sizeHint()
  3. 布局变化通知

1. QScrollArea与QScrollBar的关系

QScrollArea中的QSrollBar实际上并不直接的父子关系,QScrollBar存在于一个Widget的布局中,直接调用其setGeomerty()是不行的。而且,由于QScrollArea存在垂直与水平两个滚动条,其中一个的粗细变化都会影响另外一个的长度变化,进而影响滑块(Handle)。

2. 理解QWidget::sizeHint()

当创建并显示窗口时,总是需要一个值来决定显示多大,不显式指定时,Qt会通过QWidget::sizeHint()计算一个值,但不一定会使用,比如没有布局的QWidget会得到无效值。

需要注意的是,该值是建议值,窗口尺寸不绝对是该值。如果Widget存在布局,则是布局的sizeHint()与该建议值的综合结果,保证能够容纳建议布局且不小于该建议值。

类似地,存在QWidget::minimumSizeHint(),表示widget的最小建议尺寸,在不指定minimumSize的时候使用此值作为最小尺寸。

所以,通过重写QWidget::sizeHint()和QWidget::minimumSizeHint()可以实现某些特殊的需求。

Qt内部对sizeHint的计算是比较复杂的,需要根据内容、样式、布局等计算出合理的值,因此,重写QWidget::sizeHint()总是尽可能避免复杂的样式设定。我在工作中遇到过这样一个需求,一个列表当行数不大于N时,窗口刚好能完整显示;当大于N时则出现滚动条。有多种实现方案,我选择了QListView, 通过 “行高*行数” 来建议显示尺寸,但这就要求style sheet不能指定QListView的padding等,因为前面计算的建议值再需要加上QListView本身的padding、border等信息才行。

3. 布局变化通知

当一个Widget没有布局时,子控件的变化不会影响到该Widget的尺寸。更多时候,Widget需要布局管理子控件,其布局随着内容的变化需要不断调整。例如,重设QLabel的text,使内容超出原本布局,由于QLabel不会自动省略显示过长的内容,所以会触发父窗口调整尺寸,使其能容纳新的布局。也有部分Widget,比如QLineEdit,其默认的sizeHint并不依赖于其text的变化,仅会在样式变化时需要重新计算。

因此,要实现布局的动态调整,需要在手动更新sizeHint后通知布局。

Qt提供了QWidget::updateGeometry()来通知布局,该接口会触发QEvent::LayoutRequest,之后布局会重新调用QWidget::sizeHint()。


部分代码:

基本代码,为了简略,去掉了延时Timer。

//varAnima: 变量动画
//preferWidth: 临时记录动态的滚动条宽度
//_expandedWidth: 滚动条最大宽度

AnimatedScrollBar::AnimatedScrollBar(QWidget *parent):
    QScrollBar(parent)
{
    varAnima = new QVariantAnimation(this); //创建动画
    varAnima->setDuration(100);
    connect(varAnima, &QVariantAnimation::valueChanged, this, [this](const QVariant &val){
        //valueChanged时,动画不一定在运行,需要约束
        if(varAnima->state() == QAbstractAnimation::Running)
        {
            preferWidth = val.toInt();
            updateGeometry(); //通知变化
        }
    });
}

QSize AnimatedScrollBar::sizeHint() const
{
    QSize tmp = QScrollBar::sizeHint(); //样式指定的宽度值,可以通过默认的sizeHint获取
    if(this->orientation() == Qt::Horizontal)
    {
        return QSize(tmp.width(), preferWidth); //仅改变宽度,实际由于布局的存在,长度值并不重要
    }
    return QSize(preferWidth, tmp.height());
}

bool AnimatedScrollBar::event(QEvent *e)
{
    if(e->type() == QEvent::Polish)
    {
        //初始化preferWidth,也可以在第一次sizeHint()被调用时初始化
        QSize tmp = QScrollBar::sizeHint();
        preferWidth = this->orientation() == Qt::Horizontal ? tmp.height() : tmp.width();
    }
    else if(e->type() == QEvent::HoverEnter)
    {
        if(varAnima->state() == QAbstractAnimation::Running)
            varAnima->stop();

        varAnima->setStartValue(preferWidth);
        varAnima->setEndValue(_expandedWidth);
        varAnima->start();
    }
    else if(e->type() == QEvent::HoverLeave)
    {
        if(varAnima->state() == QAbstractAnimation::Running)
            varAnima->stop();

        QSize tmp = QScrollBar::sizeHint();
        int  normalWidth = this->orientation() == Qt::Horizontal ? tmp.height() : tmp.width();
        varAnima->setStartValue(preferWidth);
        varAnima->setEndValue(normalWidth);
        varAnima->start();
    }
    return QScrollBar::event(e);
}

你可能感兴趣的:(Qt技术总结,Qt常见问题,Qt,滚动条,缩放,QScrollBar,动画)