├── .gitignore ├── LinearCheckBox.pro ├── README.md ├── linear_check_box ├── anicheckbox.cpp ├── anicheckbox.h ├── checkbox1.cpp └── checkbox1.h ├── main.cpp ├── mainwindow.cpp ├── mainwindow.h ├── mainwindow.ui └── screenshot.gif /.gitignore: -------------------------------------------------------------------------------- 1 | # This file is used to ignore files which are generated 2 | # ---------------------------------------------------------------------------- 3 | 4 | *~ 5 | *.autosave 6 | *.a 7 | *.core 8 | *.moc 9 | *.o 10 | *.obj 11 | *.orig 12 | *.rej 13 | *.so 14 | *.so.* 15 | *_pch.h.cpp 16 | *_resource.rc 17 | *.qm 18 | .#* 19 | *.*# 20 | core 21 | !core/ 22 | tags 23 | .DS_Store 24 | .directory 25 | *.debug 26 | Makefile* 27 | *.prl 28 | *.app 29 | moc_*.cpp 30 | ui_*.h 31 | qrc_*.cpp 32 | Thumbs.db 33 | *.res 34 | *.rc 35 | /.qmake.cache 36 | /.qmake.stash 37 | 38 | # qtcreator generated files 39 | *.pro.user* 40 | 41 | # xemacs temporary files 42 | *.flc 43 | 44 | # Vim temporary files 45 | .*.swp 46 | 47 | # Visual Studio generated files 48 | *.ib_pdb_index 49 | *.idb 50 | *.ilk 51 | *.pdb 52 | *.sln 53 | *.suo 54 | *.vcproj 55 | *vcproj.*.*.user 56 | *.ncb 57 | *.sdf 58 | *.opensdf 59 | *.vcxproj 60 | *vcxproj.* 61 | 62 | # MinGW generated files 63 | *.Debug 64 | *.Release 65 | 66 | # Python byte code 67 | *.pyc 68 | 69 | # Binaries 70 | # -------- 71 | *.dll 72 | *.exe 73 | 74 | -------------------------------------------------------------------------------- /LinearCheckBox.pro: -------------------------------------------------------------------------------- 1 | QT += core gui 2 | 3 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 4 | 5 | CONFIG += c++11 6 | 7 | # The following define makes your compiler emit warnings if you use 8 | # any Qt feature that has been marked deprecated (the exact warnings 9 | # depend on your compiler). Please consult the documentation of the 10 | # deprecated API in order to know how to port your code away from it. 11 | DEFINES += QT_DEPRECATED_WARNINGS 12 | 13 | # You can also make your code fail to compile if it uses deprecated APIs. 14 | # In order to do so, uncomment the following line. 15 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 16 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 17 | 18 | SOURCES += \ 19 | linear_check_box/anicheckbox.cpp \ 20 | linear_check_box/checkbox1.cpp \ 21 | main.cpp \ 22 | mainwindow.cpp 23 | 24 | HEADERS += \ 25 | linear_check_box/anicheckbox.h \ 26 | linear_check_box/checkbox1.h \ 27 | mainwindow.h 28 | 29 | INCLUDEPATH += \ 30 | linear_check_box 31 | 32 | FORMS += \ 33 | mainwindow.ui 34 | 35 | # Default rules for deployment. 36 | qnx: target.path = /tmp/$${TARGET}/bin 37 | else: unix:!android: target.path = /opt/$${TARGET}/bin 38 | !isEmpty(target.path): INSTALLS += target 39 | 40 | DISTFILES += \ 41 | README.md \ 42 | screenshot.gif 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CheckBox 2 | === 3 | 4 | 一个带切换动画的 CheckBox。没有其他特殊效果。 5 | 6 | ![截图](screenshot.gif) -------------------------------------------------------------------------------- /linear_check_box/anicheckbox.cpp: -------------------------------------------------------------------------------- 1 | #include "anicheckbox.h" 2 | 3 | AniCheckBox::AniCheckBox(QWidget *parent) : QCheckBox(parent) 4 | { 5 | setCursor(Qt::PointingHandCursor); 6 | 7 | connect(this, &QCheckBox::stateChanged, this, [=](int state) { 8 | // qInfo() << "状态变化:" << static_cast(state); 9 | checkStateChanged(state); 10 | }); 11 | } 12 | 13 | void AniCheckBox::setForeColor(QColor c) 14 | { 15 | this->foreColor = c; 16 | } 17 | 18 | void AniCheckBox::paintEvent(QPaintEvent *) 19 | { 20 | // QCheckBox::paintEvent(e); 21 | QPainter painter(this); 22 | // painter.setRenderHint(QPainter::Antialiasing, true); 23 | 24 | QRectF rect; 25 | double textLeft; 26 | if (boxSide <= 0) 27 | { 28 | // 自适应大小:优先一行文字大小,其次按比例 29 | const double fixedProp = 0.8; // 默认比例 30 | QFontMetricsF fm(painter.font()); 31 | double side = fm.height(); // 一行文字的高度 32 | if (side >= this->height() * fixedProp) 33 | side = this->height() * fixedProp; 34 | 35 | double margin = side / 2; 36 | rect = QRectF(margin, (height() - side) / 2, side, side); 37 | textLeft = rect.right() + margin; 38 | } 39 | else 40 | { 41 | // 固定大小 42 | double margin = (this->height() - boxSide) / 2; 43 | rect = QRectF(margin, margin, boxSide, boxSide); 44 | textLeft = rect.right() + margin; 45 | } 46 | 47 | // 绘制选择框 48 | painter.save(); 49 | drawBox(painter, rect); 50 | painter.restore(); 51 | 52 | // 绘制文字 53 | painter.save(); 54 | painter.drawText(QRectF(textLeft, 0, this->width() - textLeft, this->height()), this->text(), Qt::AlignVCenter | Qt::AlignLeft); 55 | painter.restore(); 56 | } 57 | 58 | void AniCheckBox::enterEvent(QEvent *e) 59 | { 60 | QCheckBox::enterEvent(e); 61 | startAnimation("hover_prog", getHoverProg(), 1); 62 | } 63 | 64 | void AniCheckBox::leaveEvent(QEvent *e) 65 | { 66 | QCheckBox::leaveEvent(e); 67 | startAnimation("hover_prog", getHoverProg(), 0); 68 | } 69 | 70 | bool AniCheckBox::hitButton(const QPoint &) const 71 | { 72 | return true; 73 | } 74 | 75 | void AniCheckBox::checkStateChanged(int state) 76 | { 77 | if (state == Qt::Unchecked) 78 | { 79 | startAnimation("check_prog", getCheckProg(), 0, 800, QEasingCurve::OutBounce); 80 | } 81 | else if (state == Qt::PartiallyChecked) 82 | { 83 | 84 | } 85 | else if (state == Qt::Checked) 86 | { 87 | startAnimation("check_prog", getCheckProg(), 1, 500, QEasingCurve::OutBack); 88 | } 89 | } 90 | 91 | void AniCheckBox::drawBox(QPainter& painter, QRectF rect) 92 | { 93 | painter.setPen(foreColor); 94 | painter.setRenderHint(QPainter::Antialiasing, true); 95 | 96 | // 绘制边缘方框,和悬浮状态有关 97 | double radius = 3; 98 | radius *= (1 - hoverProg); 99 | painter.drawRoundedRect(rect, radius, radius); 100 | 101 | // 绘制选中状态 102 | int state = this->checkState(); 103 | double prop = 0.6; 104 | prop *= checkProg; 105 | rect = QRectF( 106 | rect.left() + rect.width() * (1 - prop) / 2, 107 | rect.top() + rect.height() * (1 - prop) / 2, 108 | rect.width() * prop, 109 | rect.height() * prop 110 | ); 111 | QPainterPath path; 112 | path.addRoundedRect(rect, radius, radius); 113 | painter.fillPath(path, foreColor); 114 | 115 | if (state == Qt::Unchecked) 116 | { 117 | 118 | } 119 | else if (state == Qt::PartiallyChecked) 120 | { 121 | 122 | } 123 | else if (state == Qt::Checked) 124 | { 125 | 126 | } 127 | } 128 | 129 | QPropertyAnimation *AniCheckBox::startAnimation(const QByteArray &property, double begin, double end, int duration, QEasingCurve curve) 130 | { 131 | QPropertyAnimation* ani = new QPropertyAnimation(this, property); 132 | ani->setStartValue(begin); 133 | ani->setEndValue(end); 134 | ani->setDuration(duration); 135 | ani->setEasingCurve(curve); 136 | connect(ani, SIGNAL(finished()), ani, SLOT(deleteLater())); 137 | connect(ani, SIGNAL(valueChanged(const QVariant&)), this, SLOT(update())); 138 | ani->start(); 139 | return ani; 140 | } 141 | 142 | double AniCheckBox::getHoverProg() const 143 | { 144 | return hoverProg; 145 | } 146 | 147 | void AniCheckBox::setHoverProg(double prog) 148 | { 149 | this->hoverProg = prog; 150 | } 151 | 152 | double AniCheckBox::getPartProg() const 153 | { 154 | return partyProg; 155 | } 156 | 157 | void AniCheckBox::setPartProg(double prog) 158 | { 159 | this->partyProg = prog; 160 | } 161 | 162 | double AniCheckBox::getCheckProg() const 163 | { 164 | return checkProg; 165 | } 166 | 167 | void AniCheckBox::setCheckProg(double prog) 168 | { 169 | this->checkProg = prog; 170 | } 171 | -------------------------------------------------------------------------------- /linear_check_box/anicheckbox.h: -------------------------------------------------------------------------------- 1 | #ifndef LINEARCHECKBOX_H 2 | #define LINEARCHECKBOX_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | class AniCheckBox : public QCheckBox 11 | { 12 | Q_OBJECT 13 | Q_PROPERTY(double hover_prog READ getHoverProg WRITE setHoverProg) 14 | Q_PROPERTY(double part_prog READ getPartProg WRITE setPartProg) 15 | Q_PROPERTY(double check_prog READ getCheckProg WRITE setCheckProg) 16 | public: 17 | AniCheckBox(QWidget* parent = nullptr); 18 | 19 | void setForeColor(QColor c); 20 | 21 | protected: 22 | void paintEvent(QPaintEvent *) override; 23 | void enterEvent(QEvent *e) override; 24 | void leaveEvent(QEvent *e) override; 25 | bool hitButton(const QPoint &) const override; 26 | 27 | virtual void checkStateChanged(int state); 28 | 29 | virtual void drawBox(QPainter &painter, QRectF rect); 30 | 31 | QPropertyAnimation* startAnimation(const QByteArray &property, double begin, double end, int duration = 500, QEasingCurve curve = QEasingCurve::OutQuad); 32 | 33 | protected: 34 | double getHoverProg() const; 35 | void setHoverProg(double prog); 36 | double getPartProg() const; 37 | void setPartProg(double prog); 38 | double getCheckProg() const; 39 | void setCheckProg(double prog); 40 | 41 | protected: 42 | int boxSide = 0; // 选择框边长,0为自适应 43 | QColor foreColor = QColor("#2753ff"); // 前景颜色 44 | 45 | double hoverProg = 0; // 鼠标移上去的进度 46 | double partyProg = 0; // 部分选中的进度 47 | double checkProg = 0; // 选中的进度 48 | }; 49 | 50 | #endif // LINEARCHECKBOX_H 51 | -------------------------------------------------------------------------------- /linear_check_box/checkbox1.cpp: -------------------------------------------------------------------------------- 1 | #include "checkbox1.h" 2 | #include 3 | 4 | CheckBox1::CheckBox1(QWidget *parent) : AniCheckBox(parent) 5 | { 6 | 7 | } 8 | 9 | void CheckBox1::setUncheckedColor(QColor c) 10 | { 11 | this->uncheckedColor = c; 12 | update(); 13 | } 14 | 15 | void CheckBox1::enterEvent(QEvent *e) 16 | { 17 | QCheckBox::enterEvent(e); 18 | startAnimation("hover_prog", getHoverProg(), 1, 300, QEasingCurve::OutBack); 19 | } 20 | 21 | void CheckBox1::leaveEvent(QEvent *e) 22 | { 23 | QCheckBox::leaveEvent(e); 24 | startAnimation("hover_prog", getHoverProg(), 0, 300, QEasingCurve::OutBack); 25 | } 26 | 27 | void CheckBox1::checkStateChanged(int state) 28 | { 29 | if (state == Qt::Unchecked) 30 | { 31 | startAnimation("check_prog", getCheckProg(), 0, 1000, QEasingCurve::OutQuad); 32 | } 33 | else if (state == Qt::PartiallyChecked) 34 | { 35 | 36 | } 37 | else if (state == Qt::Checked) 38 | { 39 | startAnimation("check_prog", getCheckProg(), 1, 1000, QEasingCurve::Linear); 40 | } 41 | } 42 | 43 | void CheckBox1::drawBox(QPainter &painter, QRectF rect) 44 | { 45 | painter.setRenderHint(QPainter::Antialiasing, true); 46 | painter.setPen(QPen(uncheckedColor, qMax(rect.width() / 16, 1.0), Qt::SolidLine, Qt::RoundCap)); 47 | 48 | /// 绘制边缘方框,和悬浮状态有关 49 | QPainterPath borderPath; 50 | double radius = rect.width() / 8; 51 | double l = rect.left(), t = rect.top(), r = rect.right(), b = rect.bottom(), 52 | w = rect.width(), h = rect.height(); 53 | 54 | // arcTo:从0度开始,逆时针扫,参数为角度制 55 | double quadDishu = 5; 56 | borderPath.moveTo(l, t + radius); 57 | borderPath.arcTo(QRectF(l, t, radius * 2, radius * 2), 180, -90); 58 | borderPath.quadTo(QPointF(l + w / 2, t + h / quadDishu * hoverProg), QPointF(r - radius, t)); 59 | // borderPath.lineTo(r - radius, t); 60 | borderPath.arcTo(QRectF(r - radius * 2, t, radius * 2, radius * 2), 90, -90); 61 | borderPath.quadTo(QPointF(r - w / quadDishu * hoverProg, t + h / 2), QPointF(r, b - radius)); 62 | borderPath.arcTo(QRectF(r - radius * 2, b - radius * 2, radius * 2, radius * 2), 0, -90); 63 | borderPath.quadTo(QPointF(r - w / 2, b - h / quadDishu * hoverProg), QPointF(l + radius, b)); 64 | borderPath.arcTo(QRectF(l, b - radius * 2, radius * 2, radius * 2), -90, -90); 65 | borderPath.quadTo(QPointF(l + w / quadDishu * hoverProg, b - h / 2), QPointF(l, t + radius)); 66 | painter.drawPath(borderPath); 67 | 68 | /// 画选中 69 | painter.setPen(QPen(foreColor, qMax(rect.width() / 20, 1.0), Qt::SolidLine, Qt::RoundCap)); 70 | QPainterPath circlePath; 71 | circlePath.moveTo(l, t); 72 | const double outRad = sqrt(w * w + h * h) / 2; // 外接圆半径 73 | const double threshold = 0.6; // 到什么时刻转完一圈 74 | 75 | // 边框转圈 76 | double borderVal = checkProg; 77 | if (borderVal > threshold) 78 | borderVal = threshold; 79 | borderVal = borderVal / threshold; // 边缘的完成度的比例 80 | if (borderVal >= 1) 81 | borderVal = 1; 82 | circlePath.arcTo(QRectF(l + w / 2 - outRad, t + h / 2 - outRad, outRad * 2, outRad * 2), 135, 360 * borderVal); 83 | painter.save(); 84 | painter.setClipPath(circlePath); 85 | painter.drawPath(borderPath); 86 | painter.restore(); 87 | 88 | // 勾的出现 89 | if (checkProg > threshold) 90 | { 91 | double gou = checkProg - threshold; 92 | const double threshold = 0.13; 93 | const double restThreshold = 0.27; 94 | // QPointF point0(l, t + radius); // 从边缘连接到√ 95 | QPointF point1(l + w * 0.30, t + h * 0.45); 96 | QPointF point2(l + w * 0.45, t + h * 0.70); 97 | QPointF point3(l + w * 0.70, t + h * 0.32); 98 | 99 | // 第一段 100 | double prop = gou; 101 | if (prop >= threshold) 102 | prop = threshold; 103 | prop = prop / threshold; // 在当前段的比例(偏中心) 104 | QPainterPath path; 105 | path.moveTo(point1); 106 | path.lineTo(point2); 107 | painter.drawLine(QLineF(point1, path.pointAtPercent(prop))); 108 | 109 | // 第二段 110 | if (gou > threshold) 111 | { 112 | gou -= threshold; 113 | double prop = gou / restThreshold; // 在当前段的比例 114 | QPainterPath path2; 115 | path2.moveTo(point2); 116 | path2.lineTo(point3); 117 | painter.drawLine(QLineF(point2, path2.pointAtPercent(prop))); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /linear_check_box/checkbox1.h: -------------------------------------------------------------------------------- 1 | #ifndef CHECKBOX1_H 2 | #define CHECKBOX1_H 3 | 4 | #include "anicheckbox.h" 5 | 6 | class CheckBox1 : public AniCheckBox 7 | { 8 | public: 9 | CheckBox1(QWidget* parent = nullptr); 10 | 11 | void setUncheckedColor(QColor c); 12 | 13 | protected: 14 | void enterEvent(QEvent *e) override; 15 | void leaveEvent(QEvent *e) override; 16 | 17 | virtual void checkStateChanged(int state) override; 18 | 19 | virtual void drawBox(QPainter &painter, QRectF rect) override; 20 | 21 | protected: 22 | QColor uncheckedColor = QColor("#88888888"); 23 | }; 24 | 25 | #endif // CHECKBOX1_H 26 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | 3 | #include 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | QApplication a(argc, argv); 8 | MainWindow w; 9 | w.show(); 10 | return a.exec(); 11 | } 12 | -------------------------------------------------------------------------------- /mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include "ui_mainwindow.h" 3 | 4 | MainWindow::MainWindow(QWidget *parent) 5 | : QMainWindow(parent) 6 | , ui(new Ui::MainWindow) 7 | { 8 | ui->setupUi(this); 9 | } 10 | 11 | MainWindow::~MainWindow() 12 | { 13 | delete ui; 14 | } 15 | 16 | 17 | void MainWindow::on_checkBox_toggled(bool checked) 18 | { 19 | 20 | } 21 | -------------------------------------------------------------------------------- /mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | 6 | QT_BEGIN_NAMESPACE 7 | namespace Ui { class MainWindow; } 8 | QT_END_NAMESPACE 9 | 10 | class MainWindow : public QMainWindow 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | MainWindow(QWidget *parent = nullptr); 16 | ~MainWindow(); 17 | 18 | private slots: 19 | void on_checkBox_toggled(bool checked); 20 | 21 | private: 22 | Ui::MainWindow *ui; 23 | }; 24 | #endif // MAINWINDOW_H 25 | -------------------------------------------------------------------------------- /mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 493 10 | 204 11 | 12 | 13 | 14 | MainWindow 15 | 16 | 17 | 18 | 19 | 20 | 50 21 | 60 22 | 101 23 | 41 24 | 25 | 26 | 27 | CheckBox 28 | 29 | 30 | 31 | 32 | 33 | 190 34 | 50 35 | 231 36 | 71 37 | 38 | 39 | 40 | font-size: 30px; 41 | 42 | 43 | CheckBox 44 | 45 | 46 | 47 | 48 | 49 | 50 | 0 51 | 0 52 | 493 53 | 23 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | AniCheckBox 62 | QCheckBox 63 |
anicheckbox.h
64 |
65 | 66 | CheckBox1 67 | QCheckBox 68 |
checkbox1.h
69 |
70 |
71 | 72 | 73 |
74 | -------------------------------------------------------------------------------- /screenshot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/Qt-AniCheckBox/366cee9375b9c3800bb00bbf0a0ffc7038f6f704/screenshot.gif --------------------------------------------------------------------------------