QT自定义开关按钮,实现手机风格的开关效果

先看效果图

 项目结构文件如下,没有使用ui文件

 untitled8.pro项目文件内容

QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

CONFIG += c++17

# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \
    main.cpp \
    mainwindow.cpp

HEADERS += \
    ToggleSwitch.h \
    mainwindow.h

FORMS +=

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
untitled8.pro

头文件内容

// mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include "ToggleSwitch.h"

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    ToggleSwitch *togglePort;
    void initUI();
    void initConnections();
};

#endif // MAINWINDOW_H
mainwindow.h
/* ToggleSwitch.h */
#ifndef TOGGLESWITCH_H
#define TOGGLESWITCH_H

#include <QAbstractButton>
#include <QPainter>
#include <QPropertyAnimation>
#include <QMouseEvent>

/**
 * @brief 自定义开关按钮(Toggle Switch),继承自 QAbstractButton
 *
 * 通过绘制圆角矩形轨道和圆形滑块,实现 iOS 风格的开关效果。
 * 支持点击切换状态,并带有滑动动画。
 */
class ToggleSwitch : public QAbstractButton {
    Q_OBJECT
    // 声明一个可动画的属性:offset,用于控制滑块水平偏移量
    // WRITE 表示可写,NOTIFY 表示属性变化时发出 offsetChanged 信号
    Q_PROPERTY(int offset READ offset WRITE setOffset NOTIFY offsetChanged)

public:
    /**
     * @brief 构造函数
     * @param parent 父组件指针,默认为 nullptr
     */
    explicit ToggleSwitch(QWidget *parent = nullptr)
        : QAbstractButton(parent)
        , m_offset(0)  // 初始滑块偏移量为 0
    {
        // 设置按钮为可切换状态,以维护 on/off 两种状态
        setCheckable(true);
        // 固定大小策略,不随布局伸缩
        setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
        // 鼠标悬停时显示手型光标
        setCursor(Qt::PointingHandCursor);

        // 创建属性动画,用于滑块平滑移动
        m_animation = new QPropertyAnimation(this, "offset", this);
        m_animation->setDuration(120); // 动画时长 120 毫秒
    }

    /**
     * @brief 推荐的组件大小
     * @return QSize 推荐宽度 50,高度 30
     */
    QSize sizeHint() const override {
        return QSize(50, 30);
    }

    /** @brief 读取当前滑块偏移量 */
    int offset() const { return m_offset; }

    /**
     * @brief 设置滑块偏移量(动画执行时调用)
     * @param off 新的偏移量值
     */
    void setOffset(int off) {
        if (m_offset == off)
            return;               // 若无变化,则直接返回
        m_offset = off;           // 更新偏移量
        update();                 // 触发重绘
        emit offsetChanged(off);  // 发出属性变化信号
    }

signals:
    /**
     * @brief offset 属性变化通知信号
     * @param offset 新的偏移量
     */
    void offsetChanged(int offset);

protected:
    /**
     * @brief 重写绘制事件
     * @param 未使用的 QPaintEvent 指针
     */
    void paintEvent(QPaintEvent *) override {
        QPainter p(this);
        p.setRenderHint(QPainter::Antialiasing); // 开启抗锯齿

        // ---- 绘制轨道 ----
        // 轨道区域:从 (0,0) 开始,宽度为控件宽度,高度为控件高度
        QRectF trackRect(0, 0, width(), height());
        // 根据开关状态选择轨道颜色:打开时绿色,关闭时浅灰色
        QColor trackColor = isChecked()
                                ? QColor(0, 200, 0)     // 打开:绿色
                                : QColor(200, 200, 200); // 关闭:浅灰色
        p.setBrush(trackColor);
        p.setPen(Qt::NoPen); // 不绘制边框
        // 圆角半径设为高度一半,使轨道呈胶囊形
        p.drawRoundedRect(trackRect, height() / 2, height() / 2);

        // ---- 绘制滑块(thumb) ----
        // 滑块直径比控件高度小 4 像素
        int diameter = height() - 4;
        // 滑块位置:水平偏移 m_offset + 左边距 2 像素,垂直偏移 2 像素
        QRectF thumbRect(2 + m_offset, 2, diameter, diameter);
        p.setBrush(Qt::white); // 滑块填充白色
        p.drawEllipse(thumbRect); // 绘制圆形滑块
    }

    /**
     * @brief 重写鼠标释放事件,响应点击切换
     * @param e 鼠标事件指针
     */
    void mouseReleaseEvent(QMouseEvent *e) override {
        // 仅在左键释放且点击区域在控件内部时才处理切换逻辑
        if (e->button() == Qt::LeftButton && rect().contains(e->pos())) {
            // 调用父类,切换 checked 状态并发射 toggled() 信号
            QAbstractButton::mouseReleaseEvent(e);

            // 重新配置动画起始值和结束值
            m_animation->stop();                 // 停止当前动画
            m_animation->setStartValue(m_offset); // 当前偏移作为起点
            // 目标偏移:打开时移动到 (宽度 - 高度),否则回到 0
            int endValue = isChecked() ? (width() - height()) : 0;
            m_animation->setEndValue(endValue);  // 设置终点
            m_animation->start();                // 启动动画
        } else {
            // 其他情况交由父类处理(如键盘激活等)
            QAbstractButton::mouseReleaseEvent(e);
        }
    }

private:
    int m_offset;                      // 滑块当前水平偏移量
    QPropertyAnimation *m_animation;   // 控制滑块移动的动画对象
};

#endif // TOGGLESWITCH_H
ToggleSwitch.h

源文件内容

// main.cpp
#include <QApplication>
#include "mainwindow.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}
main.cpp
// mainwindow.cpp
#include "mainwindow.h"
#include <QHBoxLayout>
#include <QWidget>
#include <QMessageBox>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    initUI();
    initConnections();
}

MainWindow::~MainWindow()
{
}

void MainWindow::initUI()
{
    // 创建一个中央 widget 和布局
    QWidget *central = new QWidget(this);
    auto *layout = new QHBoxLayout(central);

    // 新建 ToggleSwitch
    togglePort = new ToggleSwitch;
    // togglePort->setToolTip("打开/关闭串口");

    layout->addStretch();
    layout->addWidget(togglePort);
    layout->addStretch();

    setCentralWidget(central);
    resize(200, 100);
}

void MainWindow::initConnections()
{
    // 监听滑动开关状态
    connect(togglePort, &ToggleSwitch::toggled, this, [this](bool on){
        if (on) {
            // TODO: 打开串口
        } else {
            // TODO: 关闭串口
        }
    });
}
mainwindow.cpp

 

posted @ 2025-06-16 15:46  阿坦  阅读(76)  评论(0)    收藏  举报