Qt 绘制彩色文本,包括字符颜色分割、动画效果和渐变等多种花式效果

(创作不易,点个关注,不胜感谢)

一、问题与相关技术

:如何实现绘制一个多色彩的字符?

:可以使用QPainter::setClipRect 方法,它用于设置绘制区域的裁剪矩形。设置后,所有的绘制操作将被限制在这个矩形区域内,只有在这个矩形内的部分才会被绘制出来,矩形外的部分将被裁剪掉(不会显示)。

QPainter::setClipRect 是 Qt 绘图系统中的重要功能,用于设置裁剪区域,限制绘图操作只在指定的矩形区域内生效。

作用:

  • 限制绘制区域:只允许在指定的矩形区域内进行绘制

  • 提高性能:避免在不可见区域进行不必要的绘制操作

  • 实现特殊效果:创建遮罩、部分显示等视觉效果

函数原型:

cpp 复制代码
void QPainter::setClipRect(const QRectF &rectangle,
        Qt::ClipOperation operation = Qt::ReplaceClip)

void QPainter::setClipRect(const QRect &rectangle,
        Qt::ClipOperation operation = Qt::ReplaceClip)

void QPainter::setClipRect(int x, int y, int width, int height, 
        Qt::ClipOperation operation = Qt::ReplaceClip)

先看程序运行效果:

二、源代码

说明】代码量有点多,其实核心就是设置文本的各种风格的颜色,然后调用 Painter::setClipRect 和 QPainter::drawText 等方法绘制文本。

1、SplitColorTextWidget.h

cpp 复制代码
#pragma once

#include <QWidget>

class SplitColorTextWidget : public QWidget
{
    Q_OBJECT    
public:
    SplitColorTextWidget(QWidget *parent = nullptr);
    
protected:
    void paintEvent(QPaintEvent *event) override;
};

2、SplitColorTextWidget.cpp

cpp 复制代码
#include "SplitColorTextWidget.h"
#include <QPainter>

SplitColorTextWidget::SplitColorTextWidget(QWidget *parent) : QWidget(parent)
{
}

void SplitColorTextWidget::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event);
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);
    
    // 设置字体
    QFont font("Arial", 36, QFont::Bold);
    painter.setFont(font);
    
    QString text = "我本将心向明月";
    
    // 获取文本尺寸
    QFontMetrics fm(font);
    QRect textRect = fm.boundingRect(text);
    textRect.moveCenter(rect().center());
    
    // 绘制左半部分(红色)
    painter.save();
    // 设置左半部分剪切区域
    QRect leftClip = textRect;
    leftClip.setWidth(textRect.width() * 0.51);
    painter.setClipRect(leftClip, Qt::ReplaceClip);
    painter.setPen(QColor(255, 0, 0)); // 红色
    painter.drawText(textRect, Qt::AlignCenter, text);
    painter.restore();
    
    // 绘制右半部分(蓝色)
    painter.save();
    // 设置右半部分剪切区域
    QRect rightClip = textRect;
    rightClip.setLeft(textRect.left() + leftClip.width());
    painter.setClipRect(rightClip, Qt::ReplaceClip);
    painter.setPen(QColor(0, 0, 255)); // 蓝色
    painter.drawText(textRect, Qt::AlignCenter, text);
    painter.restore();
}

3、PerCharacterSplitWidget.h

cpp 复制代码
#pragma once

#include <QWidget>

class PerCharacterSplitWidget : public QWidget
{
    Q_OBJECT
    
public:
    PerCharacterSplitWidget(QWidget *parent = nullptr);
    
protected:
    void paintEvent(QPaintEvent *event) override;
};

4、PerCharacterSplitWidget.cpp

cpp 复制代码
#include "PerCharacterSplitWidget.h"
#include <QPainter>
#include <QFontMetrics>

PerCharacterSplitWidget::PerCharacterSplitWidget(QWidget *parent) : QWidget(parent)
{
}

