【高心星出品】
仿微信聊天界面
闲暇之余开发了一个基于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)
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。