【鸿蒙】仿网易云音乐界面设计思路【附源码】
由于本次制作页面对笔者来说挑战难度稍大。于是,笔者的思路/代码会混杂,望前辈指正修改!
·
一、前言
由于本次制作页面对笔者来说挑战难度稍大。于是,笔者的思路/代码会混杂,望前辈指正修改!
还是先放上一张结果图:

二、对整体进行设置并加一个导航栏
2.1 背景设置
// 背景设置等……
Column(){
}
.width('100%')
.height('100%')
.backgroundImage($r('app.media.backgruond'))
// 背景设置等……

2.2 导航栏添加
首先,我们先把整体背景嵌套进导航栏中
// 导航栏
Tabs() {
// 第一个页面
TabContent() {
// 背景设置等……
Column() {
}
.width('100%')
.height('100%')
.backgroundImage($r('app.media.backgruond'))
// 背景设置等……
}
// 第一个页面
}
// 导航栏

由于导航栏默认BarPosition是顶部,我们把他放到底部
Tabs({barPosition:BarPosition.End})
然后,添加五个TabContent

然后用自定义导航栏添加导航栏的图片以及文字
@State currentIndex : number = 0
private tabController :TabsController = new TabsController()
……
// 设置导航栏文字图片大小以及点击事件
@Builder TabBuilder(title: string, targetIndex: number, selectedImg: Resource, normalImg: Resource) {
Column() {
Image(this.currentIndex === targetIndex ? selectedImg : normalImg)
.size({ width: 30, height: 30 })
Text(title)
.fontColor(this.currentIndex === targetIndex ? '#000000' : '#000000')
}
.onClick(() => {
this.currentIndex = targetIndex;
this.tabController.changeIndex(this.currentIndex);
})
.width('100%')
.height(60)
.justifyContent(FlexAlign.Center)
}
……
Tabs({ barPosition: BarPosition.End, controller: this.tabController }) {
// 第一个页面
TabContent() {
// 背景设置等……
Column() {
}
.width('100%')
.height('100%')
.backgroundImage($r('app.media.backgruond'))
// 背景设置等……
}.tabBar(this.TabBuilder('发现', 0, $r('app.media.faxian'), $r('app.media.faxian1')))
//第二个选项卡的内容
TabContent() {
Column().width('100%').height('100%').backgroundImage($r('app.media.backgruond'))
}
.tabBar(this.TabBuilder('漫游', 1, $r('app.media.xihuan1'), $r('app.media.xihuan')))
//第三个选项卡
TabContent() {
Column().width('100%').height('100%').backgroundImage($r('app.media.backgruond'))
}
.tabBar(this.TabBuilder('我的', 2, $r('app.media.music1'), $r('app.media.music')))
// 第四个选项卡的内容
TabContent() {
Column().width('100%').height('100%').backgroundImage($r('app.media.backgruond'))
}
.tabBar(this.TabBuilder('动态', 3, $r('app.media.dongtai1'), $r('app.media.dongtai')))
// 第五个
TabContent() {
Column().width('100%').height('100%').backgroundImage($r('app.media.backgruond'))
}
.tabBar(this.TabBuilder('播客', 4, $r('app.media.boke1'), $r('app.media.boke')))
}
// 导航栏

2.3 添加搜索框和菜单按钮
通过stack层叠布局将搜索框和菜单按钮固定在顶部
// 顶部置顶搜索框
Stack() {
Row() {
// 菜单栏
Image($r("app.media.options"))
.width(50)
// 搜索框
TextInput({ placeholder: '踊 Ado' })
.offset({ y: 3 })
.width('70%')
.backgroundColor('#fff')
// 语音识别
Image($r('app.media.microphone'))
.width(50)
}
}.margin({ bottom: 5 })

