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

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

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

最近忙完工作,突然想起这个需求,花点时间实现了一下,确实不难。以下是实际效果图(使用style sheet调整过样式,包括初始宽度):
滚动条悬停效果
示例代码已上传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);
}
  • 1
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Qt中,如果要实现使用滚轮来缩放Label的大小,当Label的大小过大时,会出现滚动条。 首先,需要将Label放置在一个可以滚动的窗口控件内,例如QScrollArea。可以通过如下代码创建一个QScrollArea控件,并将Label放置在其中: ```cpp QScrollArea *scrollArea = new QScrollArea; QWidget *widget = new QWidget; QVBoxLayout *layout = new QVBoxLayout; QLabel *label = new QLabel("文本内容"); label->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); // 设置Label的大小策略为可调整大小 layout->addWidget(label); widget->setLayout(layout); scrollArea->setWidget(widget); scrollArea->setWidgetResizable(true); // 设置滚动区域可以调整大小 scrollArea->show(); ``` 接下来,我们可以通过重写QLabel的wheelEvent事件来实现滚轮缩放Label的功能。在wheelEvent中,可以根据鼠标滚轮的滚动方向来改变Label的大小,同时重新计算滚动区域的大小,以确保Label的完整显示。下面是一个示例代码: ```cpp void QLabel::wheelEvent(QWheelEvent *event) { int delta = event->angleDelta().y(); QSize labelSize = size(); // 根据滚动方向来调整大小 if (delta > 0) { labelSize *= 1.1; // 缩放因子,可以根据需要自行调整 } else { labelSize *= 0.9; } setFixedSize(labelSize); // 重新计算滚动区域大小 QScrollArea *scrollArea = qobject_cast<QScrollArea*>(parentWidget()); if (scrollArea) { QWidget *widget = scrollArea->widget(); widget->adjustSize(); scrollArea->setWidgetResizable(true); } event->accept(); } ``` 通过以上代码,当鼠标滚轮滚动时,Label的大小会相应地增加或减小,并更新滚动区域的大小,确保Label的完整显示。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值