简介
今日实现界面效果

联系人列表
我们自定义一个ChatUserList类,用来管理聊天列表。其声明如下:
class ContactUserList : public QListWidget{Q_OBJECTpublic:ContactUserList(QWidget *parent = nullptr);void ShowRedPoint(bool bshow = true);protected:bool eventFilter(QObject *watched, QEvent *event) override ;private:void addContactUserList();public slots:void slot_item_clicked(QListWidgetItem *item);// void slot_add_auth_firend(std::shared_ptr<AuthInfo>);// void slot_auth_rsp(std::shared_ptr<AuthRsp>);signals:void sig_loading_contact_user();void sig_switch_apply_friend_page();void sig_switch_friend_info_page();private:ConUserItem* _add_friend_item;QListWidgetItem * _groupitem;};
具体实现
ContactUserList::ContactUserList(QWidget *parent){Q_UNUSED(parent);this->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);this->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);// 安装事件过滤器this->viewport()->installEventFilter(this);//模拟从数据库或者后端传输过来的数据,进行列表加载addContactUserList();//连接点击的信号和槽connect(this, &QListWidget::itemClicked, this, &ContactUserList::slot_item_clicked);// //链接对端同意认证后通知的信号// connect(TcpMgr::GetInstance().get(), &TcpMgr::sig_add_auth_friend,this,// &ContactUserList::slot_add_auth_firend);// //链接自己点击同意认证后界面刷新// connect(TcpMgr::GetInstance().get(), &TcpMgr::sig_auth_rsp,this,// &ContactUserList::slot_auth_rsp);}void ContactUserList::ShowRedPoint(bool bshow /*= true*/){_add_friend_item->ShowRedPoint(bshow);}void ContactUserList::addContactUserList(){auto * groupTip = new GroupTipItem();QListWidgetItem *item = new QListWidgetItem;item->setSizeHint(groupTip->sizeHint());this->addItem(item);this->setItemWidget(item, groupTip);item->setFlags(item->flags() & ~Qt::ItemIsSelectable);_add_friend_item = new ConUserItem();_add_friend_item->setObjectName("new_friend_item");_add_friend_item->SetInfo(0,tr("新的朋友"),":/res/add_friend.png");_add_friend_item->SetItemType(ListItemType::APPLY_FRIEND_ITEM);QListWidgetItem *add_item = new QListWidgetItem;//qDebug()<<"chat_user_wid sizeHint is " << chat_user_wid->sizeHint();add_item->setSizeHint(_add_friend_item->sizeHint());this->addItem(add_item);this->setItemWidget(add_item, _add_friend_item);//默认设置新的朋友申请条目被选中this->setCurrentItem(add_item);auto * groupCon = new GroupTipItem();groupCon->SetGroupTip(tr("联系人"));_groupitem = new QListWidgetItem;_groupitem->setSizeHint(groupCon->sizeHint());this->addItem(_groupitem);this->setItemWidget(_groupitem, groupCon);_groupitem->setFlags(_groupitem->flags() & ~Qt::ItemIsSelectable);// 创建QListWidgetItem,并设置自定义的widgetfor(int i = 0; i < 13; i++){int randomValue = QRandomGenerator::global()->bounded(100); // 生成0到99之间的随机整数int str_i = randomValue%strs.size();int head_i = randomValue%heads.size();int name_i = randomValue%names.size();auto *con_user_wid = new ConUserItem();con_user_wid->SetInfo(0,names[name_i], heads[head_i]);QListWidgetItem *item = new QListWidgetItem;//qDebug()<<"chat_user_wid sizeHint is " << chat_user_wid->sizeHint();item->setSizeHint(con_user_wid->sizeHint());this->addItem(item);this->setItemWidget(item, con_user_wid);}}bool ContactUserList::eventFilter(QObject *watched, QEvent *event){// 检查事件是否是鼠标悬浮进入或离开if (watched == this->viewport()) {if (event->type() == QEvent::Enter) {// 鼠标悬浮,显示滚动条this->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);} else if (event->type() == QEvent::Leave) {// 鼠标离开,隐藏滚动条this->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);}}// 检查事件是否是鼠标滚轮事件if (watched == this->viewport() && event->type() == QEvent::Wheel) {QWheelEvent *wheelEvent = static_cast<QWheelEvent*>(event);int numDegrees = wheelEvent->angleDelta().y() / 8;int numSteps = numDegrees / 15; // 计算滚动步数// 设置滚动幅度this->verticalScrollBar()->setValue(this->verticalScrollBar()->value() - numSteps);// 检查是否滚动到底部QScrollBar *scrollBar = this->verticalScrollBar();int maxScrollValue = scrollBar->maximum();int currentValue = scrollBar->value();//int pageSize = 10; // 每页加载的联系人数量if (maxScrollValue - currentValue <= 0) {// 滚动到底部,加载新的联系人qDebug()<<"load more contact user";//发送信号通知聊天界面加载更多聊天内容emit sig_loading_contact_user();}return true; // 停止事件传递}return QListWidget::eventFilter(watched, event);}void ContactUserList::slot_item_clicked(QListWidgetItem *item){QWidget *widget = this->itemWidget(item); // 获取自定义widget对象if(!widget){qDebug()<< "slot item clicked widget is nullptr";return;}// 对自定义widget进行操作, 将item 转化为基类ListItemBaseListItemBase *customItem = qobject_cast<ListItemBase*>(widget);if(!customItem){qDebug()<< "slot item clicked widget is nullptr";return;}auto itemType = customItem->GetItemType();if(itemType == ListItemType::INVALID_ITEM|| itemType == ListItemType::GROUP_TIP_ITEM){qDebug()<< "slot invalid item clicked ";return;}if(itemType == ListItemType::APPLY_FRIEND_ITEM){// 创建对话框,提示用户qDebug()<< "apply friend item clicked ";//跳转到好友申请界面emit sig_switch_apply_friend_page();return;}if(itemType == ListItemType::CONTACT_USER_ITEM){// 创建对话框,提示用户qDebug()<< "contact user item clicked ";//跳转到好友申请界面emit sig_switch_friend_info_page();return;}}
构造函数中关闭了滚动条的显示,重写了事件过滤器,实现了根据鼠标区域判断是否显示滚动条的功能。
并且实现了点击其中某个item响应对应的功能。并根据不同的item类型跳转不同的页面。
联系人item
因为每一个item都是我们自己定义的,所以我们添加设计师界面类,界面布局如下所示