三、 在Scroll容器完成主体
3.1 创建Scroll容器并设置大小
private scrollerForScroll :Scroller = new Scroller()
……
Scroll(this.scrollerForScroll) {
Column(){
}
}.height(600).scrollBar(BarState.Off).edgeEffect(EdgeEffect.Spring)
// 设置取消滚动条,设置触底回弹
3.2 创建一个Swiper轮播图
private swiperController : SwiperController = new SwiperController()```````````````````
// 轮播图
Column() {
Swiper(this.swiperController) {
Image($r('app.media.swiper1'))
Image($r('app.media.swiper2'))
Image($r('app.media.swiper3'))
}
.autoPlay(true)
.interval(1000)
.indicator(true)
.loop(true)
.width('90%')
.indicatorStyle({ left: 1, bottom: 1 })
}
.height(100)
.borderRadius(20)
.margin({ bottom: 3 })

3.3 创建构造方法
export class FliData {
pic_url: Resource
name: string
singer: string
pic: Resource
constructor(pic_url: Resource,
name: string,
singer: string, pic: Resource) {
this.pic_url = pic_url
this.name = name
this.singer = singer
this.pic = pic
}
}
export class SongData {
pic_url1: Resource
name1: string
text:string
constructor(pic_url1: Resource,
name1: string,
text:string
) {
this.pic_url1 = pic_url1
this.name1 = name1
this.text = text
}
}
export class Menu {
pic: Resource
name2: string
constructor(pic: Resource,
name2: string,
) {
this.pic = pic
this.name2 = name2
}
}
export class Date {
pic_date: Resource
date: string
taici: string
constructor(pic_date: Resource,
taici: string,
date: string,
) {
this.pic_date = pic_date
this.taici = taici
this.date = date
}
}
3.4 菜单栏数据渲染+布局
dataList2: Menu[] = []
……
aboutToAppear() {
this.dataList2.push(new Menu($r('app.media.date'), '每日推荐'))
this.dataList2.push(new Menu($r('app.media.radio'), '`漫游'))
this.dataList2.push(new Menu($r('app.media.rank'), '排行榜'))
this.dataList2.push(new Menu($r('app.media.singlist'), '歌单'))
this.dataList2.push(new Menu($r('app.media.book'), '有声书'))
this.dataList2.push(new Menu($r('app.media.zhuanji'), '歌手专辑'))
this.dataList2.push(new Menu($r('app.media.singer'), '关注新歌'))
this.dataList2.push(new Menu($r('app.media.nicelink'), '妙时'))
this.dataList2.push(new Menu($r('app.media.buyer'), '收藏家'))
this.dataList2.push(new Menu($r('app.media.singhouse'), '歌房'))
}
做一个循环,然后列表渲染上来
……
Row() {
List({ space: 20 }) {
ForEach(this.dataList2, (item: Menu, index) => {
ListItem() {
Column() {
Image(item.pic).width(35).height(30)
Text(item.name2).width(45).fontSize(11).textAlign(TextAlign.Center)
}
}
}, item => item)
}
.listDirection(Axis.Horizontal)
.padding({ left: 15, top: 10, right: 15 })
}
.height(80)

3.5 今日推荐数据渲染+布局
dataList1: SongData[] = []
@State SongData: SongData[] = []
aboutToAppear() {
this.dataList.push(new FliData($r('app.media.data1'), 'まにまに', 'r-906/初音ミク', $r('app.media.play')))
this.dataList.push(new FliData($r('app.media.data2'), 'まにまに', 'r-906/初音ミク', $r('app.media.play')))
this.dataList.push(new FliData($r('app.media.data3'), 'まにまに', 'r-906/初音ミク', $r('app.media.play')))
this.dataList1.push(new SongData($r('app.media.song1'), '女神异闻录 Persona系列【3~5代】','▶186.3亿'))
this.dataList1.push(new SongData($r('app.media.song2'), 'FF14最终幻想14最全音乐合集','▶186.3亿'))
this.dataList1.push(new SongData($r('app.media.song3'), '日语||那些听着就忍不住唱起来的','▶186.3亿'))
this.dataList1.push(new SongData($r('app.media.song1'), '女神异闻录 Persona系列【3~5代】','▶186.3亿'))
this.dataList1.push(new SongData($r('app.media.song2'), 'FF14最终幻想14最全音乐合集','▶186.3亿'))
this.dataList1.push(new SongData($r('app.media.song3'), '日语||那些听着就忍不住唱起来的','▶186.3亿'))
}
// 推荐
Column() {
Row() {
Text('今日推荐')
.fontSize(20)
.fontWeight(FontWeight.Bold)
Text('More>')
.fontSize(20)
.fontWeight(FontWeight.Bold)
}
.height(40)
.width('90%')
.justifyContent(FlexAlign.SpaceBetween)
// 图片列表
做一个循环,然后列表渲染上来
Column() {
List({ space: 30 }) {
ForEach(this.dataList1, (item: SongData, index) => {
ListItem() {
Column() {
Image(item.pic_url1).width(130).height(130).borderRadius(20)
Text(item.name1)
.fontSize(20)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.maxLines(2)
.width(130)
.margin({ top: 5 })
}
}
}, item => item)
}
.listDirection(Axis.Horizontal)
.padding({ left: 15, top: 7, right: 15 })
}
.margin({ top: 10, bottom: 10 })
}.margin({ bottom: 10 })
.height(250)
Divider()