void PerCharacterSplitWidget::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event);
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);
    
    QFont font("宋体", 36, QFont::Bold);
    painter.setFont(font);
    QFontMetrics fm(font);
    
    QString text = "每个文字颜色分成两部分";
    
    // 计算文本总宽度
    int totalWidth = 0;
    for (int i = 0; i < text.length(); ++i) {
        totalWidth += fm.horizontalAdvance(text[i]);
    }
    
    // 起始位置(居中)
    int startX = (width() - totalWidth) / 2;
    int currentX = startX;
    int baseY = height() / 2 + fm.ascent() / 2;
    
    // 绘制每个字符
    for (int i = 0; i < text.length(); ++i) {
        QChar ch = text[i];
        int charWidth = fm.horizontalAdvance(ch);
        
        // 绘制左半部分
        painter.save();
        QRect charRect(currentX, baseY - fm.height(), charWidth, fm.height());
        QRect leftClip = charRect;
        leftClip.setWidth(charWidth / 2);
        painter.setClipRect(leftClip);
        painter.setPen(QColor(255, 100, 100)); // 浅红色
        painter.drawText(QRect(currentX, baseY - fm.height(), charWidth, fm.height()), 
                         Qt::AlignLeft | Qt::AlignTop, ch);
        painter.restore();
        
        // 绘制右半部分
        painter.save();
        QRect rightClip = charRect;
        rightClip.setLeft(currentX + charWidth / 2);
        painter.setClipRect(rightClip);
        painter.setPen(QColor(100, 100, 255)); // 浅蓝色
        painter.drawText(QRect(currentX, baseY - fm.height(), charWidth, fm.height()), 
                         Qt::AlignLeft | Qt::AlignTop, ch);
        painter.restore();
        
        currentX += charWidth;
    }
}

5、CustomSplitTextWidget.h

cpp 复制代码
#pragma once

#include <QWidget>
#include <QVector>

struct SplitColorChar {
    QChar character;
    QColor leftColor;
    QColor rightColor;
};

class CustomSplitTextWidget : public QWidget
{
    Q_OBJECT    
public:
    CustomSplitTextWidget(QWidget *parent = nullptr);
    
    void setText(const QVector<SplitColorChar> &text);
    
protected:
    void paintEvent(QPaintEvent *event) override;
    
    void initDemoText();
    
    void drawSplitColorText(QPainter &painter, const QVector<SplitColorChar> &text, const QPoint &startPos);
    
private:
    QVector<SplitColorChar> m_text;
    QVector<SplitColorChar> m_rainbowText;
};

6、CustomSplitTextWidget.cpp

cpp 复制代码
#include "CustomSplitTextWidget.h"
#include <QPainter>

CustomSplitTextWidget::CustomSplitTextWidget(QWidget *parent) : QWidget(parent)
{
    // 初始化示例数据
    initDemoText();
}

void CustomSplitTextWidget::setText(const QVector<SplitColorChar> &text)
{
    m_text = text;
    update();
}

void CustomSplitTextWidget::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event);
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);
    
    drawSplitColorText(painter, m_text, QPoint(100, 160));
    drawSplitColorText(painter, m_rainbowText, QPoint(100, 280));
}

void CustomSplitTextWidget::initDemoText()
{
    // 普通双色文本
    QString demoText = "自定义文字颜色分割";
    for (int i = 0; i < demoText.length(); ++i) {
        SplitColorChar scc;
        scc.character = demoText[i];
        scc.leftColor = QColor(255, 100, 100);  // 浅红
        scc.rightColor = QColor(100, 100, 255); // 浅蓝
        m_text.append(scc);
    }
    
    // 彩虹色文本
    QString rainbowText = "彩虹色文字效果展示";
    QVector<QColor> leftColors = {
        QColor(255, 0, 0),     // 红
        QColor(255, 127, 0),   // 橙
        QColor(255, 255, 0),   // 黄
        QColor(0, 255, 0),     // 绿
        QColor(0, 0, 255),     // 蓝
        QColor(75, 0, 130),    // 靛
        QColor(148, 0, 211)    // 紫
    };
    
    QVector<QColor> rightColors = {
        QColor(255, 192, 203), // 粉红
        QColor(255, 165, 0),   // 橙红
        QColor(255, 255, 224), // 浅黄
        QColor(144, 238, 144), // 浅绿
        QColor(173, 216, 230), // 浅蓝
        QColor(221, 160, 221), // 浅紫
        QColor(255, 182, 193)  // 浅粉
    };
    
    for (int i = 0; i < rainbowText.length(); ++i) {
        SplitColorChar scc;
        scc.character = rainbowText[i];
        scc.leftColor = leftColors[i % leftColors.size()];
        scc.rightColor = rightColors[i % rightColors.size()];
        m_rainbowText.append(scc);
    }
}

