40.qt quick- 高仿微信實現區域網聊天V4版本(支援gif動圖表情包、訊息聊天、拖動縮放視窗)

諾謙發表於2021-07-27

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

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

 

 

相關文章