3.6 相似推荐数据渲染+布局
@State FliData: FliData[] = []
dataList: FliData[] = []
aboutToAppear() {
this.dataList.push(new FliData($r('app.media.data1'), 'まにまに', 'r-906/初音ミク', $r('app.media.play')))
this.dataList.push(new FliData($r('app.media.data2'), 'まにまに', 'r-906/初音ミク', $r('app.media.play')))
this.dataList.push(new FliData($r('app.media.data3'), 'まにまに', 'r-906/初音ミク', $r('app.media.play')))
}
// 相似推荐
Column() {
Row() {
Text('[Beyond the way]相似推荐')
.fontSize(18)
.fontWeight(FontWeight.Bold)
Text('More>')
.fontSize(20)
.fontWeight(FontWeight.Bold)
}
.height(40)
.width('90%')
.justifyContent(FlexAlign.SpaceBetween)
做一个循环,然后列表渲染上来
List({ space: 30, initialIndex: 0 }) {
ForEach(this.dataList, (item: FliData, index) => {
ListItem() {
Row() {
Image(item.pic_url).width(70).height(70).borderRadius(10)
Blank()
Column({space:5}) {
Text(item.name).fontSize(20)
Text(item.singer).fontSize(20)
}.width(140).alignItems(HorizontalAlign.Start)
Blank()
Image(item.pic).width(30).height(30)
}.width('90%').margin({left:10})
}
}, item => item)
}
.margin({ left: 10 })
.height(300)
}
.margin({ top: 10, bottom: 10 })

3.7 音乐日历数据渲染+布局
// 音乐日历
Column() {
Row() {
Text('音乐日历')
.fontSize(18)
.fontWeight(FontWeight.Bold)
Row() {
Text('今日3条')
}
.width(80)
.height(30)
.backgroundColor('#ffd0d0d6')
.justifyContent(FlexAlign.Center)
.margin({ right: 100 })
.borderRadius(10)
.opacity(0.3)
Text('More>')
.fontSize(20)
.fontWeight(FontWeight.Bold)
}
.height(40)
.width('90%')
.margin({ bottom: 20 })
.justifyContent(FlexAlign.SpaceBetween)
List({ space: 30, initialIndex: 0 }) {
ForEach(this.dataList3, (item: Date, index) => {
ListItem() {
Row({ space: 40 }) {
Column({space:10}) {
Text(item.date).fontSize(20)
Text(item.taici).fontSize(20)
}.width(200).alignItems(HorizontalAlign.Start)
Blank()
Column() {
Image(item.pic_date).width(70).height(70).borderRadius(10)
}
}.width('80%')
}.margin(10)
}, item => item)
}
.width('90%')
.height(300).backgroundColor('#ffeaecec').borderRadius(20)
}
.margin({ top: 10, bottom: 10 })

