文章目录

  • ​​1、简介​​
  • ​​2、效果图​​
  • ​​3、实现原理​​
  • ​​4、核心代码​​
  • ​​4.1、头文件​​
  • ​​4.2、源文件​​
  • ​​5、代码分享​​
  • ​​5.1、Github​​
  • ​​5.2、码云​​

1、简介

由于最近的项目需要,做了些相关IM的工作。所以聊天框也是必不可少的一部分。聊天框的制作分很多种,本文以QListWidget+QPainter绘制的Item做了一个Demo。该Demo只是做一个示例,代码已公布如下,需要的拿去!

2、效果图

Qt5气泡式聊天框——QListWidget+QPainter实现_QListWidget

3、实现原理

气泡式聊天的显示是由QListWidget作为控件,每个气泡是由QListWidgetItem提升成QWidget来实现的。每个气泡可以理解位是一个QWidget,这样可以自由布置QWidget里面的内容。每个Item保存聊天的对话、发送状态、时间、种类等。

这个QWidget主要是显示一个头像+气泡,气泡里面是聊天的内容等。
气泡是在paintEvent事件中,采用QPainter来绘制的。

4、核心代码

4.1、头文件

#ifndef QNCHATMESSAGE_H
#define QNCHATMESSAGE_H

#include <QWidget>

class QPaintEvent;
class QPainter;
class QLabel;
class QMovie;

class QNChatMessage : public QWidget
{
Q_OBJECT
public:
explicit QNChatMessage(QWidget *parent = nullptr);

enum User_Type{
User_System,//系统
User_Me, //自己
User_She, //用户
User_Time, //时间
};
void setTextSuccess();
void setText(QString text, QString time, QSize allSize, User_Type userType);

QSize getRealString(QString src);
QSize fontRect(QString str);

inline QString text() {return m_msg;}
inline QString time() {return m_time;}
inline User_Type userType() {return m_userType;}
protected:
void paintEvent(QPaintEvent *event);
private:
QString m_msg;
QString m_time;
QString m_curTime;

QSize m_allSize;
User_Type m_userType = User_System;

int m_kuangWidth;
int m_textWidth;
int m_spaceWid;
int m_lineHeight;

QRect m_iconLeftRect;
QRect m_iconRightRect;
QRect m_sanjiaoLeftRect;
QRect m_sanjiaoRightRect;
QRect m_kuangLeftRect;
QRect m_kuangRightRect;
QRect m_textLeftRect;
QRect m_textRightRect;
QPixmap m_leftPixmap;
QPixmap m_rightPixmap;
QLabel* m_loading = Q_NULLPTR;
QMovie* m_loadingMovie = Q_NULLPTR;
bool m_isSending = false;
};

#endif // QNCHATMESSAGE_H

4.2、源文件

#include "qnchatmessage.h"
#include <QFontMetrics>
#include <QPaintEvent>
#include <QDateTime>
#include <QPainter>
#include <QMovie>
#include <QLabel>
#include <QDebug>

QNChatMessage::QNChatMessage(QWidget *parent) : QWidget(parent)
{
QFont te_font = this->font();
te_font.setFamily("MicrosoftYaHei");
te_font.setPointSize(12);
// te_font.setWordSpacing(0);
// te_font.setLetterSpacing(QFont::PercentageSpacing,0);
// te_font.setLetterSpacing(QFont::PercentageSpacing, 100); //300%,100为默认 //设置字间距%
// te_font.setLetterSpacing(QFont::AbsoluteSpacing, 0); //设置字间距为3像素 //设置字间距像素值
this->setFont(te_font);
m_leftPixmap = QPixmap(":/img/Customer Copy.png");
m_rightPixmap = QPixmap(":/img/CustomerService.png");

m_loadingMovie = new QMovie(this);
m_loadingMovie->setFileName(":/img/loading4.gif");
m_loading = new QLabel(this);
m_loading->setMovie(m_loadingMovie);
m_loading->resize(16,16);
m_loading->setAttribute(Qt::WA_TranslucentBackground , true);
m_loading->setAutoFillBackground(false);
}

void QNChatMessage::setTextSuccess()
{
m_loading->hide();
m_loadingMovie->stop();
m_isSending = true;
}

