回复
     148.[HarmonyOS NEXT 实战案例八 :List系列] 粘性头部列表进阶篇 原创
全栈若城
 发布于 2025-6-30 15:46
 浏览
 0收藏
[HarmonyOS NEXT 实战案例八 :List系列] 粘性头部列表进阶篇
项目已开源,开源地址: https://gitcode.com/nutpi/HarmonyosNextCaseStudyTutorial , 欢迎fork & star
效果演示
![148.[HarmonyOS NEXT 实战案例八 :List系列] 粘性头部列表进阶篇-鸿蒙开发者社区 148.[HarmonyOS NEXT 实战案例八 :List系列] 粘性头部列表进阶篇-鸿蒙开发者社区](https://dl-harmonyos.51cto.com/images/202506/b6feac970de1cd3af4a3088af7c7f46e92a0e2.png?x-oss-process=image/resize,w_373,h_597)
在基础篇中,我们学习了如何实现一个基本的粘性头部列表。本篇教程将深入探讨粘性头部列表的进阶功能和优化技巧,帮助你构建更加强大、用户体验更佳的粘性头部列表应用。
一、进阶功能概述
在本教程中,我们将探讨以下进阶功能:
- 自定义粘性头部样式:定制粘性头部的外观和行为
 - 头部交互增强:为头部添加更丰富的交互功能
 - 分组折叠与展开:实现分组的折叠和展开功能
 - 动画与过渡效果:为列表添加流畅的动画和过渡效果
 - 列表项交互优化:改善列表项的点击、长按等交互体验
 - 播放控制功能增强:实现更完善的音乐播放控制功能
 - 列表性能优化:提高大量数据下的列表性能
 
二、自定义粘性头部样式
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中粘性头部列表的进阶功能和优化技巧, 通过这些进阶功能,你可以构建一个功能丰富、用户体验出色的粘性头部列表应用。这些技巧不仅适用于音乐播放器应用,也可以应用到其他需要分组显示内容的场景,如联系人列表、设置页面、电商应用等。
©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
     
        赞
        
 
        收藏 
      
 回复
  相关推荐
 



















