Qt利用字体库实现图标的动态换肤

引言

为了界面的美观,符合简约设计,界面会引入图标用在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;
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Arui丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值