【高心星出品】
仿微信聊天界面
闲暇之余开发了一个基于 HarmonyOS5.0 的仿微信聊天界面,里面主要用到了 ArkUI 的技术。
List 布局:列表是一种复杂的容器,当列表项达到一定数量,内容超过屏幕大小时,可以自动提供滚动功能。它适合用于呈现同类数据类型或数据类型集,例如图片和文本。在列表中显示数据集合是许多应用程序中的常见要求(如通讯录、音乐列表、购物清单等)。
Grid 布局:网格布局是由“行”和“列”分割的单元格所组成,通过指定“项目”所在的单元格做出各种各样的布局。网格布局具有较强的页面均分能力,子组件占比控制能力,是一种重要自适应布局,其使用场景有九宫格图片展示、日历、计算器等。
Swiper 布局:Swiper 本身是一个容器组件,当设置了多个子组件后,可以对这些子组件进行轮播显示。通常,在一些应用首页显示推荐的内容时,需要用到轮播显示的能力。
LazyForeach 懒加载:LazyForEach 从提供的数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。当在滚动容器中使用了 LazyForEach,框架会根据滚动容器可视区域按需创建组件,当组件滑出可视区域外时,框架会进行组件销毁回收以降低内存占用。
运行效果:
开发步骤:
项目结构
聊天标题
聊天标题是一个水平布局,包括左边的箭头,用户名称和功能图片。其中左边的箭头是由线性布局旋转之后外边框绘制而来。
titlebar() { Row() { if (this.badgevalue > 0) { // 生成箭头 this.arrow(ArrowType.LEFT) Badge({ value: '' + this.badgevalue, position: BadgePosition.Right, style: { badgeSize: 24, badgeColor: Color.Gray, borderColor: Color.Gray } }) { Text(' ') } } else { this.arrow(ArrowType.LEFT) } Blank() if (this.title) { Text(this.title).fontSize(22).fontColor(Color.Black) } Blank() Image($r('app.media.sangedian')).width('6%').aspectRatio(1)
} .width('100%') .padding('5%') .border({ width: { bottom: 1 }, color: this.divdercolor }) .margin({ bottom: 5 }) } //绘制左右箭头 @Builder arrow(type: ArrowType) { if (type == ArrowType.LEFT) { Row() .rotate({ angle: -45 }) .border({ width: { top: 2, left: 2 }, color: Color.Black }) .width('5%') .height('5%') .aspectRatio(1) .margin({ right: 20 }) } else if (type == ArrowType.RIGHT) { Row() .rotate({ angle: 45 }) .border({ width: { top: 2, left: 2 }, color: Color.Black }) .width('5%') .height('5%') .aspectRatio(1) .margin({ right: 20 }) } }
复制代码
聊天内容
聊天内容整体是一个 list,里面的 listitem 会根据消息来源来进行调整,发出的消息采取右侧布局,接受的消息左侧布局。
// 消息展示区域 @Builder content() { List() { LazyForEach(this.chats, (item: ChatInfo) => { ListItem() { if (item.type == ChatType.TIME) { this.itemtime(item) } else { this.itemmsg(item) } } }) }.layoutWeight(1) } // 发消息和收消息的子布局 @Builder itemmsg(chat: ChatInfo) { if (chat.from === currentuser) { //发出的消息 Row() { Text(chat.message) .padding({ left: '4%', right: '4%', top: '2%', bottom: '2%' }) .backgroundColor('rgba(200,200,200,0.5)') .fontSize(18) .borderRadius({ topLeft: 10, bottomLeft: 10, topRight: 15, bottomRight: 15 }) Image($r('app.media.Bellboy')) .width(35) .height(35) .objectFit(ImageFit.Fill) .borderRadius(8) .margin({ left: 10 }) }.width('100%') .padding('2%') .justifyContent(FlexAlign.End) } else { //接受的消息 Row() { Image($r('app.media.girl')).width(35).height(35).objectFit(ImageFit.Fill).borderRadius(8) Text(chat.message) .padding({ left: '4%', right: '4%', top: '2%', bottom: '2%' }) .backgroundColor('rgba(200,200,200,0.5)') .fontSize(18) .borderRadius({ topLeft: 15, bottomLeft: 15, topRight: 10, bottomRight: 10 }) .margin({ left: 10 }) }.width('100%') .padding('2%') } } // 展示时间的子布局 @Builder itemtime(msg: ChatInfo) { Column() { Text(msg.time).fontSize(16).fontColor(Color.Gray)
}.width('100%').padding('3%') }
复制代码
发消息布局
发消息布局采取的是水平线性布局,从左至右分别是语音图标,输入框,图标和新增图标。
// 发消息布局 @Builder msgbar() { Column() { Row({ space: 10 }) { Image($r('app.media.voicemessage')).width(40).height(40).objectFit(ImageFit.Fill) TextInput().layoutWeight(1).borderRadius(10).backgroundColor(Color.White) Image($r('app.media.smile')).width(40).height(40).objectFit(ImageFit.Fill) Image($r('app.media.add')).width(40).height(40).objectFit(ImageFit.Fill) .onClick(() => { animateTo({curve:curves.springMotion()},()=>{ this.showhidebar = !this.showhidebar
}) }) }.padding('3%').width('100%') .border({ width: { top: 1 }, color: this.divdercolor })
// 隐藏栏 this.hidebar() }.width('100%') }
复制代码
隐藏功能卡片布局
隐藏功能卡片区是用了 swiper 布局里面嵌套了 Grid 网格布局。
@Builder hidebar() { if (this.showhidebar) { Swiper() { this.gongnengpage(gongnengs.slice(0, 8)) this.gongnengpage(gongnengs.slice(8)) } .border({width:{top:1},color:this.divdercolor}) } }
// 功能页面 @Builder gongnengpage(gongnengs: GongnengType[]) { Grid() { ForEach(gongnengs, (gongneng: GongnengType) => { GridItem() { this.card(gongneng) } }) }.columnsTemplate('1fr 1fr 1fr 1fr') .width('100%') .height('30%')
.rowsGap('10%') .margin({top:'10%'}) } // 功能也的功能卡片 @Builder card(gongneng: GongnengType) { Column() { Row() { Image(gongneng.icon) .width(40) .height(40) .objectFit(ImageFit.Fill) }.width(60).height(60).backgroundColor(Color.White).justifyContent(FlexAlign.Center) .borderRadius(8) Text(gongneng.name).fontSize(14).margin({top:10}) }.margin({ bottom: '5%' }) }
复制代码
数据源的填充
填充消息的时候还要考虑,消息之间的时间间隔。消息之间时间间隔如果比较小就不会显示时间,如果时间间隔比较大,就需要增加一个时间显示列表项。
// 如果上下两个消息之间时间间隔超过1个小时 增加一个时间 adddata(data: ChatInfo) { if (this.datas.length > 0) { let time = data.time let time2 = this.datas[this.datas.length-1].time let hour = Number.parseInt(time.slice(time.indexOf(' ') + 1, time.indexOf(':'))) let hour2 = Number.parseInt(time2.slice(time.indexOf(' ') + 1, time.indexOf(':'))) if (Math.abs(hour - hour2) >= 1) { this.datas.push({ type: ChatType.TIME, time: data.time }) } } this.datas.push(data) }
复制代码
项目仓库 Gitee
仓库地址: https://gitee.com/gxx01/zwechatroom/
评论