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::Linear
、QEasingCurve::InQuad
、QEasingCurve::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,我的設計幾乎從這里派生出來!