148.[HarmonyOS NEXT 实战案例八 :List系列] 粘性头部列表进阶篇
2025-06-30 22:25:11
103次阅读
0个评论
[HarmonyOS NEXT 实战案例八 :List系列] 粘性头部列表进阶篇
项目已开源,开源地址: https://gitcode.com/nutpi/HarmonyosNextCaseStudyTutorial , 欢迎fork & star
效果演示
在基础篇中,我们学习了如何实现一个基本的粘性头部列表。本篇教程将深入探讨粘性头部列表的进阶功能和优化技巧,帮助你构建更加强大、用户体验更佳的粘性头部列表应用。
一、进阶功能概述
在本教程中,我们将探讨以下进阶功能:
- 自定义粘性头部样式:定制粘性头部的外观和行为
- 头部交互增强:为头部添加更丰富的交互功能
- 分组折叠与展开:实现分组的折叠和展开功能
- 动画与过渡效果:为列表添加流畅的动画和过渡效果
- 列表项交互优化:改善列表项的点击、长按等交互体验
- 播放控制功能增强:实现更完善的音乐播放控制功能
- 列表性能优化:提高大量数据下的列表性能
二、自定义粘性头部样式
1. 粘性头部样式变化
在基础实现中,粘性头部在滚动时保持原样。我们可以为粘性头部添加滚动时的样式变化,使其更加紧凑:
// 在AlbumHeader构建器中添加isSticky参数
@Builder
AlbumHeader(album: string, artist: string, cover: Resource, year: string, isSticky: boolean = false) {
Row() {
// 专辑封面
Image(cover)
.width(isSticky ? 40 : 60)
.height(isSticky ? 40 : 60)
.borderRadius(isSticky ? 4 : 8)
.margin({ right: 16 })
// 专辑信息
Column() {
Text(album)
.fontSize(isSticky ? 16 : 18)
.fontWeight(FontWeight.Bold)
if (!isSticky) {
Text(artist)
.fontSize(16)
.fontColor('#666666')
.margin({ top: 4 })
Text(year)
.fontSize(14)
.fontColor('#999999')
.margin({ top: 2 })
} else {
Text(artist)
.fontSize(14)
.fontColor('#666666')
}
}
.alignItems(HorizontalAlign.Start)
}
.width('100%')
.backgroundColor(isSticky ? '#F8F8F8' : '#FFFFFF')
.padding({
left: 16,
right: 16,
top: isSticky ? 8 : 12,
bottom: isSticky ? 8 : 12
})
.shadow(isSticky ? {
radius: 6,
color: 'rgba(0, 0, 0, 0.1)',
offsetY: 2
} : null)
}
然后,我们需要在ListItemGroup中检测粘性状态:
ListItemGroup({
header: this.AlbumHeader(albumData.album, albumData.artist, albumData.cover, albumData.year, false),
stickyHeader: this.AlbumHeader(albumData.album, albumData.artist, albumData.cover, albumData.year, true),
space: 0
})
这样,当头部变为粘性状态时,会使用更紧凑的样式。
2. 粘性头部背景模糊效果
为了增强视觉效果,我们可以为粘性头部添加背景模糊效果:
// 在粘性状态下添加背景模糊
.backdropBlur(isSticky ? 10 : 0)
.backgroundColor(isSticky ? 'rgba(255, 255, 255, 0.8)' : '#FFFFFF')
三、头部交互增强
1. 头部点击展开详情
我们可以为专辑头部添加点击事件,点击时展开专辑详情:
// 专辑详情状态
@State expandedAlbum: string | null = null
// 切换专辑详情展开状态
toggleAlbumDetails(album: string) {
if (this.expandedAlbum === album) {
this.expandedAlbum = null
} else {
this.expandedAlbum = album
}
}
// 在AlbumHeader中添加点击事件
.onClick(() => {
if (!isSticky) { // 只有在非粘性状态下才响应点击
this.toggleAlbumDetails(album)
}
})
然后,在ListItemGroup中添加专辑详情视图:
ListItemGroup({
header: this.AlbumHeader(albumData.album, albumData.artist, albumData.cover, albumData.year, false),
stickyHeader: this.AlbumHeader(albumData.album, albumData.artist, albumData.cover, albumData.year, true),
space: 0
}) {
// 专辑详情(展开时显示)
if (this.expandedAlbum === albumData.album) {
this.AlbumDetails(albumData)
}
// 歌曲列表
ForEach(albumData.songs, (song: SongsType) => {
ListItem() { /* ... */ }
})
}
2. 专辑详情构建器
@Builder
AlbumDetails(albumData: MusicType) {
Column() {
// 专辑详细信息
Row() {
// 大封面
Image(albumData.cover)
.width(120)
.height(120)
.borderRadius(8)
.margin({ right: 16 })
// 详细信息
Column() {
Text(albumData.album)
.fontSize(20)
.fontWeight(FontWeight.Bold)
Text(albumData.artist)
.fontSize(18)
.fontColor('#666666')
.margin({ top: 8 })
Text(`发行年份: ${albumData.year}`)
.fontSize(14)
.fontColor('#999999')
.margin({ top: 4 })
Text(`${albumData.songs.length} 首歌曲`)
.fontSize(14)
.fontColor('#999999')
.margin({ top: 4 })
// 操作按钮
Row({ space: 16 }) {
Button('播放全部')
.fontSize(14)
.backgroundColor('#007DFF')
.height(32)
.onClick(() => {
if (albumData.songs.length > 0) {
this.playSong(albumData.album, albumData.songs[0].title)
}
})
Button('收藏')
.fontSize(14)
.backgroundColor('#4CAF50')
.height(32)
}
.margin({ top: 12 })
}
.alignItems(HorizontalAlign.Start)
}
.padding({ left: 16, right: 16, top: 16, bottom: 16 })
.width('100%')
}
.backgroundColor('#F5F5F5')
.width('100%')
}
四、分组折叠与展开
1. 实现分组折叠功能
我们可以为每个专辑分组添加折叠/展开功能:
// 折叠的专辑列表
@State collapsedAlbums: string[] = []
// 切换专辑折叠状态
toggleAlbumCollapse(album: string) {
const index = this.collapsedAlbums.indexOf(album)
if (index === -1) {
this.collapsedAlbums.push(album)
} else {
this.collapsedAlbums.splice(index, 1)
}
}
// 在AlbumHeader中添加折叠/展开按钮
Row() {
// ... 原有内容 ...
// 折叠/展开按钮
Image(this.collapsedAlbums.includes(album) ? $r('app.media.arrow_down') : $r('app.media.arrow_up'))
.width(24)
.height(24)
.onClick((event) => {
this.toggleAlbumCollapse(album)
event.stopPropagation() // 阻止事件冒泡,避免触发头部点击事件
})
}
然后,在ListItemGroup中根据折叠状态显示或隐藏歌曲:
ListItemGroup({
header: this.AlbumHeader(albumData.album, albumData.artist, albumData.cover, albumData.year, false),
stickyHeader: this.AlbumHeader(albumData.album, albumData.artist, albumData.cover, albumData.year, true),
space: 0
}) {
// 专辑详情(展开时显示)
if (this.expandedAlbum === albumData.album) {
this.AlbumDetails(albumData)
}
// 歌曲列表(非折叠状态才显示)
if (!this.collapsedAlbums.includes(albumData.album)) {
ForEach(albumData.songs, (song: SongsType) => {
ListItem() { /* ... */ }
})
}
}
2. 添加折叠/展开动画
为了提升用户体验,我们可以为折叠/展开操作添加动画效果:
// 在ListItemGroup中添加动画
.transition({
type: TransitionType.All,
opacity: !this.collapsedAlbums.includes(albumData.album) ? 1 : 0,
scale: { y: !this.collapsedAlbums.includes(albumData.album) ? 1 : 0.8 },
translate: { y: !this.collapsedAlbums.includes(albumData.album) ? 0 : -20 }
})
五、动画与过渡效果
1. 列表项动画
为列表项添加进入和退出动画:
// 在ListItem中添加动画
.transition({
type: TransitionType.All,
opacity: 1,
scale: { x: 1, y: 1 },
translate: { x: 0, y: 0 }
})
.animation({
duration: 300,
curve: Curve.EaseInOut,
delay: index * 50, // 根据索引添加延迟,创建级联效果
iterations: 1,
playMode: PlayMode.Normal
})
2. 当前播放歌曲高亮动画
为当前播放的歌曲添加呼吸灯效果:
// 在ListItem中添加呼吸灯动画
.animation(this.currentSong?.album === albumData.album && this.currentSong?.title === song.title ? {
duration: 1500,
curve: Curve.Linear,
delay: 0,
iterations: -1, // 无限循环
playMode: PlayMode.Alternate // 交替播放
} : null)
.backgroundColor(
this.currentSong?.album === albumData.album && this.currentSong?.title === song.title ?
'#F0F7FF' : '#FFFFFF'
)
.opacity(this.currentSong?.album === albumData.album && this.currentSong?.title === song.title ?
this.breatheOpacity : 1) // 呼吸灯透明度
// 添加呼吸灯透明度状态
@State breatheOpacity: number = 0.8
六、列表项交互优化
1. 滑动操作
为歌曲项添加滑动操作功能:
// 在ListItem中添加滑动操作
.swipeAction({
end: [
{
action: () => {
// 从播放列表中移除
console.info(`从播放列表中移除: ${song.title}`)
},
backgroundcolor: '#F44336',
label: '移除'
}
],
start: [
{
action: () => {
// 切换喜欢状态
this.toggleLiked(albumData.album, song.title)
},
backgroundcolor: song.isLiked ? '#F44336' : '#FFC107',
label: song.isLiked ? '取消喜欢' : '喜欢'
}
]
})
2. 切换喜欢状态
// 切换歌曲喜欢状态
toggleLiked(album: string, title: string) {
// 查找并更新歌曲的喜欢状态
this.musicData.forEach((albumData) => {
if (albumData.album === album) {
albumData.songs.forEach((song) => {
if (song.title === title) {
song.isLiked = !song.isLiked
}
})
}
})
}
3. 长按菜单
为歌曲项添加长按菜单:
// 在ListItem中添加长按手势
.gesture(
LongPressGesture()
.onAction(() => {
this.showSongMenu(albumData.album, song.title)
})
)
// 显示歌曲菜单
showSongMenu(album: string, title: string) {
// 实现一个简单的菜单
this.menuVisible = true
this.menuAlbum = album
this.menuSong = title
}
// 添加菜单状态
@State menuVisible: boolean = false
@State menuAlbum: string = ''
@State menuSong: string = ''
然后,在UI中添加菜单面板:
// 在Stack中添加菜单面板
if (this.menuVisible) {
Column() {
// 菜单标题
Text('歌曲操作')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 16 })
// 菜单选项
this.MenuOption('播放', $r('app.media.play_icon'), () => {
this.playSong(this.menuAlbum, this.menuSong)
this.menuVisible = false
})
this.MenuOption('添加到播放列表', $r('app.media.add_icon'), () => {
console.info('添加到播放列表')
this.menuVisible = false
})
this.MenuOption('分享', $r('app.media.share_icon'), () => {
console.info('分享歌曲')
this.menuVisible = false
})
// 取消按钮
Button('取消')
.width('100%')
.height(48)
.margin({ top: 16 })
.onClick(() => {
this.menuVisible = false
})
}
.width('90%')
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(16)
.shadow({
radius: 12,
color: 'rgba(0, 0, 0, 0.2)',
offsetY: 4
})
}
.width('100%')
.height('100%')
.backgroundColor('rgba(0, 0, 0, 0.5)')
.onClick(() => {
this.menuVisible = false
})
4. 菜单选项构建器
@Builder
MenuOption(text: string, icon: Resource, action: () => void) {
Row() {
Image(icon)
.width(24)
.height(24)
.margin({ right: 16 })
Text(text)
.fontSize(16)
}
.width('100%')
.height(48)
.padding({ left: 16, right: 16 })
.onClick(action)
.stateStyles({
pressed: {
backgroundColor: '#F5F5F5'
},
normal: {
backgroundColor: '#FFFFFF'
}
})
}
七、播放控制功能增强
1. 播放状态管理
添加播放状态管理:
// 播放状态
@State isPlaying: boolean = false
@State currentTime: number = 0 // 当前播放时间(秒)
@State duration: number = 0 // 歌曲总时长(秒)
// 播放/暂停切换
togglePlay() {
this.isPlaying = !this.isPlaying
}
// 播放歌曲(增强版)
playSong(album: string, title: string) {
this.currentSong = { album, title }
this.isPlaying = true
// 查找歌曲时长
this.musicData.forEach((albumData) => {
if (albumData.album === album) {
albumData.songs.forEach((song) => {
if (song.title === title) {
// 将时长字符串转换为秒数
const [minutes, seconds] = song.duration.split(':').map(Number)
this.duration = minutes * 60 + seconds
this.currentTime = 0
}
})
}
})
// 如果正在播放,启动定时器更新进度
if (this.isPlaying) {
this.startProgressTimer()
}
}
// 启动进度定时器
startProgressTimer() {
// 清除现有定时器
if (this.progressTimer) {
clearInterval(this.progressTimer)
}
// 创建新定时器,每秒更新一次进度
this.progressTimer = setInterval(() => {
if (this.isPlaying) {
this.currentTime += 1
if (this.currentTime >= this.duration) {
// 播放完毕,可以实现自动播放下一首
this.playNextSong()
}
}
}, 1000)
}
// 播放下一首歌曲
playNextSong() {
if (!this.currentSong) return
let found = false
let nextSong: { album: string, title: string } | null = null
// 查找当前歌曲的下一首
for (const albumData of this.musicData) {
if (albumData.album === this.currentSong.album) {
for (let i = 0; i < albumData.songs.length; i++) {
if (found) {
nextSong = { album: albumData.album, title: albumData.songs[i].title }
break
}
if (albumData.songs[i].title === this.currentSong.title) {
found = true
}
}
}
if (nextSong) break
}
// 如果找到下一首,播放它
if (nextSong) {
this.playSong(nextSong.album, nextSong.title)
} else {
// 没有下一首,停止播放
this.isPlaying = false
clearInterval(this.progressTimer)
}
}
// 播放上一首歌曲
playPrevSong() {
// 类似playNextSong的实现,但查找前一首歌曲
}
// 组件销毁时清除定时器
aboutToDisappear() {
if (this.progressTimer) {
clearInterval(this.progressTimer)
}
}
// 进度定时器
private progressTimer: number | null = null
2. 增强的播放控制栏
// 底部播放控制栏(当有歌曲播放时显示)
if (this.currentSong) {
Column() {
// 进度条
Slider({
value: this.currentTime,
min: 0,
max: this.duration,
step: 1
})
.width('100%')
.height(20)
.onChange((value) => {
this.currentTime = value
})
// 播放控制和信息
Row() {
// 当前播放歌曲信息
Column() {
Text(this.currentSong.title)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text(this.currentSong.album)
.fontSize(14)
.fontColor('#666666')
.margin({ top: 2 })
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
// 播放控制按钮
Row({ space: 16 }) {
// 上一首
Image($r('app.media.prev_icon'))
.width(24)
.height(24)
.onClick(() => this.playPrevSong())
// 播放/暂停
Image(this.isPlaying ? $r('app.media.pause_icon') : $r('app.media.play_icon'))
.width(32)
.height(32)
.onClick(() => this.togglePlay())
// 下一首
Image($r('app.media.next_icon'))
.width(24)
.height(24)
.onClick(() => this.playNextSong())
}
}
.width('100%')
.padding({ left: 16, right: 16 })
}
.width('100%')
.backgroundColor('#FFFFFF')
.borderColor('#E5E5E5')
.borderWidth({ top: 1 })
}
八、列表性能优化
1. 懒加载与虚拟化
对于大量数据,可以使用懒加载和虚拟化技术:
// 在List中启用懒加载
.cachedCount(5) // 缓存5个不在可视区域的列表项
.onReachStart(() => {
// 当滚动到顶部时加载更多数据
})
.onReachEnd(() => {
// 当滚动到底部时加载更多数据
})
2. 分页加载
实现分页加载功能,逐步加载更多专辑:
// 分页加载状态
@State currentPage: number = 1
@State pageSize: number = 5
@State hasMoreData: boolean = true
// 获取当前页的数据
get paginatedData() {
return this.musicData.slice(0, this.currentPage * this.pageSize)
}
// 加载更多数据
loadMoreData() {
if (this.currentPage * this.pageSize >= this.musicData.length) {
this.hasMoreData = false
return
}
this.currentPage += 1
}
然后,在List中使用分页数据:
// 在List中使用分页数据
ForEach(this.paginatedData, (albumData: MusicType) => {
ListItemGroup({ /* ... */ }) { /* ... */ }
})
// 在List底部添加加载更多按钮
if (this.hasMoreData) {
ListItem() {
Button('加载更多')
.width('100%')
.height(48)
.onClick(() => this.loadMoreData())
}
.height(64)
}
九、代码结构与样式设置
功能模块 | 主要实现方法 | 关键属性和样式 |
---|---|---|
自定义粘性头部 | AlbumHeader构建器 | isSticky参数, backdropBlur, shadow |
头部交互增强 | toggleAlbumDetails, AlbumDetails | onClick事件, 详情视图 |
分组折叠与展开 | toggleAlbumCollapse | collapsedAlbums状态, 条件渲染 |
动画与过渡效果 | transition, animation | opacity, scale, translate, curve |
列表项交互优化 | swipeAction, LongPressGesture | 滑动操作, 长按菜单 |
播放控制功能 | playSong, togglePlay, playNextSong | 播放状态管理, 进度控制 |
列表性能优化 | cachedCount, 分页加载 | 懒加载, 虚拟化, 分页 |
十、总结
本教程详细讲解了HarmonyOS NEXT中粘性头部列表的进阶功能和优化技巧, 通过这些进阶功能,你可以构建一个功能丰富、用户体验出色的粘性头部列表应用。这些技巧不仅适用于音乐播放器应用,也可以应用到其他需要分组显示内容的场景,如联系人列表、设置页面、电商应用等。
00
- 0回答
- 4粉丝
- 0关注
相关话题
- 147.[HarmonyOS NEXT 实战案例八 :List系列] 粘性头部列表基础篇
- 156.[HarmonyOS NEXT 实战案例十二 :List系列] 聊天消息列表 - 进阶篇
- 146.[HarmonyOS NEXT 实战案例七 :List系列] 可选择列表进阶篇
- 140.[HarmonyOS NEXT 实战案例八:List系列] 滑动操作列表组件实战:打造高效待办事项应用 进阶篇
- 154.[HarmonyOS NEXT 实战案例十一 :List系列] 自定义内容列表 - 进阶篇
- 134.[HarmonyOS NEXT 实战案例六:List系列] 垂直列表组件实战:打造高效联系人列表 进阶篇
- 142.[HarmonyOS NEXT 实战案例九:List系列] 分组列表组件实战:打造分类设置菜单 进阶篇
- 138.[HarmonyOS NEXT 实战案例七:List系列] 多列列表组件实战:打造精美应用推荐页 进阶篇
- 152.[HarmonyOS NEXT 实战案例十二:List系列] 卡片样式列表组件实战:打造精美电商应用 进阶篇
- 144.[HarmonyOS NEXT 实战案例十:List系列] 字母索引列表组件实战:打造高效联系人应用 进阶篇
- 136.[HarmonyOS NEXT 实战案例七:List系列] 水平列表组件实战:打造精美图片库 进阶篇
- 150.[HarmonyOS NEXT 实战案例十一:List系列] 下拉刷新和上拉加载更多列表组件实战:打造高效新闻应用 进阶篇
- 179.[HarmonyOS NEXT 实战案例八:Grid] 瀑布流网格布局进阶篇
- 139.[HarmonyOS NEXT 实战案例八:List系列] 滑动操作列表组件实战:打造高效待办事项应用 基础篇
- 155.[HarmonyOS NEXT 实战案例十二 :List系列] 聊天消息列表 - 基础篇