在聊天页面中渲染对话消息
功能简介
本文介绍了如何使用 ZIM SDK 在基本的聊天页面中渲染消息。

前提条件
已在项目中集成了 ZIM SDK 并实现了基本收发消息的功能,详情请参考 快速开始 - 实现基本收发消息。
消息数据来源概述
页面上需要渲染的消息数据来源主要有以下几种:
- 历史对话消息: 首次进入聊天页面和查看历史消息时,需要查询历史对话消息并渲染到聊天界面。
- 实时收到的消息: 实时收到的聊天消息需要渲染到聊天界面。
- 本地发送的消息: 本地发送的聊天消息(包含发送中、发送成功、发送失败等状态)需要渲染到聊天界面。
获取消息数据渲染到聊天界面
以单聊会话为例,跳转到聊天页面时,需要保存对应单聊的 conversationID(对方的 userID)并维护一个用于保存当前会话的消息列表 myMessageList。
在获取消息数据时建议实现 addMessage 工具方法用于合并历史消息、实时接收的消息和本地发送的消息。
public void addMessage(List<ZIMMessage> addList) {
if (addList == null || addList.isEmpty()) return;
List<ZIMMessage> mutableList = new ArrayList<>();
if (myMessageList != null) {
mutableList.addAll(myMessageList);
}
for (ZIMMessage newMsg : addList) {
boolean replaced = false;
for (int i = 0; i < mutableList.size(); i++) {
ZIMMessage oldMsg = mutableList.get(i);
if ((newMsg.getMessageID() != null && newMsg.getMessageID().equals(oldMsg.getMessageID())) ||
(newMsg.getLocalMessageID() != null && newMsg.getLocalMessageID().equals(oldMsg.getLocalMessageID()))) {
mutableList.set(i, newMsg);
replaced = true;
break;
}
}
if (!replaced) {
mutableList.add(newMsg);
}
}
mutableList.sort(Comparator.comparingLong(ZIMMessage::getOrderKey));
myMessageList = mutableList;
}public void addMessage(List<ZIMMessage> addList) {
if (addList == null || addList.isEmpty()) return;
List<ZIMMessage> mutableList = new ArrayList<>();
if (myMessageList != null) {
mutableList.addAll(myMessageList);
}
for (ZIMMessage newMsg : addList) {
boolean replaced = false;
for (int i = 0; i < mutableList.size(); i++) {
ZIMMessage oldMsg = mutableList.get(i);
if ((newMsg.getMessageID() != null && newMsg.getMessageID().equals(oldMsg.getMessageID())) ||
(newMsg.getLocalMessageID() != null && newMsg.getLocalMessageID().equals(oldMsg.getLocalMessageID()))) {
mutableList.set(i, newMsg);
replaced = true;
break;
}
}
if (!replaced) {
mutableList.add(newMsg);
}
}
mutableList.sort(Comparator.comparingLong(ZIMMessage::getOrderKey));
myMessageList = mutableList;
}// 有新消息时,通过该方法将消息更新到消息列表中
- (void)addMessage:(NSArray<ZIMMessage *> *)addList {
if (!addList || addList.count == 0) return;
NSMutableArray<ZIMMessage *> *mutableList = [self.myMessageList mutableCopy];
if (!mutableList) {
mutableList = [NSMutableArray array];
}
for (ZIMMessage *newMsg in addList) {
BOOL replaced = NO;
for (NSInteger i = 0; i < mutableList.count; i++) {
ZIMMessage *oldMsg = mutableList[i];
if ((newMsg.messageID && oldMsg.messageID && [newMsg.messageID isEqualToString:oldMsg.messageID]) ||
(newMsg.localMessageID && oldMsg.localMessageID && [newMsg.localMessageID isEqualToString:oldMsg.localMessageID])) {
// 替换已有消息
mutableList[i] = newMsg;
replaced = YES;
break;
}
}
if (!replaced) {
[mutableList addObject:newMsg];
}
}
// 根据 orderKey 从小到大排序
[mutableList sortUsingComparator:^NSComparisonResult(ZIMMessage *msg1, ZIMMessage *msg2) {
if (msg1.orderKey < msg2.orderKey) return NSOrderedAscending;
if (msg1.orderKey > msg2.orderKey) return NSOrderedDescending;
return NSOrderedSame;
}];
self.myMessageList = [mutableList copy];
}// 有新消息时,通过该方法将消息更新到消息列表中
- (void)addMessage:(NSArray<ZIMMessage *> *)addList {
if (!addList || addList.count == 0) return;
NSMutableArray<ZIMMessage *> *mutableList = [self.myMessageList mutableCopy];
if (!mutableList) {
mutableList = [NSMutableArray array];
}
for (ZIMMessage *newMsg in addList) {
BOOL replaced = NO;
for (NSInteger i = 0; i < mutableList.count; i++) {
ZIMMessage *oldMsg = mutableList[i];
if ((newMsg.messageID && oldMsg.messageID && [newMsg.messageID isEqualToString:oldMsg.messageID]) ||
(newMsg.localMessageID && oldMsg.localMessageID && [newMsg.localMessageID isEqualToString:oldMsg.localMessageID])) {
// 替换已有消息
mutableList[i] = newMsg;
replaced = YES;
break;
}
}
if (!replaced) {
[mutableList addObject:newMsg];
}
}
// 根据 orderKey 从小到大排序
[mutableList sortUsingComparator:^NSComparisonResult(ZIMMessage *msg1, ZIMMessage *msg2) {
if (msg1.orderKey < msg2.orderKey) return NSOrderedAscending;
if (msg1.orderKey > msg2.orderKey) return NSOrderedDescending;
return NSOrderedSame;
}];
self.myMessageList = [mutableList copy];
}void addMessage(const std::vector<std::shared_ptr<ZIMMessage>>& addList) {
if (addList.empty()) return;
std::vector<std::shared_ptr<ZIMMessage>> mutableList = myMessageList;
for (auto& newMsg : addList) {
bool replaced = false;
for (size_t i = 0; i < mutableList.size(); ++i) {
auto& oldMsg = mutableList[i];
if ((!newMsg->messageID.empty() && newMsg->messageID == oldMsg->messageID) ||
(!newMsg->localMessageID.empty() && newMsg->localMessageID == oldMsg->localMessageID)) {
mutableList[i] = newMsg;
replaced = true;
break;
}
}
if (!replaced) {
mutableList.push_back(newMsg);
}
}
std::sort(mutableList.begin(), mutableList.end(),
[](const std::shared_ptr<ZIMMessage>& a, const std::shared_ptr<ZIMMessage>& b) {
return a->orderKey < b->orderKey;
});
myMessageList = mutableList;
}void addMessage(const std::vector<std::shared_ptr<ZIMMessage>>& addList) {
if (addList.empty()) return;
std::vector<std::shared_ptr<ZIMMessage>> mutableList = myMessageList;
for (auto& newMsg : addList) {
bool replaced = false;
for (size_t i = 0; i < mutableList.size(); ++i) {
auto& oldMsg = mutableList[i];
if ((!newMsg->messageID.empty() && newMsg->messageID == oldMsg->messageID) ||
(!newMsg->localMessageID.empty() && newMsg->localMessageID == oldMsg->localMessageID)) {
mutableList[i] = newMsg;
replaced = true;
break;
}
}
if (!replaced) {
mutableList.push_back(newMsg);
}
}
std::sort(mutableList.begin(), mutableList.end(),
[](const std::shared_ptr<ZIMMessage>& a, const std::shared_ptr<ZIMMessage>& b) {
return a->orderKey < b->orderKey;
});
myMessageList = mutableList;
}function addMessage(addList: ZIMMessage[]) {
if (!addList || addList.length === 0) return;
const mutableList: ZIMMessage[] = myMessageList ? [...myMessageList] : [];
for (const newMsg of addList) {
let replaced = false;
for (let i = 0; i < mutableList.length; i++) {
const oldMsg = mutableList[i];
if ((newMsg.messageID && oldMsg.messageID && newMsg.messageID === oldMsg.messageID) ||
(newMsg.localMessageID && oldMsg.localMessageID && newMsg.localMessageID === oldMsg.localMessageID)) {
mutableList[i] = newMsg;
replaced = true;
break;
}
}
if (!replaced) {
mutableList.push(newMsg);
}
}
mutableList.sort((a, b) => a.orderKey - b.orderKey);
myMessageList = mutableList;
}function addMessage(addList: ZIMMessage[]) {
if (!addList || addList.length === 0) return;
const mutableList: ZIMMessage[] = myMessageList ? [...myMessageList] : [];
for (const newMsg of addList) {
let replaced = false;
for (let i = 0; i < mutableList.length; i++) {
const oldMsg = mutableList[i];
if ((newMsg.messageID && oldMsg.messageID && newMsg.messageID === oldMsg.messageID) ||
(newMsg.localMessageID && oldMsg.localMessageID && newMsg.localMessageID === oldMsg.localMessageID)) {
mutableList[i] = newMsg;
replaced = true;
break;
}
}
if (!replaced) {
mutableList.push(newMsg);
}
}
mutableList.sort((a, b) => a.orderKey - b.orderKey);
myMessageList = mutableList;
}
void addMessage(List<ZIMMessage>? addList) {
if (addList == null || addList.isEmpty) return;
List<ZIMMessage> mutableList = myMessageList != null ? List.from(myMessageList!) : [];
for (var newMsg in addList) {
bool replaced = false;
for (var i = 0; i < mutableList.length; i++) {
var oldMsg = mutableList[i];
if ((newMsg.messageID != null && oldMsg.messageID != null && newMsg.messageID == oldMsg.messageID) ||
(newMsg.localMessageID != null && oldMsg.localMessageID != null && newMsg.localMessageID == oldMsg.localMessageID)) {
mutableList[i] = newMsg;
replaced = true;
break;
}
}
if (!replaced) {
mutableList.add(newMsg);
}
}
mutableList.sort((a, b) => a.orderKey.compareTo(b.orderKey));
myMessageList = mutableList;
}
void addMessage(List<ZIMMessage>? addList) {
if (addList == null || addList.isEmpty) return;
List<ZIMMessage> mutableList = myMessageList != null ? List.from(myMessageList!) : [];
for (var newMsg in addList) {
bool replaced = false;
for (var i = 0; i < mutableList.length; i++) {
var oldMsg = mutableList[i];
if ((newMsg.messageID != null && oldMsg.messageID != null && newMsg.messageID == oldMsg.messageID) ||
(newMsg.localMessageID != null && oldMsg.localMessageID != null && newMsg.localMessageID == oldMsg.localMessageID)) {
mutableList[i] = newMsg;
replaced = true;
break;
}
}
if (!replaced) {
mutableList.add(newMsg);
}
}
mutableList.sort((a, b) => a.orderKey.compareTo(b.orderKey));
myMessageList = mutableList;
}说明
每次调用消息相关的接口时(比如以下展示的接口),会得到一个消息列表 ZIMMessage[]。获得的消息列表需要进行合并数据的操作,从而得到一个无重复消息、有序的消息列表用于在消息页面进行渲染。消息排序规则为根据 ZIMMessage 中的 orderkey 属性进行排序(orderkey 越大表示当前消息越新);去重规则为根据消息的 localMessageID 判断消息对象是否为同一条消息,重复时新数据替换旧数据。
查询历史消息并渲染到聊天界面
调用 queryHistoryMessage 接口从新分页到旧分页(按时间顺序从最近的消息到更久之前的消息)进行查询,获取双方的历史消息并渲染到聊天界面。
ZIMMessageQueryConfig queryConfig = new ZIMMessageQueryConfig();
queryConfig.count = 20;
queryConfig.nextMessage = null; // 首次查询时为空,需要更多历史消息时传入最早消息
zim.queryHistoryMessage(
myConversationID,
ZIMConversationType.PEER,
queryConfig,
(conversationID, conversationType, messageList, errorInfo) -> {
addMessage(messageList);
// 更新 UI
});
ZIMMessageQueryConfig queryConfig = new ZIMMessageQueryConfig();
queryConfig.count = 20;
queryConfig.nextMessage = null; // 首次查询时为空,需要更多历史消息时传入最早消息
zim.queryHistoryMessage(
myConversationID,
ZIMConversationType.PEER,
queryConfig,
(conversationID, conversationType, messageList, errorInfo) -> {
addMessage(messageList);
// 更新 UI
});ZIMMessageQueryConfig *queryConfig = [[ZIMMessageQueryConfig alloc] init];
queryConfig.count = 20;//
queryConfig.nextMessage = nil;//首次查询时传空,需要更多历史消息时,传入消息列表中最早的消息再次调用
[zim queryHistoryMessageByConversationID:myConversationID
conversationType:ZIMConversationTypePeer
config:queryConfig
callback:callback:^(NSString * _Nonnull conversationID, ZIMConversationType conversationType, NSArray<ZIMMessage *> * _Nonnull messageList, ZIMError * _Nonnull errorInfo){
[self addMessage:messageList];
// update UI
}];ZIMMessageQueryConfig *queryConfig = [[ZIMMessageQueryConfig alloc] init];
queryConfig.count = 20;//
queryConfig.nextMessage = nil;//首次查询时传空,需要更多历史消息时,传入消息列表中最早的消息再次调用
[zim queryHistoryMessageByConversationID:myConversationID
conversationType:ZIMConversationTypePeer
config:queryConfig
callback:callback:^(NSString * _Nonnull conversationID, ZIMConversationType conversationType, NSArray<ZIMMessage *> * _Nonnull messageList, ZIMError * _Nonnull errorInfo){
[self addMessage:messageList];
// update UI
}];
ZIMMessageQueryConfig queryConfig;
queryConfig.count = 20;
queryConfig.nextMessage = nullptr; // 首次查询为空
zim->queryHistoryMessage(
myConversationID,
ZIMConversationType::Peer,
queryConfig,
[this](const std::string& conversationID, ZIMConversationType type,
const std::vector<ZIMMessage>& messageList, const ZIMError& errorInfo) {
addMessage(messageList);
refreshUI(); // 自定义 UI 刷新函数
});
ZIMMessageQueryConfig queryConfig;
queryConfig.count = 20;
queryConfig.nextMessage = nullptr; // 首次查询为空
zim->queryHistoryMessage(
myConversationID,
ZIMConversationType::Peer,
queryConfig,
[this](const std::string& conversationID, ZIMConversationType type,
const std::vector<ZIMMessage>& messageList, const ZIMError& errorInfo) {
addMessage(messageList);
refreshUI(); // 自定义 UI 刷新函数
});
const queryConfig: ZIMMessageQueryConfig = {
count: 20,
nextMessage: null // 首次查询
};
zim.queryHistoryMessage(
myConversationID,
ZIMConversationType.Peer,
queryConfig,
(conversationID, conversationType, messageList, errorInfo) => {
addMessage(messageList);
updateUI(); // 自定义刷新消息列表函数
}
);const queryConfig: ZIMMessageQueryConfig = {
count: 20,
nextMessage: null // 首次查询
};
zim.queryHistoryMessage(
myConversationID,
ZIMConversationType.Peer,
queryConfig,
(conversationID, conversationType, messageList, errorInfo) => {
addMessage(messageList);
updateUI(); // 自定义刷新消息列表函数
}
);
final queryConfig = ZIMMessageQueryConfig();
queryConfig.count = 20;
queryConfig.nextMessage = null; // 首次查询
zim.queryHistoryMessage(
myConversationID,
ZIMConversationType.peer,
queryConfig,
(String conversationID, ZIMConversationType conversationType,
List<ZIMMessage> messageList, ZIMError errorInfo) {
addMessage(messageList);
// 更新 UI
},
);
final queryConfig = ZIMMessageQueryConfig();
queryConfig.count = 20;
queryConfig.nextMessage = null; // 首次查询
zim.queryHistoryMessage(
myConversationID,
ZIMConversationType.peer,
queryConfig,
(String conversationID, ZIMConversationType conversationType,
List<ZIMMessage> messageList, ZIMError errorInfo) {
addMessage(messageList);
// 更新 UI
},
);获取实时接收的消息并渲染到聊天界面
监听 onPeerMessageReceived 接口获取实时接收的消息,并渲染到聊天界面。
public void onPeerMessageReceived(ZIM zim, List<ZIMMessage> messageList,
ZIMMessageReceivedInfo info, String fromUserID) {
// 只处理当前会话的消息
if (!fromUserID.equals(myConversationID)) {
return; // 非当前会话的消息忽略
}
// 合并消息到消息列表
addMessage(messageList);
// 更新 UI,比如刷新 RecyclerView
runOnUiThread(() -> {
myAdapter.notifyDataSetChanged();
if (!myMessageList.isEmpty()) {
recyclerView.scrollToPosition(myMessageList.size() - 1);
}
});
}public void onPeerMessageReceived(ZIM zim, List<ZIMMessage> messageList,
ZIMMessageReceivedInfo info, String fromUserID) {
// 只处理当前会话的消息
if (!fromUserID.equals(myConversationID)) {
return; // 非当前会话的消息忽略
}
// 合并消息到消息列表
addMessage(messageList);
// 更新 UI,比如刷新 RecyclerView
runOnUiThread(() -> {
myAdapter.notifyDataSetChanged();
if (!myMessageList.isEmpty()) {
recyclerView.scrollToPosition(myMessageList.size() - 1);
}
});
}
- (void)zim:(ZIM *)zim
peerMessageReceived:(NSArray<ZIMMessage *> *)messageList
info:(ZIMMessageReceivedInfo *)info
fromUserID:(NSString *)fromUserID{
if(fromUserID != myConversationID){
return;// 非当前会话收到的聊天消息,忽略
}
[self addMessage: messageList];
// update UI
}
- (void)zim:(ZIM *)zim
peerMessageReceived:(NSArray<ZIMMessage *> *)messageList
info:(ZIMMessageReceivedInfo *)info
fromUserID:(NSString *)fromUserID{
if(fromUserID != myConversationID){
return;// 非当前会话收到的聊天消息,忽略
}
[self addMessage: messageList];
// update UI
}
void onPeerMessageReceived(ZIM* zim, const std::vector<ZIMMessage>& messageList,
const ZIMMessageReceivedInfo& info, const std::string& fromUserID) {
// 只处理当前会话消息
if (fromUserID != myConversationID) {
return; // 非当前会话消息忽略
}
// 合并消息到消息列表
addMessage(messageList);
// 更新 UI
// 假设有刷新函数 refreshUI()
refreshUI();
}
void onPeerMessageReceived(ZIM* zim, const std::vector<ZIMMessage>& messageList,
const ZIMMessageReceivedInfo& info, const std::string& fromUserID) {
// 只处理当前会话消息
if (fromUserID != myConversationID) {
return; // 非当前会话消息忽略
}
// 合并消息到消息列表
addMessage(messageList);
// 更新 UI
// 假设有刷新函数 refreshUI()
refreshUI();
}
function onPeerMessageReceived(
zim: ZIM,
messageList: ZIMMessage[],
info: ZIMMessageReceivedInfo,
fromUserID: string
) {
// 只处理当前会话消息
if (fromUserID !== myConversationID) return;
// 合并消息到消息列表
addMessage(messageList);
// 更新 UI,例如重新渲染消息列表
updateUI();
}
function onPeerMessageReceived(
zim: ZIM,
messageList: ZIMMessage[],
info: ZIMMessageReceivedInfo,
fromUserID: string
) {
// 只处理当前会话消息
if (fromUserID !== myConversationID) return;
// 合并消息到消息列表
addMessage(messageList);
// 更新 UI,例如重新渲染消息列表
updateUI();
}
void onPeerMessageReceived(
ZIM zim,
List<ZIMMessage> messageList,
ZIMMessageReceivedInfo info,
String fromUserID,
) {
// 只处理当前会话消息
if (fromUserID != myConversationID) return;
// 合并消息到消息列表
addMessage(messageList);
// 更新 UI
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {});
if (myMessageList.isNotEmpty) {
scrollController.jumpTo(scrollController.position.maxScrollExtent);
}
});
}
void onPeerMessageReceived(
ZIM zim,
List<ZIMMessage> messageList,
ZIMMessageReceivedInfo info,
String fromUserID,
) {
// 只处理当前会话消息
if (fromUserID != myConversationID) return;
// 合并消息到消息列表
addMessage(messageList);
// 更新 UI
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {});
if (myMessageList.isNotEmpty) {
scrollController.jumpTo(scrollController.position.maxScrollExtent);
}
});
}将本地发送的消息渲染到聊天界面
监听 onMessageSentStatusChanged ,获取本地发送消息的发送状态的变化(发送中、发送成功、发送失败),渲染本地发送的消息及其发送状态到聊天界面。
@Override
public void onMessageSentStatusChanged(List<ZIMMessageSentStatusChangeInfo> messageSentStatusChangeInfoList) {
for (ZIMMessageSentStatusChangeInfo info : messageSentStatusChangeInfoList) {
if (!info.getMessage().getConversationID().equals(myConversationID)) {
continue;
}
addMessage(Collections.singletonList(info.getMessage()));
}
// 更新 UI
runOnUiThread(() -> {
myAdapter.notifyDataSetChanged();
if (!myMessageList.isEmpty()) {
recyclerView.scrollToPosition(myMessageList.size() - 1);
}
});
}
@Override
public void onMessageSentStatusChanged(List<ZIMMessageSentStatusChangeInfo> messageSentStatusChangeInfoList) {
for (ZIMMessageSentStatusChangeInfo info : messageSentStatusChangeInfoList) {
if (!info.getMessage().getConversationID().equals(myConversationID)) {
continue;
}
addMessage(Collections.singletonList(info.getMessage()));
}
// 更新 UI
runOnUiThread(() -> {
myAdapter.notifyDataSetChanged();
if (!myMessageList.isEmpty()) {
recyclerView.scrollToPosition(myMessageList.size() - 1);
}
});
}- (void)zim:(ZIM *)zim
messageSentStatusChanged:
(NSArray<ZIMMessageSentStatusChangeInfo *> *)messageSentStatusChangeInfoList{
for(ZIMMessageSentStatusChangeInfo *info in messageSentStatusChangeInfoList){
if(info.message.conversationID != myConversationID){
continue;
}
[self addMessage:info.message];
}
// update UI
}- (void)zim:(ZIM *)zim
messageSentStatusChanged:
(NSArray<ZIMMessageSentStatusChangeInfo *> *)messageSentStatusChangeInfoList{
for(ZIMMessageSentStatusChangeInfo *info in messageSentStatusChangeInfoList){
if(info.message.conversationID != myConversationID){
continue;
}
[self addMessage:info.message];
}
// update UI
}void onMessageSentStatusChanged(const std::vector<ZIMMessageSentStatusChangeInfo>& messageSentStatusChangeInfoList) {
for (const auto& info : messageSentStatusChangeInfoList) {
if (info.message.conversationID != myConversationID) continue;
addMessage({info.message}); // 假设 addMessage 支持 vector 或 list
}
refreshUI(); // 自定义刷新 UI 函数
}void onMessageSentStatusChanged(const std::vector<ZIMMessageSentStatusChangeInfo>& messageSentStatusChangeInfoList) {
for (const auto& info : messageSentStatusChangeInfoList) {
if (info.message.conversationID != myConversationID) continue;
addMessage({info.message}); // 假设 addMessage 支持 vector 或 list
}
refreshUI(); // 自定义刷新 UI 函数
}zim.onMessageSentStatusChanged = (messageSentStatusChangeInfoList: ZIMMessageSentStatusChangeInfo[]) => {
for (const info of messageSentStatusChangeInfoList) {
if (info.message.conversationID !== myConversationID) continue;
addMessage([info.message]);
}
updateUI(); // 自定义刷新 UI 函数
};zim.onMessageSentStatusChanged = (messageSentStatusChangeInfoList: ZIMMessageSentStatusChangeInfo[]) => {
for (const info of messageSentStatusChangeInfoList) {
if (info.message.conversationID !== myConversationID) continue;
addMessage([info.message]);
}
updateUI(); // 自定义刷新 UI 函数
};void onMessageSentStatusChanged(List<ZIMMessageSentStatusChangeInfo> messageSentStatusChangeInfoList) {
for (var info in messageSentStatusChangeInfoList) {
if (info.message.conversationID != myConversationID) continue;
addMessage([info.message]);
}
// 更新 UI
}void onMessageSentStatusChanged(List<ZIMMessageSentStatusChangeInfo> messageSentStatusChangeInfoList) {
for (var info in messageSentStatusChangeInfoList) {
if (info.message.conversationID != myConversationID) continue;
addMessage([info.message]);
}
// 更新 UI
}通过以上方式,即可实现在聊天页面中渲染历史消息、实时接收的消息和本地发送的消息(包含发送状态)。

