本文将介绍如何在 HarmonyOS NEXT 中构建一个具备瀑布流展示和即时通讯功能的社交类应用核心模块,重点讲解背景层叠布局、自适应图片网格以及聊天气泡的实现细节。
**全局背景层叠方案**
应用多个页面共享渐变背景,采用底层图片铺满、上层内容浮动的架构。通过 Stack 容器实现背景与内容的分层:
Stack({ alignContent: Alignment.Top }) {
Image($r('app.media.gradient_bg'))
.width('100%')
.height('100%')
.objectFit(ImageFit.Fill)
// 页面具体内容
Column() {
// 导航栏、列表等组件
}
}
**顶部导航栏构建**
导航栏采用三栏式横向排布:左侧定位信息、中部标签切换、右侧功能入口。标签选中状态通过动态字体样式与下划线标识:
@Builder CustomNavBar() {
Row() {
// 定位区域
Row({ space: 4 }) {
Image($r('app.media.pin_icon'))
.width(14)
.height(14)
Text('北京市')
.fontSize(12)
.fontColor('#444444')
}
.width(64)
.height(24)
.backgroundColor('#FFFFFF')
.borderRadius(12)
// 标签切换
Row({ space: 16 }) {
ForEach(this.tabTitles, (title: string, idx: number) => {
Column() {
Text(title)
.fontSize(this.activeTab === idx ? 18 : 16)
.fontColor(this.activeTab === idx ? '#1A1A1A' : '#888888')
if (this.activeTab === idx) {
Image($r('app.media.underline'))
.width(16)
.height(4)
}
}
.height(32)
.justifyContent(FlexAlign.Start)
})
}
// 更多操作
Image($r('app.media.ellipsis'))
.width(20)
.height(20)
}
.width('100%')
.height(48)
.padding({ left: 16, right: 16 })
.justifyContent(FlexAlign.SpaceBetween)
.alignItems(VerticalAlign.Center)
}
**瀑布流内容展示**
利用 WaterFlow 组件实现图片自适应网格,关键在于为每幅图片预存宽高比,据此计算 FlowItem 高度。数据层封装 IDataSource 接口以支持懒加载:
export class PictureDataProvider implements IDataSource {
private imageSet: PhotoInfo[] = [
{ src: $r('app.media.p1'), nickname: '林夕', range: '1.2km', w: 720, h: 960 },
{ src: $r('app.media.p2'), nickname: '阿默', range: '2.5km', w: 1080, h: 720 },
{ src: $r('app.media.p3'), nickname: '小鹿', range: '0.8km', w: 800, h: 1200 },
{ src: $r('app.media.p4'), nickname: '南风', range: '3.1km', w: 960, h: 960 },
{ src: $r('app.media.p5'), nickname: '七海', range: '1.9km', w: 640, h: 1136 },
{ src: $r('app.media.p6'), nickname: '橙子', range: '4.2km', w: 1080, h: 810 },
]
private observers: DataChangeListener[] = []
private dataStore: Object[] = []
constructor() {
this.dataStore = [...this.imageSet]
}
totalCount(): number {
return this.dataStore.length
}
getData(pos: number): Object {
return this.dataStore[pos]
}
registerDataChangeListener(listener: DataChangeListener): void {
if (this.observers.indexOf(listener) < 0) {
this.observers.push(listener)
}
}
unregisterDataChangeListener(listener: DataChangeListener): void {
const position = this.observers.indexOf(listener)
if (position >= 0) {
this.observers.splice(position, 1)
}
}
notifyReload(): void {
this.observers.forEach(cb => cb.onDataReloaded())
}
appendItem(): void {
const newIdx = this.dataStore.length
this.dataStore.push(this.imageSet[newIdx % this.imageSet.length])
this.observers.forEach(cb => cb.onDataAdded(newIdx))
}
removeAt(index: number): void {
this.dataStore.splice(index, 1)
this.observers.forEach(cb => cb.onDataDeleted(index))
}
}
WaterFlow 绑定数据源时,根据图片原始宽高动态计算每个单元格高度:
WaterFlow() {
LazyForEach(this.pictureProvider, (pic: PhotoInfo) => {
FlowItem() {
ProfileCard({ info: pic })
.width(this.colWidth)
.height(this.colWidth * pic.h / pic.w)
}
}, (pic: PhotoInfo) => pic.nickname + pic.range)
}
.columnsTemplate('1fr 1fr')
.columnsGap(8)
.rowsGap(8)
.width('100%')
.height('100%')
**聊天气泡对齐与约束**
消息区分收发双方,通过 Flex 主轴对齐实现左右分布,并借助 constraintSize 限制气泡最大宽度避免过度拉伸:
@Component struct SentBubble {
@Prop content: string
build() {
Row() {
Text(this.content)
.fontColor('#FFFFFF')
.fontSize(15)
.lineHeight(20)
.padding({ left: 20, right: 20, top: 12, bottom: 12 })
.backgroundColor('#7B68EE')
.borderRadius({
topLeft: 20,
topRight: 4,
bottomLeft: 20,
bottomRight: 20
})
.constraintSize({
maxWidth: AppMetrics.screenWidth * 0.65
})
}
.width('100%')
.justifyContent(FlexAlign.End)
.padding({ top: 8, bottom: 8, left: 12, right: 12 })
}
}
@Component struct ReceivedBubble {
@Prop content: string
build() {
Row() {
Text(this.content)
.fontColor('#333333')
.fontSize(15)
.lineHeight(20)
.padding({ left: 20, right: 20, top: 12, bottom: 12 })
.backgroundColor('#F0F0F0')
.borderRadius({
topLeft: 4,
topRight: 20,
bottomLeft: 20,
bottomRight: 20
})
.constraintSize({
maxWidth: AppMetrics.screenWidth * 0.65
})
}
.width('100%')
.justifyContent(FlexAlign.Start)
.padding({ top: 8, bottom: 8, left: 12, right: 12 })
}
}
实际渲染时根据消息发送方类型条件选择组件,配合 List 滚动容器即可实现完整的对话列表。