首页 > 其他分享 >鸿蒙开发融云demo聊天界面以及加载历史消息

鸿蒙开发融云demo聊天界面以及加载历史消息

时间:2024-10-26 16:45:40浏览次数:8  
标签:false 鸿蒙 mediator demo 100% height width 融云 app

鸿蒙开发融云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的私信我

标签:false,鸿蒙,mediator,demo,100%,height,width,融云,app
From: https://blog.csdn.net/u010074743/article/details/143201527

相关文章

  • 鸿蒙基础篇-组件
    “在科技的浪潮中,鸿蒙操作系统宛如一颗璀璨的新星,引领着创新的方向。作为鸿蒙开天组,今天我们将一同踏上鸿蒙基础的探索之旅,为您揭开这一神奇系统的神秘面纱。”各位小伙伴们我们又见面了,我就是鸿蒙开天组,下面让我们进入今天的学习,鸿蒙基础篇-组件首先在鸿蒙开发中,组件(Comp......
  • 鸿蒙编程江湖:ArkTS 容器与原生容器在行为上的差异
    本文旨在深入探讨华为鸿蒙HarmonyOSNext系统(截止目前API12)的技术细节,基于实际开发实践进行总结。主要作为技术分享与交流载体,难免错漏,欢迎各位同仁提出宝贵意见和问题,以便共同进步。本文为原创内容,任何形式的转载必须注明出处及原作者。ArkTS提供了一套容器集,包括Array、Map......
  • 鸿蒙编程江湖:ArkTS开发综合案例与最佳实践
    本文旨在深入探讨华为鸿蒙HarmonyOSNext系统(截止目前API12)的技术细节,基于实际开发实践进行总结。主要作为技术分享与交流载体,难免错漏,欢迎各位同仁提出宝贵意见和问题,以便共同进步。本文为原创内容,任何形式的转载必须注明出处及原作者。简介:构建复杂应用的全方位指南在掌握了......
  • 鸿蒙编程江湖:ArkTS 的多线程与序列化支持
    本文旨在深入探讨华为鸿蒙HarmonyOSNext系统(截止目前API12)的技术细节,基于实际开发实践进行总结。主要作为技术分享与交流载体,难免错漏,欢迎各位同仁提出宝贵意见和问题,以便共同进步。本文为原创内容,任何形式的转载必须注明出处及原作者。提升性能的高级技术在当今的软件开发领......
  • 鸿蒙编程江湖:ArkUI 的声明式 UI 编程与状态管理
    本文旨在深入探讨华为鸿蒙HarmonyOSNext系统(截止目前API12)的技术细节,基于实际开发实践进行总结。主要作为技术分享与交流载体,难免错漏,欢迎各位同仁提出宝贵意见和问题,以便共同进步。本文为原创内容,任何形式的转载必须注明出处及原作者。ArkTS的UI编程范式ArkUI是华为鸿蒙......
  • 鸿蒙编程江湖:I/O 密集型任务处理及 ArkTS 的异步锁机制
    本文旨在深入探讨华为鸿蒙HarmonyOSNext系统(截止目前API12)的技术细节,基于实际开发实践进行总结。主要作为技术分享与交流载体,难免错漏,欢迎各位同仁提出宝贵意见和问题,以便共同进步。本文为原创内容,任何形式的转载必须注明出处及原作者。I/O密集型任务是指需要进行大量磁盘读......
  • 鸿蒙案例实践:图像处理应用中多线程任务调度与性能优化
    本文旨在深入探讨华为鸿蒙HarmonyOSNext系统(截止目前API12)的技术细节,基于实际开发实践进行总结。主要作为技术分享与交流载体,难免错漏,欢迎各位同仁提出宝贵意见和问题,以便共同进步。本文为原创内容,任何形式的转载必须注明出处及原作者。1.项目需求与目标分析背景:图像处理......
  • 鸿蒙案例实践:智能家居控制面板的并发任务与UI交互设计
    本文旨在深入探讨华为鸿蒙HarmonyOSNext系统(截止目前API12)的技术细节,基于实际开发实践进行总结。主要作为技术分享与交流载体,难免错漏,欢迎各位同仁提出宝贵意见和问题,以便共同进步。本文为原创内容,任何形式的转载必须注明出处及原作者。1.项目概述与需求分析背景:随着物联网......
  • 鸿蒙NEXT应用上架与分发步骤详解
    大家好,我是V哥。今天的文章来聊一聊HarmonyOSNEXT应用上架。当你开发、调试完HarmonyOS应用/元服务,就可以前往AppGalleryConnect申请上架,华为审核通过后,用户即可在华为应用市场获取您的HarmonyOS应用/元服务。HarmonyOS会通过数字证书与Profile文件等签名信息来保证应用的完......
  • 鸿蒙NEXT+Flutter开发5-第一个鸿蒙应用
      通过前面步骤的操作,开发所需的硬件设备,软件运行环境均已配备完毕,接下来我们创建第一个应用,并使其运行在鸿蒙NEXT系统的手机中。1.创建鸿蒙项目  使用下面的命令,进入工作目录,并创建鸿蒙项目cd~/work/harmonyfluttercreate--platformsohos--orgcom.cdrviewerdemo1......