目录
引言
为了界面的美观,符合简约设计,界面会引入图标用在button或是label上。随着项目的不断演进,业务逻辑的增多,图标的数量也随之膨胀。
常见的图标使用步骤
1.设计切图
2.qrc增加图片资源
3.css增加样式
button样式
QPushButton#tmp_btn{
background: transparent;
border-image: url(:/skin/btn_img.png) 0 60 0 0;
}
QPushButton#tmp_btn:hover{
border-image: url(:/skin/btn_img.png) 0 40 0 20;
}
QPushButton#tmp_btn:pressed{
border-image: url(:/skin/btn_img.png) 0 20 0 40;
}
QPushButton#tmp_btn:disabled{
border-image: url(:/skin/btn_img.png) 0 0 0 60;
}
label样式
QLabel#tmp_label{
background: transparent;
border-image: url(:/skin/labele_img.png);
}
上面是两个常用的样式表,btn_img一般是四种状态的组图,样式表通过九宫格分割完成,如下:
多数情况下四态只是颜色不同,内容形状完全相同,而多数情况是没有渐变色的,换一种表述方式:按钮图标其实在某一状态都是单色图标,只是不同状态的图标颜色不同。如果能将图标的形状抽象出来,不同转态下设置不同的颜色,就能实现不同状态下的图标复用。
如果项目中出客制化的需求,即不同的客户要求整体风格不同,如之前的软件界面的整体风格是蓝色,现在需要切换成红色,更换背景颜色比较容易通过脚本修改css就行,但图标限制于传统的实现方式,需要再准备一组红色图标。
一两个客制化需求还好,当到五六个客户的时候,图标数量要拓展到五六倍,而且每增加一个客户就需要重新准备图片,耗费UI的精力。同时也拖慢开发后续功能的速度,增加一个新的图标按钮就需要增加五六个图标。
在这过程中通常是会有疏忽的,有太多变量导致最后某些地方的图标被遗漏,而这种错误在客户看来又是极为明显,因此迫切需要另一种图标的使用方式去避免该过程的浪费。
实现逻辑
参考web的实现方式,将图标转换成文字,颜色通过样式表的字体颜色控制,可以满足上述复用图标形状、通过代码控制图标颜色的需求。
1.利用iconfont进行图标管理及字库生成
使用阿里的iconfont进行图标的管理,iconfont比较方便,添加、查看、修改等等。添加完图标后直接下载到本地,里面有我们需要的字库文件ttf,除了字库我们代码使用时还需要编码值写成宏,供代码调用。可以手输,我是用代码读取压缩包里的css文件并生成宏的,代码如下:
if(ui->iconfontPath->text().isEmpty())
return;
QFile file(ui->iconfontPath->text());
if(!file.exists())
return;
if(!file.open(QIODevice::ReadOnly | QIODevice::Text))
return;
QTextStream stream(&file);
QString exportPath = "iconfontCssConvert.txt";
QFile::remove(exportPath);
QFile exportFile(exportPath);
if(!exportFile.open(QIODevice::WriteOnly | QIODevice::Text))
return;
QTextStream exportStrem(&exportFile);
QString tmpStr;
QString rowStr;
QString outFormat("ICON_%1 = 0x%2,\n");
while (!stream.atEnd()) {
rowStr = stream.readLine();
if(!rowStr.isEmpty()){
tmpStr += rowStr;
}
else{
if(tmpStr.contains("content")){
QString name = tmpStr.mid(tmpStr.indexOf("-") + 1, tmpStr.indexOf(":") - tmpStr.indexOf("-") - 1).toUpper().replace("-", "_");
QString val = tmpStr.mid(tmpStr.indexOf("\\") + 1, tmpStr.lastIndexOf("\"") - tmpStr.indexOf("\\") - 1);
exportStrem << outFormat.arg(name).arg(val);
}
tmpStr.clear();
}
}
file.close();
exportFile.close();
css文件:
生成文件:
2.字库管理
生成字库后,代码中需要实际使用该字库,通过编码值找到所需要的图标进行展示。
2.1 字体类
#include <QString>
#include <QFont>
#include <QMutex>
#include <QResizeEvent>
#include <QPushButton>
#include <QLabel>
class IconFont
{
public:
//随用随加
enum ICON_INDEX{
ICON_ADD = 0xe664,
ICON_ADD_CIRCLE = 0xe665,
ICON_ADJUST = 0xe666,
ICON_BROWSE = 0xe667,
ICON_CAMERA = 0xe669,
ICON_BINGBAO2 = 0xe6b4,
};
explicit IconFont(const QString &_name);
QFont getIconFont()const{return m_iconFont;}
QString getIconName()const{return m_name;}
private:
QFont m_iconFont;
QString m_name;
};
#include <QFileDialog>
#include <QTextStream>
#include <QFontDatabase>
#define FONT_PATH ":/font/"
IconFont::IconFont(const QString &name)
{
m_name = name;
//加载字体,得到字体库中ID
int fontId = QFontDatabase::addApplicationFont(FONT_PATH + name + QString(".ttf"));
//根据ID得到字体名称
QString fontName = QFontDatabase::applicationFontFamilies(fontId).at(0);
m_iconFont = QFont(fontName);
}
2.2 字体管理类
使用单例,为后续动态更换字体库提供接口
class IconManager : public QObject
{
Q_OBJECT
public:
static IconManager* instance()
{
static QMutex mutex;
if(!m_iconManager)
{
QMutexLocker locker(&mutex);
if(!m_iconManager)
{
m_iconManager=new IconManager;
}
}
return m_iconManager;
}
private:
IconManager();
~IconManager();
public:
void setFont(const IconFont &newFont);
void restoreFont(const QString& fontName);
QFont getFont() { return m_iconFont.getIconFont(); }
IconFont getIconFont() { return m_iconFont; }
private:
static IconManager* m_iconManager;
IconFont m_iconFont;
};
IconManager* IconManager::m_iconManager = nullptr;
IconManager::IconManager()
: m_iconFont(IconFont("iconfont"))
{
}
IconManager::~IconManager()
{
}
void IconManager::setFont(const IconFont &newFont)
{
if (newFont.getIconName() == m_iconFont.getIconName())
return;
m_iconFont = newFont;
}
void IconManager::restoreFont(const QString& fontName)
{
if (IconFont(fontName).getIconName() == m_iconFont.getIconName())
return;
m_iconFont = IconFont(fontName);
}
2.3 帮助类
提供更换图标及图标大小、图标转图片的公共函数
namespace IconHelper {
//设置widget显示当前字体库中第iconIndex代表的字符
template<typename WidgetType>
void setIcon(WidgetType *wig, int iconIndex)
{
QChar c(iconIndex);
wig->setFont(IconManager::instance()->getFont());
wig->setText(c);
}
template<typename WidgetType>
void setIconSize(WidgetType *wig, int iconPxSize)
{
QFont font = wig->font();
font.setPixelSize(iconPxSize);
wig->setFont(font);
}
//字体图标转像素
QPixmap transFontToPixmap(int size, int fontSize, int iconIndex, const QColor & iconColor);
QPixmap transFontToPixmap(int w, int h, int fontSize, int iconIndex, const QColor & iconColor);
}
QPixmap IconHelper::transFontToPixmap(int size, int fontSize, int iconIndex, const QColor & iconColor)
{
return transFontToPixmap(size, size, fontSize, iconIndex, iconColor);
}
QPixmap IconHelper::transFontToPixmap(int w, int h, int fontSize, int iconIndex, const QColor & iconColor)
{
QString iconStyle = QString("QLabel{color:rgba(%1,%2,%3,%4)};").arg(iconColor.red()).arg(iconColor.green()).arg(iconColor.blue()).arg(iconColor.alpha());
QLabel widget;
widget.setAttribute(Qt::WA_TranslucentBackground);
widget.setFixedSize(w, h);
widget.setAlignment(Qt::AlignCenter);
widget.setText(QChar(iconIndex));
widget.setStyleSheet(iconStyle);
QFont font = IconManager::instance()->getFont();
font.setPointSize(fontSize);
widget.setFont(font);
return QPixmap::grabWidget(&widget, widget.rect());
}
3.基础控件
实现了两种常用控件(按钮以及标签),主要是按钮的实现,考虑到按钮图标多态(如选中状态下图标不相同),引入qt状态机简化代码,效果如下:
#define ICON_METHOD \
Q_PROPERTY(int iconIndex READ icon WRITE setIcon)\
public:\
void setIcon(int index) {\
if (index == m_iconIndex)\
return;\
m_iconIndex = index;\
IconHelper::setIcon(this, index);\
emit sig_iconIndexChanged(index);\
}\
void setIconSize(int pxSize) {\
m_pxSize = pxSize;\
IconHelper::setIconSize(this, m_pxSize);\
}\
int icon() { return m_iconIndex; }\
protected:\
virtual void resizeEvent(QResizeEvent *) Q_DECL_OVERRIDE { adjustIconSize(); }\
void adjustIconSize() {\
IconHelper::setIconSize(this, m_pxSize ? m_pxSize : qMin(this->width(), this->height()));\
}\
int m_pxSize;\
int m_iconIndex;\
class IconButton : public QPushButton
{
Q_OBJECT
ICON_METHOD
public:
explicit IconButton(QWidget *parent = nullptr);
explicit IconButton(int iconIndex, QWidget *parent = nullptr);
explicit IconButton(const QList<int>& iconList, QWidget *parent = nullptr);
signals:
void sig_iconIndexChanged(int index);
};
class IconLabel : public QLabel
{
Q_OBJECT
ICON_METHOD
public:
explicit IconLabel(QWidget *parent = nullptr);
explicit IconLabel(int iconIndex, QWidget *parent = nullptr);
signals:
void sig_iconIndexChanged(int index);
};
#include <QState>
#include <QStateMachine>
IconButton::IconButton(QWidget *parent)
: QPushButton(parent)
, m_pxSize(0)
{
IconHelper::setIcon(this, IconFont::ICON_BINGBAO2);
}
IconButton::IconButton(int iconIndex, QWidget *parent)
: QPushButton(parent)
, m_pxSize(0)
{
IconHelper::setIcon(this, iconIndex);
}
IconButton::IconButton(const QList<int>& iconList, QWidget *parent /*= nullptr*/)
: QPushButton(parent)
, m_pxSize(0)
{
if (!iconList.isEmpty()) {
QList<QState*> stateList;
foreach(int index, iconList) {
auto state = new QState;
state->assignProperty(this, "iconIndex", index);
stateList << state;
}
auto stateMachine = new QStateMachine(this);
for (int i=0; i < stateList.count(); i++){
stateList.at(i)->addTransition(this, &QPushButton::clicked, stateList.at((i + 1) % stateList.count()));
stateMachine->addState(stateList.at(i));
}
stateMachine->setInitialState(stateList.first());
stateMachine->start();
}
else {
this->setIcon(IconFont::ICON_BINGBAO2);
}
}
/
IconLabel::IconLabel(QWidget *parent /*= 0*/)
: QLabel(parent)
, m_pxSize(0)
{
IconHelper::setIcon(this, IconFont::ICON_BINGBAO2);
this->setAlignment(Qt::AlignCenter);
}
IconLabel::IconLabel(int iconIndex, QWidget *parent /*= nullptr*/)
: QLabel(parent)
, m_pxSize(0)
{
IconHelper::setIcon(this, iconIndex);
this->setAlignment(Qt::AlignCenter);
}
4.动态换肤
此处动态换肤是通过直接替换样式表的方式,也可通过css的属性选择器完成,效果如下:
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui;
private:
IconButton* m_iconBtn;
IconLabel* m_iconLabel;
QPushButton* m_changeBtn;
QString m_btnStyle;
QString m_labelStyle;
QColor m_color;
};
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
setFixedSize(400,400);
ui->setupUi(this);
//m_iconBtn = new IconButton(QList<int>() << IconFont::ICON_BINGBAO2 << IconFont::ICON_CAMERA << IconFont::ICON_BROWSE,this);
m_iconBtn = new IconButton(this);
m_iconLabel = new IconLabel(IconFont::ICON_CAMERA, this);
m_btnStyle = "IconButton {\
color:%1;\
border:0px;\
background-color:transparent;}\
IconButton:hover {\
color:%2;}\
IconButton:pressed {\
color:%3;}";
m_labelStyle = "IconLabel {\
color:%1;\
border:0px;\
background-color:transparent;}";
m_changeBtn = new QPushButton("change style",this);
connect(m_changeBtn, &QPushButton::clicked, this, [this]{
m_color = m_color == QColor(Qt::red) ? QColor(Qt::green) : QColor(Qt::red);
auto color1 = m_color;
color1.setAlphaF(0.2);
auto color2 = color1;
color1.setAlphaF(0.6);
auto color3 = color1;
m_iconBtn->setStyleSheet(m_btnStyle.arg(m_color.name(QColor::HexArgb)).arg(color2.name(QColor::HexArgb)).arg(color3.name(QColor::HexArgb)));
m_iconLabel->setStyleSheet(m_labelStyle.arg(m_color.name()));
});
auto widget = new QWidget(this);
setCentralWidget(widget);
auto mainLayout = new QGridLayout(widget);
mainLayout->addWidget(m_iconBtn,0,0,1,1);
mainLayout->addWidget(m_iconLabel,0,1,1,1);
mainLayout->addWidget(m_changeBtn,1,0,1,2);
}
MainWindow::~MainWindow()
{
delete ui;
}