#星光不负 码向未来# 鸿蒙 ArkUI 小白笔记:从 0 到 1 搓个聊天列表(保姆级教程) 原创
@[toc]
前言
嘿,小伙伴们!刚入门鸿蒙 ArkUI,是不是感觉“我是谁,我在哪,我要干什么”?别慌,今天我(一个和你一样的小白)就带大家用一份(我自个儿写的)Demo,来拆解一下怎么在鸿蒙上“搓”一个聊天列表。
不求精通,但求看懂!咱们的目标是:代码一句不改,知识点一个不落!
准备好了吗?发车!
第一站:开局与“状态”管理
首先,我们得有个“门面”。在 ArkUI 里,@Entry
就是告诉系统:“嘿,从这儿进!” @Component
则是定义一个“自定义组件”,你可以理解为咱们正在盖的“积木房子”。
然后,我们隆重请出 ArkUI 的“魔法师”—— @State
。这玩意儿可厉害了,被它“点化”过的变量,一旦发生变化,UI 界面就会“Duang”一下自动刷新。
咱们这里定义了三个状态变量:
keyword
: 用来存搜索框里的字(虽然这个 Demo 里还没完全实现搜索联动,但坑先占上)。chatList
: 咱们的“数据库”,存着原始的聊天数据。filteredList
: 真正显示在界面上的列表。为啥要多一个?因为我们要根据keyword
来“过滤”嘛!
@Entry
@Component
export struct ChatListDemo {
aboutToAppear() {
this.updateFiltered();
}
@State keyword: string = '';
@State chatList: ChatItem[] = [
new ChatItem(1, '张三', '最近项目进展怎么样?', '09:20', 2, '#FF6B6B'),
new ChatItem(2, '李四', '好的,下午开会见。', '昨天', 0, '#4A90E2'),
new ChatItem(3, '产品群', 'PRD已更新到v1.2,请查收。', '周一', 5, '#34C759'),
new ChatItem(4, '技术交流群', '谁了解一下ArkUI最新特性?', '08:05', 0, '#FF9500'),
new ChatItem(5, '王五', '下班一起打球?', '10:12', 1, '#AF52DE')
];
@State filteredList: ChatItem[] = [];
第二站:数据“蓝图” (Class)
光有数据还不行,我们得告诉程序,咱们的“聊天数据”长啥样。class ChatItem
就是这个“蓝图”。它规定了每条聊天记录都必须有 id
、name
、lastMessage
等属性。
这就是面向对象里的“建模”,说白了就是“丑话说在前头,你要存数据,必须按这个格式来”。
class ChatItem {
id: number;
name: string;
lastMessage: string;
time: string;
unreadCount: number;
color: string;
constructor(id: number, name: string, lastMessage: string, time: string, unreadCount: number, color: string) {
this.id = id;
this.name = name;
this.lastMessage = lastMessage;
this.time = time;
this.unreadCount = unreadCount;
this.color = color;
}
}
第三站:“开机自启”与“过滤神功”
aboutToAppear()
是一个“生命周期”函数。你可以理解为“演员准备登台前的那一刻”。组件马上要显示了,它就会被自动调用。
我们在这里调用了 this.updateFiltered()
,目的就是在“开机”时,先把 chatList
里的所有数据显示出来。
updateFiltered()
则是我们的“过滤神功”。
- 它先检查
this.keyword
是不是空的。 - 如果是空的(
!k
),太好了,filteredList
直接“全盘复制”chatList
。 - 如果不是空的,它就会用 JS/TS 祖传的
.filter()
方法,去base
(也就是chatList
) 里“筛”一遍,只留下name
或lastMessage
包含关键词(k
)的条目。
<!-- end list -->
private updateFiltered() {
const base = Array.isArray(this.chatList) ? this.chatList : [];
const k = (this.keyword ?? '').trim();
this.filteredList = !k ? base : base.filter(item => item.name.includes(k) || item.lastMessage.includes(k));
}
第四站:build
!开搭界面!
build()
函数是重头戏!它就是我们 UI 的“总设计师”。ArkUI 是“声明式 UI”,意思就是:你别管怎么画,你只要告诉我“长啥样”就行。
我们先用一个 Column()
(垂直布局),把“顶部标题区”和“聊天列表区”从上到下“码”起来。
build() {
Column() {
第五站:精致的“顶栏” (AppBar)
在第一个 Column
里,我们再放一个 Column
来装“顶部标题区”(包含标题和搜索框)。
首先,用 Row()
(水平布局)来做标题栏。
- 中间的
Text('消息')
被包在一个Row
里,并且设置了.layoutWeight(1)
和.justifyContent(FlexAlign.Center)
。layoutWeight(1)
是个神器!意思是:“在这一行里,你(中间的标题)能占多宽占多宽,把其他兄弟挤到边上去!”justifyContent(FlexAlign.Center)
则是让标题在它占据的广阔空间里“居中”。
- 右侧的
Button
(那个“+”号)就老实待在它该在的地方。
<!-- end list -->
// 顶部标题区:大标题 + 次要操作(新聊天)
Column() {
// 顶部 AppBar 风格
Row()
{
// 中间标题
Row() {
Text('消息')
.fontSize(22)
.fontWeight(FontWeight.Bold)
.fontColor('#111111')
}
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
// 右侧操作:新聊天(改为图标按钮)
Button() {
// 使用简单加号图标符号
Text('+')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF')
}
.type(ButtonType.Circle)
.width(36)
.height(36)
.backgroundColor('#0D9FFB')
}
.height(56)
.width('100%')
.backgroundColor('#F8F9FA')
.padding({ left: 16, right: 16 })
第六站:搜索框登场
在“标题栏” Row
的下面,还是在“顶部标题区” Column
内部,我们再放一个 Row
,用来装 TextInput
(搜索框)。
这里我们让它 .layoutWeight(1)
占满整行,并用 .borderRadius(20)
把它变成圆角矩形。它就是我们未来实现搜索功能的“入口”!
// 标题下方放置搜索框,提高层次感
Row() {
TextInput({ placeholder: '搜索联系人或消息内容' })
.height(40)
.border({ width: 1, color: '#E5E5EA' })
.borderRadius(20)
.padding({ left: 12, right: 12 })
.layoutWeight(1)
}
.padding({ top: 8, left: 16, right: 16, bottom: 8 })
}
第七站:列表“本体” (List
& ForEach
)
“顶部标题区” Column
结束后,轮到“聊天列表区” List
登场了。
List
是专门用来显示长列表的(有性能优化哦)。
ForEach
则是“循环器”,它干的活就是:
- 遍历
this.filteredList
(还记得吗?被@State
点化过的那个)。 - 每遍历到一条
item
数据,就创建一个ListItem
。 - 在
ListItem
里面干啥呢?调用this.renderChatItem(item)
。 item.id.toString()
是给每一项一个“身份证”(key
),这样 ArkUI 在刷新列表时能精确知道谁是谁,效率更高。
最后,我们又用了 .layoutWeight(1)
,让列表占满“顶部标题区”以外的 所有 剩余垂直空间。
// 注:搜索框已移动到标题区下方,这里删除重复输入框
// 聊天列表
List() {
ForEach(this.filteredList, (item: ChatItem, index: number) => {
ListItem() {
this.renderChatItem(item)
}
}, (item: ChatItem, index: number) => item.id.toString())
}
.height('100%')
.width('100%')
.layoutWeight(1)
.divider({ strokeWidth: 0.5, color: '#F2F2F7' })
.edgeEffect(EdgeEffect.None)
.margin({ top: 8 })
}
.height('100%')
.backgroundColor('#FFFFFF')
}
第八站:可复用的“积木” (@Builder
)
build
函数是不是快“爆炸”了?太乱了!
ArkUI 提供了 @Builder
装饰器,让我们能“封装”一小块 UI。它就像一个“返回 UI 的函数”或者“UI 积木”,让 build
函数更清爽。
我们先看这个 renderAvatar
,它专门负责画“头像”。它用 Stack()
(堆叠布局)把一个单字 Text
放在一个彩色圆圈 Stack
的正中间。
@Builder
private renderAvatar(item: ChatItem) {
// 使用首字作为简易头像
Stack() {
Text(item.name.substring(0, 1))
.fontSize(18)
.fontColor('#FFFFFF')
.align(Alignment.Center)
}
.width(48)
.height(48)
.backgroundColor(item.color)
.borderRadius(24)
.margin({ right: 12 })
}
第九站:“积木”拼装 (renderChatItem
)
现在我们看 renderChatItem
这个“大积木”。
- 它先用
Row
把“头像区”和“内容区”横向摆放。 - 头像区:这里用了
Stack()
布局。- 底层是
this.renderAvatar(item)
(刚刚造的“头像积木”)。 - 上层是
if (item.unreadCount > 0)
(判断是否有未读消息)。 - 如果有,就在上面“堆”一个红点
Row
(包含Text
)。 .position({ x: 34, y: -4 })
是点睛之笔!它用“绝对定位”把红点“精确”地““糊””在了头像的右上角。
- 底层是
- 内容区:用
Column
纵向摆放“第一行(名字+时间)”和“第二行(消息)”。- 第一行
Row
使用了justifyContent(FlexAlign.SpaceBetween)
,让“名字”和“时间”自动“两端对齐”。 - 第二行
Text
简单显示消息,并用textOverflow
处理了“超出部分显示省略号(…)”。
- 第一行
<!-- end list -->
@Builder
private renderChatItem(item: ChatItem) {
Row() {
// 头像(带未读角标)
Stack() {
this.renderAvatar(item)
if (item.unreadCount > 0) {
Row() {
Text(item.unreadCount.toString())
.fontSize(10)
.fontColor('#FFFFFF')
.backgroundColor('#FF3B30')
.borderRadius(8)
.padding({ left: 5, right: 5, top: 2, bottom: 2 })
}
.position({ x: 34, y: -4 })
}
}
// 内容
Column() {
// 顶部一行:name 和 time 两端对齐
Row()
{
Text(item.name)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#111111')
.layoutWeight(1)
.textAlign(TextAlign.Start)
Text(item.time)
.fontSize(12)
.fontColor('#8E8E93')
.textAlign(TextAlign.End)
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
// 第二行:lastMessage 居左
Text(item.lastMessage)
.fontSize(14)
.fontColor('#8E8E93')
.width('100%')
.textAlign(TextAlign.Start)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
}
.padding({ left: 12, right: 12 })
.width('75%')
}
.height(72)
.padding({ left: 16, right: 16 })
.backgroundColor('#FFFFFF')
.onClick(() => {
// 点击条目,可跳转到聊天详情页(此Demo暂不实现详情)
// 可扩展:router.pushUrl({ url: 'pages/ChatDetail', params: { id: item.id.toString() } })
console.info(`Open chat with ${item.name}`)
})
}
} // 别忘了,这是 ChatListDemo struct 的结尾 }
收工总结!
呼… 一口气看完了!我们来总结一下今天学到的“黑话”:
@Entry
/@Component
:组件的“户口本”。@State
:能让 UI 自动刷新的“魔法”变量。build()
:UI“总设计师”,在这里“声明”你的界面长啥样。Column
/Row
/Stack
:布局三剑客(垂直、水平、堆叠)。.layoutWeight(1)
:“占满剩余空间”的霸道属性。List
/ForEach
:渲染列表的黄金搭档。@Builder
:可复用的“UI 积木”,让代码更整洁。- 生命周期 (如
aboutToAppear
):在特定时机(如“即将显示”)自动执行的函数。
怎么样,是不是感觉 ArkUI 也没那么“劝退”?它就是用各种组件和布局,像搭积木一样把界面“拼”出来,我们来看看预览器中的效果图:
怎么样,是不是很有成就感!赶紧动手试试吧!跟着我的笔记操作,你也能写出来主流的聊天列表!如果游刃有余的同学,可以尝试自己实现一下聊天详情页面。
好了,到站了,同学,别睡了,快下车!下次再见,哈哈~
-
-
-
-
-
-
-
-
-
-
- 小刘大人 40分钟前 最后回复来自 • hm688c72063924c
-
-
-
-
-
-
-
-
非常不错,幽默风趣间就学会了。。。赞一个
6666~~~