void CustomSplitTextWidget::drawSplitColorText(QPainter &painter, const QVector<SplitColorChar> &text, const QPoint &startPos)
{
    QFont font("宋体", 36, QFont::Normal);
    painter.setFont(font);
    QFontMetrics fm(font);
    
    int currentX = startPos.x();
    int baseY = startPos.y();
    
    for (const SplitColorChar &scc : text) {
        QChar ch = scc.character;
        int charWidth = fm.horizontalAdvance(ch);
        
        // 绘制左半部分
        painter.save();
        QRect charRect(currentX, baseY - fm.height(), charWidth, fm.height());
        QRect leftClip = charRect;
        leftClip.setWidth(charWidth / 2);
        painter.setClipRect(leftClip);
        painter.setPen(scc.leftColor);
        painter.drawText(QRect(currentX, baseY - fm.height(), charWidth, fm.height()), 
                         Qt::AlignLeft | Qt::AlignTop, ch);
        painter.restore();
        
        // 绘制右半部分
        painter.save();
        QRect rightClip = charRect;
        rightClip.setLeft(currentX + charWidth / 2);
        painter.setClipRect(rightClip);
        painter.setPen(scc.rightColor);
        painter.drawText(QRect(currentX, baseY - fm.height(), charWidth, fm.height()), 
                         Qt::AlignLeft | Qt::AlignTop, ch);
        painter.restore();
        
        currentX += charWidth;
    }
}

7、AnimatedSplitTextWidget.h

cpp 复制代码
#pragma once

#include <QWidget>

class AnimatedSplitTextWidget : public QWidget
{
    Q_OBJECT    
public:
    AnimatedSplitTextWidget(QWidget *parent = nullptr);
    
protected:
    void paintEvent(QPaintEvent *event) override;
    
    void drawAnimatedText(QPainter &painter);
    
    QColor interpolateColor(const QColor &start, const QColor &end, double ratio);
    
private:
    QTimer *m_timer;
    double m_animationOffset = 0.0;
};

8、AnimatedSplitTextWidget.cpp

cpp 复制代码
#include "AnimatedSplitTextWidget.h"
#include <QPainter>
#include <QTimer>
#include <QTime>
#include <QtMath>

AnimatedSplitTextWidget::AnimatedSplitTextWidget(QWidget *parent) : QWidget(parent)
{
    // 设置动画定时器
    m_timer = new QTimer(this);
    connect(m_timer, &QTimer::timeout, this, [this]() {
        m_animationOffset += 0.02;
        if (m_animationOffset > 1.0) {
            m_animationOffset = 0.0;
        }
        update();
    });
    m_timer->start(50); // 20 FPS
}

void AnimatedSplitTextWidget::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event);
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);
    
    drawAnimatedText(painter);
}

void AnimatedSplitTextWidget::drawAnimatedText(QPainter &painter)
{
    QFont font("Arial", 36, QFont::Bold);
    painter.setFont(font);
    QFontMetrics fm(font);
    
    QString text = "动画文字颜色分割效果";
    
    int totalWidth = 0;
    for (int i = 0; i < text.length(); ++i) {
        totalWidth += fm.horizontalAdvance(text[i]);
    }
    
    int startX = (width() - totalWidth) / 2;
    int currentX = startX;
    int baseY = height() / 2 + fm.ascent() / 2;
    
    for (int i = 0; i < text.length(); ++i) {
        QChar ch = text[i];
        int charWidth = fm.horizontalAdvance(ch);
        
        // 计算动画分割点(在字符宽度内移动)
        double splitRatio = 0.5 + 0.3 * qSin(m_animationOffset * 2 * M_PI + i * 0.3);
        int splitPoint = charWidth * splitRatio;
        
        // 左半部分颜色(从红到蓝渐变)
        QColor leftColor = interpolateColor(QColor(255, 0, 0), QColor(0, 0, 255), 
                                            qAbs(qSin(m_animationOffset * M_PI + i * 0.2)));
        
        // 右半部分颜色(从蓝到红渐变)
        QColor rightColor = interpolateColor(QColor(0, 0, 255), QColor(255, 0, 0), 
                                             qAbs(qCos(m_animationOffset * M_PI + i * 0.2)));
        
        // 绘制左半部分
        painter.save();
        QRect leftClip(currentX, baseY - fm.height(), splitPoint, fm.height());
        painter.setClipRect(leftClip);
        painter.setPen(leftColor);
        painter.drawText(QRect(currentX, baseY - fm.height(), charWidth, fm.height()), 
                         Qt::AlignLeft | Qt::AlignTop, ch);
        painter.restore();
        
        // 绘制右半部分
        painter.save();
        QRect rightClip(currentX + splitPoint, baseY - fm.height(), 
                        charWidth - splitPoint, fm.height());
        painter.setClipRect(rightClip);
        painter.setPen(rightColor);
        painter.drawText(QRect(currentX, baseY - fm.height(), charWidth, fm.height()), 
                         Qt::AlignLeft | Qt::AlignTop, ch);
        painter.restore();
        
        currentX += charWidth;
    }
}

