在上章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;
- }
- }
- }
其它的好友收發訊息,則依葫蘆畫瓢,通通實現一遍即可.