Qt实践:一个简单的丝滑侧滑栏实现

news2025/1/24 16:48:42

Qt实践:一个简单的丝滑侧滑栏实现

笔者前段时间突然看到了侧滑栏,觉得这个抽屉式的侧滑栏非常的有趣,打算这里首先尝试实现一个简单的丝滑侧滑栏。

首先是上效果图

(C,GIF帧率砍到毛都不剩了)

QPropertyAnimation

官方的QPropertyAnimation Class | Qt Core 6.8.1

也就是说,这个类封装了我们的Qt动画播放的类,我们针对Widgets的属性对其变化进行动画播放。Qt的抽象非常的好,只需要设置我们的起点和终点状态,以及设置一下时间间隔和播放的变化方式,就完事了。

  • setDuration(int msec): 设置动画的持续时间,单位是毫秒。

  • setStartValue(const QVariant &startValue): 设置动画的起始值。

  • setEndValue(const QVariant &endValue): 设置动画的结束值。

  • setEasingCurve(const QEasingCurve &curve): 设置动画的插值曲线,控制动画的速度变化(如加速、减速、匀速等)。常用的曲线类型有 QEasingCurve::LinearQEasingCurve::InQuadQEasingCurve::OutBounce 等。

笔者实现的效果的API就是用到了上面四个。

首先我们思考一下,SideBar看似是一个侧滑栏,但是跟随变动的,考虑上夹在中间的按钮,是三个部分。我们按照上面的思考思路。

