Qt 分类导航区的实现flyfishQt Creator 17.0.1Desktop Qt 6.9.3 MSVC2022 64bitWindows 平台完整代码在文末可执行文件下载ErFenSS 顶部 5 个分类按钮ErFenSS 顶栏那一排分类按钮All / Folders / Pictures / Audio / Videos视觉效果图标在上、文字在下悬停时图标轻轻浮起、背景微微发黑按下时图标下陷、颜色加深选中态有一层从上到下的暗色渐变而且还能跟随主题色自动适配。一、UI 基础5 个 QToolButton 的静态布局在 mainwindow.ui 中5 个按钮以绝对几何排布在topWidget的y30以下每个90×90横向间距 20pxwidgetclassQToolButtonnametbAllx20/xy30/ywidth90/widthheight90/height/widgetwidgetclassQToolButtonnametbFoldersx110/xy30/ywidth90/widthheight90/height/widgetwidgetclassQToolButtonnametbPicturesx200/xy30/ywidth90/widthheight90/height/widgetwidgetclassQToolButtonnametbAudiox290/xy30/ywidth90/widthheight90/height/widgetwidgetclassQToolButtonnametbVideosx380/xy30/ywidth90/widthheight90/height/widget选用QToolButton而不是QPushButton是因为它原生支持图标在上、文字在下的Qt::ToolButtonTextUnderIcon样式省去自绘。二、按钮初始化图标 文字 属性标记入口是 setupCategoryNav() mainwindow.cpp它调用 [optionToolButtons() 给 5 个按钮设置图标和文字optionToolButton(ui-tbAll,QIcon(:/tab/images/tab/all.png),All);optionToolButton(ui-tbFolders,QIcon(:/tab/images/tab/folder.png),Folders);optionToolButton(ui-tbPictures,QIcon(:/tab/images/tab/picture.png),Pictures);optionToolButton(ui-tbAudio,QIcon(:/tab/images/tab/audio.png),Audio);optionToolButton(ui-tbVideos,QIcon(:/tab/images/tab/video.png),Videos);真正干活的是 optionToolButton()voidMainWindow::optionToolButton(QToolButton*tb,QIcon icon,QString text){QAction*pActionnewQAction(this);pAction-setText(text);pAction-setIcon(icon);tb-setIconSize(QSize(50,50));// 图标 50×50tb-setDefaultAction(pAction);// 文字图标绑到 Actiontb-setToolButtonStyle(Qt::ToolButtonTextUnderIcon);// 图标在上、文字在下tb-setProperty(mainwindow_toolbutton,true);// 打上属性标记供 QSS 选择tb-setToolTip();// 禁止 QToolButton 自动把 action 文本弹成 tooltip 黑框}这里有 3 个关键点setDefaultAction把文字和图标统一交给QAction管理后续换肤时只需改 Action 的图标即可。setProperty(mainwindow_toolbutton, true)自定义属性让 styles.qss 里的*[mainwindow_toolbuttontrue]选择器能精确命中这些按钮。setToolTip()QToolButton 默认会把 action 文本作为 tooltip鼠标悬停时弹一个黑框这里显式清空避免干扰视觉。三、QSS 基础样式白字 透明 状态渐变styles.qss 定义了按钮的三态视觉/* 基础白字、透明背景、无边框 */*[mainwindow_toolbuttontrue]{border:none;background-color:transparent;color:#FFFFFF;padding:0px;}/* hover从顶到底微微发黑rgba 18 */*[mainwindow_toolbuttontrue]:hover{background-color:qlineargradient(x1:0,y1:0,x2:0,y2:1,stop:0rgba(0,0,0,0),stop:1rgba(0,0,0,18));}/* pressed更深的黑色渐变rgba 46 */*[mainwindow_toolbuttontrue]:pressed{background-color:qlineargradient(x1:0,y1:0,x2:0,y2:1,stop:0rgba(0,0,0,0),stop:1rgba(0,0,0,46));}:hover和:pressed是 Qt 自动跟踪鼠标状态的伪状态不需要写任何 C 代码就能触发。顶部stop:0设为完全透明让渐变从标题栏背景色自然过渡到底部的暗色视觉上像一层阴影」浮在按钮上。四、选中态两套 QSS 切换 属性联动点击分类按钮会调用 setTabBarIndex()它用两套内联 QSS切换选中/未选中// 选中态顶透明 → 底 rgba(0,0,0,45) 的暗色渐变constQString ssSelectedQToolButton{background-color:qlineargradient(x1:0,y1:0,x2:0,y2:1,stop:0 rgba(0,0,0,0),stop:1 rgba(0,0,0,45));color:#FFFFFF;border:none;}QToolButton:hover{...stop:1 rgba(0,0,0,55)...}QToolButton:pressed{...stop:1 rgba(0,0,0,70)...};// 未选中态透明背景 轻微 hover 加深constQString ssNormalQToolButton{background-color:transparent;color:#FFFFFF;border:none;}QToolButton:hover{...stop:1 rgba(0,0,0,18)...}QToolButton:pressed{...stop:1 rgba(0,0,0,46)...};QToolButton*tabs[5]{ui-tbAll,ui-tbFolders,ui-tbPictures,ui-tbAudio,ui-tbVideos};for(inti0;i5;i){tabs[i]-setStyleSheet(iindex?ssSelected:ssNormal);tabs[i]-setProperty(mainwindow_toolbutton_selected,(iindex));updateStyle(tabs[i]);// unpolish → polish → update 强制重刷}几个值得注意的设计用数组统一管理5 个按钮放进tabs[5]循环赋值避免 5 段重复代码。内联 QSS 而非属性选择器选中态直接setStyleSheet优先级高于外部styles.qss确保选中视觉不被基础样式覆盖。updateStyle()三板斧style()-unpolish(w)清掉旧样式缓存 →polish(w)重新应用 →update()触发重绘。这是 Qt 动态改样式后必须做的否则可能看不到变化。选中态的 hover/pressed 也单独定义选中按钮在 hover 时渐变更深55 vs 45pressed 时最深70保证选中状态下交互反馈依然清晰。五、CatButtonEffect—— 图标缓动 加深态合成上面 QSS 只做了背景色的状态变化真正让按钮活起来」的是 CatButtonEffect 这个内部类。它为每个分类按钮做两件事图标尺寸缓动和运行时合成加深态图标。5.1 安装与初始化在setupCategoryNav()末尾qDeleteAll(m_catEffects);m_catEffects.clear();for(QToolButton*tb:{ui-tbAll,ui-tbFolders,ui-tbPictures,ui-tbAudio,ui-tbVideos}){auto*effnewCatButtonEffect(tb,this);m_catEffects.append(eff);}构造函数里m_origSizebtn-iconSize();// 记录原始 50×50m_btn-setAttribute(Qt::WA_Hover,true);// 开启 hover 事件m_animnewQVariantAnimation(this);m_anim-setEasingCurve(QEasingCurve::OutCubic);// 缓动曲线connect(m_anim,QVariantAnimation::valueChanged,this,[this](constQVariantv){m_btn-setIconSize(v.toSize());});m_btn-installEventFilter(this);5.2 运行时合成加深态图标不增加资源文件常规做法是每个图标配一张压暗 20% 的_b.pngQt 在按下/选中时自动从QIcon的Active/Selected模式取深色位图。这个项目不新增任何 PNG而是在运行时合成QPixmap basecur.pixmap(sz,QIcon::Normal,QIcon::Off);// 原始 50×50 图标// 按下态Active叠加 22% 黑alpha56QPixmap dPressbase.copy();{QPainterp(dPress);p.setCompositionMode(QPainter::CompositionMode_SourceAtop);// 关键只在不透明区域上色p.fillRect(dPress.rect(),QColor(0,0,0,56));}// 选中态Selected叠加 15% 黑alpha38QPixmap dSelbase.copy();{QPainterp(dSel);p.setCompositionMode(QPainter::CompositionMode_SourceAtop);p.fillRect(dSel.rect(),QColor(0,0,0,38));}cur.addPixmap(dPress,QIcon::Active,QIcon::On);cur.addPixmap(dPress,QIcon::Active,QIcon::Off);cur.addPixmap(dSel,QIcon::Selected,QIcon::On);cur.addPixmap(dSel,QIcon::Selected,QIcon::Off);m_btn-setIcon(cur);CompositionMode_SourceAtop是精髓黑色只覆盖在原图已有像素的地方透明区域保持透明所以得到的是图标本身变暗而不是整个方形变黑。把这两张加深图填回QIcon的Active按下和Selected选中模式后Qt 会在对应状态自动切换无需手动管理。5.3 图标尺寸缓动浮起 / 下陷eventFilter监听 4 个事件驱动QVariantAnimation在图标尺寸之间插值caseQEvent::Enter:// 放大到 112%50→56140ms OutCubic —— 浮起反馈animateTo(QSize(orig*1.12,orig*1.12),140);break;caseQEvent::Leave:animateTo(m_origSize,160);// 回到 50break;caseQEvent::MouseButtonPress:// 缩到 92%56→4680ms —— 下陷反馈animateTo(QSize(orig*0.92,orig*0.92),80);break;caseQEvent::MouseButtonRelease:// 松手后若仍在按钮内 → 回到 56hover 态否则 → 50constboolstillInm_btn-rect().contains(me-position().toPoint());animateTo(stillIn?QSize(orig*1.12,orig*1.12):m_origSize,130);break;animateTo每次都会stop()正在运行的动画再重新start()保证快速移动鼠标时不会叠加多个动画导致抖动。OutCubic缓动让动画快起慢停」手感更自然。5.4 三层反馈叠加最终一个分类按钮的交互是三层视觉同时作用的结果交互背景层QSS图标颜色层QIcon mode图标尺寸层CatButtonEffect常态透明Normal 原色50×50hoverrgba(0,0,0,18) 渐变Normal 原色56×56放大pressedrgba(0,0,0,46) 渐变Active 加深 22%46×46缩小选中rgba(0,0,0,45) 渐变Selected 加深 15%50×50六、换肤联动选中态自动适配新背景applyTheme() 换主题色时会重新调用setTabBarIndex(m_currentTabIndex)ui-backgroundWidget-setStyleSheet(background-color:t.primaryColor);setTabBarIndex(m_currentTabIndex);// 重刷选中态渐变因为选中态的渐变是rgba(0,0,0,45)这种与背景色无关的黑色半透明所以无论顶栏换成绿色、蓝色还是紫色选中按钮都会自然地「压暗一层」无需为每个主题单独写样式。这就是为什么 6 套主题下分类按钮都能保持一致的质感。七、缩放时的特殊处理分类按钮不参与等比缩放在 applyScaling() 的最后添加链接描述// 顶部分类按钮保持设计时的原始几何for(inti0;im_catButtons.size()im_catOrigGeoms.size();i){QToolButton*bm_catButtons.at(i);constQRect rm_catOrigGeoms.at(i);b-setGeometry(r.x(),r.y(),r.width(),r.height());// 强制恢复 90×90}窗口缩放时标题栏宽度会拉伸、右侧按钮会右移但5 个分类按钮始终保持 90×90。如果让它们也等比缩放图标和文字会被拉变形比如窗口变宽时按钮变胖反而难看。保持固定尺寸 居中/左对齐是更稳妥的选择。源码下载地址https://github.com/shaoshengsong/ErFenSS