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

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 SideBarUtilsToolsSideBarWidget::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 layoutresize(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 SideBarWidgetStaticConfignamespace Ui {
class SideBarWidget;
}class SideBarWidget : public QWidget {Q_OBJECTpublic: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.mzph.cn/web/66778.shtml

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

相关文章

工作流引擎Camunda与LiteFlow核心组件对比

以下为 Camunda 7 和 LiteFlow 详细的介绍&#xff0c;包括它们的核心组件和用途。 1. Camunda 7 详细介绍 Camunda 7 是一个基于 BPMN 2.0 标准的企业级工作流和决策自动化平台。它被广泛应用于复杂业务流程的管理和执行&#xff0c;其核心目标是通过流程自动化来提升企业效…

css动画水球图

由于echarts水球图动画会导致ios卡顿&#xff0c;所以纯css模拟 展示效果 组件 <template><div class"water-box"><div class"water"><div class"progress" :style"{ --newProgress: newProgress % }"><…

iOS 权限管理:同时请求相机和麦克风权限的最佳实践

引言 在开发视频类应用时&#xff0c;我们常常会遇到需要同时请求相机和麦克风权限的场景。比如&#xff0c;在用户发布视频动态时&#xff0c;相机用于捕捉画面&#xff0c;麦克风用于录制声音&#xff1b;又或者在直播功能中&#xff0c;只有获得这两项权限&#xff0c;用户…

Java 泛型上下限详解:以 Info 泛型类和方法实现为例

本文将通过一个实际示例&#xff0c;来深入讲解 Java 泛型中的上下限及其应用场景。在这个示例中&#xff0c;我们会实现一个泛型类 Info 和两个泛型方法 upperLimit 和 lowerLimit&#xff0c;并解释其工作机制。 1. 什么是 Java 泛型上下限&#xff1f; Java 泛型的上下限是…

客户服务创新:数字化时代的策略与实践

在数字化时代背景下&#xff0c;客户服务已成为企业竞争的关键领域。随着消费者需求的日益多样化和个性化&#xff0c;传统的客户服务模式已难以满足市场的要求。因此&#xff0c;企业需要不断探索和创新客户服务策略&#xff0c;以适应数字化时代的变化。 一、数字化时代客户服…

【PyCharm】远程连接Linux服务器

【PyCharm】相关链接 【PyCharm】连接Jupyter Notebook【PyCharm】快捷键使用【PyCharm】远程连接Linux服务器【PyCharm】设置为中文界面 【PyCharm】远程连接Linux服务器 PyCharm 提供了远程开发的功能&#xff0c;使得开发者可以在本地编辑代码或使用服务器资源。 下面将详…

十三、数据的的输入与输出(3)

数据的输出 writeClipboard&#xff08;&#xff09;函数 writeClipboard&#xff08;&#xff09;函数可以将数据输出至剪贴板。 例如&#xff0c;将R的内置数据集iris输出到剪贴板&#xff0c;在进入Excel中点击"粘贴"。 head(iris) #查看数据集Sepal.L…

PyQt5之QDialog

1.描述 QDialog是对话窗口的基类&#xff0c;对话窗口是顶级窗口&#xff0c;主要用于短期任务和与用户的简短通信。 可分为模态对话框和非模态对话框。 模态对话框又可以分为应用程序级别和窗口级别。 ​ 应用程序级别&#xff1a;当该种模态的对话框出现时&#xff0c;用…

Next.js:构建大模型智能体GPT研究者应用的 Web开发框架

Next.js&#xff1a;构建大模型智能体GPT研究者应用的 Web开发框架 Next.js 基础知识 Next.js 是由 Vercel 公司开发维护的框架&#xff0c;极大地简化了 React 应用的开发流程。其核心特性包括&#xff1a; 服务器端渲染&#xff08;SSR&#xff09;与静态站点生成&#xff…

车载软件架构 --- CP和AP作为中央计算平台的软件架构双核心

我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 简单&#xff0c;单纯&#xff0c;喜欢独处&#xff0c;独来独往&#xff0c;不易合同频过着接地气的生活…

华为EC6110T-海思Hi3798MV310_安卓9.0_通刷-强刷固件包

华为EC6110T-海思Hi3798MV310_安卓9.0_通刷-强刷固件包 刷机教程说明&#xff1a; 适用机型&#xff1a;华为EC6110-T、华为EC6110-U、华为EC6110-M 破解总分为两个部分&#xff1a;拆机短接破解&#xff08;保留IPTV&#xff09;和OTT卡刷&#xff08;不保留IPTV&#xff09…

Element使用表单重置如果不使用prop,重置无法生效

文章目录 为什么需要 prop&#xff1f;示例&#xff1a;使用 prop 的正确方式关键点总结 在 element-ui 的 el-form 组件中&#xff0c; prop 属性是与表单验证和表单字段绑定密切相关的&#xff0c;尤其在使用 resetFields() 重置表单数据时。 如果不使用 prop&#xff0…

使用pyboard、micropython和tja1050进行can通信

单片机和can收发器之间tx、rx不需要交叉接线&#xff01;&#xff01;&#xff01; tja1050的rx接Y3、tx接Y4 from pyb import CANcan CAN(1) can.init(modecan.NORMAL, prescaler6, sjw1, bs14, bs22, auto_restartTrue) # 1Mbps的配置&#xff0c;本文使用的micropython1.…

【信息系统项目管理师】高分论文:论信息系统项目的干系人管理(社保信息管理系统)

更多内容请见: 备考信息系统项目管理师-专栏介绍和目录 文章目录 论文1、识别干系人2、规划干系人参与3、管理干系人4、监督干系人论文 2016年3月,我作为项目经理参与了XX市社保信息管理系统项目的建设,该项目投资共450万元人民币,建设工期为1年,通过该项目的实施,在XX市…

JavaScript系列(39)-- Web Workers技术详解

JavaScript Web Workers技术详解 &#x1f504; 今天&#xff0c;让我们深入了解Web Workers技术&#xff0c;这是一种能够在后台线程中运行脚本的强大特性&#xff0c;可以避免阻塞主线程&#xff0c;提升Web应用的性能和响应性。 Web Workers基础概念 &#x1f31f; &#…

26、正则表达式

目录 一. 匹配字符 .&#xff1a;匹配除换行符外的任意单个字符。 二. 位置锚点 ^&#xff1a;匹配输入字符串的开始位置。 $&#xff1a;匹配输入字符串的结束位置。 \b&#xff1a;匹配单词边界。 \B&#xff1a;匹配非单词边界。 三. 重复限定符 *&#xff1a;匹配…

Chrome远程桌面无法连接怎么解决?

Chrome远程桌面连接已停止工作 Chrome远程桌面是一款极为便捷的浏览器插件&#xff0c;能够帮助用户将自己的计算机连接到其他设备&#xff0c;无论是手机、平板电脑还是其他电脑。然而&#xff0c;在实际使用中&#xff0c;许多用户可能会面临各种各样的问题&#xff0c;比如…

备赛蓝桥杯之第十五届职业院校组省赛第一题:智能停车系统

提示&#xff1a;本篇文章仅仅是作者自己目前在备赛蓝桥杯中&#xff0c;自己学习与刷题的学习笔记&#xff0c;写的不好&#xff0c;欢迎大家批评与建议 由于个别题目代码量与题目量偏大&#xff0c;请大家自己去蓝桥杯官网【连接高校和企业 - 蓝桥云课】去寻找原题&#xff0…

基于AutoDL云计算平台+LLaMA-Factory训练平台微调本地大模型

1. 注册与认证 访问AutoDL官网&#xff1a;前往 AutoDL官网。 注册账号&#xff1a;完成注册流程。 实名认证&#xff1a;按照要求完成实名认证&#xff0c;以确保账号的合规性。 2. 选择GPU资源 进入算力市场&#xff1a;在官网首页点击“算力市场”菜单。 挑选GPU&#x…

C语言练习(19)

已知5个学生的4门课的成绩&#xff0c;要求求出每个学生的平均成绩&#xff0c;然后对平均成绩从高到低将各学生的成绩记录排序&#xff08;成绩最高的学生排在数组最前面的行&#xff0c;成绩最低的学生排在数组最后面的行&#xff09;。 #include <stdio.h> #include &…