(创作不易,点个关注,不胜感谢)
一、问题与相关技术
问:如何实现绘制一个多色彩的字符?
答 :可以使用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();
}
三、运行结果





