在上章37.qt quick- 高仿微信實現區域網聊天V3版本(新增登入介面、UDP校驗登入、皮膚更換、3D旋轉),我們已經實現了:
- 新增登入介面、
- UDP校驗登入、
- 皮膚更換、
- 3D旋轉(主介面和登入介面之間切換) 、
所以本章實現:
- 2、支援拖動和更改視窗大小、
- 3、可以單獨聊天、也可以在聊天室所有人聊天、
- 4、支援收發gif表情包(支援貼上複製)、
- 5、自動重新整理當前好友線上人數等、
1.介面展示
介面佈局如下所示:
介面截圖如下所示:
效果圖如下所示:
有點大,可能載入不了,不過已經上傳到bilibili了https://www.bilibili.com/video/BV1Ao4y1S7zX
由於程式碼量有點多,所以講解重點的部分
2.Text中的gif管理
2.1 在Text中加入一個gif
// 新增一個gif
void GifTextHandler::inset(QString fileName)
{
if (!m_documnt)
return;
QTextCursor cursor = QTextCursor(m_documnt->textDocument());
cursor.setPosition(m_cursorStart);
if (m_cursorStart > m_cursorEnd) {
cursor.setPosition(0);
} else if (m_cursorStart != m_cursorEnd)
cursor.setPosition(m_cursorEnd, QTextCursor::KeepAnchor);
addMovie(fileName); // 通過QMovie載入一個gif
QTextImageFormat imageFormat;
imageFormat.setName(fileName);
imageFormat.setWidth(m_width);
imageFormat.setHeight(m_height);
cursor.insertImage(imageFormat, QTextFrameFormat::InFlow);
}
新增一個gif後,又如何去管理這個gif,假如我當前文字把這個gif刪除了,那麼這個QMovie也應該釋放才行,否則就記憶體溢位啦.
2.2 gif動態釋放與管理
所以每隔1秒就去讀取下文字,gif是否還存在,如果不存在則釋放QMovie:
QList<QString> list = m_movies.keys();
for(int i=0;i<list.length();i++) {
m_movies[list[i]]->setProperty("status",false);
}
QTextBlock block = m_documnt->textDocument()->firstBlock();
QVector<QTextFormat> allFormats = m_documnt->textDocument()->allFormats();
while(block.isValid()) {
QTextBlockFormat blockFmt = block.blockFormat();
for(QTextBlock::iterator it = block.begin(); !it.atEnd(); it++) {
QTextCharFormat charFmt = it.fragment().charFormat(); // fragment是存放文字,位置的類
if (charFmt.objectType() == QTextFormat::ImageObject) {
QString file = charFmt.property(QTextFormat::ImageName).toString();
if (!m_movies.contains(file)) {
addMovie(file);
}
m_movies[file]->setProperty("status",true);
// qDebug()<<"status: "<<file;
}
}
block = block.next();
}
for(int i=0;i<list.length();i++) {
if (m_movies[list[i]]->property("status") == false) {
qDebug()<<"釋放gif:"<<i<<list[i];
delete m_movies[list[i]];
m_movies.remove(list[i]);
continue;
}
}
如果文字釋放了,那麼就遍歷整個gif表,釋放所有:
GifTextHandler::~GifTextHandler() // 釋放資源
{
QList<QString> list = m_movies.keys();
for(int i=0;i<list.length();i++) { // 釋放所有GIF
delete m_movies[list[i]];
}
m_movies.clear();
}
2.3 文字傳送
由於文字中包含了gif圖,所以傳送的時候,我們需要將文字內容(包含gif)進行編碼,轉換成字串,程式碼如下所示:
QString GifTextHandler::coding()
{
QString ret("");
QTextBlock block = m_documnt->textDocument()->firstBlock();
QVector<QTextFormat> allFormats = m_documnt->textDocument()->allFormats();
while(block.isValid()) {
for(QTextBlock::iterator it = block.begin(); !it.atEnd(); it++) {
QTextCharFormat charFmt = it.fragment().charFormat(); // fragment是存放文字,位置的類
if (charFmt.objectType() == QTextFormat::ImageObject) {
ret.append("["+charFmt.property(QTextFormat::ImageName).toString().remove(GIF_PREFIXDIR)+"]");
} else {
ret.append(it.fragment().text());
}
}
if (block.next().isValid()) // QTextBlock以換行為分割成每個塊,所以每次塊遍歷完後需要加換行符
ret.append("\r\n");
block = block.next();
}
return ret;
}
2.4 文字接收
由於接收到的資料是一段字串,所以我們需要解碼,將gif標誌顯示成一個gif動圖,程式碼如下所示:
void GifTextHandler::encoding(QString text)
{
if (!m_documnt) {
qDebug()<<"encoding: m_documnt == nullptr";
return;
}
QTextCursor cursor = QTextCursor(m_documnt->textDocument());
cursor.setPosition(0);
QRegularExpression re("\\[\\d+\\.gif\\]");
QRegularExpressionMatch match;
int offset = 0;
int fileNum;
QString file;
QString ret;
while (offset < text.length()) {
match = re.match(text,offset);
if (match.hasMatch()) {
sscanf(match.captured(0).toLocal8Bit(),"[%d.gif]", &fileNum);
file = QString(GIF_PREFIXDIR+"%1.gif").arg(fileNum);
cursor.insertText(text.mid(offset, match.capturedStart(0) - offset)); // 新增前面的
ret.append(text.mid(offset, match.capturedStart(0) - offset));
addMovie(file);
QTextImageFormat imageFormat;
imageFormat.setName(file);
imageFormat.setWidth(m_width);
imageFormat.setHeight(m_height);
cursor.insertImage(imageFormat, QTextFrameFormat::InFlow);
offset = match.capturedEnd(0);
} else {
ret.append(text.mid(offset, text.length() - offset));
cursor.insertText(text.mid(offset, text.length() - offset)); // 新增前面的
break;
}
}
}
3.C++ QAbstractListModel類
qml中使用的是ListView,而model資料是使用C++ model(繼承於QAbstractListModel).
所以當有好友上線時,我們需要往model中新增一行好友資料,並通知ListView重新整理區域性資料,程式碼如下所示:
void FriendModel::addFriend(MessageDesc* msg)
{
for (int i=1; i < m_data.count(); i++) {
if (m_data[i]->title() == msg->srcUser) {
return;
}
}
// 將頭像 Arr資料轉換成jpg檔案
QPixmap pixmap;
QString str = AppCfg::getInstance()->read(AppCfg::FriendHeadDir).remove("file:///")+msg->srcUser+".jpg";
qDebug()<<msg->headArr.length()<<pixmap.loadFromData((uchar *)msg->headArr.data(), msg->headArr.length());
bool ret = pixmap.save(str);
AppCfg::getInstance()->fileLogWrite(QString("addFriend str%1 str%2 ret%3").arg(str).arg(QUrl::fromLocalFile(str).toString()).
arg(ret));
beginInsertRows(QModelIndex(), 1, 1); // 由於聊天室始終頂置,所以只能插入到第2行
m_data.insert(1, new FriendChatData(msg->srcUser, QUrl::fromLocalFile(str).toString()));
endInsertRows();
hintInsetChatRoom("\""+msg->srcUser+"\" 於"+QDateTime::currentDateTime().toString(" hh:mm ")+"上線!");
}
當好友下線時,則需要刪除好友資料,程式碼如下所示:
void FriendModel::removeFriend(MessageDesc* msg)
{
for (int i=1; i < m_data.count(); i++) {
if (m_data[i]->title() == msg->srcUser) {
beginRemoveRows(QModelIndex(), i, i);
// 假如聊天介面的model等於當前下線的好友,那麼需要釋放聊天model資料,並重新整理檢視
if (m_chat != NULL && m_chat->data() == m_data[i]) {
delete m_chat;
m_chat = NULL;
emit removeFriendcloseChat();
}
delete m_data[i];
m_data.removeAt(i);
endRemoveRows();
hintInsetChatRoom("\""+msg->srcUser+"\" 於"+QDateTime::currentDateTime().toString(" hh:mm ")+"下線!");
break;
}
}
}
其它的好友收發訊息,則依葫蘆畫瓢,通通實現一遍即可.