效果示例:
MSwitchButton.h
//********************************************************
/// @brief 开关按钮
/// @author y974183789@gmail.com
/// @date 2021/9/1
/// @note 状态切换时,支持动画效果
/// @version 1.0.0
//********************************************************
#pragma once
#include <QtCore/QPropertyAnimation>
#include <QtWidgets/QAbstractButton>
#include "MDefine.h"
class MSwitchButton : public QAbstractButton {
Q_OBJECT
Q_PROPERTY(int offset READ offset WRITE setOffset)
public:
//开关布局方向
typedef enum IndicatorLayoutDirection{
LeftToRight,//左到右
Horizontal = LeftToRight,//水平
RightToLeft,//右到左
TopToBottom,//上到下
Vertical = TopToBottom,//垂直
BottomToTop,//下到上
}IndicatorLayoutDirection;
public:
MSwitchButton(QWidget* parent = nullptr);
void setChecked(bool bChecked);
void setIndicatorLayout(IndicatorLayoutDirection layoutDirection);
protected:
void paintEvent(QPaintEvent*) override;
void mouseReleaseEvent(QMouseEvent*) override;
//鼠标进入事件
void enterEvent(MEnterEvent *pEvent) Q_DECL_OVERRIDE;
void keyPressEvent(QKeyEvent *e) override;
QSize sizeHint() const Q_DECL_OVERRIDE;
private:
int offset() const;
void setOffset(int nOffset) {
m_nOffset = nOffset;
update();
}
private:
int m_nOffset = 0;
int m_nRadius = 7;
int m_nMargin = 2;
QPropertyAnimation *m_animation = nullptr;
IndicatorLayoutDirection m_layoutDirection;
};
MSwitchButton.cpp
//********************************************************
/// @brief 开关按钮
/// @author y974183789@gmail.com
/// @date 2021/9/1
/// @note 状态切换时,支持动画效果
/// @version 1.0.0
//********************************************************
#include "MSwitchButton.h"
#include <QtCore/QDebug>
#include <QtGui/QPainter>
#include <QtGui/QMouseEvent>
#include "Components/MDefine.h"
#include <cmath>
MSwitchButton::MSwitchButton(QWidget *parent) : QAbstractButton(parent)
{
setIndicatorLayout(Horizontal);
setOffset(m_nRadius + m_nMargin);
setCheckable(true);
setStyleSheet("/* /");
m_animation = new QPropertyAnimation(this, "offset", this);
}
void MSwitchButton::paintEvent(QPaintEvent *e)
{
QPainter p(this);
p.setPen(Qt::NoPen);
QRect rectBg(QRect(0, 0, width(), height()));
QRectF rectIndicator;
switch (m_layoutDirection) {
case LeftToRight:
rectIndicator = QRectF(offset() - m_nRadius, qMax(0, height() / 2 - m_nRadius), m_nRadius * 2, m_nRadius * 2);
break;
case RightToLeft:
rectIndicator = QRectF(width() - offset() - m_nRadius, qMax(0, height() / 2 - m_nRadius), m_nRadius * 2, m_nRadius * 2);
break;
case TopToBottom:
rectIndicator = QRectF(qMax(0, width() / 2 - m_nRadius), offset() - m_nRadius, m_nRadius * 2, m_nRadius * 2);
break;
case BottomToTop:
rectIndicator = QRectF(qMax(0, width() / 2 - m_nRadius), height() - offset() - m_nRadius, m_nRadius * 2, m_nRadius * 2);
break;
default:
break;
}
p.setBrush(isChecked() ? QBrush(M_COLOR_HIGHLIGHT_BACKGROUND) : QBrush(M_COLOR_BACKGROUND));
p.setOpacity(1.0);
p.setRenderHint(QPainter::Antialiasing, true);
p.drawRoundedRect(rectBg, qMin(width(), height()) / 2.0, qMin(width(), height()) / 2.0);
p.setBrush(isChecked() ? QBrush(M_COLOR_HIGHLIGHT_TEXT) : QBrush(M_COLOR_NORMAL_TEXT));
p.setOpacity(1.0);
p.drawEllipse(rectIndicator);
}
void MSwitchButton::mouseReleaseEvent(QMouseEvent *e)
{
if (e->button() & Qt::LeftButton)
{
int nLeftOffset = m_nRadius + m_nMargin;
int nRightOffset = 0;
switch (m_layoutDirection) {
case LeftToRight:
case RightToLeft:
nRightOffset = width() - (m_nRadius + m_nMargin);
break;
case TopToBottom:
case BottomToTop:
nRightOffset = height() - (m_nRadius + m_nMargin);
break;
default:
break;
}
if(isChecked()) {
std::swap(nLeftOffset, nRightOffset);
}
m_animation->setStartValue(nLeftOffset);
m_animation->setEndValue(nRightOffset);
m_animation->setDuration(120);
m_animation->start();
}
QAbstractButton::mouseReleaseEvent(e);
}
void MSwitchButton::enterEvent(MEnterEvent *event)
{
setCursor(Qt::PointingHandCursor);
QAbstractButton::enterEvent(event);
}
void MSwitchButton::keyPressEvent(QKeyEvent *e)
{
if (e->key() == Qt::Key_Space)
{
return;
}
return QAbstractButton::keyPressEvent(e);
}
void MSwitchButton::setChecked(bool bChecked)
{
if(bChecked != isChecked()) {
QAbstractButton::setChecked(bChecked);
switch (m_layoutDirection) {
case LeftToRight:
setOffset(isChecked() ? width() - (m_nRadius + m_nMargin) : m_nRadius + m_nMargin);
break;
case RightToLeft:
setOffset(isChecked() ? m_nRadius + m_nMargin : width() - (m_nRadius + m_nMargin));
break;
case TopToBottom:
setOffset(isChecked() ? height() - (m_nRadius + m_nMargin) : m_nRadius + m_nMargin);
break;
case BottomToTop:
setOffset(isChecked() ? m_nRadius + m_nMargin : height() - (m_nRadius + m_nMargin));
break;
default:
break;
}
update();
}
}
void MSwitchButton::setIndicatorLayout(IndicatorLayoutDirection layoutDirection) {
m_layoutDirection = layoutDirection;
}
QSize MSwitchButton::sizeHint() const
{
QSize size = QAbstractButton::sizeHint();
if (TopToBottom == m_layoutDirection || BottomToTop == m_layoutDirection)
{
size.setHeight(qMax(30, size.width()));
size.setWidth(20);
} else {
size.setHeight(qMax(20, size.height()));
size.setWidth(qMax(40, size.width()));
}
return size;
}
int MSwitchButton::offset() const {
switch (m_layoutDirection) {
case LeftToRight:
case RightToLeft:
return qMax(m_nMargin + m_nRadius, qMin(m_nOffset, width() - m_nRadius - m_nMargin));
case TopToBottom:
case BottomToTop:
return qMax(m_nMargin, qMin(m_nOffset, height() - m_nRadius - m_nMargin));
default:
break;
}
return m_nOffset;
}