1. 隐藏侧边栏(do_hide_animations

使侧边栏从可见状态过渡到隐藏状态。具体变化如下:

  • 侧边栏动画 (animation_side)

    • 起始状态:侧边栏的当前几何位置(ui->widgetSiderBar->geometry())。

    • 结束状态:侧边栏移动到视图外部,即其横坐标变为负值,具体位置为 ( - ui->widgetSiderBar->width(), ui->widgetSiderBar->y() )。这样侧边栏就被“隐藏”到屏幕外。

  • 按钮动画 (animation_button)

    • 起始状态:操作按钮当前的几何位置(ui->btn_operate->geometry())。

    • 结束状态:按钮的位置将移动到屏幕左侧,具体位置为 ( 0, ui->btn_operate->y() )。这样按钮会被移到左侧,表示侧边栏已隐藏。

  • 主界面动画 (animation_main)

    • 起始状态:主界面的当前几何位置(ui->widget_mainside->geometry())。

    • 结束状态:主界面位置根据按钮的位置进行调整,具体为 ( ui->btn_operate->width(), ui->widget_mainside->y() ),这意味着主界面会向左移动,避开被隐藏的侧边栏。

  • 操作按钮文本

    • 操作按钮的文本更改为 ">",表示点击后侧边栏会“展开”。

  • 执行动画:调用 group->start() 启动所有动画,产生隐藏效果。

2. 显示侧边栏(do_show_animations

当用户点击按钮以显示侧边栏时,执行 do_show_animations,将侧边栏从隐藏状态恢复到可见状态。具体变化如下:

  • 侧边栏动画 (animation_side)

    • 起始状态:侧边栏当前的几何位置(ui->widgetSiderBar->geometry())。

    • 结束状态:侧边栏移动到其原始位置,即横坐标变为 0,具体位置为 ( 0, ui->widgetSiderBar->y() ),使其重新显示在屏幕上。

  • 按钮动画 (animation_button)

    • 起始状态:操作按钮当前的几何位置(ui->btn_operate->geometry())。

    • 结束状态:按钮的位置将移动到侧边栏的右侧,具体为 ( ui->widgetSiderBar->width(), ui->btn_operate->y() ),表示按钮回到右侧,侧边栏已重新显示。

  • 主界面动画 (animation_main)

    • 起始状态:主界面的当前几何位置(ui->widget_mainside->geometry())。

    • 结束状态:主界面的位置调整为 ( ui->widgetSiderBar->width() + ui->btn_operate->width(), ui->widget_mainside->y() ),并且宽度变为 width() - ui->btn_operate->width() - ui->widgetSiderBar->width(),使得主界面重新适应显示的侧边栏。

  • 操作按钮文本

    • 操作按钮的文本更改为 "<",表示点击后侧边栏会“隐藏”。

  • 执行动画:调用 group->start() 启动所有动画,产生显示效果。

代码上的体现就是

void SideBarWidget::do_hide_animations() {
    animation_side->setStartValue(ui->widgetSiderBar->geometry());
    /* move to the hidden place */
    animation_side->setEndValue(
        QRect(-ui->widgetSiderBar->width(), ui->widgetSiderBar->y(),
              ui->widgetSiderBar->width(), ui->widgetSiderBar->height()));
​
    animation_button->setStartValue(ui->btn_operate->geometry());
    animation_button->setEndValue(QRect(0, ui->btn_operate->y(),
                                        ui->btn_operate->width(),
                                        ui->btn_operate->height()));
​
    animation_main->setStartValue(ui->widget_mainside->geometry());
    animation_main->setEndValue(QRect(
        ui->btn_operate->width(), ui->widget_mainside->y(),
        width() - ui->btn_operate->width(), ui->widget_mainside->height()));
​
    ui->btn_operate->setText(">");
    group->start();
}
void SideBarWidget::do_show_animations() {
    animation_side->setStartValue(ui->widgetSiderBar->geometry());
    /* move to the hidden place */
    animation_side->setEndValue(QRect(0, ui->widgetSiderBar->y(),
                                      ui->widgetSiderBar->width(),
                                      ui->widgetSiderBar->height()));
​
    animation_button->setStartValue(ui->btn_operate->geometry());
    animation_button->setEndValue(
        QRect(ui->widgetSiderBar->width(), ui->btn_operate->y(),
              ui->btn_operate->width(), ui->btn_operate->height()));
​
    animation_main->setStartValue(ui->widget_mainside->geometry());
    animation_main->setEndValue(
        QRect(ui->widgetSiderBar->width() + ui->btn_operate->width(),
              ui->widget_mainside->y(),
              width() - ui->btn_operate->width() - ui->widgetSiderBar->width(),
              ui->widget_mainside->height()));
    ui->btn_operate->setText("<");
    ui->widgetSiderBar->setVisible(true);
    group->start();
}
上面体现了一个优化,那就是使用动画组Group来同步的进行操作。防止出现动画抢跑。

源码

完整的测试源码在:CCQt_Libs/Widget/SideBarWidget at main · Charliechen114514/CCQt_Libs (github.com)

C++源码如下

#include "SideBarWidget.h"
#include "ui_SideBarWidget.h"
#include <QParallelAnimationGroup>
#include <QPropertyAnimation>

namespace SideBarUtilsTools {
void clearLayout(QLayout* layout) {
    if (!layout) return;

    QLayoutItem* item;
    while ((item = layout->takeAt(0)) != nullptr) {
        if (item->widget()) {
            item->widget()->hide();  // 隐藏控件,但不删除
        } else {
            clearLayout(item->layout());  // 递归清理子布局
        }
    }
}
}  // namespace SideBarUtilsTools

SideBarWidget::SideBarWidget(QWidget* parent)
    : QWidget(parent), ui(new Ui::SideBarWidget) {
    ui->setupUi(this);
    __initMemory();
    __initConnection();
}

void SideBarWidget::switch_state() {
    setState(!hidden_state);
}

void SideBarWidget::switch_button_visible() {
    setButtonVisible(!ui->btn_operate->isVisible());
}

void SideBarWidget::removeLayout(Role r) {
    switch (r) {
        case Role::SideBar:
            SideBarUtilsTools::clearLayout(ui->widgetSiderBar->layout());
            break;
        case Role::MainSide:
            SideBarUtilsTools::clearLayout(ui->widget_mainside->layout());
            break;
    }
}

void SideBarWidget::setButtonVisible(bool visible) {
    ui->btn_operate->setVisible(visible);
    ui->btn_operate->setText(hidden_state ? ">" : "<");
}

void SideBarWidget::addLayout(QLayout* layout, const QWidgetList& widgetList,
                              Role r) {
    switch (r) {
        case Role::SideBar:
            ui->widgetSiderBar->setLayout(layout);
            for (auto& w : widgetList) {
                ui->widgetSiderBar->layout()->addWidget(w);
            }
            break;
        case Role::MainSide:
            ui->widget_mainside->setLayout(layout);
            for (auto& w : widgetList) {
                ui->widget_mainside->layout()->addWidget(w);
            }
            break;
    }
}

/* setTypes */
void SideBarWidget::setAnimationDuration(int duration) {
    animation_button->setDuration(duration);
    animation_main->setDuration(duration);
    animation_side->setDuration(duration);
}
void SideBarWidget::setAnimationCurve(QEasingCurve::Type curve) {
    animation_button->setEasingCurve(curve);
    animation_main->setEasingCurve(curve);
    animation_side->setEasingCurve(curve);
}

void SideBarWidget::__initMemory() {
    animation_main = new QPropertyAnimation(ui->widget_mainside, "geometry");
    animation_main->setDuration(SideBarWidgetStaticConfig::ANIMATION_DURATION);
    animation_main->setEasingCurve(SideBarWidgetStaticConfig::ANIMATION_CURVE);
    animation_side = new QPropertyAnimation(ui->widgetSiderBar, "geometry");
    animation_side->setDuration(SideBarWidgetStaticConfig::ANIMATION_DURATION);
    animation_side->setEasingCurve(SideBarWidgetStaticConfig::ANIMATION_CURVE);
    animation_button = new QPropertyAnimation(ui->btn_operate, "geometry");
    animation_button->setDuration(
        SideBarWidgetStaticConfig::ANIMATION_DURATION);
    animation_main->setDuration(SideBarWidgetStaticConfig::ANIMATION_DURATION);
    group = new QParallelAnimationGroup(this);
    group->addAnimation(animation_main);
    group->addAnimation(animation_side);
    group->addAnimation(animation_button);
}

void SideBarWidget::__initConnection() {
    connect(ui->btn_operate, &QPushButton::clicked, this,
            [this]() { setState(!hidden_state); });
    connect(group, &QParallelAnimationGroup::finished, this, [this] {
        ui->widgetSiderBar->setVisible(!hidden_state);
        // have no better idea :(, to update the layout
        resize(size().width() + 1, size().height() + 1);
        resize(size().width() - 1, size().height() - 1);
    });
}

void SideBarWidget::do_hide_animations() {
    animation_side->setStartValue(ui->widgetSiderBar->geometry());
    /* move to the hidden place */
    animation_side->setEndValue(
        QRect(-ui->widgetSiderBar->width(), ui->widgetSiderBar->y(),
              ui->widgetSiderBar->width(), ui->widgetSiderBar->height()));

    animation_button->setStartValue(ui->btn_operate->geometry());
    animation_button->setEndValue(QRect(0, ui->btn_operate->y(),
                                        ui->btn_operate->width(),
                                        ui->btn_operate->height()));

    animation_main->setStartValue(ui->widget_mainside->geometry());
    animation_main->setEndValue(QRect(
        ui->btn_operate->width(), ui->widget_mainside->y(),
        width() - ui->btn_operate->width(), ui->widget_mainside->height()));

    ui->btn_operate->setText(">");
    group->start();
}
void SideBarWidget::do_show_animations() {
    animation_side->setStartValue(ui->widgetSiderBar->geometry());
    /* move to the hidden place */
    animation_side->setEndValue(QRect(0, ui->widgetSiderBar->y(),
                                      ui->widgetSiderBar->width(),
                                      ui->widgetSiderBar->height()));

    animation_button->setStartValue(ui->btn_operate->geometry());
    animation_button->setEndValue(
        QRect(ui->widgetSiderBar->width(), ui->btn_operate->y(),
              ui->btn_operate->width(), ui->btn_operate->height()));

    animation_main->setStartValue(ui->widget_mainside->geometry());
    animation_main->setEndValue(
        QRect(ui->widgetSiderBar->width() + ui->btn_operate->width(),
              ui->widget_mainside->y(),
              width() - ui->btn_operate->width() - ui->widgetSiderBar->width(),
              ui->widget_mainside->height()));
    ui->btn_operate->setText("<");
    ui->widgetSiderBar->setVisible(true);
    group->start();
}

SideBarWidget::~SideBarWidget() {
    delete ui;
}

接口文件如下:

#ifndef SIDEBARWIDGET_H
#define SIDEBARWIDGET_H

#include <QEasingCurve>
#include <QWidget>
class QPropertyAnimation;
class QParallelAnimationGroup;
namespace SideBarWidgetStaticConfig {
static constexpr const bool               INIT_STATE         = false;
static constexpr const int                ANIMATION_DURATION = 500;
static constexpr const QEasingCurve::Type ANIMATION_CURVE =
    QEasingCurve::InOutQuad;
};  // namespace SideBarWidgetStaticConfig

namespace Ui {
class SideBarWidget;
}

class SideBarWidget : public QWidget {
    Q_OBJECT

public:
    explicit SideBarWidget(QWidget* parent = nullptr);
    void inline showSideBar() {
        setState(false);
    }
    void inline hideSideBar() {
        setState(true);
    }
    enum class Role { SideBar, MainSide };
    /* addWidgets to the two sides */
    void addLayout(QLayout* layout, const QWidgetList& widgetList, Role r);
    /* remove the display widgets */
    void removeLayout(Role r);
    /* enable or disable the button visibilities */
    void setButtonVisible(bool visible);
    /* setTypes and durations */
    void setAnimationDuration(int duration);
    void setAnimationCurve(QEasingCurve::Type curve);

    ~SideBarWidget();
public slots:
    void switch_state();
    void switch_button_visible();

private:
    QPropertyAnimation*      animation_main;
    QPropertyAnimation*      animation_side;
    QPropertyAnimation*      animation_button;
    QParallelAnimationGroup* group;
    void inline setState(bool st) {
        hidden_state = st;
        hidden_state ? do_hide_animations() : do_show_animations();
    }
    void               __initMemory();
    void               __initConnection();
    void               do_hide_animations();
    void               do_show_animations();
    bool               hidden_state{SideBarWidgetStaticConfig::INIT_STATE};
    Ui::SideBarWidget* ui;
};

#endif  // SIDEBARWIDGET_H

Reference

感谢https://zhuanlan.zhihu.com/p/614475116?utm_id=0,我的设计几乎从这里派生出来!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2281524.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

10. SpringCloud Alibaba Sentinel 规则持久化部署详细剖析

10. SpringCloud Alibaba Sentinel 规则持久化部署详细剖析 文章目录 10. SpringCloud Alibaba Sentinel 规则持久化部署详细剖析1. 规则持久化1.1 Nacos Server 配置中心-规则持久化实例 2. 最后&#xff1a; 1. 规则持久化 规则没有持久化的问题 如果 sentinel 流控规则没有…

SpringCloud微服务Gateway网关简单集成Sentinel

Sentinel是阿里巴巴开源的一款面向分布式服务架构的轻量级流量控制、熔断降级组件。Sentinel以流量为切入点&#xff0c;从流量控制、熔断降级、系统负载保护等多个维度来帮助保护服务的稳定性。 官方文档&#xff1a;https://sentinelguard.io/zh-cn/docs/introduction.html …

正则表达式以及Qt中的使用

目录 一、正则表达式 1、基本匹配&#xff1a; 2、元字符&#xff1a; 2.1 .运算符&#xff1a; 2.2 字符集&#xff1a; 2.3 重复次数&#xff1a; 2.4 量词{} 2.5 特征标群() 2.6 或运算符 2.7 \反斜线转码特殊字符 2.8 锚点 3、简写字符 4、零宽度断言 4.1 正…

【STM32HAL-----GPIO】

1. 什么是GPIO&#xff1f;&#xff08;了解&#xff09; 2. STM32 GPIO简介 2.1. GPIO特点 2.2. GPIO电气特性 2.3. GPIO引脚分布图 IO引脚分布特点&#xff1a;按组存在、组数视芯片而定、每组最多16个IO引脚。 3. IO端口基本结构介绍 4. GPIO八种工作模式 4.1. 输入浮空 特…

亲测有效!解决PyCharm下PyEMD安装报错 ModuleNotFoundError: No module named ‘PyEMD‘

解决PyCharm下PyEMD安装报错 PyEMD安装报错解决方案 PyEMD安装报错 PyCharm下通过右键自动安装PyEMD后运行报错ModuleNotFoundError: No module named ‘PyEMD’ 解决方案 通过PyCharm IDE python package搜索EMD-signal&#xff0c;选择版本后点击“install”执行安装

低代码系统-产品架构案例介绍、简道云(七)

今天分析另外一个零代码、低代码产品-简道云&#xff0c;跟所有低代码产品的架构图一样&#xff0c;高、大、炫、美。 依然是从下至上&#xff0c;从左到右的顺序。 开发层 搭建中心 表单、流程、报表、用户中心&#xff0c;还是这些内容&#xff0c;自定义打印很多平台都有&am…

Chrome 132 版本新特性

Chrome 132 版本新特性 一、Chrome 132 版本浏览器更新 1. 在 iOS 上使用 Google Lens 搜索 在 Chrome 132 版本中&#xff0c;开始在所有平台上推出这一功能。 1.1. 更新版本&#xff1a; Chrome 126 在 ChromeOS、Linux、Mac、Windows 上&#xff1a;在 1% 的稳定版用户…

计算机网络三张表(ARP表、MAC表、路由表)总结

参考&#xff1a; 网络三张表&#xff1a;ARP表, MAC表, 路由表&#xff0c;实现你的网络自由&#xff01;&#xff01;_mac表、arp表、路由表-CSDN博客 网络中的三张表&#xff1a;ARP表、MAC表、路由表 首先要明确一件事&#xff0c;如果一个主机要发送数据&#xff0c;那么必…

cursor重构谷粒商城04——vagrant技术快速部署虚拟机

前言&#xff1a;这个系列将使用最前沿的cursor作为辅助编程工具&#xff0c;来快速开发一些基础的编程项目。目的是为了在真实项目中&#xff0c;帮助初级程序员快速进阶&#xff0c;以最快的速度&#xff0c;效率&#xff0c;快速进阶到中高阶程序员。 本项目将基于谷粒商城…

【玩转全栈】----Django连接MySQL

阅前先赞&#xff0c;养好习惯&#xff01; 目录 1、ORM框架介绍 选择建议 2、安装mysqlclient 3、创建数据库 4、修改settings&#xff0c;连接数据库 5、对数据库进行操作 创建表 删除表 添加数据 删除数据 修改&#xff08;更新&#xff09;数据&#xff1a; 获取数据 1、OR…

Jmeter使用Request URL请求接口

简介 在Jmeter调试接口时&#xff0c;有时不清楚后端服务接口的具体路径&#xff0c;可以使用Request URL和cookie来实现接口请求。以下内容以使用cookie鉴权的接口举例。 步骤 ① 登录网站后获取具体的Request URL和cookie信息 通过浏览器获取到Request URL和cookie&#…

mock可视化生成前端代码

介绍&#xff1a;mock是我们前后端分离的必要一环、ts、axios编写起来也很麻烦。我们就可以使用以下插件&#xff0c;来解决我们的问题。目前支持vite和webpack。&#xff08;配置超级简单&#xff01;&#xff09; 欢迎小伙伴们提issues、我们共建。提升我们的开发体验。 vi…

输入网址到网页显示,发生了什么--讲述

输入www.baidu.com作为网址&#xff0c; 孤身的人-HTTP 浏览器要做的第一步就是 解析URL&#xff0c;根据url里面的资源路径&#xff0c;确认服务器资源和路径&#xff0c;生成http请求消息&#xff0c;包括请求消息&#xff08;请求行 消息头 请求体&#xff09; 举例&am…

1.CSS的三大特性

css有三个非常重要的三个特性&#xff1a;层叠性、继承性、优先级 1.1 层叠性 想通选择器给设置想听的样式&#xff0c;此时一个样式就会覆盖&#xff08;层叠&#xff09;另一个冲突的样式。层叠性主要是解决样式冲突的问题。 <!DOCTYPE html> <html lang"en&…

Lock和Synchronized的区别,源码分析

Lock和Synchronized的区别&#xff0c;源码分析 探究Lock锁&#xff08;指实现Lock接口的锁&#xff0c;比如是ReentrantLock锁&#xff09;与Synchronized的区别。 以上区别都体现在Lock接口里定义的方法&#xff0c;以及实现Lock接口的类&#xff08;比如ReentrantLock&#…

如何把jupyter的一个.ipynb文件的多个单元格cell合并为1个cell

1 jupyter的一个.ipynb文件的多个单元格cell合并为1个cell 步骤 1&#xff1a;打开 your_notebook.ipynb 文件 启动 Jupyter Notebook。 导航到你的工作目录&#xff08;例如 F:\main&#xff09;。 打开 your_notebook.ipynb 文件。 步骤 2&#xff1a;选择所有单元格 点击…

Linux中的几个基本指令(二)

文章目录 1、cp指令例一&#xff1a;例二&#xff1a;例三&#xff1a;例四&#xff1a;例五&#xff1a; 2、mv 指令例一&#xff1a;例二&#xff1a; 3、cat指令例一&#xff1a; 4、tac指令5、which指令6、date指令时间戳&#xff1a;7、zip指令 今天我们继续学习Linux下的…

SSM开发(一)JAVA,javaEE,spring,springmvc,springboot,SSM,SSH等几个概念区别

目录 JAVA 框架 javaEE spring springmvc springboot SSM SSH maven JAVA 一种面向对象、高级编程语言&#xff0c;Python也是高级编程语言&#xff1b;不是框架(框架&#xff1a;一般用于大型复杂需求项目&#xff0c;用于快速开发)具有三大特性&#xff0c;所谓Jav…

GS论文阅读--GeoTexDensifier

前言 本文是一个关于高斯致密化策略对高斯地图进行优化&#xff0c;他主要关注了几何结构和纹理信息。我最近对于高斯点的分布比较感兴趣&#xff0c;因为高斯点的分布决定了之后重建质量的好坏&#xff0c;初始化高斯很重要&#xff0c;但之后的维护需要致密化与修建策略&…

计算机视觉算法实战——无人机检测

✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连✨ ​ ​ 1. 引言✨✨ 随着无人机技术的快速发展&#xff0c;无人机在农业、物流、监控等领域的应用越来越广泛。然而&#xff0c;无人机的滥用也带…