QColor AnimatedSplitTextWidget::interpolateColor(const QColor &start, const QColor &end, double ratio)
{
    int r = start.red() + (end.red() - start.red()) * ratio;
    int g = start.green() + (end.green() - start.green()) * ratio;
    int b = start.blue() + (end.blue() - start.blue()) * ratio;
    return QColor(r, g, b);
}

9、GradientSplitTextWidget.h

cpp 复制代码
#pragma once

#include <QWidget>

class GradientSplitTextWidget : public QWidget
{
    Q_OBJECT    
public:
    GradientSplitTextWidget(QWidget *parent = nullptr);
    
protected:
    void paintEvent(QPaintEvent *event) override;
};

10、GradientSplitTextWidget.cpp

cpp 复制代码
#include "GradientSplitTextWidget.h"
#include <QPainter>
#include <QLinearGradient>

GradientSplitTextWidget::GradientSplitTextWidget(QWidget *parent) : QWidget(parent)
{
}

void GradientSplitTextWidget::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event);
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);
    
    QFont font("Arial", 30, QFont::Bold);
    painter.setFont(font);
    
    QString text = "醉后不知天在水,满船清梦压星河";
    QFontMetrics fm(font);
    QRect textRect = fm.boundingRect(text);
    textRect.moveCenter(rect().center());
    
    // 创建线性渐变(从左到右)
    QLinearGradient gradient(textRect.left(), 0, textRect.right(), 0);
    gradient.setColorAt(0, QColor(255, 0, 0));
    gradient.setColorAt(0.2, QColor(230, 100, 0));
    gradient.setColorAt(0.36, QColor(200, 220, 0));
    gradient.setColorAt(0.5, QColor(30, 250, 50));
    gradient.setColorAt(0.7, QColor(60, 200, 150));
    gradient.setColorAt(0.85, QColor(90, 10, 200));
    gradient.setColorAt(1, QColor(0, 0, 255));
    
    painter.setPen(QPen(gradient, 1));
    painter.drawText(textRect, Qt::AlignCenter, text);
}

11、HTMLSplitTextWidget.h

cpp 复制代码
#pragma once

#include <QWidget>

class HTMLSplitTextWidget : public QWidget
{
    Q_OBJECT    
public:
    HTMLSplitTextWidget(QWidget *parent = nullptr);
    
protected:
    void paintEvent(QPaintEvent *event) override;
    
private:
    QString createSplitColorHTML(const QString &text);
    
private:
    QString m_htmlText;
};

12、HTMLSplitTextWidget.cpp

cpp 复制代码
#include "HTMLSplitTextWidget.h"
#include <QTextDocument>
#include <QPainter>
#include <QTextCursor>
#include <QTextCharFormat>
#include <QAbstractTextDocumentLayout>
#include <QRandomGenerator>

HTMLSplitTextWidget::HTMLSplitTextWidget(QWidget *parent) : QWidget(parent)
{
    m_htmlText = QString("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
                         "<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\np, li { white-space: pre-wrap; }\n"
                         "</style></head><body style=\" font-family:'SimSun'; font-size:28pt; font-weight:600; font-style:bold;\">\n"
                         "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0;"
                         " text-indent:0px;\">%1</p></body></html>");
}

void HTMLSplitTextWidget::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event);
    QPainter painter(this);
    
    // 创建 HTML 内容
    QString plainText = "天苍苍,野茫茫,风吹草低见牛羊";
    QString html = m_htmlText.arg(createSplitColorHTML(plainText));
    
    // 使用 QTextDocument 渲染
    QTextDocument doc;
    doc.setHtml(html);
    //doc.setDefaultFont(QFont("Arial", 20, QFont::Bold));
    
    // 居中绘制
    doc.setTextWidth(width() * 0.8);
    QRect docRect(0, 0, width(), height());
    QSize offset = doc.documentLayout()->documentSize().toSize() / 2;
    painter.translate(docRect.center() - QPoint(offset.width(), offset.height()));
    doc.drawContents(&painter);
}

QString HTMLSplitTextWidget::createSplitColorHTML(const QString &text)
{
    QString html;
    for (int i = 0; i < text.length(); ++i) {
        QChar ch = text[i];
        int random = QRandomGenerator::global()->bounded(256);
        html += QString("<span style=\"background:transparent;"
                        "color:rgb(%1,%2,%3);\">%4</span>")
                    .arg(255 - random).arg(random).arg(255 - 0.5 * random).arg(ch);
    }
    return html;
}

