Qt自定义控件------SwitchButton

12 篇文章 10 订阅

简介

  一般用来两种互斥状态的切换。

效果

下面放效果图
在这里插入图片描述

控件拆分

一、控件大体有两部分组成,一个是背景,一个是白色的滑块。原本背景是想用QWidget然后直接设置圆角的,然后发现效果很差。所以背景改成QPainter来绘制,拆分为2个180°的圆弧和一个矩形。

二、背景 = 左圆弧 + 矩形 + 右圆弧。有两种做法,一种就是字面上的做法,通过drawArc画两个圆弧,drawLine画上下两条线,然后填充。另外的简单粗暴做法就是直接左右两端画椭圆,中间画矩形,然后填充背景,类似下图做法。
在这里插入图片描述
三、滑块的动画个人做法是把整个按钮的宽度分成N等份,也就是width / N,得到的值就是每次滑块移动的距离,这边我直接称为步进(step)。然后通过定时器定时重绘按钮,每次重绘时,滑块的位置都是上次距离+步进,这样就形成一个简单的动画了。如果不要动画直接定义一个start和end的位置直接重绘就行了。

四、按钮的圆弧宽度和矩形宽度的话,个人是取宽高的最小值作为圆弧的半径,然后矩形宽度为宽高最大值减去宽高的最小值。当然这有个毛病,宽高一样的时候,button就是个圆形了,所以设置宽高时候,宽和高最好有10px左右的差值。

五、为了美观,滑块和背景最好有2~3px左右的间距。

2022/11/16修改

  感谢网友 jeneralriter 的指正。原本的switchbutton单独采用一个定时器,在大量控件或性能吃紧的情况下,会造成卡顿。现在将switchbutton拆为两个模块:一个背景Widget,一个滑块Widget。动画统一采用Qt的Animation管理,暂时避免大量定时器情况。

实现

#ifndef SWITCHBUTTON_H
#define SWITCHBUTTON_H

#include <QWidget>

class Slider;

class SwitchButton : protected QWidget
{
    Q_OBJECT

public:
    explicit SwitchButton(QWidget *parent = nullptr);
    ~SwitchButton();
    /**
     * @brief SetSize 设置按钮的尺寸
     * @param nWidth 按钮的新宽度
     * @param nHeight 按钮的新高度
     */
    void SetSize(int nWidth, int nHeight);

    /**
     * @brief SetActiveColor 设置按钮激活时候的颜色
     * @param color 激活颜色
     */
    void SetActiveColor(const QColor& color);

    /**
     * @brief SetInactiveColor 设置按钮未激活时候的颜色
     * @param color 未激活颜色
     */
    void SetInactiveColor(const QColor& color);

    /**
     * @brief SetSliderColor 设置滑块颜色
     * @param color 滑块的颜色
     */
    void SetSliderColor(const QColor& color);

    /**
     * @brief SetStatus 设置按钮状态
     * @param bActive true: 激活,false: 未激活
     */
    void SetStatus(bool bActive);

    /**
     * @brief GetStatus 获取按钮当前状态
     * @return  true: 激活,false: 未激活
     */
    bool GetStatus() const;

protected:
    void paintEvent(QPaintEvent *event) override;
    void mousePressEvent(QMouseEvent *event) override;

    /**
     * @brief ToActive 按钮状态变为Active
     */
    void ToActive();

    /**
     * @brief ToInactive 按钮状态变为Inactive
     */
    void ToInactive();

private:
    bool m_bActive; // 是否激活
    int m_nArcRadius; // 圆弧的半径
    int m_nRectWidth; // 矩形的宽度
    const short m_nMargin = 2;
    const int m_nDuration = 100; // 动画时间,单位毫秒
    bool m_bClicked; // 能否被点击。如果动画还没结束,无法进行点击/状态切换
    QColor m_colorActive; // 激活时的颜色
    QColor m_colorInactive;
    Slider* m_pSlider;

signals:
    /**
     * @brief Clicked 按钮被点击后发出的信号
     * @param status 当前按钮状态。true为active,false为inactive
     */
    void Clicked(bool status);
};


class Slider : public QWidget
{
    Q_OBJECT
public:
    explicit Slider(QWidget* parent = nullptr);
    ~Slider();

    /**
     * @brief SetSliderColor 设置滑块颜色
     * @param color
     */
    void SetSliderColor(const QColor& color);

protected:
    void paintEvent(QPaintEvent* e) override;

private:
    QColor m_sliderColor;
};

#endif



#include "SwitchButton.h"
#include <QPainter>
#include <QPainterPath>
#include <QPropertyAnimation>

SwitchButton::SwitchButton(QWidget *parent) :
    QWidget(parent)
{
    resize(60, 30); // 默认60,30宽高
    m_bClicked = true;
    m_pSlider = new Slider(this);
    m_pSlider->resize(height() - m_nMargin * 2, height() - m_nMargin * 2);
    m_pSlider->move(m_nMargin, m_nMargin);
    m_bActive = false; // 默认未激活
    m_nArcRadius = std::min(width(), height()); // 默认半径
    m_nRectWidth = width() - m_nArcRadius;
    m_colorActive = Qt::green;
    m_colorInactive = Qt::red;
    setCursor(QCursor(Qt::PointingHandCursor));
}

