在上章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

  1. // 新增一個gif
  2. void GifTextHandler::inset(QString fileName)
  3. {
  4. if (!m_documnt)
  5. return;
  6.  
  7. QTextCursor cursor = QTextCursor(m_documnt->textDocument());
  8. cursor.setPosition(m_cursorStart);
  9. if (m_cursorStart > m_cursorEnd) {
  10. cursor.setPosition(0);
  11. } else if (m_cursorStart != m_cursorEnd)
  12. cursor.setPosition(m_cursorEnd, QTextCursor::KeepAnchor);
  13.  
  14. addMovie(fileName); // 通過QMovie載入一個gif
  15. QTextImageFormat imageFormat;
  16. imageFormat.setName(fileName);
  17. imageFormat.setWidth(m_width);
  18. imageFormat.setHeight(m_height);
  19. cursor.insertImage(imageFormat, QTextFrameFormat::InFlow);
  20.  
  21. }

新增一個gif後,又如何去管理這個gif,假如我當前文字把這個gif刪除了,那麼這個QMovie也應該釋放才行,否則就記憶體溢位啦.

2.2 gif動態釋放與管理

所以每隔1秒就去讀取下文字,gif是否還存在,如果不存在則釋放QMovie:

  1. QList<QString> list = m_movies.keys();
  2. for(int i=0;i<list.length();i++) {
  3. m_movies[list[i]]->setProperty("status",false);
  4. }
  5.  
  6. QTextBlock block = m_documnt->textDocument()->firstBlock();
  7. QVector<QTextFormat> allFormats = m_documnt->textDocument()->allFormats();
  8. while(block.isValid()) {
  9. QTextBlockFormat blockFmt = block.blockFormat();
  10. for(QTextBlock::iterator it = block.begin(); !it.atEnd(); it++) {
  11. QTextCharFormat charFmt = it.fragment().charFormat(); // fragment是存放文字,位置的類
  12. if (charFmt.objectType() == QTextFormat::ImageObject) {
  13. QString file = charFmt.property(QTextFormat::ImageName).toString();
  14. if (!m_movies.contains(file)) {
  15. addMovie(file);
  16. }
  17. m_movies[file]->setProperty("status",true);
  18. // qDebug()<<"status: "<<file;
  19. }
  20. }
  21. block = block.next();
  22. }
  23.  
  24. for(int i=0;i<list.length();i++) {
  25. if (m_movies[list[i]]->property("status") == false) {
  26. qDebug()<<"釋放gif:"<<i<<list[i];
  27. delete m_movies[list[i]];
  28. m_movies.remove(list[i]);
  29. continue;
  30. }
  31. }

如果文字釋放了,那麼就遍歷整個gif表,釋放所有:

  1. GifTextHandler::~GifTextHandler() // 釋放資源
  2. {
  3. QList<QString> list = m_movies.keys();
  4. for(int i=0;i<list.length();i++) { // 釋放所有GIF
  5. delete m_movies[list[i]];
  6. }
  7. m_movies.clear();
  8. }

2.3 文字傳送

由於文字中包含了gif圖,所以傳送的時候,我們需要將文字內容(包含gif)進行編碼,轉換成字串,程式碼如下所示:

  1. QString GifTextHandler::coding()
  2. {
  3. QString ret("");
  4. QTextBlock block = m_documnt->textDocument()->firstBlock();
  5. QVector<QTextFormat> allFormats = m_documnt->textDocument()->allFormats();
  6.  
  7. while(block.isValid()) {
  8. for(QTextBlock::iterator it = block.begin(); !it.atEnd(); it++) {
  9. QTextCharFormat charFmt = it.fragment().charFormat(); // fragment是存放文字,位置的類
  10. if (charFmt.objectType() == QTextFormat::ImageObject) {
  11. ret.append("["+charFmt.property(QTextFormat::ImageName).toString().remove(GIF_PREFIXDIR)+"]");
  12. } else {
  13. ret.append(it.fragment().text());
  14. }
  15. }
  16. if (block.next().isValid()) // QTextBlock以換行為分割成每個塊,所以每次塊遍歷完後需要加換行符
  17. ret.append("\r\n");
  18.  
  19. block = block.next();
  20. }
  21. return ret;
  22. }

2.4 文字接收