类的声明如下
class ConUserItem : public ListItemBase{Q_OBJECTpublic:explicit ConUserItem(QWidget *parent = nullptr);~ConUserItem();QSize sizeHint() const override;void SetInfo(std::shared_ptr<AuthInfo> auth_info);void SetInfo(std::shared_ptr<AuthRsp> auth_rsp);void SetInfo(int uid, QString name, QString icon);void ShowRedPoint(bool show = false);private:Ui::ConUserItem *ui;std::shared_ptr<UserInfo> _info;};
具体实现
ConUserItem::ConUserItem(QWidget *parent) :ListItemBase(parent),ui(new Ui::ConUserItem){ui->setupUi(this);SetItemType(ListItemType::CONTACT_USER_ITEM);ui->red_point->raise();ShowRedPoint(true);}ConUserItem::~ConUserItem(){delete ui;}QSize ConUserItem::sizeHint() const{return QSize(250, 70); // 返回自定义的尺寸}void ConUserItem::SetInfo(std::shared_ptr<AuthInfo> auth_info){_info = std::make_shared<UserInfo>(auth_info);// 加载图片QPixmap pixmap(_info->_icon);// 设置图片自动缩放ui->icon_lb->setPixmap(pixmap.scaled(ui->icon_lb->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));ui->icon_lb->setScaledContents(true);ui->user_name_lb->setText(_info->_name);}void ConUserItem::SetInfo(int uid, QString name, QString icon){_info = std::make_shared<UserInfo>(uid,name, icon);// 加载图片QPixmap pixmap(_info->_icon);// 设置图片自动缩放ui->icon_lb->setPixmap(pixmap.scaled(ui->icon_lb->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));ui->icon_lb->setScaledContents(true);ui->user_name_lb->setText(_info->_name);}void ConUserItem::SetInfo(std::shared_ptr<AuthRsp> auth_rsp){_info = std::make_shared<UserInfo>(auth_rsp);// 加载图片QPixmap pixmap(_info->_icon);// 设置图片自动缩放ui->icon_lb->setPixmap(pixmap.scaled(ui->icon_lb->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));ui->icon_lb->setScaledContents(true);ui->user_name_lb->setText(_info->_name);}void ConUserItem::ShowRedPoint(bool show){if(show){ui->red_point->show();}else{ui->red_point->hide();}}
这样我们启动程序就能看到模拟的联系人列表被加载进来了。
申请列表
申请页面ui布局如下

我们新增ApplyFriendPage类,用来显示申请列表
class ApplyFriendPage : public QWidget{Q_OBJECTpublic:explicit ApplyFriendPage(QWidget *parent = nullptr);~ApplyFriendPage();void AddNewApply(std::shared_ptr<AddFriendApply> apply);protected:void paintEvent(QPaintEvent *event);private:void loadApplyList();Ui::ApplyFriendPage *ui;std::unordered_map<int, ApplyFriendItem*> _unauth_items;public slots:void slot_auth_rsp(std::shared_ptr<AuthRsp> );signals:void sig_show_search(bool);};
具体实现
ApplyFriendPage::ApplyFriendPage(QWidget *parent) :QWidget(parent),ui(new Ui::ApplyFriendPage){ui->setupUi(this);connect(ui->apply_friend_list, &ApplyFriendList::sig_show_search, this, &ApplyFriendPage::sig_show_search);loadApplyList();//接受tcp传递的authrsp信号处理connect(TcpMgr::GetInstance().get(), &TcpMgr::sig_auth_rsp, this, &ApplyFriendPage::slot_auth_rsp);}ApplyFriendPage::~ApplyFriendPage(){delete ui;}void ApplyFriendPage::AddNewApply(std::shared_ptr<AddFriendApply> apply){//先模拟头像随机,以后头像资源增加资源服务器后再显示int randomValue = QRandomGenerator::global()->bounded(100); // 生成0到99之间的随机整数int head_i = randomValue % heads.size();auto* apply_item = new ApplyFriendItem();auto apply_info = std::make_shared<ApplyInfo>(apply->_from_uid,apply->_name, apply->_desc,heads[head_i], apply->_name, 0, 0);apply_item->SetInfo( apply_info);QListWidgetItem* item = new QListWidgetItem;//qDebug()<<"chat_user_wid sizeHint is " << chat_user_wid->sizeHint();item->setSizeHint(apply_item->sizeHint());item->setFlags(item->flags() & ~Qt::ItemIsEnabled & ~Qt::ItemIsSelectable);ui->apply_friend_list->insertItem(0,item);ui->apply_friend_list->setItemWidget(item, apply_item);apply_item->ShowAddBtn(true);//收到审核好友信号connect(apply_item, &ApplyFriendItem::sig_auth_friend, [this](std::shared_ptr<ApplyInfo> apply_info) {// auto* authFriend = new AuthenFriend(this);// authFriend->setModal(true);// authFriend->SetApplyInfo(apply_info);// authFriend->show();});}void ApplyFriendPage::paintEvent(QPaintEvent *event){QStyleOption opt;opt.init(this);QPainter p(this);style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);}void ApplyFriendPage::loadApplyList(){//添加好友申请auto apply_list = UserMgr::GetInstance()->GetApplyList();for(auto &apply: apply_list){int randomValue = QRandomGenerator::global()->bounded(100); // 生成0到99之间的随机整数int head_i = randomValue % heads.size();auto* apply_item = new ApplyFriendItem();apply->SetIcon(heads[head_i]);apply_item->SetInfo(apply);QListWidgetItem* item = new QListWidgetItem;//qDebug()<<"chat_user_wid sizeHint is " << chat_user_wid->sizeHint();item->setSizeHint(apply_item->sizeHint());item->setFlags(item->flags() & ~Qt::ItemIsEnabled & ~Qt::ItemIsSelectable);ui->apply_friend_list->insertItem(0,item);ui->apply_friend_list->setItemWidget(item, apply_item);if(apply->_status){apply_item->ShowAddBtn(false);}else{apply_item->ShowAddBtn(true);auto uid = apply_item->GetUid();_unauth_items[uid] = apply_item;}//收到审核好友信号connect(apply_item, &ApplyFriendItem::sig_auth_friend, [this](std::shared_ptr<ApplyInfo> apply_info) {// auto* authFriend = new AuthenFriend(this);// authFriend->setModal(true);// authFriend->SetApplyInfo(apply_info);// authFriend->show();});}// 模拟假数据,创建QListWidgetItem,并设置自定义的widgetfor(int i = 0; i < 13; i++){int randomValue = QRandomGenerator::global()->bounded(100); // 生成0到99之间的随机整数int str_i = randomValue%strs.size();int head_i = randomValue%heads.size();int name_i = randomValue%names.size();auto *apply_item = new ApplyFriendItem();auto apply = std::make_shared<ApplyInfo>(0, names[name_i], strs[str_i],heads[head_i], names[name_i], 0, 1);apply_item->SetInfo(apply);QListWidgetItem *item = new QListWidgetItem;//qDebug()<<"chat_user_wid sizeHint is " << chat_user_wid->sizeHint();item->setSizeHint(apply_item->sizeHint());item->setFlags(item->flags() & ~Qt::ItemIsEnabled & ~Qt::ItemIsSelectable);ui->apply_friend_list->addItem(item);ui->apply_friend_list->setItemWidget(item, apply_item);//收到审核好友信号connect(apply_item, &ApplyFriendItem::sig_auth_friend, [this](std::shared_ptr<ApplyInfo> apply_info){// auto *authFriend = new AuthenFriend(this);// authFriend->setModal(true);// authFriend->SetApplyInfo(apply_info);// authFriend->show();});}}void ApplyFriendPage::slot_auth_rsp(std::shared_ptr<AuthRsp> auth_rsp){auto uid = auth_rsp->_uid;auto find_iter = _unauth_items.find(uid);if (find_iter == _unauth_items.end()) {return;}find_iter->second->ShowAddBtn(false);}
因为每个item自定义,所以我们新增设计师界面类ApplyFriendItem
界面布局

类的声明如下:
class ApplyFriendItem : public ListItemBase{Q_OBJECTpublic:explicit ApplyFriendItem(QWidget *parent = nullptr);~ApplyFriendItem();void SetInfo(std::shared_ptr<ApplyInfo> apply_info);void ShowAddBtn(bool bshow);QSize sizeHint() const override {return QSize(250, 80); // 返回自定义的尺寸}int GetUid();private:Ui::ApplyFriendItem *ui;std::shared_ptr<ApplyInfo> _apply_info;bool _added;signals:void sig_auth_friend(std::shared_ptr<ApplyInfo> apply_info);};
以下为具体实现
ApplyFriendItem::ApplyFriendItem(QWidget *parent) :ListItemBase(parent), _added(false),ui(new Ui::ApplyFriendItem){ui->setupUi(this);SetItemType(ListItemType::APPLY_FRIEND_ITEM);ui->addBtn->SetState("normal","hover", "press");ui->addBtn->hide();connect(ui->addBtn, &ClickedBtn::clicked, [this](){emit this->sig_auth_friend(_apply_info);});}ApplyFriendItem::~ApplyFriendItem(){delete ui;}void ApplyFriendItem::SetInfo(std::shared_ptr<ApplyInfo> apply_info){_apply_info = apply_info;// 加载图片QPixmap pixmap(_apply_info->_icon);// 设置图片自动缩放ui->icon_lb->setPixmap(pixmap.scaled(ui->icon_lb->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));ui->icon_lb->setScaledContents(true);ui->user_name_lb->setText(_apply_info->_name);ui->user_chat_lb->setText(_apply_info->_desc);}void ApplyFriendItem::ShowAddBtn(bool bshow){if (bshow) {ui->addBtn->show();ui->already_add_lb->hide();_added = false;}else {ui->addBtn->hide();ui->already_add_lb->show();_added = true;}}int ApplyFriendItem::GetUid() {return _apply_info->_uid;}
申请列表类ApplyFriendList的声明如下
class ApplyFriendList: public QListWidget{Q_OBJECTpublic:ApplyFriendList(QWidget *parent = nullptr);protected:bool eventFilter(QObject *watched, QEvent *event) override;private slots:signals:void sig_show_search(bool);};
具体实现
ApplyFriendList::ApplyFriendList(QWidget *parent){Q_UNUSED(parent);this->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);this->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);// 安装事件过滤器this->viewport()->installEventFilter(this);}bool ApplyFriendList::eventFilter(QObject *watched, QEvent *event){// 检查事件是否是鼠标悬浮进入或离开if (watched == this->viewport()) {if (event->type() == QEvent::Enter) {// 鼠标悬浮,显示滚动条this->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);} else if (event->type() == QEvent::Leave) {// 鼠标离开,隐藏滚动条this->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);}}if (watched == this->viewport()) {if (event->type() == QEvent::MouseButtonPress) {emit sig_show_search(false);}}// 检查事件是否是鼠标滚轮事件if (watched == this->viewport() && event->type() == QEvent::Wheel) {QWheelEvent *wheelEvent = static_cast<QWheelEvent*>(event);int numDegrees = wheelEvent->angleDelta().y() / 8;int numSteps = numDegrees / 15; // 计算滚动步数// 设置滚动幅度this->verticalScrollBar()->setValue(this->verticalScrollBar()->value() - numSteps);return true; // 停止事件传递}return QListWidget::eventFilter(watched, event);}
然后在ChatDialog的stackedWidget中将friend_apply_page升级为ApplyFriendPage.
这样我们启动程序就能看到联系人列表和申请列表了。
下一步还需要写QSS美化以下
#con_user_list {background-color: rgb(247,247,248);border: none;}#con_user_list::item:selected {background-color: #d3d7d4;border: none;outline: none;}#con_user_list::item:hover {background-color: rgb(206,207,208);border: none;outline: none;}#con_user_list::focus {border: none;outline: none;}#GroupTipItem {background-color: #eaeaea;border: none;}#GroupTipItem QLabel{color: #2e2f30;font-size: 12px; /* 设置字体大小 */font-family: "Microsoft YaHei"; /* 设置字体 */border: none;}#new_friend_item {border-bottom: 1px solid #eaeaea;}#LineItem {background-color:rgb(247,247,247);border: none;}#friend_apply_lb {font-family: "Microsoft YaHei";font-size: 18px;font-weight: normal;}#friend_apply_wid {background-color: #f1f2f3;border-bottom: 1px solid #ede9e7;}#apply_friend_list {background-color: #f1f2f3;border-left: 1px solid #ede9e7;border-top: none;border-right: none;border-bottom: none;}ApplyFriendItem {background-color: #f1f2f3;border-bottom: 2px solid #dbd9d9;}ApplyFriendItem #user_chat_lb{color: #a2a2a2;font-size: 14px; /* 设置字体大小 */font-family: "Microsoft YaHei"; /* 设置字体 */}ApplyFriendItem #addBtn[state='normal'] {background-color: #d3d7d4;color: #2cb46e;font-size: 16px; /* 设置字体大小 */font-family: "Microsoft YaHei"; /* 设置字体 */border-radius: 20px; /* 设置圆角 */}ApplyFriendItem #addBtn[state='hover'] {background-color: #D3D3D3;color: #2cb46e;font-size: 16px; /* 设置字体大小 */font-family: "Microsoft YaHei"; /* 设置字体 */border-radius: 20px; /* 设置圆角 */}ApplyFriendItem #addBtn[state='press'] {background-color: #BEBEBE;color: #2cb46e;font-size: 16px; /* 设置字体大小 */font-family: "Microsoft YaHei"; /* 设置字体 */border-radius: 20px; /* 设置圆角 */}#already_add_lb{color:rgb(153,153,153);font-size: 12px;font-family: "Microsoft YaHei";}#user_name_lb{color:rgb(0,0,0);font-size: 16px;font-weight: normal;font-family: "Microsoft YaHei";}
源码连接
https://gitee.com/secondtonone1/llfcchat
视频连接
https://www.bilibili.com/video/BV1SS42197Yo/?vd_source=8be9e83424c2ed2c9b2a3ed1d01385e9