void QNChatMessage::setText(QString text, QString time, QSize allSize, QNChatMessage::User_Type userType)
{
m_msg = text;
m_userType = userType;
m_time = time;
m_curTime = QDateTime::fromTime_t(time.toInt()).toString("hh:mm");
m_allSize = allSize;
if(userType == User_Me) {
if(!m_isSending) {
m_loading->move(m_kuangRightRect.x() - m_loading->width() - 10, m_kuangRightRect.y()+m_kuangRightRect.height()/2- m_loading->height()/2);
m_loading->show();
m_loadingMovie->start();
}
} else {
m_loading->hide();
}

this->update();
}

QSize QNChatMessage::fontRect(QString str)
{
m_msg = str;
int minHei = 30;
int iconWH = 40;
int iconSpaceW = 20;
int iconRectW = 5;
int iconTMPH = 10;
int sanJiaoW = 6;
int kuangTMP = 20;
int textSpaceRect = 12;
m_kuangWidth = this->width() - kuangTMP - 2*(iconWH+iconSpaceW+iconRectW);
m_textWidth = m_kuangWidth - 2*textSpaceRect;
m_spaceWid = this->width() - m_textWidth;
m_iconLeftRect = QRect(iconSpaceW, iconTMPH, iconWH, iconWH);
m_iconRightRect = QRect(this->width() - iconSpaceW - iconWH, iconTMPH, iconWH, iconWH);

QSize size = getRealString(m_msg); // 整个的size

qDebug() << "fontRect Size:" << size;
int hei = size.height() < minHei ? minHei : size.height();

m_sanjiaoLeftRect = QRect(iconWH+iconSpaceW+iconRectW, m_lineHeight/2, sanJiaoW, hei - m_lineHeight);
m_sanjiaoRightRect = QRect(this->width() - iconRectW - iconWH - iconSpaceW - sanJiaoW, m_lineHeight/2, sanJiaoW, hei - m_lineHeight);

if(size.width() < (m_textWidth+m_spaceWid)) {
m_kuangLeftRect.setRect(m_sanjiaoLeftRect.x()+m_sanjiaoLeftRect.width(), m_lineHeight/4*3, size.width()-m_spaceWid+2*textSpaceRect, hei-m_lineHeight);
m_kuangRightRect.setRect(this->width() - size.width() + m_spaceWid - 2*textSpaceRect - iconWH - iconSpaceW - iconRectW - sanJiaoW,
m_lineHeight/4*3, size.width()-m_spaceWid+2*textSpaceRect, hei-m_lineHeight);
} else {
m_kuangLeftRect.setRect(m_sanjiaoLeftRect.x()+m_sanjiaoLeftRect.width(), m_lineHeight/4*3, m_kuangWidth, hei-m_lineHeight);
m_kuangRightRect.setRect(iconWH + kuangTMP + iconSpaceW + iconRectW - sanJiaoW, m_lineHeight/4*3, m_kuangWidth, hei-m_lineHeight);
}
m_textLeftRect.setRect(m_kuangLeftRect.x()+textSpaceRect,m_kuangLeftRect.y()+iconTMPH,
m_kuangLeftRect.width()-2*textSpaceRect,m_kuangLeftRect.height()-2*iconTMPH);
m_textRightRect.setRect(m_kuangRightRect.x()+textSpaceRect,m_kuangRightRect.y()+iconTMPH,
m_kuangRightRect.width()-2*textSpaceRect,m_kuangRightRect.height()-2*iconTMPH);

return QSize(size.width(), hei);
}

QSize QNChatMessage::getRealString(QString src)
{
QFontMetricsF fm(this->font());
m_lineHeight = fm.lineSpacing();
int nCount = src.count("\n");
int nMaxWidth = 0;
if(nCount == 0) {
nMaxWidth = fm.width(src);
QString value = src;
if(nMaxWidth > m_textWidth) {
nMaxWidth = m_textWidth;
int size = m_textWidth / fm.width(" ");
int num = fm.width(value) / m_textWidth;
int ttmp = num*fm.width(" ");
num = ( fm.width(value) ) / m_textWidth;
nCount += num;
QString temp = "";
for(int i = 0; i < num; i++) {
temp += value.mid(i*size, (i+1)*size) + "\n";
}
src.replace(value, temp);
}
} else {
for(int i = 0; i < (nCount + 1); i++) {
QString value = src.split("\n").at(i);
nMaxWidth = fm.width(value) > nMaxWidth ? fm.width(value) : nMaxWidth;
if(fm.width(value) > m_textWidth) {
nMaxWidth = m_textWidth;
int size = m_textWidth / fm.width(" ");
int num = fm.width(value) / m_textWidth;
num = ((i+num)*fm.width(" ") + fm.width(value)) / m_textWidth;
nCount += num;
QString temp = "";
for(int i = 0; i < num; i++) {
temp += value.mid(i*size, (i+1)*size) + "\n";
}
src.replace(value, temp);
}
}
}
return QSize(nMaxWidth+m_spaceWid, (nCount + 1) * m_lineHeight+2*m_lineHeight);
}

