一、前言
由于本次制作页面对笔者来说挑战难度稍大。于是,笔者的思路/代码会混杂,望前辈指正修改!
还是先放上一张结果图:
二、对整体进行设置并加一个导航栏
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 } }