Qt下的悬浮窗

最近项目需要一个类似于360悬浮球类似的悬浮窗,当鼠标放入停留一段时间,就会展开悬浮窗,移出区域就会自动收起。随便在网上找了一下,没找到,想着熟悉Qt提升自己编程技术的出发点,我就自己造了个轮子,如果有问题,希望大家指正。

QPropertyAnimation

我用的是Qt自带的动画类,官方文档的解释是:

Qt QPropertyAnimation+QTimer实现自制悬浮窗-小白菜博客
上面画红框的意思是,你可以指定属性的开始和结束值。

使用方法如下:

// 设置property为geometry,代表位置大小
m_Animation = new QPropertyAnimation(this, "geometry");
// 设置动画持续时间(单位ms)
m_Animation->setDuration(600);

// 设置动画的终止值
m_Animation->setEndValue(QRect(m_posX,m_posY,
                               this->width(), this->height()));
// 设置动画的开始
m_Animation->setStartValue(QRect(m_posX - ui->m_menu->width(), m_posY,
                                 this->width(), this->height()));
// 设置动画的运动轨迹
m_Animation->setEasingCurve(QEasingCurve::InQuad);
m_Animation->start();

关于setEasingCurve()这个函数,这个是设置动画的行动轨迹,参看Qt官方文档:

这里介绍了很多的动画曲线。

QTimer

另外一个核心的是定时器,简易的逻辑是:

  1. 当鼠标移入标题栏时,会开启弹出定时器,到时间就会执行弹出函数,当没有到时间,但是移除了标题时,关闭弹出计时器;

  2. 当鼠标移入菜单栏时,会关闭收起定时器,当移出菜单栏时,会开启收起定时器,到时间就会执行收起函数,当没有到时间就移入的时,关闭收起定时器;

定时器的初始化代码如下:

m_expandTimer = new QTimer();
m_flodTimer = new QTimer();
// 设置定时器时间(单位ms)
m_expandTimer->setInterval(700);
m_flodTimer->setInterval(700);

// 连接信号和槽
connect(m_expandTimer, &QTimer::timeout, this, &Floating::expandMenu); 
connect(m_flodTimer, &QTimer::timeout, this, &Floating::flodMenu);     

定时器时间到处理函数如下:

void Floating::expandMenu()
{
    if (m_Animation->state() == QPropertyAnimation::Running) {
        return;
    }

    m_isExpand = true;

    setTitleIcon();

    m_Animation->setStartValue(QRect(m_posX, m_posY,
                                     this->width(), this->height()));
    m_Animation->setEndValue(QRect(m_posX - ui->m_menu->width(), m_posY,
                                  this->width(), this->height()));
    m_Animation->start();

    m_expandTimer->stop();
}

void Floating::flodMenu()
{
    if (m_Animation->state() == QPropertyAnimation::Running) {
        return;
    }

    m_isExpand = false;

    setTitleIcon();

    m_Animation->setEndValue(QRect(m_posX,m_posY,
                                   this->width(), this->height()));
    m_Animation->setStartValue(QRect(m_posX - ui->m_menu->width(), m_posY,
                                     this->width(), this->height()));
    m_Animation->start();

    m_flodTimer->stop();
}

事件过滤

需要将悬浮窗的子控件的鼠标移入移出事件进行一个设计,代码如下:

bool Floating::eventFilter(QObject *target, QEvent *event)
{

    //拖动
    // TODO

    if (target == ui->m_title) {
        if (event->type() == QEvent::Enter) {
            if (!m_bDragFlag && !m_isExpand) {
                m_expandTimer->start();
                return QWidget::eventFilter(target, event);
            }
        }

        if (event->type() == QEvent::Leave) {
            m_expandTimer->stop();
        }
    }

    if (target == ui->m_menu || target == this) {
        if (event->type() == QEvent::Enter) {
            m_flodTimer->stop();
            return QWidget::eventFilter(target, event);
        }

        if (event->type() == QEvent::Leave) {
            if (!m_bDragFlag && m_isExpand) {
                m_flodTimer->start();
            }
        }
    }

    return QWidget::eventFilter(target, event);
}

图标变换

void Floating::setTitleIcon()
{
    m_isExpand ? ui->m_title->setProperty("status", "show")
               : ui->m_title->setProperty("status", "hide");

    // 设置完之后,一定要polish,不然样式可能不会显示
    ui->m_title->style()->polish(ui->m_title);
}

这里是根据设置的动态属性来设置对应的样式,详情请参见这篇博客

自适应窗口大小

由于有时候会出现窗口大小调整后,悬浮窗的位置可能会重新变化,或者分辨率改变之后。所以需要重载需要放置的窗口的resizeEvent来动态的设置悬浮窗的大小。

代码如下:

// mainwindow.h
class MainWindow
{
 // ...
protected:
    virtual void resizeEvent(QResizeEvent* event) override;
}

// mainwindow.cpp

void resizeEvent(QResizeEvent* event)
{
    m_floating->adjustParent(this.width());
}

// floating.cpp
void Floating::adjustParent(int parentWidth)
{
    // 这里我的posY是设置的固定的50,
    // m_posX = 主窗口的宽度 - (标题栏的宽度  +布局的间隙)
    int horSpacing = static_cast<QGridLayout*>(this->layout())->horizontalSpacing();
    m_posX = parentWidth - ui->m_title->width() - horSpacing;
    m_posY = 50;

    // 移动窗口
    this->move(m_posX, m_posY);
}

使用方法

  1. 把文件复制到项目文件夹下

  2. 在pro文件里引入pri文件

    include(floating/floating.pri)

  3. 加入头文件

    include "floating/floating"

  4. 获取单例对象:

// .h 
Floating* m_floating;

// 且需要在使用的类的析构函数里,加上这一句
// 不然就会出现多次释放的问题,因为这个m_floating释放在析构函数之前
// 但是之前创建的子窗口的关系树还在,在主窗口析构的时候还会去析构子对象
// 就会出错。所以需要在对象树中删除这段关系。
m_floating->setParent(nullptr);

// .cpp
m_floating = &Floating::getInstance(this);
  1. 设置样式表
/* 设置收起的样式 */
Floating QWidget#m_title[status=hide]
{
/*    border-image:url(:/img/images/ZT.png);*/
background-color: rgba(12,55,214,1);
}

/* 设置展开的样式 */
Floating QWidget#m_title[status=show]
{
/*    border-image:url(:/img/images/YT.png);*/
background-color: rgba(12,55,214,1);
}

/* 设置菜单栏的样式 */
Floating QWidget#m_menu
{
    border: 2px solid #969696;
    background-color: rgba(0, 25, 67, 0.9);;
    border-top-left-radius: 10px;       /* 角弧度:左下角*/
    border-bottom-left-radius: 10px;      /* 角弧度:右下角*/

}
  1. 如果你需要让主窗体变化之后,悬浮窗的位置也能跟着变化,请重载主窗口的resize事件,以手动更改悬浮窗的大小
// mainwindow.h
class MainWindow
{
 // ...
protected:
    virtual void resizeEvent(QResizeEvent* event) override;
}

// mainwindow.cpp

void resizeEvent(QResizeEvent* event)
{
    m_floating->adjustParent(this.width());
}

果然只有通过写出来或者教别人的方式,才能够真正检验自己的知识学的到不到位,在写博客的同时,我又对这些代码和QPropertyAnimation这个类了解更深了。

代码资源请去此处下载