void QNChatMessage::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);

QPainter painter(this);
painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);//消锯齿
painter.setPen(Qt::NoPen);
painter.setBrush(QBrush(Qt::gray));

if(m_userType == User_Type::User_She) { // 用户
//头像
// painter.drawRoundedRect(m_iconLeftRect,m_iconLeftRect.width(),m_iconLeftRect.height());
painter.drawPixmap(m_iconLeftRect, m_leftPixmap);

//框加边
QColor col_KuangB(234, 234, 234);
painter.setBrush(QBrush(col_KuangB));
painter.drawRoundedRect(m_kuangLeftRect.x()-1,m_kuangLeftRect.y()-1,m_kuangLeftRect.width()+2,m_kuangLeftRect.height()+2,4,4);
//框
QColor col_Kuang(255,255,255);
painter.setBrush(QBrush(col_Kuang));
painter.drawRoundedRect(m_kuangLeftRect,4,4);

//三角
QPointF points[3] = {
QPointF(m_sanjiaoLeftRect.x(), 30),
QPointF(m_sanjiaoLeftRect.x()+m_sanjiaoLeftRect.width(), 25),
QPointF(m_sanjiaoLeftRect.x()+m_sanjiaoLeftRect.width(), 35),
};
QPen pen;
pen.setColor(col_Kuang);
painter.setPen(pen);
painter.drawPolygon(points, 3);

//三角加边
QPen penSanJiaoBian;
penSanJiaoBian.setColor(col_KuangB);
painter.setPen(penSanJiaoBian);
painter.drawLine(QPointF(m_sanjiaoLeftRect.x() - 1, 30), QPointF(m_sanjiaoLeftRect.x()+m_sanjiaoLeftRect.width(), 24));
painter.drawLine(QPointF(m_sanjiaoLeftRect.x() - 1, 30), QPointF(m_sanjiaoLeftRect.x()+m_sanjiaoLeftRect.width(), 36));

//内容
QPen penText;
penText.setColor(QColor(51,51,51));
painter.setPen(penText);
QTextOption option(Qt::AlignLeft | Qt::AlignVCenter);
option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
painter.setFont(this->font());
painter.drawText(m_textLeftRect, m_msg,option);
} else if(m_userType == User_Type::User_Me) { // 自己
//头像
// painter.drawRoundedRect(m_iconRightRect,m_iconRightRect.width(),m_iconRightRect.height());
painter.drawPixmap(m_iconRightRect, m_rightPixmap);

//框
QColor col_Kuang(75,164,242);
painter.setBrush(QBrush(col_Kuang));
painter.drawRoundedRect(m_kuangRightRect,4,4);

//三角
QPointF points[3] = {
QPointF(m_sanjiaoRightRect.x()+m_sanjiaoRightRect.width(), 30),
QPointF(m_sanjiaoRightRect.x(), 25),
QPointF(m_sanjiaoRightRect.x(), 35),
};
QPen pen;
pen.setColor(col_Kuang);
painter.setPen(pen);
painter.drawPolygon(points, 3);

//内容
QPen penText;
penText.setColor(Qt::white);
painter.setPen(penText);
QTextOption option(Qt::AlignLeft | Qt::AlignVCenter);
option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
painter.setFont(this->font());
painter.drawText(m_textRightRect,m_msg,option);
} else if(m_userType == User_Type::User_Time) { // 时间
QPen penText;
penText.setColor(QColor(153,153,153));
painter.setPen(penText);
QTextOption option(Qt::AlignCenter);
option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
QFont te_font = this->font();
te_font.setFamily("MicrosoftYaHei");
te_font.setPointSize(10);
painter.setFont(te_font);
painter.drawText(this->rect(),m_curTime,option);
}
}

5、代码分享

5.1、Github

地址:​​https://github.com/ShaShiDiZhuanLan/Demo_MessageChat_Qt​

5.2、码云

地址:​​https://gitee.com/ShaShiDiZhuanLan/Demo_MessageChat​