SwitchButton::~SwitchButton()
{
}

void SwitchButton::SetSize(int nWidth, int nHeight)
{
    resize(nWidth, nHeight);
    m_pSlider->resize(height() - m_nMargin * 2, height() - m_nMargin * 2);
    m_pSlider->move(m_nMargin, m_nMargin);
    m_nArcRadius = std::min(width(), height());
    m_nRectWidth = width() - m_nArcRadius;
}

void SwitchButton::SetActiveColor(const QColor& color)
{
    m_colorActive = color;
}

void SwitchButton::SetInactiveColor(const QColor& color)
{
    m_colorInactive = color;
}

void SwitchButton::SetSliderColor(const QColor& color)
{
    m_pSlider->SetSliderColor(color);
}

void SwitchButton::SetStatus(bool bActive)
{
    if(m_bActive == bActive || !m_bClicked)
    {
        return;
    }
    m_bClicked = false;
    m_bActive = bActive;
    if(m_bActive)
    {
        ToActive();
    }
    else
    {
        ToInactive();
    }
    emit Clicked(m_bActive);
}

bool SwitchButton::GetStatus() const
{
    return m_bActive;
}

void SwitchButton::paintEvent(QPaintEvent *)
{
    QPainter p;
    p.begin(this);
    p.setRenderHint(QPainter::Antialiasing, true);
    p.setPen(Qt::NoPen);
    if(m_bActive) p.setBrush(QBrush(m_colorActive));
    else p.setBrush(QBrush(m_colorInactive));

    QPainterPath leftPath;
    leftPath.addEllipse(0, 0, m_nArcRadius, m_nArcRadius);

    QPainterPath middlePath;
    middlePath.addRect(m_nArcRadius / 2, 0, m_nRectWidth, m_nArcRadius);

    QPainterPath rightPath;
    rightPath.addEllipse(m_nRectWidth, 0, m_nArcRadius, m_nArcRadius);

    QPainterPath path = leftPath + middlePath + rightPath;

    p.drawPath(path);

    p.end();
}

void SwitchButton::mousePressEvent(QMouseEvent *event)
{
    SetStatus(!m_bActive);
    QWidget::mousePressEvent(event);
}

void SwitchButton::ToActive()
{
    QPropertyAnimation* pAnimation = new QPropertyAnimation(m_pSlider, "geometry");
    pAnimation->setDuration(m_nDuration);
    pAnimation->setStartValue(m_pSlider->rect());
    pAnimation->setEndValue(QRect(width() - m_pSlider->width() - m_nMargin,
                                  m_nMargin,
                                  m_pSlider->width(),
                                  m_pSlider->height()));
    connect(pAnimation, &QPropertyAnimation::finished, this, [&]{
       if(!m_bClicked)
       {
           m_bClicked = true; // 结束后还原标识位
       }
    });
    connect(pAnimation, &QPropertyAnimation::valueChanged, this, [&](const QVariant &value){
       Q_UNUSED(value)
       update();
    });
    pAnimation->start(QAbstractAnimation::DeleteWhenStopped);
}

void SwitchButton::ToInactive()
{
    QPropertyAnimation* pAnimation = new QPropertyAnimation(m_pSlider, "geometry");
    pAnimation->setDuration(m_nDuration);
    pAnimation->setStartValue(QRect(m_pSlider->x(),
                                    m_pSlider->y(),
                                    m_pSlider->width(),
                                    m_pSlider->height()));
    pAnimation->setEndValue(QRect(m_nMargin,
                                  m_nMargin,
                                  m_pSlider->width(),
                                  m_pSlider->height()));
    connect(pAnimation, &QPropertyAnimation::finished, this, [&]{
       if(!m_bClicked)
       {
           m_bClicked = true; // 结束后还原标识位
       }
    });
    connect(pAnimation, &QPropertyAnimation::valueChanged, this, [&](const QVariant &value){
       Q_UNUSED(value)
       update();
    });
    pAnimation->start(QAbstractAnimation::DeleteWhenStopped);
}


///
/// Slider  滑块类  //
//

Slider::Slider(QWidget *parent) : QWidget(parent)
{
    m_sliderColor = Qt::white;
    resize(60, 60);
}

Slider::~Slider()
{

}

void Slider::SetSliderColor(const QColor &color)
{
    m_sliderColor = color;
    update();
}

void Slider::paintEvent(QPaintEvent *e)
{
    QPainter p(this);
    p.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
    p.fillRect(rect(), Qt::transparent);
    p.setBrush(m_sliderColor);
    p.setPen(Qt::NoPen);
    p.drawRoundedRect(rect(), width() / 2, height() / 2);
    QWidget::paintEvent(e);
}


若有更好的方法,评论区欢迎讨论指正~

其它控件

更多控件查看Qt自定义控件合集

  • 7
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 30
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 30
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值