【鸿蒙版-钓鱼云助手】第五章:首页信息流-List组件,轻松拿捏列表
代码如诗,列表如流。当指尖在屏幕滑动,信息如溪水般流淌,这大概就是数字时代最美的垂钓体验。
List组件介绍
首先我们先来看看官网对List组件的介绍
列表是一种复杂的容器,当列表项达到一定数量,内容超过屏幕大小时,可以自动提供滚动功能。它适合用于呈现同类数据类型或数据类型集,例如图片和文本。在列表中显示数据集合是许多应用程序中的常见要求(如通讯录、音乐列表、购物清单等)。
使用列表可以轻松高效地显示结构化、可滚动的信息。通过在List组件中按垂直或者水平方向线性排列子组件ListItemGroup或ListItem,为列表中的行或列提供单个视图,或使用循环渲染迭代一组行或列,或混合任意数量的单个视图和ForEach结构,构建一个列表。List组件支持使用条件渲染、循环渲染、懒加载等渲染控制方式生成子组件。
官网对List组件给出了篇幅很大的介绍,里面有非常详细的使用介绍,当然列表也是我们开发中最常用的组件之一。
官网文档:创建列表
根据官网文档,简单的整理了下常用的知识点:
分类 | 核心概念 | 说明 | 代码示例 |
---|---|---|---|
基础概念 | List容器 | 垂直排列的滚动容器 | List() { ListItem() { } } |
ListItem | 列表项,必须放在List内 | ListItem() { Content() } |
|
列表方向 | 默认垂直,可设置水平 | .listDirection(Axis.Vertical) |
|
数据渲染 | ForEach | 遍历数组渲染列表项 | ForEach(arr, item => ListItem() { }) |
键值生成器 | 优化渲染性能的关键 | (item) => item.id |
|
LazyForEach | 懒加载大数据集 | LazyForEach(dataSource) |
|
布局控制 | 间距设置 | 列表项之间的间距 | List({ space: 10 }) |
边距设置 | 列表内容与边界的距离 | .padding(20) |
|
对齐方式 | 列表项在容器中的对齐 | .alignListItem(ListItemAlign.Center) |
|
滚动控制 | 滚动条 | 显示/隐藏滚动条 | .scrollBar(BarState.On) |
边缘效果 | 滚动到边缘的效果 | .edgeEffect(EdgeEffect.Spring) |
|
滚动位置 | 控制滚动到指定位置 | .scroller(controller) |
|
性能优化 | 缓存数量 | 预渲染不可见项的数量 | .cachedCount(5) |
链式动画 | 开启/关闭链式动画 | .chainAnimation(true) |
|
懒加载 | 延迟创建复杂组件 | LazyForEach + 简单子组件 |
|
交互功能 | 点击事件 | 列表项点击处理 | .onClick(() => {}) |
滑动删除 | 支持侧滑删除操作 | .deleteTransition |
|
拖拽排序 | 支持拖拽重新排序 | .dragStart 相关事件 |
|
高级特性 | 粘性头部 | 头部悬停效果 | ListItem().sticky(StickyStyle.Header) |
分组列表 | 带分组的列表 | 结合Section组件使用 | |
多类型项 | 不同样式的列表项 | 在ForEach中返回不同的ListItem | |
响应式布局 | 适应不同屏幕尺寸 | 使用百分比或响应式单位 |
项目实战
接下来基于我们【钓鱼云助手】首页的列表调整为例,一起看下怎么使用List组件吧
1. 模拟数据
调整一下我们的数据结构,增加点赞相关元素。
interface FishingSpot {
id: string
name: string;
distance: string;
status: string;
imageUrl: string;
isHot: boolean;
likeCount: number;
}
因为我们暂时还没有调用后端接口,所以直接生成一批模拟数据。
const locations = ['东湖', '西山', '滨海', '翠竹', '青龙'];
const suffixes = ['垂钓中心', '生态渔场', '野钓基地', '休闲钓点'];
const statusOptions = [
'最近一小时有人上鱼',
'环境优美,设施完善',
'今日鱼获量超过50斤',
'新开放优质钓位',
'周末举办钓鱼比赛',
'提供渔具租赁服务'
];
@State frequentlyPlaces: FishingSpot[] = (() => {
const result: FishingSpot[] = [];
for (let index = 0; index < 50; index++) {
result.push({
id: `${index + 1}`,
name: `${locations[index % 5]}${suffixes[index % 4]}`,
distance: `${(Math.random() * 15 + 1).toFixed(1)}km`,
status: statusOptions[Math.floor(Math.random() * 6)],
imageUrl: 'https://image.xiaoxiaofeng.site/blog/image/686eb37643134371e5cca347c4c87edb.jpg?xiaoxiaofeng',
isHot: Math.random() > 0.6,
likeCount: Math.floor(Math.random() * 100)
});
}
return result;
})();
2. 调整列表轮廓
添加新增的字段,增加列表点击后跳转到详情navigateToSpotDetail
的事件。这里使用伪代码,模拟跳转详情页的实现了。
这里使用List组件
,针对每一个ListItem属性
,我们额外定义了一个FishingSpotItem
,去实现内部的展示效果。
// 钓点列表
@Component
struct FishingSpotList {
@Prop frequentlyPlaces: FishingSpot[];
@State listHeight: number = 0;
// 显示更多钓点
showMoreSpots(): void {
console.log('显示更多钓点');
}
// 导航到钓点详情
navigateToSpotDetail(spotId: string): void {
console.log('导航到钓点详情:', spotId);
}
build() {
Column() {
// 列表标题
Row() {
Text('钓点列表')
.fontSize(18)
.fontWeight(FontWeight.Bold)
Blank()
Text('更多')
.fontSize(14)
.fontColor('#007DFF')
.onClick(() => {
this.showMoreSpots();
})
}
.width('100%')
.padding({
top: 20,
bottom: 10,
left: 20,
right: 20
})
List({ space: 12 }) {
ForEach(this.frequentlyPlaces, (item: FishingSpot) => {
ListItem() {
FishingSpotItem({
name: item.name,
distance: item.distance,
status: item.status,
imageUrl: item.imageUrl,
isHot: item.isHot, // 新增热门标识
likeCount: item.likeCount // 新增点赞数
})
.onClick(() => {
this.navigateToSpotDetail(item.id);
})
}
}, (item: FishingSpot) => item.id)
}
.width('100%')
}
}
}
3. 增强列表项交互体验
3.1 页面布局
这里增强每一行列表的展示方式。采用多层级布局组合。
-
采用 Row → [Stack, Column → [Row, Text, Row]] 的复合布局结构:
-
外层 Row 实现水平整体布局
-
Stack 处理图片和标签的层叠效果
-
内层 Column 垂直排列文本信息
-
最内层 Row 实现底部按钮的水平分布
实现单个列表的布局,效果如下:
3.2 样式与视觉效果
条件样式渲染
根据状态动态改变样式:
- 按压状态改变背景色:
this.isPressed ? '#F5F5F5' : '#FFFFFF'
- 点赞状态改变文字颜色:
this.isLiked ? '#FF6B6B' : '#999999'
- 热门标签的条件渲染:
if (this.isHot)
控制显示/隐藏
高级样式特性
- 阴影效果:
.shadow({ radius: 4, color: '#1A000000', offsetX: 0, offsetY: 2 })
- 图片填充模式:
ImageFit.Cover
保持比例并填满容器 - 复杂圆角设置:
borderRadius({ topLeft: 8, bottomRight: 8 })
- 文本溢出处理:
textOverflow
配合maxLines
防止文字溢出
具体代码如下:
@Component
struct FishingSpotItem {
private id: string = '';
private name: string = '';
private distance: string = '';
private status: string = '';
private imageUrl: string = '';
private isHot: boolean = false;
private likeCount: number = 0;
@State isLiked: boolean = false;
@State isPressed: boolean = false;
build() {
Row() {
// 图片区域
Stack() {
Image(this.imageUrl)
.width(120)
.height(80)
.borderRadius(8)
.objectFit(ImageFit.Cover) // 保证图片比例
// 热门标签
if (this.isHot) {
Row() {
Text('热门')
.fontSize(10)
.fontColor('#FFFFFF')
}
.position({ x: 0, y: 0 })
.backgroundColor('#FF6B6B')
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
.borderRadius({ topLeft: 8, bottomRight: 8 })
}
}
// 信息区域
Column() {
// 第一行:名称和距离
Row() {
Text(this.name)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.maxLines(1)
Text(this.distance)
.fontSize(12)
.fontColor('#666666')
}
.width('100%')
// 第二行:状态信息
Text(this.status)
.fontSize(12)
.fontColor('#999999')
.width('100%')
.textAlign(TextAlign.Start)
.margin({ top: 4 })
// 第三行:互动区域
Row() {
// 点赞按钮
Row() {
Image(this.isLiked ? $r('app.media.liked') : $r('app.media.like'))
.width(16)
.height(16)
Text(this.likeCount.toString())
.fontSize(12)
.fontColor(this.isLiked ? '#FF6B6B' : '#999999')
.margin({ left: 4 })
}
.onClick(() => {
this.handleLike();
})
Blank()
// 导航按钮
Button('导航', { type: ButtonType.Normal })
.width(60)
.height(24)
.fontSize(12)
.backgroundColor('#007DFF')
}
.width('100%')
.margin({ top: 8 })
}
.margin({ left: 12 })
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
}
.width('100%')
.padding(12)
.backgroundColor(this.isPressed ? '#F5F5F5' : '#FFFFFF')
.borderRadius(12)
.shadow({ radius: 4, color: '#1A000000', offsetX: 0, offsetY: 2 })
.onTouch((event: TouchEvent) => {
if (event.type === TouchType.Down) {
this.isPressed = true;
} else if (event.type === TouchType.Up || event.type === TouchType.Cancel) {
this.isPressed = false;
}
})
}
// 点赞处理
private handleLike(): void {
this.isLiked = !this.isLiked;
this.likeCount += this.isLiked ? 1 : -1;
console.log(`钓点 ${this.id} 点赞状态: ${this.isLiked}`);
}
}
4. 优化性能与用户体验
长列表的处理
循环渲染适用于短列表,当构建具有大量列表项的长列表时,如果直接采用循环渲染方式,会一次性加载所有的列表元素,会导致页面启动时间过长,影响用户体验。因此,推荐使用数据懒加载(LazyForEach)方式实现按需迭代加载数据,从而提升列表性能。
当使用懒加载方式渲染列表时,为了更好的列表滚动体验,减少列表滑动时出现白块,List组件提供了cachedCount参数用于设置列表项缓存数,懒加载方式只会预加载List显示区域外cachedCount的内容,而非懒加载会全部加载。无论懒加载还是非懒加载都只布局List显示区域+List显示区域外cachedCount的内容。
List() { // ...}.cachedCount(3)
以垂直列表为例:
- List设置cachedCount后,显示区域外上下各会预加载并布局cachedCount行ListItem。计算ListItem行数时,会计算ListItemGroup内部的ListItem行数。如果ListItemGroup内没有ListItem,则整个ListItemGroup算一行。
- List下嵌套使用LazyForEach,并且LazyForEach下嵌套使用ListItemGroup时,LazyForEach会在List显示区域外上下各会创建cachedCount个ListItemGroup。
说明
- cachedCount的增加会增大UI的CPU、内存开销。使用时需要根据实际情况,综合性能和用户体验进行调整。
- 列表使用数据懒加载时,除了显示区域的列表项和前后缓存的列表项,其他列表项会被销毁。
// 高性能列表配置
List({ space: 12 }) {
ForEach(this.frequentlyPlaces, (item: FishingSpot) => {
ListItem() {
LazyFishingSpotItem({ // 使用懒加载组件
spotData: item
})
}
}, (item: FishingSpot) => item.id)
}
.cachedCount(5) // 缓存可见区域外的5个项
.chainAnimation(false) // 关闭链式动画提升性能
成果展示
进入页面后,展示效果:
往下滑动,展示效果:
滑动到底部,展示效果:
更多推荐
所有评论(0)