简介
一般用来两种互斥状态的切换。
效果
下面放效果图
控件拆分
一、控件大体有两部分组成,一个是背景,一个是白色的滑块。原本背景是想用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自定义控件合集