Qt實現一個支援QSS的Switch Button(開關按鈕)
Qt實現一個支援QSS的Switch Button(開關按鈕)
本文會比較長,目的是為了提供一種實現自定義複雜控制元件的方式,對於使用 QSS 應用樣式的專案可能會有幫助。
實現的過程會相對比較複雜和難理解,僅作為研究,對於實際開發可能沒什麼太大價值。
放上最終的實現效果圖:
問題
- 常見的 Switch Button ,至少包含兩部分,槽和滑塊,這種由多個小部件組合的控制元件,在 Qt 內部屬於 Complex Control(複雜控制元件),比如 QComboBox、QSlider。使用樣式表定義各部分子控制元件的樣式,需要使用子控制元件選擇器:
QProgressBar::chunk { background-color: #05B8CC; width: 20px; }
- Qt 確實沒有開放 QStyleSheetStyle 以及相關的 QSS 解析,所以擴充 QSS 的方式實現自定義複雜控制元件時不可能的。
解決思路
-
我在 QComboBox文字居中的一種解決辦法 中發現,QStyle 使用該介面繪製控制元件(也有其他類似介面):
void QStyle::drawControl(QStyle::ControlElement, const QStyleOption *, QPainter *, const QWidget *)
需要 QStyleOption 和 QWidget , 但 QStyleOption 不需要與 QWidget 的型別對應。比如可以使用 QStyleOptionSlider ,但傳遞 QPushButoon 型別的控制元件,這樣定義在 QPushButoon 上的屬於 QSlider 的樣式同樣可以繪製出來,儘管 QPushButoon 並不支援這些屬性:
QPushButton{ color : red; } QPushButton::handle{ background: blue; }
這樣從側面證明了,QSS 僅僅只是樣式定義的集合,當選擇器匹配到控制元件時,並不關心控制元件型別,只要繪製時對應的 QStyleOption 能獲取到定義的樣式即可,而這些樣式會被覆蓋到 QStyleOption::palette,來實現動態的樣式,這也是為什麼 QWidget::palette() 並不能影響 QSS 的原因。
實現方式
- 使用 QPushButton 作為基類,將 Switch Button 各部分繪製到按鈕上,這樣可以保留按鈕原生的屬性和訊號。 Switch Button 可以分為兩個部分,槽和滑塊,槽可以使用按鈕背景控制,滑塊作為子控制元件,使用 QSlider 或其子類。
-
繪製槽
定義好樣式,固定高度和圓角,Checked 的偽狀態使用 on(實際 Qt 原始碼在 Checked 時也使用 QStyle::State_On):
SwitchButton{ background:#CCCCCC; /*Unchecked背景*/ border: none; border-radius: 15px; /*圓角*/ height: 30px; } SwitchButton:on{ background: #4CCCE6; }
重寫QPushButton::paintEvent ,分兩層繪製按鈕背景。
QStyleOptionButton buttonOpt; initStyleOption(&buttonOpt); // 初始化狀態 buttonOpt.rect.adjust(0, 0, -1, 0); // 繪製滑塊可能會有一畫素偏差 buttonOpt.state &= ~QStyle::State_On; // 先繪製Unchecked時背景 style()->drawControl(QStyle::CE_PushButtonBevel, &buttonOpt, &painter, this); painter.setOpacity(progress); // 定義一個動態漸變值,0~1變化,用透明度動畫控制切換 buttonOpt.state |= QStyle::State_On; // 繪製Checked時背景 style()->drawControl(QStyle::CE_PushButtonBevel, &buttonOpt, &painter, this);
由於滑塊滑動過程是個動態過程,背景從 Unchecked 到 Checked 需要切換,這裡為了簡單控制,使用了兩層繪製,所以可能不適合使用帶有透明度的顏色值。其他方式在後面會簡單介紹。
-
繪製滑塊
定義滑塊樣式。Switch Button 的滑塊意義上也可以叫做 handle,所以使用 handle 子控制元件,滑塊高度預設是槽的高度,我這裡使用了 QScrollBar 的樣式,所以需要限制滑塊寬度,也可以使用 QSlider 等。
SwitchButton::handle{ background: white; border:none; min-width:30px; max-width:30px; border-radius:15px; /*圓角*/ }
在繪製背景色後繪製滑塊,使用了 QStyleOptionSlider :
QStyleOptionSlider sliderOpt; sliderOpt.init(this); sliderOpt.minimum = 0; sliderOpt.maximum = sliderOpt.rect.width(); // 直接使用畫素範圍 int position = int(progress * (sliderOpt.rect.width())); // 根據動態值控制滑塊範圍 sliderOpt.sliderPosition = qMin(qMax(position, 0), sliderOpt.maximum); sliderOpt.sliderValue = sliderOpt.sliderPosition; // 重設滑塊區域,Qt原始碼會這麼做,否則會繪製到整個按鈕上 sliderOpt.rect = style()->subControlRect(QStyle::CC_ScrollBar, &sliderOpt, QStyle::SC_ScrollBarSlider, this); style()->drawControl(QStyle::CE_ScrollBarSlider, &sliderOpt, &painter, this); // 繪製滑塊
-
定義動畫
最後可以定義個動畫,當狀態切換時觸發動畫,設定 0~1 變化來繪製滑塊位置和背景色的漸變。
QVariantAnimation *animation = new QVariantAnimation(this); animation->setStartValue(0.0); animation->setEndValue(1.0); animation->setDuration(200); connect(animation, &QVariantAnimation::valueChanged, this, [this](const QVariant & val){ progress = val.toReal(); // progress定義為成員 update(); });
按鈕狀態切換為 Checked 時,progress 需要從 0 → 1 變化,所以為正向;狀態切換為Unchecked時,progress從 1 → 0 ,所以反向。快讀點選時需要暫停動畫,重設方向並繼續。
QAbstractAnimation::Direction direction = checked ? QAbstractAnimation::Forward : QAbstractAnimation::Backward; bool pause = animation->state() == QAbstractAnimation::Running; if(pause) animation->pause(); animation->setDirection(direction); if(pause) animation->resume(); else animation->start(QAbstractAnimation::KeepWhenStopped); update();
-
更復雜的樣式
整個繪製全部使用 QStyle 介面,除了動畫時間使用了固定值,其他所有樣式完全通過 QSS 設計,按鈕 pressed、hover 等狀態下的樣式也不會受到影響。
如果要針對 handle 單獨設定 hover、pressed 等樣式,需要根據滑鼠位置計算是否在 handle 上,並設定 QStyleOptionComplex::activeSubControls 和 QStyleOption::state 後繪製,判斷座標點位置的子控制元件也有對應的介面:
virtual QStyle::SubControl QStyle::hitTestComplexControl(...)
如果handle不需要拖拽動作,支援的意義不大。不過,Win10 設定裡的 Switch Button 是支援滑鼠拖拽的,當拖拽超過半個按鈕寬度會切換狀態,釋放後 handle 會從釋放位置滑向一邊。要支援的話需要兼顧 QPushButon 的原生的滑鼠動作,比較麻煩。
其他不同的 Switch Button
- 文章開頭的動態圖展示了上述程式碼最終的結果。網路上也有其他稍有差別的 Switch Button,最後做個總結。
-
槽與滑塊高度不同
這種可以通過修改上述樣式實現:/* 通過修改 marin實現 */ SwitchButton{ border: none; border-radius: 10px; height: 20px; margin: 5px; } SwitchButton::handle{ background: white; border:none; min-width:30px; max-width:30px; margin:-5px; border-radius:15px; }
-
有表示開、關狀態的文字
這種可以增加繪製按鈕文字的邏輯,不過需要控制文字位置,可以根據滑塊位置和按鈕左側,居中繪製文字。 -
滑塊左側和右側顏色不同
這種可以通過修改繪製繪製區域來實現:sliderOpt.rect = style()->subControlRect(QStyle::CC_ScrollBar, &sliderOpt, QStyle::SC_ScrollBarSlider, this); // 將繪製槽的第二層背景色程式碼移動到獲取handle位置之後,修改繪製區域右側到handle右側 buttonOpt.state |= QStyle::State_On; buttonOpt.rect.setRight(sliderOpt.rect.right()); style()->drawControl(QStyle::CE_PushButtonBevel, &buttonOpt, &painter, this); style()->drawControl(QStyle::CE_ScrollBarSlider, &sliderOpt, &painter, this);
改變繪製區域可能會因為一些margin、padding等不一致引起偏移。
總結
- 如果使用 QPainter 自己繪製,開放介面設定顏色、圓角等樣式可能更方便快速開發,上述方法確實過於複雜。
- 嘗試實現的過程試過不同的 QStyleOption ,不合適就要重來,檢視Qt原始碼來確定狀態和介面是可用的,花費的時間太長。多種控制元件的混合繪製導致預設樣式非常醜,也僅能用於使用 QSS 定製樣式的專案。
- 就這些,希望各位能有所收穫。
相關文章
- switch button 待完善,做出一個合理的開關按鈕
- Jquery實現的Switch開關按鈕(仿iOS開關)jQueryiOS
- Android 自定義實現switch開關按鈕Android
- Qt實現畫板部件並和自定義button按鈕結合例項QT
- Flutter Button(按鈕)Flutter
- Android 自定義Switch開關按鈕的樣式Android
- HTML input button按鈕HTML
- HTML input button 按鈕HTML
- Qt自定義開關按鈕控制元件QT控制元件
- Tkinter (02) 按鈕部件 Button
- 舉例說明寫一個button的按鈕的方法有哪些?
- Qt5.9中QSS(qt Style Sheet)用法之一設定按鈕顏色和背景色(設定按鈕間相互間隔、設定按鈕與周圍邊緣間隔)QT
- QT樣式: QSpinBox按鈕箭頭 up-button 和 down-button變換位置QT
- 如何點選一個按鈕實現列印
- css3實現button按鈕美化效果程式碼例項CSSS3
- QT經驗(一)——按鈕長按事件分析QT事件
- c# event在form中手工編寫一個按鈕buttonC#ORM
- 一個簡單的選單按鈕的實現 (轉)
- css3實現的開關按鈕程式碼例項CSSS3
- 用CSS Houdini實現一個Material風格的按鈕CSS
- 一個BUTTON,實現點選播放錄音,長按錄音
- WPF Button按鈕設定圓角
- C++ Qt開發:PushButton按鈕元件C++QT元件
- 用屬性動畫實現一個確認按鈕動畫
- Qt控制元件精講一:按鈕QT控制元件
- (原創)【MAUI】一步一步實現“懸浮操作按鈕”(FAB,Floating Action Button)UI
- react實現一個button元件React元件
- 使用SVG實現的一個Android播放/暫停按鈕SVGAndroid
- tkinter中button按鈕控制元件(三)控制元件
- 仿switch風格滑動按鈕
- element ui switch開關 點選按鈕後,彈窗確認再改變開關狀態UI
- QComboBox 右側下拉按鈕qss樣式設定
- Blazor入門100天 : 自做一個支援長按事件的按鈕元件Blazor事件元件
- Element原始碼分析系列3-Button(按鈕)原始碼
- Android UI控制元件系列:Button(按鈕)AndroidUI控制元件
- CSS開關按鈕三例CSS
- SVG 和 CSS3 實現一個超酷愛心 Like 按鈕SVGCSSS3
- 【QT】QSS美化——Buttons篇QT