鸿蒙开发融云demo聊天界面以及加载历史消息
跟着我一步步搭建鸿蒙版本融云demo,这次说的是鸿蒙融云聊天界面以及如何加载历史消息
先看下效果图:
关键代码:
@Builder
ContentBuilder(){
// 不用Column,滑动有问题
Stack() {
this.ListPagingBuilder()
if (this.conversationType === ConversationType.Private) {
Column(){
// 底部输入框
Row({space:10}) {
Image(this.isVoiceInput ? $r('app.media.rc_ext_toggle_keyboard') : $r('app.media.rc_ext_toggle_voice'))
.height(28)
.width(28)
.onClick(() => {
if (this.isVoiceInput) {
// 自动显示焦点
this.isShowKeyboard = true
// 这里调requestFocus("inputMsg")会报组件不存在
//this.getUIContext().getFocusController().requestFocus("inputMsg")
} else {
KeyboardUtil.hide()
this.isShowKeyboard = false
}
this.isVoiceInput = !this.isVoiceInput
this.showEmojiPanel = false
this.showExtPanel = false
})
if (this.isVoiceInput) {
Text($r("app.string.voice_record_dynamic_effect_button"))
.height(40)
.layoutWeight(1)
.borderRadius(20)
.focusable(true)
.textAlign(TextAlign.Center)
.backgroundColor($r('app.color.color_F1F3F5'))
.fontColor(Color.Black)
.bindContentCover($$this.showTalkContainer, this.TalkContainerBuilder,
{ modalTransition: ModalTransition.NONE })
.onTouch(this.onPressTalk)
} else {
TextInput({ placeholder: '', text: this.keyboardStr, controller: this.inputController })
.height(40)
.id('inputMsg')
.onAppear(() => {
// 自动显示焦点
if (this.isShowKeyboard) {
this.getUIContext().getFocusController().requestFocus("inputMsg")
}
})
.layoutWeight(1)
.borderRadius(20)
.backgroundColor($r('app.color.color_F1F3F5'))
.caretColor($r('app.color.color_main'))
.onFocus(() => {
this.isShowKeyboard = true
this.showEmojiPanel = false
this.showExtPanel = false
this.isVoiceInput = false
})
.onChange((value: string) => {
this.keyboardStr = value
})
}
Image(this.showEmojiPanel ? $r('app.media.rc_ext_toggle_keyboard') : $r('app.media.rc_ext_input_panel_emoji'))
.height(28)
.width(28)
.onClick(() => {
if (this.showEmojiPanel) {
// 自动显示焦点
this.isShowKeyboard = true
this.getUIContext().getFocusController().requestFocus("inputMsg")
} else {
KeyboardUtil.hide()
this.isShowKeyboard = false
}
this.showEmojiPanel = !this.showEmojiPanel
this.showExtPanel = false
this.isVoiceInput = false
})
if (!this.keyboardStr) {
Image($r('app.media.rc_ext_input_panel_add'))
.height(28)
.width(28)
.onClick(() => {
if (this.showExtPanel) {
// 自动显示焦点
this.isShowKeyboard = true
this.getUIContext().getFocusController().requestFocus("inputMsg")
} else {
KeyboardUtil.hide()
this.isShowKeyboard = false
}
this.showExtPanel = !this.showExtPanel
this.showEmojiPanel = false
this.isVoiceInput = false
})
}
if (this.keyboardStr) {
Text($r('app.string.send'))
.width(60)
.height(30)
.fontColor($r('app.color.color_white'))
.fontSize(14)
.textAlign(TextAlign.Center)
.backgroundColor($r('app.color.color_main'))
.borderRadius(14)
.onClick(() => {
ImUtils.sendTextMessage(this.targetId, this.keyboardStr,'',
()=>{
this.closeAllPanel()
})
})
}
}
.borderRadius({ topLeft: 20, topRight: 20 })
.backgroundColor(Color.White)
.width('100%')
.height(56 + CommonConstants.NAVIGATION_INDICATOR_HEIGHT)
.padding({ left:10,right:10,bottom: CommonConstants.NAVIGATION_INDICATOR_HEIGHT-20 })
if (this.showExtPanel || this.showEmojiPanel) {
Stack(){
if (this.showExtPanel) {
Grid() {
ForEach(this.conversationPlugins, (ext: ConversationPlugin) => {
GridItem() {
Column() {
Image(ext.icon)
.width(60)
.draggable(false)
.height(60)
Text(ext.title)
.margin({ top: 8 })
.fontSize(12)
}
.borderRadius(4)
.margin(10)
.padding(4)
.onClick(ext.action)
}
}, (ext: ConversationPlugin) => ext.title)
}
.columnsTemplate('1fr 1fr 1fr 1fr')
.columnsGap(10)
.rowsGap(10)
.margin(0)
.padding(0)
.width('100%')
.height('100%')
} else if (this.showEmojiPanel){
Swiper() {
ForEach(CommonConstants.EMOJI_DATA,(emojiData:number[], index1:number) =>{
Column(){
Grid(){
ForEach(emojiData,(item:number, index2:number) =>{
GridItem(){
Stack(){
if(item === -1){
Image($r('app.media.rc_icon_emoji_delete')).width(28).height(28)
}else{
Text(String.fromCodePoint(item)).fontColor($r('app.color.color_black')).fontSize(24)
}
}
.width('100%')
.height('100%')
.onClick(() =>{
if(item === -1){
// 删除(表情长度有1有2,没法确定)
// if(this.commentText.length - 1 > 0){
// this.commentText = this.commentText.slice(0,this.commentText.length - 1)
// }
}else{
// 添加
let offsetIndex: number = this.inputController.getCaretOffset().index // 光标位置
this.keyboardStr = `${this.keyboardStr.slice(0,offsetIndex)}${String.fromCodePoint(item)}${this.keyboardStr.slice(offsetIndex,this.keyboardStr.length)}`
}
})
}
})
}
.columnsTemplate('1fr 1fr 1fr 1fr 1fr 1fr 1fr')
.rowsTemplate('1fr 1fr 1fr 1fr')
.width('100%')
.aspectRatio(6/4)
}
.width('100%')
.padding({bottom:8})
})
}
.height('100%')
.width('100%')
.loop(false)
.padding(8)
}
}
.width('100%')
.backgroundColor($r('app.color.color_ed'))
.height(this.keyboardHeight === 0?320:this.keyboardHeight)
}
if (!this.showEmojiPanel && !this.showExtPanel && this.isShowKeyboard) {
Stack(){
}
.width('100%')
.height(this.keyboardHeight === 0?320:this.keyboardHeight)
.backgroundColor($r('app.color.color_ed'))
}
}
.width('100%')
}
}
.width('100%')
.height('100%')
.alignContent(Alignment.BottomEnd)
.clip(true)
}
@Builder
ListPagingBuilder() {
List({ scroller: this.mediator.scroller, initialIndex: 19 }) {
LazyForEach(this.mediator.lazyDataSource, (item: Message, index) => {
ListItem() {
ChatDetailItemView({
msg: item,
preMsgTime: index > 0 ? this.mediator.lazyDataSource.getData(index - 1).sentTime : 0,
imUserInfo: this.toImUserInfo,
index: index,
delCallback: (delIndex) => {
this.mediator.lazyDataSource.deleteData(delIndex)
EventKeys.postEvent(EventKeys.RefreshMsgListEvents)
},
clickToInfoCallback: () => {
},
playVoiceCallback: async (messageId) => {
// 释放资源,因为播放器最多16个
this.voicePlayMessageId = 0
this.voicePlayState = AnimationStatus.Stopped
this.avPlayer?.release()
AudioPlayer.startPlayHQVoiceMessage(messageId, (avPlayer) => {
this.avPlayer = avPlayer
},(avPlayerState) =>{
if (avPlayerState === 'playing') {
this.voicePlayMessageId = messageId
this.voicePlayState = AnimationStatus.Running
} else if (this.voicePlayMessageId === messageId){
this.voicePlayState = AnimationStatus.Stopped
}
})
},
voicePlayState: this.voicePlayState,
voicePlayMessageId: this.voicePlayMessageId,
notifyDataChangeMyself:()=>{
this.mediator.lazyDataSource.notifyDataChange(index)
}
})
}
// 不加new Date().getTime(),但是得要加会变的东西,否则刷新时,界面没变
}, (item: Message, index: number) => `${index}-${JSON.stringify(item)}`)
// 加个空白的item撑底部,不让输入框挡住
ListItem() {
}.width('100%')
.height(68 + CommonConstants.NAVIGATION_INDICATOR_HEIGHT)
}
.padding({
left: 10,
right: 10,
})
.width("100%")
.height('100%')
.edgeEffect(EdgeEffect.None)
.scrollBar(BarState.Off)
.onReachEnd(() => {
this.mediator.refreshMediator.scrollerReachEnd()
})
.onAreaChange((_, newValue: Area) => {
this.mediator.refreshMediator.scrollerAreaChange(newValue)
})
.onScrollFrameBegin((offset: number, _) => {
logContent('offsetChat',this.mediator.refreshMediator.enableRefresh+" "+offset)
if (this.mediator.refreshMediator.isShowNoMoreData() && offset < 0) {
return { offsetRemain: 0 }
} else {
return {
offsetRemain: this.mediator.collapsibleMediator?.getScrollerFrameRemainOffset(offset)
||
this.mediator.refreshMediator.getScrollerFrameRemainOffset(offset, this.mediator.collapsibleMediator == null)
}
}
})
.onClick(()=>{
this.closeAllPanel()
})
}
加载历史消息的代码:
loadHistoryMessages(){
let conId = new ConversationIdentifier();
conId.conversationType = this.conversationType;
conId.targetId = this.targetId;
if (this.hasLocalMsg) {
let optionLocal: IGetLocalMsgByTimeOption = {
time: this.lastMsgTime,
beforeCount: 20,
afterCount: 0
}
IMEngine.getInstance().getHistoryMessagesByTime(conId, optionLocal)
.then(result => {
this.mediator.finishRefresh(true)
if (EngineError.Success !== result.code) {
// 获取消息失败
return;
}
if (!result.data) {
// 消息不存在,本地数据没有了,获取远端服务器数据
this.hasLocalMsg = false
this.loadRemoteHistoryMessages()
return;
}
// 消息列表
let msgList = result.data as AList<Message>;
this.hasLocalMsg = msgList.length === 20
this.dealOldListMessage(msgList)
})
} else {
this.loadRemoteHistoryMessages()
}
}
loadRemoteHistoryMessages(){
let conId = new ConversationIdentifier();
conId.conversationType = this.conversationType;
conId.targetId = this.targetId;
let optionRemote: IGetRemoteMsgOption = {
time: this.lastMsgTime,
count: 20,
order: Order.Ascending,
isCheckDup: false
}
IMEngine.getInstance().getRemoteHistoryMessages(conId, optionRemote).then(result => {
this.mediator.finishRefresh(true)
if (!result.data) {
// 消息不存在
return;
}
// 消息列表
let msgList = result.data as AList<Message>;
this.dealOldListMessage(msgList)
});
}
dealOldListMessage(msgList:AList<Message>){
if (msgList && msgList.length > 0) {
let msgListArray: Message[] = msgList.convertToArray().filter(item => item.objectName !== CustomizeReadReceiptMessageObjectName);
if (this.mediator.lazyDataSource.totalCount() === 0) {
this.lastMsgTime = msgListArray[0].sentTime
this.mediator.finishRefresh(true, false, msgListArray)
// 进来正常时发一次已读回执,有数据才发
ImUtils.sendMyReadReceiptMessage(this.targetId, Date.now())
// 延迟滚动才有效果,设置了initIndex,但这方法还需要
setTimeout(() => {
this.mediator.scroller.scrollToIndex(this.mediator.lazyDataSource.totalCount() - 1)
}, 400)
} else {
if (this.lastMsgTime !== msgListArray[0].sentTime) {
this.lastMsgTime = msgListArray[0].sentTime
let addCount = msgListArray.length
// 因为以刷新方法加载下一页的
msgListArray.push(...this.mediator.lazyDataSource.getAllData())
this.mediator.finishRefresh(true, false, msgListArray)
this.mediator.scroller.scrollToIndex(addCount - 1);
}
}
}
}
鸿蒙融云Demo源码结构图:
有问题或者需要完整源码demo的私信我