13、dialog.h

cpp 复制代码
#ifndef DIALOG_H
#define DIALOG_H

#include <QDialog>

class Dialog : public QDialog
{
    Q_OBJECT
public:
    Dialog(QWidget *parent = nullptr);
    ~Dialog();
    
private:
    void init();
};
#endif // DIALOG_H

14、dialog.cpp

cpp 复制代码
#include "dialog.h"
#include "AnimatedSplitTextWidget.h"
#include "CustomSplitTextWidget.h"
#include "GradientSplitTextWidget.h"
#include "HTMLSplitTextWidget.h"
#include "PerCharacterSplitWidget.h"
#include "SplitColorTextWidget.h"
#include <QTabWidget>
#include <qboxlayout.h>

Dialog::Dialog(QWidget *parent)
    : QDialog(parent)
{
    init();
}

Dialog::~Dialog() {}

void Dialog::init()
{
    setWindowTitle("QT 文字颜色分割效果演示");
    setWindowFlags(Qt::Dialog |Qt::WindowCloseButtonHint |Qt::WindowMinMaxButtonsHint);
    setMinimumSize(750, 500);
        
    QTabWidget *tabWidget = new QTabWidget(this);
    
    // 标签1: 基本分割效果
    SplitColorTextWidget *basicWidget = new SplitColorTextWidget(this);
    tabWidget->addTab(basicWidget, "基本分割");
    
    // 标签2: 逐个字符分割
    PerCharacterSplitWidget *charWidget = new PerCharacterSplitWidget(this);
    tabWidget->addTab(charWidget, "逐个字符");
    
    // 标签3: 自定义颜色
    CustomSplitTextWidget *customWidget = new CustomSplitTextWidget(this);
    tabWidget->addTab(customWidget, "自定义颜色");
    
    // 标签4: 动画效果
    AnimatedSplitTextWidget *animatedWidget = new AnimatedSplitTextWidget(this);
    tabWidget->addTab(animatedWidget, "动画效果");
    
    GradientSplitTextWidget *gradientWidget = new GradientSplitTextWidget(this);
    tabWidget->addTab(gradientWidget, "渐变和蒙版");
    
    HTMLSplitTextWidget *htmlWidget = new HTMLSplitTextWidget(this);
    tabWidget->addTab(htmlWidget, "html彩色文本");
    
    QHBoxLayout *hLay = new QHBoxLayout(this);
    hLay->addWidget(tabWidget);
}

15、main.cpp

cpp 复制代码
#include "dialog.h"
#include <QApplication>
#include <windows.h>
#include <qtextcodec.h>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    
    // 设置控制台输出编码为UTF-8
    SetConsoleOutputCP(65001);
    
    // 设置控制台输入编码为UTF-8(如果需要)
    SetConsoleCP(65001);
    
    // 设置Qt编码
    QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8"));
    
    // 设置标准输出编码
    // std::setlocale(LC_ALL, ".UTF-8");
    
    Dialog w;
    w.show();
    
    return a.exec();
}

三、运行结果

相关推荐
知南x3 小时前
【QT界面设计学习篇】qt快速开发技巧
开发语言·qt
泽虞3 小时前
《Qt应用开发》笔记p4
linux·开发语言·数据库·c++·笔记·qt·算法
ajassi20003 小时前
开源 C++ QT QML 开发(十三)多线程
c++·qt·开源
feiyangqingyun3 小时前
全网首发/Qt结合ffmpeg实现rist推拉流/可信赖的互联网流媒体协议/跨平台支持各个系统
qt·ffmpeg·rist推拉流
Qt程序员3 小时前
Qt C++ 教程:无边框窗体 + 自定义标题栏 + 圆角 + 拖拽拉升 + 阴影
c++·qt·qt编程·qt开发·qt教程·qt界面开发·qt界面
泽虞3 小时前
《Qt应用开发》笔记p5
linux·开发语言·c++·笔记·qt·算法
周之鸥4 小时前
Qt 项目国际化从零到一:用 Qt Linguist 实现多语言动态切换(含源码与踩坑指南)
qt·i18n·cmake·qmake·linguist·lupdate·lrelease
打码的猿4 小时前
在Qt中实现SwitchButton(开关按钮)
开发语言·qt·ui
友友马4 小时前
『 QT 』QT窗口坐标体系详解
开发语言·qt