├── screenshot.gif ├── README.md ├── main.cpp ├── mainwindow.h ├── .gitignore ├── Qt-PurelinButton.pro ├── mainwindow.ui ├── purelinbutton.h ├── mainwindow.cpp └── purelinbutton.cpp /screenshot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/Qt-PurelinButton/HEAD/screenshot.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 纯线按钮 2 | === 3 | 4 | 完全使用线条组成前景的按钮。 5 | 6 | 传入坐标与颜色,实时绘制 n 根线条,每一个显示效果之间的切换都带有干净利落的非线性动画变换效果。 7 | 8 | ![截图](screenshot.gif) -------------------------------------------------------------------------------- /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.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_pushButton_clicked(); 20 | 21 | private: 22 | Ui::MainWindow *ui; 23 | }; 24 | #endif // MAINWINDOW_H 25 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /Qt-PurelinButton.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 | main.cpp \ 20 | mainwindow.cpp \ 21 | purelinbutton.cpp 22 | 23 | HEADERS += \ 24 | mainwindow.h \ 25 | purelinbutton.h 26 | 27 | FORMS += \ 28 | mainwindow.ui 29 | 30 | # Default rules for deployment. 31 | qnx: target.path = /tmp/$${TARGET}/bin 32 | else: unix:!android: target.path = /opt/$${TARGET}/bin 33 | !isEmpty(target.path): INSTALLS += target 34 | 35 | DISTFILES += \ 36 | README.md \ 37 | screenshot.gif 38 | -------------------------------------------------------------------------------- /mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 360 10 | 268 11 | 12 | 13 | 14 | MainWindow 15 | 16 | 17 | 18 | 19 | 20 | 120 21 | 60 22 | 100 23 | 100 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 0 35 | 0 36 | 360 37 | 23 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | PurelinButton 46 | QPushButton 47 |
purelinbutton.h
48 |
49 |
50 | 51 | 52 |
53 | -------------------------------------------------------------------------------- /purelinbutton.h: -------------------------------------------------------------------------------- 1 | #ifndef PURELINBUTTON_H 2 | #define PURELINBUTTON_H 3 | 4 | #include 5 | #include 6 | 7 | #define MAX_LINE_COUNT 3 8 | 9 | struct PurelinStatus 10 | { 11 | QSizeF bgSize; // 背景尺寸 12 | double bgRadius = 0; // 背景圆角 13 | QColor bgColor = Qt::white; // 背景颜色 14 | int useLine = 0; // 使用的线条数量 15 | QLineF linePoss[MAX_LINE_COUNT]; // 各线条位置 16 | QPointF lineHide[MAX_LINE_COUNT]; // 默认消失的位置 17 | QColor lineColors[MAX_LINE_COUNT]; // 各线条颜色 18 | 19 | PurelinStatus(int useLine = 0) : useLine(useLine) 20 | { 21 | for (int i = useLine; i < MAX_LINE_COUNT; i++) 22 | { 23 | linePoss[i] = QLineF(0, 0, 0, 0); 24 | lineColors[i] = Qt::transparent; 25 | } 26 | } 27 | }; 28 | 29 | class PurelinButton : public QPushButton 30 | { 31 | Q_OBJECT 32 | Q_PROPERTY(double purelin_ani READ getPurelinAni WRITE setPurelinAni) 33 | public: 34 | PurelinButton(QWidget* parent = nullptr); 35 | 36 | const PurelinStatus& getCurrentStatus() const; 37 | void setCurrentStatus(PurelinStatus status); 38 | 39 | /// 加载显示状态 40 | void load(PurelinStatus status); 41 | 42 | protected: 43 | void paintEvent(QPaintEvent *) override; 44 | 45 | private: 46 | void setPurelinAni(double prog); 47 | double getPurelinAni() const; 48 | 49 | private slots: 50 | virtual void aniProgChanged(const QVariant& var); 51 | void aniProgFinished(); 52 | 53 | private: 54 | bool animating = false; 55 | double animationProg = 0; 56 | 57 | PurelinStatus currentStatus; 58 | PurelinStatus prevStatus; 59 | PurelinStatus nextStatus; 60 | }; 61 | 62 | #endif // PURELINBUTTON_H 63 | -------------------------------------------------------------------------------- /mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include "ui_mainwindow.h" 3 | #include 4 | #include 5 | 6 | MainWindow::MainWindow(QWidget *parent) 7 | : QMainWindow(parent) 8 | , ui(new Ui::MainWindow) 9 | { 10 | ui->setupUi(this); 11 | 12 | QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect(this); 13 | shadow->setOffset(3, 5); 14 | shadow->setColor(QColor("#44888888")); 15 | shadow->setBlurRadius(20); 16 | ui->pushButton->setGraphicsEffect(shadow); 17 | } 18 | 19 | MainWindow::~MainWindow() 20 | { 21 | delete ui; 22 | } 23 | 24 | 25 | void MainWindow::on_pushButton_clicked() 26 | { 27 | const int totalCount = 8; 28 | static int index = -1; 29 | int dx = 0; 30 | 31 | // - 32 | PurelinStatus status[totalCount]; 33 | int i = 0; 34 | status[i].bgSize = QSize(100, 60); 35 | status[i].bgColor = Qt::white; 36 | status[i].bgRadius = 30; 37 | status[i].useLine = 1; 38 | status[i].linePoss[0] = QLineF(35, 50, 65, 50); 39 | status[i].lineHide[0] = QPointF(50, 50); 40 | status[i].lineColors[0] = QColor("#ed657d"); 41 | 42 | // + 43 | i++; 44 | status[i].bgSize = QSize(-1, -1); 45 | status[i].bgColor = Qt::white; 46 | status[i].bgRadius = 30; 47 | status[i].useLine = 2; 48 | status[i].linePoss[0] = QLineF(35, 50, 65, 50); 49 | status[i].linePoss[1] = QLineF(50, 65, 50, 35); 50 | status[i].lineHide[0] = QPointF(50, 50); 51 | status[i].lineHide[1] = QPointF(50, 50); 52 | status[i].lineColors[0] = QColor("#5baaf8"); 53 | status[i].lineColors[1] = QColor("#5baaf8"); 54 | 55 | // x 56 | i++; 57 | status[i].bgSize = QSize(-1, -1); 58 | status[i].bgColor = Qt::white; 59 | status[i].bgRadius = 50; 60 | status[i].useLine = 2; 61 | status[i].linePoss[0] = QLineF(40, 40, 60, 60); 62 | status[i].linePoss[1] = QLineF(40, 60, 60, 40); 63 | status[i].lineHide[0] = QPointF(50, 50); 64 | status[i].lineHide[1] = QPointF(50, 50); 65 | status[i].lineColors[0] = QColor("#b9b9b9"); 66 | status[i].lineColors[1] = QColor("#b9b9b9"); 67 | 68 | // √ 69 | i++; 70 | dx = -2; 71 | status[i].bgSize = QSize(-1, -1); 72 | status[i].bgColor = Qt::white; 73 | status[i].bgRadius = 50; 74 | status[i].useLine = 2; 75 | status[i].linePoss[0] = QLineF(40 + dx, 50, 50 + dx, 60); 76 | status[i].linePoss[1] = QLineF(50 + dx, 60, 70 + dx, 40); 77 | status[i].lineHide[0] = QPointF(50 + dx, 50); 78 | status[i].lineHide[1] = QPointF(50 + dx, 50); 79 | status[i].lineColors[0] = QColor("#59ce84"); 80 | status[i].lineColors[1] = QColor("#59ce84"); 81 | 82 | // < 83 | i++; 84 | dx = -4; 85 | status[i].bgSize = QSize(-1, -1); 86 | status[i].bgColor = Qt::white; 87 | status[i].bgRadius = 50; 88 | status[i].useLine = 2; 89 | status[i].linePoss[0] = QLineF(42 + dx, 50, 58 + dx, 65); 90 | status[i].linePoss[1] = QLineF(42 + dx, 50, 58 + dx, 35); 91 | status[i].lineHide[0] = QPointF(50, 50); 92 | status[i].lineHide[1] = QPointF(50, 50); 93 | status[i].lineColors[0] = QColor("#4b6fea"); 94 | status[i].lineColors[1] = QColor("#4b6fea"); 95 | 96 | // = 97 | i++; 98 | status[i].bgSize = QSize(100, 60); 99 | status[i].bgColor = Qt::white; 100 | status[i].bgRadius = 30; 101 | status[i].useLine = 2; 102 | status[i].linePoss[0] = QLineF(35, 55, 65, 55); 103 | status[i].linePoss[1] = QLineF(35, 45, 65, 45); 104 | status[i].lineHide[0] = QPointF(50, 55); 105 | status[i].lineHide[1] = QPointF(50, 45); 106 | status[i].lineColors[0] = QColor("#eda244"); 107 | status[i].lineColors[1] = QColor("#eda244"); 108 | 109 | // 三 110 | i++; 111 | status[i].bgSize = QSize(-1, -1); 112 | status[i].bgColor = Qt::white; 113 | status[i].bgRadius = 20; 114 | status[i].useLine = 3; 115 | status[i].linePoss[1] = QLineF(34, 37, 66, 37); 116 | status[i].linePoss[0] = QLineF(40, 50, 60, 50); 117 | status[i].linePoss[2] = QLineF(46, 63, 54, 63); 118 | status[i].lineHide[1] = QPointF(50, 37); 119 | status[i].lineHide[0] = QPointF(50, 50); 120 | status[i].lineHide[2] = QPointF(50, 63); 121 | status[i].lineColors[1] = QColor("#7248e3"); 122 | status[i].lineColors[0] = QColor("#7248e3"); 123 | status[i].lineColors[2] = QColor("#7248e3"); 124 | 125 | // ≡ 126 | i++; 127 | status[i].bgSize = QSize(-1, -1); 128 | status[i].bgColor = Qt::white; 129 | status[i].bgRadius = 20; 130 | status[i].useLine = 3; 131 | status[i].linePoss[1] = QLineF(35, 37, 65, 37); 132 | status[i].linePoss[0] = QLineF(35, 50, 55, 50); 133 | status[i].linePoss[2] = QLineF(35, 63, 60, 63); 134 | status[i].lineHide[1] = QPointF(35, 37); 135 | status[i].lineHide[0] = QPointF(35, 50); 136 | status[i].lineHide[2] = QPointF(35, 63); 137 | status[i].lineColors[1] = QColor("#424649"); 138 | status[i].lineColors[0] = QColor("#424649"); 139 | status[i].lineColors[2] = QColor("#424649"); 140 | 141 | 142 | if (++index >= totalCount) 143 | index = 0; 144 | ui->pushButton->load(status[index]); 145 | } 146 | -------------------------------------------------------------------------------- /purelinbutton.cpp: -------------------------------------------------------------------------------- 1 | #include "purelinbutton.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define SET_BY_PROG(x) currentStatus.x = currentStatus.x + prog * (nextStatus.x - currentStatus.x) 8 | 9 | PurelinButton::PurelinButton(QWidget *parent) : QPushButton(parent) 10 | { 11 | 12 | } 13 | 14 | const PurelinStatus &PurelinButton::getCurrentStatus() const 15 | { 16 | return currentStatus; 17 | } 18 | 19 | void PurelinButton::setCurrentStatus(PurelinStatus status) 20 | { 21 | this->currentStatus = status; 22 | } 23 | 24 | void PurelinButton::load(PurelinStatus status) 25 | { 26 | prevStatus = currentStatus; 27 | nextStatus = status; 28 | 29 | if (prevStatus.useLine < nextStatus.useLine) 30 | { 31 | for (int i = prevStatus.useLine; i < nextStatus.useLine; i++) 32 | { 33 | prevStatus.linePoss[i] = QLineF(nextStatus.lineHide[i], nextStatus.lineHide[i]); 34 | prevStatus.lineColors[i] = nextStatus.lineColors[i]; 35 | } 36 | } 37 | else 38 | { 39 | for (int i = nextStatus.useLine; i < prevStatus.useLine; i++) 40 | { 41 | nextStatus.linePoss[i] = QLineF(prevStatus.lineHide[i], prevStatus.lineHide[i]); 42 | nextStatus.lineColors[i] = prevStatus.lineColors[i]; 43 | } 44 | } 45 | if (prevStatus.bgSize.isEmpty()) 46 | prevStatus.bgSize = this->size(); 47 | if (nextStatus.bgSize.isEmpty()) 48 | nextStatus.bgSize = this->size(); 49 | 50 | QPropertyAnimation* ani = new QPropertyAnimation(this, "purelin_ani"); 51 | ani->setStartValue(0.0); 52 | ani->setEndValue(1.0); 53 | ani->setDuration(500); 54 | ani->setEasingCurve(QEasingCurve::OutQuad); 55 | connect(ani, &QPropertyAnimation::valueChanged, this, &PurelinButton::aniProgChanged); 56 | connect(ani, SIGNAL(finished()), ani, SLOT(deleteLater())); 57 | connect(ani, SIGNAL(finished()), this, SLOT(aniProgFinished())); 58 | ani->start(); 59 | } 60 | 61 | void PurelinButton::paintEvent(QPaintEvent *) 62 | { 63 | // 背景位置 64 | QSizeF bgSize = currentStatus.bgSize; 65 | if (bgSize.isEmpty()) 66 | bgSize = this->size(); 67 | double left = (this->width() - bgSize.width()) / 2; 68 | double top = (this->height() - bgSize.height()) / 2; 69 | QRectF rect(left, top, bgSize.width(), bgSize.height()); 70 | 71 | // 绘制背景 72 | QPainter painter(this); 73 | painter.setRenderHint(QPainter::Antialiasing, true); 74 | QPainterPath path; 75 | path.addRoundedRect(rect, currentStatus.bgRadius, currentStatus.bgRadius); 76 | painter.fillPath(path, currentStatus.bgColor); 77 | 78 | // 绘制前景 79 | const double penWidth = 3.0; 80 | for (int i = 0; i < currentStatus.useLine; i++) 81 | { 82 | const QLineF& line = currentStatus.linePoss[i]; 83 | if ((line.length() < 1e-4)) 84 | continue; 85 | 86 | painter.setPen(QPen(currentStatus.lineColors[i], penWidth, Qt::SolidLine, Qt::RoundCap)); 87 | painter.drawLine(currentStatus.linePoss[i]); 88 | } 89 | } 90 | 91 | void PurelinButton::setPurelinAni(double prog) 92 | { 93 | this->animationProg = prog; 94 | } 95 | 96 | double PurelinButton::getPurelinAni() const 97 | { 98 | return this->animationProg; 99 | } 100 | 101 | void PurelinButton::aniProgChanged(const QVariant &var) 102 | { 103 | // 可以重新这个方法,自定义尺寸 104 | // 比如,按照控件的百分比大小进行调整,而不是像素数值 105 | double prog = var.toDouble(); 106 | 107 | currentStatus.bgRadius = prevStatus.bgRadius + prog * (nextStatus.bgRadius - prevStatus.bgRadius); 108 | currentStatus.bgSize = QSizeF(prevStatus.bgSize.width() + prog * (nextStatus.bgSize.width() - prevStatus.bgSize.width()), 109 | prevStatus.bgSize.height() + prog * (nextStatus.bgSize.height() - prevStatus.bgSize.height())); 110 | currentStatus.bgColor.setRgbF( 111 | prevStatus.bgColor.redF() + prog * (nextStatus.bgColor.redF() - prevStatus.bgColor.redF()), 112 | prevStatus.bgColor.greenF() + prog * (nextStatus.bgColor.greenF() - prevStatus.bgColor.greenF()), 113 | prevStatus.bgColor.blueF() + prog * (nextStatus.bgColor.blueF() - prevStatus.bgColor.blueF()), 114 | prevStatus.bgColor.alphaF() + prog * (nextStatus.bgColor.alphaF() - prevStatus.bgColor.alphaF())); 115 | int lineCount = qMax(prevStatus.useLine, nextStatus.useLine); 116 | currentStatus.useLine = lineCount; 117 | for (int i = 0; i < lineCount; i++) 118 | { 119 | currentStatus.linePoss[i].setLine( 120 | prevStatus.linePoss[i].x1() + prog * (nextStatus.linePoss[i].x1() - prevStatus.linePoss[i].x1()), 121 | prevStatus.linePoss[i].y1() + prog * (nextStatus.linePoss[i].y1() - prevStatus.linePoss[i].y1()), 122 | prevStatus.linePoss[i].x2() + prog * (nextStatus.linePoss[i].x2() - prevStatus.linePoss[i].x2()), 123 | prevStatus.linePoss[i].y2() + prog * (nextStatus.linePoss[i].y2() - prevStatus.linePoss[i].y2())); 124 | 125 | currentStatus.lineColors[i].setRgbF( 126 | prevStatus.lineColors[i].redF() + prog * (nextStatus.lineColors[i].redF() - prevStatus.lineColors[i].redF()), 127 | prevStatus.lineColors[i].greenF() + prog * (nextStatus.lineColors[i].greenF() - prevStatus.lineColors[i].greenF()), 128 | prevStatus.lineColors[i].blueF() + prog * (nextStatus.lineColors[i].blueF() - prevStatus.lineColors[i].blueF()), 129 | prevStatus.lineColors[i].alphaF() + prog * (nextStatus.lineColors[i].alphaF() - prevStatus.lineColors[i].alphaF())); 130 | } 131 | 132 | update(); 133 | } 134 | 135 | void PurelinButton::aniProgFinished() 136 | { 137 | currentStatus = nextStatus; 138 | animating = false; 139 | animationProg = 0; 140 | } 141 | --------------------------------------------------------------------------------