zoukankan      html  css  js  c++  java
  • 40.qt quick- 高仿微信实现局域网聊天V4版本(支持gif动图表情包、消息聊天、拖动缩放窗口、支持Linux编译)

    在上章37.qt quick- 高仿微信实现局域网聊天V3版本(添加登录界面、UDP校验登录、皮肤更换、3D旋转),我们已经实现了:

    • 添加登录界面、
    • UDP校验登录、
    • 皮肤更换、
    • 3D旋转(主界面和登录界面之间切换) 、

    所以本章实现:

    • 支持拖动和更改窗口大小、
    • 可以单独聊天、也可以在聊天室所有人聊天、
    • 支持收发gif表情包(支持粘贴复制)、
    • 自动刷新当前好友在线人数等、
    • 同时支持Linux和Windows编译运行

    1.界面展示

    界面布局如下所示:

    界面截图如下所示:

    效果图如下所示:

    有点大,可能加载不了,不过已经上传到bilibili了https://www.bilibili.com/video/BV1Ao4y1S7zX

    由于代码量有点多,所以讲解重点的部分

    2.支持Linux编译

    我这里是交叉编译到树莓派运行,在Linux下则取消了3D旋转,如下图所示:

     动图就不上传了,视频已经上传到bilibili了 https://www.bilibili.com/video/BV1JU4y1H7xc/

    3.Text中的gif管理

    3.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也应该释放才行,否则就内存溢出啦.

    3.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();
    }

    3.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("
    ");
    
             block = block.next();
        }
        return ret;
    }

    3.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;
            }
        }
    }

    4.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;
            }
        }
    }

    其它的好友收发消息,则依葫芦画瓢,通通实现一遍即可.

     

     


    人间有真情,人间有真爱。

    如果您喜欢这里,感觉对你有帮助,并且有多余的软妹币的话,不妨投个食吧,赞赏的时候,留下美句和你的博客地址哦~   戳这里看谁投食了


  • 相关阅读:
    常见名词解释
    主板结构解析
    计算机网络原理的总结
    Nginx的介绍
    优雅的python
    python小技巧
    python列表小程序
    学会浏览器查东西
    列表推导式
    深度优先算法与广度优先算法
  • 原文地址:https://www.cnblogs.com/lifexy/p/15065339.html
Copyright © 2011-2022 走看看