由於接收到的資料是一段字串,所以我們需要解碼,將gif標誌顯示成一個gif動圖,程式碼如下所示:

  1. void GifTextHandler::encoding(QString text)
  2. {
  3. if (!m_documnt) {
  4. qDebug()<<"encoding: m_documnt == nullptr";
  5. return;
  6. }
  7. QTextCursor cursor = QTextCursor(m_documnt->textDocument());
  8. cursor.setPosition(0);
  9. QRegularExpression re("\\[\\d+\\.gif\\]");
  10. QRegularExpressionMatch match;
  11. int offset = 0;
  12. int fileNum;
  13. QString file;
  14. QString ret;
  15. while (offset < text.length()) {
  16. match = re.match(text,offset);
  17. if (match.hasMatch()) {
  18. sscanf(match.captured(0).toLocal8Bit(),"[%d.gif]", &fileNum);
  19. file = QString(GIF_PREFIXDIR+"%1.gif").arg(fileNum);
  20. cursor.insertText(text.mid(offset, match.capturedStart(0) - offset)); // 新增前面的
  21. ret.append(text.mid(offset, match.capturedStart(0) - offset));
  22. addMovie(file);
  23. QTextImageFormat imageFormat;
  24. imageFormat.setName(file);
  25. imageFormat.setWidth(m_width);
  26. imageFormat.setHeight(m_height);
  27. cursor.insertImage(imageFormat, QTextFrameFormat::InFlow);
  28. offset = match.capturedEnd(0);
  29. } else {
  30. ret.append(text.mid(offset, text.length() - offset));
  31. cursor.insertText(text.mid(offset, text.length() - offset)); // 新增前面的
  32. break;
  33. }
  34. }
  35. }

3.C++ QAbstractListModel類

qml中使用的是ListView,而model資料是使用C++ model(繼承於QAbstractListModel).

所以當有好友上線時,我們需要往model中新增一行好友資料,並通知ListView重新整理區域性資料,程式碼如下所示:

  1. void FriendModel::addFriend(MessageDesc* msg)
  2. {
  3. for (int i=1; i < m_data.count(); i++) {
  4. if (m_data[i]->title() == msg->srcUser) {
  5. return;
  6. }
  7. }
  8. // 將頭像 Arr資料轉換成jpg檔案
  9. QPixmap pixmap;
  10. QString str = AppCfg::getInstance()->read(AppCfg::FriendHeadDir).remove("file:///")+msg->srcUser+".jpg";
  11. qDebug()<<msg->headArr.length()<<pixmap.loadFromData((uchar *)msg->headArr.data(), msg->headArr.length());
  12. bool ret = pixmap.save(str);
  13. AppCfg::getInstance()->fileLogWrite(QString("addFriend str%1 str%2 ret%3").arg(str).arg(QUrl::fromLocalFile(str).toString()).
  14. arg(ret));
  15.  
  16. beginInsertRows(QModelIndex(), 1, 1); // 由於聊天室始終頂置,所以只能插入到第2行
  17. m_data.insert(1, new FriendChatData(msg->srcUser, QUrl::fromLocalFile(str).toString()));
  18. endInsertRows();
  19. hintInsetChatRoom("\""+msg->srcUser+"\" 於"+QDateTime::currentDateTime().toString(" hh:mm ")+"上線!");
  20. }

當好友下線時,則需要刪除好友資料,程式碼如下所示:

  1. void FriendModel::removeFriend(MessageDesc* msg)
  2. {
  3. for (int i=1; i < m_data.count(); i++) {
  4. if (m_data[i]->title() == msg->srcUser) {
  5. beginRemoveRows(QModelIndex(), i, i);
  6.  
  7. // 假如聊天介面的model等於當前下線的好友,那麼需要釋放聊天model資料,並重新整理檢視
  8. if (m_chat != NULL && m_chat->data() == m_data[i]) {
  9. delete m_chat;
  10. m_chat = NULL;
  11. emit removeFriendcloseChat();
  12. }
  13. delete m_data[i];
  14. m_data.removeAt(i);
  15. endRemoveRows();
  16. hintInsetChatRoom("\""+msg->srcUser+"\" 於"+QDateTime::currentDateTime().toString(" hh:mm ")+"下線!");
  17. break;
  18. }
  19. }
  20. }

其它的好友收發訊息,則依葫蘆畫瓢,通通實現一遍即可.