四、播放器布局
4.1 在剩余空间用row和blank做布局调整
//播放器
Row({space:20}){
Image($r('app.media.data2')).width(30).borderRadius(50).margin({left:20})
Text('overdise-14565').fontSize(20).width(156)
Blank('10')
Image($r('app.media.bofang')).width(30)
Image($r('app.media.liebiao')).width(30)
}.height(60).width('100%')
![]()
五、源码
@Entry
@Component
struct Index {
@State message: string = 'Hello World'
@State Menu: Menu[] = []
@State SongData: SongData[] = []
@State FliData: FliData[] = []
@State Date: Date[] = []
private swiperController: SwiperController = new SwiperController()
private scrollerForScroll: Scroller = new Scroller()
private tabController: TabsController = new TabsController()
private scroller: Scroller = new Scroller()
@State currentIndex: number = 0
@State fontColor: string = '#182431'
@State selectedFontColor: string = '#007DFF'
// 日历
dataList3: Date[] = []
// 菜单栏数组
dataList2: Menu[] = []
// 今日推荐数组
dataList1: SongData[] = []
// 相似推荐
dataList: FliData[] = []
// 生命周期
aboutToAppear() {
this.dataList.push(new FliData($r('app.media.data1'), 'まにまに', 'r-906/初音ミク', $r('app.media.play')))
this.dataList.push(new FliData($r('app.media.data2'), 'まにまに', 'r-906/初音ミク', $r('app.media.play')))
this.dataList.push(new FliData($r('app.media.data3'), 'まにまに', 'r-906/初音ミク', $r('app.media.play')))
this.dataList1.push(new SongData($r('app.media.song1'), '女神异闻录 Persona系列【3~5代】','▶186.3亿'))
this.dataList1.push(new SongData($r('app.media.song2'), 'FF14最终幻想14最全音乐合集','▶186.3亿'))
this.dataList1.push(new SongData($r('app.media.song3'), '日语||那些听着就忍不住唱起来的','▶186.3亿'))
this.dataList1.push(new SongData($r('app.media.song1'), '女神异闻录 Persona系列【3~5代】','▶186.3亿'))
this.dataList1.push(new SongData($r('app.media.song2'), 'FF14最终幻想14最全音乐合集','▶186.3亿'))
this.dataList1.push(new SongData($r('app.media.song3'), '日语||那些听着就忍不住唱起来的','▶186.3亿'))
this.dataList2.push(new Menu($r('app.media.date'), '每日推荐'))
this.dataList2.push(new Menu($r('app.media.radio'), '`漫游'))
this.dataList2.push(new Menu($r('app.media.rank'), '排行榜'))
this.dataList2.push(new Menu($r('app.media.singlist'), '歌单'))
this.dataList2.push(new Menu($r('app.media.book'), '有声书'))
this.dataList2.push(new Menu($r('app.media.zhuanji'), '歌手专辑'))
this.dataList2.push(new Menu($r('app.media.singer'), '关注新歌'))
this.dataList2.push(new Menu($r('app.media.nicelink'), '妙时'))
this.dataList2.push(new Menu($r('app.media.buyer'), '收藏家'))
this.dataList2.push(new Menu($r('app.media.singhouse'), '歌房'))
this.dataList3.push(new Date($r('app.media.date1'), 'Sayonara 米津玄師的专辑: さよ-ならまたいつか!-', '今天 04/08'))
this.dataList3.push(new Date($r('app.media.date2'), 'Walk Off The Earth 逃离地球2024中国巡演杭州站,火热开演', '明天 04/09'))
}
@Builder TabBuilder(title: string, targetIndex: number, selectedImg: Resource, normalImg: Resource) {
Column() {
Image(this.currentIndex === targetIndex ? selectedImg : normalImg)
.size({ width: 30, height: 30 })
Text(title)
.fontColor(this.currentIndex === targetIndex ? '#ff940823' : '#000000')
}
.onClick(() => {
this.currentIndex = targetIndex;
this.tabController.changeIndex(this.currentIndex);
})
.width('100%')
.height(60)
.justifyContent(FlexAlign.End)
}
build() {
Tabs({ barPosition: BarPosition.End, controller: this.tabController }) {
TabContent() {
Column() {
// 顶部置顶搜索框
Stack() {
Row() {
// 菜单栏
Image($r("app.media.options"))
.width(50)
// 搜索框
TextInput({ placeholder: '踊 Ado' })
.offset({ y: 3 })
.width('70%')
.backgroundColor('#fff')
// 语音识别
Image($r('app.media.microphone'))
.width(50)
}
}.margin({ bottom: 5 })
// 整体scroll
Scroll(this.scrollerForScroll) {
Column() {
// 轮播图
Column() {
Swiper(this.swiperController) {
Image($r('app.media.swiper1'))
Image($r('app.media.swiper2'))
Image($r('app.media.swiper3'))
}
.autoPlay(true)
.interval(1000)
.indicator(true)
.loop(true)
.width('90%')
.indicatorStyle({ left: 1, bottom: 1 })
}
.height(100)
.borderRadius(20)
.margin({ bottom: 3 })
// 菜单栏
// 列表单
Row() {
List({ space: 20 }) {
ForEach(this.dataList2, (item: Menu, index) => {
ListItem() {
Column() {
Image(item.pic).width(35).height(30)
Text(item.name2).width(45).fontSize(11).textAlign(TextAlign.Center)
}
}
}, item => item)
}
.listDirection(Axis.Horizontal)
.padding({ left: 15, top: 10, right: 15 })
}
.height(80)
// 推荐
Column() {
Row() {
Text('今日推荐')
.fontSize(20)
.fontWeight(FontWeight.Bold)
Text('More>')
.fontSize(20)
.fontWeight(FontWeight.Bold)
}
.height(40)
.width('90%')
.justifyContent(FlexAlign.SpaceBetween)
// 图片列表
Column() {
List({ space: 30 }) {
ForEach(this.dataList1, (item: SongData, index) => {
ListItem() {
Column() {
Image(item.pic_url1).width(130).height(130).borderRadius(20)
Text(item.name1)
.fontSize(20)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.maxLines(2)
.width(130)
.margin({ top: 5 })
}
}
}, item => item)
}
.listDirection(Axis.Horizontal)
.padding({ left: 15, top: 7, right: 15 })
}
.margin({ top: 10, bottom: 10 })
}.margin({ bottom: 10 })
.height(250)
Divider()
// 相似推荐
Column() {
Row() {
Text('[Beyond the way]相似推荐')
.fontSize(18)
.fontWeight(FontWeight.Bold)
Text('More>')
.fontSize(20)
.fontWeight(FontWeight.Bold)
}
.height(40)
.width('90%')
.justifyContent(FlexAlign.SpaceBetween)
List({ space: 30, initialIndex: 0 }) {
ForEach(this.dataList, (item: FliData, index) => {
ListItem() {
Row() {
Image(item.pic_url).width(70).height(70).borderRadius(10)
Blank()
Column({space:5}) {
Text(item.name).fontSize(20)
Text(item.singer).fontSize(20)
}.width(140).alignItems(HorizontalAlign.Start)
Blank()
Image(item.pic).width(30).height(30)
}.width('90%').margin({left:10})
}
}, item => item)
}
.margin({ left: 10 })
.height(300)
}
.margin({ top: 10, bottom: 10 })
// 音乐日历
Column() {
Row() {
Text('音乐日历')
.fontSize(18)
.fontWeight(FontWeight.Bold)
Row() {
Text('今日3条')
}
.width(80)
.height(30)
.backgroundColor('#ffd0d0d6')
.justifyContent(FlexAlign.Center)
.margin({ right: 100 })
.borderRadius(10)
.opacity(0.3)
Text('More>')
.fontSize(20)
.fontWeight(FontWeight.Bold)
}
.height(40)
.width('90%')
.margin({ bottom: 20 })
.justifyContent(FlexAlign.SpaceBetween)
List({ space: 30, initialIndex: 0 }) {
ForEach(this.dataList3, (item: Date, index) => {
ListItem() {
Row({ space: 40 }) {
Column({space:10}) {
Text(item.date).fontSize(20)
Text(item.taici).fontSize(20)
}.width(200).alignItems(HorizontalAlign.Start)
Blank()
Column() {
Image(item.pic_date).width(70).height(70).borderRadius(10)
}
}.width('80%')
}.margin(10)
}, item => item)
}
.width('90%')
.height(300).backgroundColor('#ffeaecec').borderRadius(20)
}
.margin({ top: 10, bottom: 10 })
// 雷达推荐
Column() {
Row() {
Text('今日雷达')
.fontSize(20)
.fontWeight(FontWeight.Bold)
Text('More>')
.fontSize(20)
.fontWeight(FontWeight.Bold)
}
.height(40)
.width('90%')
.justifyContent(FlexAlign.SpaceBetween)
//
Column() {
List({ space: 30 }) {
ForEach(this.dataList1, (item: SongData, index) => {
ListItem() {
Column() {
Image(item.pic_url1).width(130).height(130).borderRadius(20)
Text(item.name1)
.fontSize(20)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.maxLines(2)
.width(130)
.margin({ top: 5 })
}
}
}, item => item)
}
.listDirection(Axis.Horizontal)
.padding({ left: 15, top: 7, right: 15 })
}
.margin({ top: 10, bottom: 10 })
}.margin({ bottom: 10 })
.height(250)
}
}.height(600).scrollBar(BarState.Off).edgeEffect(EdgeEffect.Spring)
Blank()
//播放器
Row({space:20}){
Image($r('app.media.data2')).width(30).borderRadius(50).margin({left:20})
Text('overdise-14565').fontSize(20).width(156)
Blank('10')
Image($r('app.media.bofang')).width(30)
Image($r('app.media.liebiao')).width(30)
}.height(60).width('100%')
}
.width('100%')
.height('100%')
.backgroundImage($r('app.media.backgruond'))
}.tabBar(this.TabBuilder('发现', 0, $r('app.media.faxian'), $r('app.media.faxian1')))
//第二个选项卡的内容
TabContent() {
Column().width('100%').height('100%').backgroundImage($r('app.media.backgruond'))
}
.tabBar(this.TabBuilder('漫游', 1, $r('app.media.xihuan1'), $r('app.media.xihuan')))
//第三个选项卡
TabContent() {
Column().width('100%').height('100%').backgroundImage($r('app.media.backgruond'))
}
.tabBar(this.TabBuilder('我的', 2, $r('app.media.music1'), $r('app.media.music')))
// 第四个选项卡的内容
TabContent() {
Column().width('100%').height('100%').backgroundImage($r('app.media.backgruond'))
}
.tabBar(this.TabBuilder('动态', 3, $r('app.media.dongtai1'), $r('app.media.dongtai')))
// 第五个
TabContent() {
Column().width('100%').height('100%').backgroundImage($r('app.media.backgruond'))
}
.tabBar(this.TabBuilder('播客', 4, $r('app.media.boke1'), $r('app.media.boke')))
}
.backgroundImage($r('app.media.backgruond')) //
.barWidth("98%")
//设置选项卡栏的高度
.barHeight(80)
//设置选项卡切换时的动画时长
.animationDuration(400)
//设置Tabs组件的宽度和高度
.scrollable(false)
// 设置层级
}
}
export class FliData {
pic_url: Resource
name: string
singer: string
pic: Resource
constructor(pic_url: Resource,
name: string,
singer: string, pic: Resource) {
this.pic_url = pic_url
this.name = name
this.singer = singer
this.pic = pic
}
}
export class SongData {
pic_url1: Resource
name1: string
text:string
constructor(pic_url1: Resource,
name1: string,
text:string
) {
this.pic_url1 = pic_url1
this.name1 = name1
this.text = text
}
}
export class Menu {
pic: Resource
name2: string
constructor(pic: Resource,
name2: string,
) {
this.pic = pic
this.name2 = name2
}
}
export class Date {
pic_date: Resource
date: string
taici: string
constructor(pic_date: Resource,
taici: string,
date: string,
) {
this.pic_date = pic_date
this.taici = taici
this.date = date
}
}
更多推荐


所有评论(0)