一多音樂(lè)專輯主頁(yè)
介紹
本示例展示了音樂(lè)專輯主頁(yè)。
- 頭部返回欄: 因元素單一、位置固定在頂部,因此適合采用自適應(yīng)拉伸,充分利用頂部區(qū)域。
- 專輯封面: 使用柵格組件控制占比,在小尺寸屏幕下封面圖與歌單描述在同一行。
- 歌曲列表: 使用柵格組件控制寬度,在小尺寸屏幕下寬度為屏幕的100%,中尺寸屏幕下寬度為屏幕的50%,大尺寸屏幕下寬度為屏幕的75%。
- 播放器: 采用自適應(yīng)拉伸,充分使用底部區(qū)域。
本示例使用一次開(kāi)發(fā)多端部署中介紹的自適應(yīng)布局能力和響應(yīng)式布局能力進(jìn)行多設(shè)備(或多窗口尺寸)適配,保證應(yīng)用在不同設(shè)備或不同窗口尺寸下可以正常顯示。
用到了媒體查詢接口[@ohos.mediaquery]。
效果預(yù)覽
本示例在預(yù)覽器中的效果:
本示例在開(kāi)發(fā)板上運(yùn)行的效果:
使用說(shuō)明:
1.啟動(dòng)應(yīng)用,查看本應(yīng)用在全屏狀態(tài)下的效果。
2.在應(yīng)用頂部,下滑出現(xiàn)窗口操作按鈕。(建議通過(guò)外接鼠標(biāo)操作,接入鼠標(biāo)只需要將鼠標(biāo)移動(dòng)至頂部即可出現(xiàn)窗口)
3.點(diǎn)擊懸浮圖標(biāo),將應(yīng)用懸浮在其他界面上顯示。
4.拖動(dòng)應(yīng)用懸浮窗口的四個(gè)頂角,改變窗口尺寸,觸發(fā)應(yīng)用顯示刷新。改變窗口尺寸的過(guò)程中,窗口尺寸可能超出屏幕尺寸,此時(shí)在屏幕中只能看到應(yīng)用部分區(qū)域的顯示??梢酝ㄟ^(guò)移動(dòng)窗口位置,查看應(yīng)用其它區(qū)域的顯示。
開(kāi)發(fā)前請(qǐng)熟悉鴻蒙開(kāi)發(fā)指導(dǎo)文檔 :[gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md
]
工程目錄
AppMarket/entry/src/main/ets/
|---model
| |---MediaData.ets // 主頁(yè)用到的圖片資源
| |---SongList.ets // 歌曲數(shù)據(jù)
| |---SongModule.ets // 事件監(jiān)聽(tīng)函數(shù)模塊
|---pages
| |---index.ets // 首頁(yè)
|---common
| |---Content.ets // 內(nèi)容組件
| |---Header.ets // 標(biāo)題欄
| |---Player.ets // app模塊(包含安裝,展示圖片,更多功能)
| |---PlayList.ets // 歌單列表
| |---PlayListCover.ets // 歌單封面
具體實(shí)現(xiàn)
本示例介紹如何使用自適應(yīng)布局能力和響應(yīng)式布局能力適配不同尺寸窗口,將頁(yè)面分拆為4個(gè)部分。
標(biāo)題欄
由于在不同斷點(diǎn)下,標(biāo)題欄始終只顯示“返回按鈕”、“歌單”以及“更多按鈕”,但“歌單”與“更多按鈕”之間的間距不同。
通過(guò)柵格實(shí)現(xiàn):將標(biāo)題欄劃分為“返回按鈕及歌單”和“更多按鈕”兩部分,這兩部分在不同斷點(diǎn)下占據(jù)的列數(shù)不同。
歌單封面
通過(guò)柵格實(shí)現(xiàn)歌單封面,它由封面圖片、歌單介紹及常用操作三部分組成這三部分的布局在md和lg斷點(diǎn)下完全相同,但在sm斷點(diǎn)下有較大差異,[源碼參考]。
/*
* Copyright (c) 2022-2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { optionList } from '../model/SongList'
@Component
export default struct PlayListCover {
@State imgHeight: number = 0
@StorageProp('coverMargin') coverMargin: number = 0
@StorageProp('currentBreakpoint') currentBreakpoint: string = 'sm'
@StorageProp('fontSize') fontSize: number = 0
@Builder
CoverImage() {
Stack({ alignContent: Alignment.BottomStart }) {
Image($r('app.media.pic_album'))
.width('100%')
.aspectRatio(1)
.borderRadius(8)
.onAreaChange((oldArea: Area, newArea: Area) = > {
this.imgHeight = newArea.height as number
})
Text($r('app.string.collection_num'))
.letterSpacing(1)
.fontColor('#fff')
.fontSize(this.fontSize - 4)
.translate({ x: 10, y: '-100%' })
}
.width('100%')
.height('100%')
.aspectRatio(1)
}
@Builder
CoverIntroduction() {
Column() {
Text($r('app.string.list_name'))
.opacity(0.9)
.fontWeight(500)
.fontColor('#556B89')
.fontSize(this.fontSize + 2)
.margin({ bottom: 10 })
Text($r('app.string.playlist_Introduction'))
.opacity(0.6)
.width('100%')
.fontWeight(400)
.fontColor('#556B89')
.fontSize(this.fontSize - 2)
}
.width('100%')
.height(this.currentBreakpoint === 'sm' ? this.imgHeight : 70)
.alignItems(HorizontalAlign.Start)
.justifyContent(FlexAlign.Center)
.padding({ left: this.currentBreakpoint === 'sm' ? 20 : 0 })
.margin({
top: this.currentBreakpoint === 'sm' ? 0 : 30,
bottom: this.currentBreakpoint === 'sm' ? 0 : 20
})
}
@Builder
CoverOptions() {
Row() {
ForEach(optionList, item = > {
Column({ space: 4 }) {
Image(item.image).height(30).width(30)
Text(item.text)
.fontColor('#556B89')
.fontSize(this.fontSize - 1)
}
})
}
.width('100%')
.height(70)
.padding({
left: this.currentBreakpoint === 'sm' ? 20 : 0,
right: this.currentBreakpoint === 'sm' ? 20 : 0
})
.margin({
top: this.currentBreakpoint === 'sm' ? 15 : 0,
bottom: this.currentBreakpoint === 'sm' ? 15 : 0
})
.justifyContent(FlexAlign.SpaceBetween)
}
build() {
if (this.currentBreakpoint === 'sm') {
Column() {
GridRow() {
GridCol({ span: { sm: 4, md: 10 }, offset: { sm: 0, md: 1, lg: 1 } }) {
this.CoverImage()
}
GridCol({ span: { sm: 8, md: 10 }, offset: { sm: 0, md: 2, lg: 2 } }) {
this.CoverIntroduction()
}
GridCol({ span: { sm: 12, md: 10 }, offset: { sm: 0, md: 2, lg: 2 } }) {
this.CoverOptions()
}
}
.margin({ left: this.coverMargin, right: this.coverMargin })
.padding({ top: this.currentBreakpoint === 'sm' ? 50 : 70 })
}
} else {
Column() {
GridRow() {
GridCol({ span: { sm: 4, md: 10 }, offset: { sm: 0, md: 1, lg: 1 } }) {
this.CoverImage()
}
GridCol({ span: { sm: 8, md: 10 }, offset: { sm: 0, md: 2, lg: 2 } }) {
this.CoverIntroduction()
}
GridCol({ span: { sm: 12, md: 10 }, offset: { sm: 0, md: 2, lg: 2 } }) {
this.CoverOptions()
}.margin({
top: this.currentBreakpoint === 'sm' ? 15 : 0,
bottom: this.currentBreakpoint === 'sm' ? 15 : 0
})
}
.margin({ left: this.coverMargin, right: this.coverMargin })
.padding({ top: this.currentBreakpoint === 'sm' ? 50 : 70 })
}
.height('100%')
}
}
}
1、在sm斷點(diǎn)下,封面圖片和歌單介紹占滿12列,常用操作此時(shí)會(huì)自動(dòng)換行顯示。
2、在lg和md斷點(diǎn)下,封面圖片,歌單和常用操作各占一行中顯示。
歌單列表
通過(guò)List組件的lanes屬性實(shí)現(xiàn):在不同斷點(diǎn)下,歌單列表的樣式一致,但sm和md斷點(diǎn)下是歌單列表是單列顯示,lg斷點(diǎn)下是雙列顯示,[源碼參考]。
/*`HarmonyOS與OpenHarmony鴻蒙文檔籽料:mau123789是v直接拿`
* Copyright (c) 2022-2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { songList } from '../model/SongList'
import MyDataSource from '../model/SongModule'
@Component
export default struct PlayList {
@StorageProp('currentBreakpoint') currentBreakpoint: string = 'sm'
@StorageProp('fontSize') fontSize: number = 0
@Consume coverHeight: number
@Builder
PlayAll() {
Row() {
Image($r("app.media.ic_play_all"))
.height(23)
.width(23)
Text($r('app.string.play_all'))
.maxLines(1)
.padding({ left: 10 })
.fontColor('#000000')
.fontSize(this.fontSize)
Blank()
Image($r('app.media.ic_order_play'))
.width(24)
.height(24)
.margin({ right: 16 })
Image($r('app.media.ic_sort_list'))
.height(24)
.width(24)
}
.height(60)
.width('100%')
.padding({ left: 12, right: 12 })
}
@Builder
SongItem(title: string, label: Resource, singer: string) {
Row() {
Column() {
Text(title)
.fontColor('#000000')
.fontSize(this.fontSize)
.margin({ bottom: 4 })
Row() {
Image(label)
.width(16)
.height(16)
.margin({ right: 4 })
Text(singer)
.opacity(0.38)
.fontColor('#000000')
.fontSize(this.fontSize - 4)
}
}
.alignItems(HorizontalAlign.Start)
Blank()
Image($r('app.media.ic_list_more'))
.height(24)
.width(24)
}
.height(60)
.width('100%')
}
build() {
Column() {
this.PlayAll()
Scroll() {
List() {
LazyForEach(new MyDataSource(songList), item = > {
ListItem() {
Column() {
this.SongItem(item.title, item.label, item.singer)
Divider()
.strokeWidth(0.5)
.color('#000')
.opacity(0.1)
}
.width('100%')
.height(50)
.padding({ left: 14, right: 14 })
}
}, item = > item.id.toString())
}
.width('100%')
.lanes(this.currentBreakpoint === 'lg' ? 2 : 1)
}
.height('100%')
.flexGrow(1)
.flexShrink(1)
}
.width('100%')
.height('100%')
.borderRadius({ topLeft: 20, topRight: 20 })
.backgroundColor(Color.White)
.padding({ bottom: this.currentBreakpoint === 'sm' ? this.coverHeight : 0 })
}
}
播放控制欄
通過(guò)Blank組件實(shí)現(xiàn)拉伸能力:在不同斷點(diǎn)下,播放控制欄顯示的內(nèi)容完全一致,唯一的區(qū)別是歌曲信息與播放控制按鈕之間的間距有差異。
總體運(yùn)行效果
通過(guò)在首頁(yè)Column()中引用上述各組件后,可實(shí)現(xiàn)首頁(yè)的組件整合渲染,即可完成整體頁(yè)面開(kāi)發(fā),[源碼參考]。
/*
* Copyright (c) 2022-2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { songList } from '../model/SongList'
import MyDataSource from '../model/SongModule'
@Component
export default struct PlayList {
@StorageProp('currentBreakpoint') currentBreakpoint: string = 'sm'
@StorageProp('fontSize') fontSize: number = 0
@Consume coverHeight: number
@Builder
PlayAll() {
Row() {
Image($r("app.media.ic_play_all"))
.height(23)
.width(23)
Text($r('app.string.play_all'))
.maxLines(1)
.padding({ left: 10 })
.fontColor('#000000')
.fontSize(this.fontSize)
Blank()
Image($r('app.media.ic_order_play'))
.width(24)
.height(24)
.margin({ right: 16 })
Image($r('app.media.ic_sort_list'))
.height(24)
.width(24)
}
.height(60)
.width('100%')
.padding({ left: 12, right: 12 })
}
@Builder
SongItem(title: string, label: Resource, singer: string) {
Row() {
Column() {
Text(title)
.fontColor('#000000')
.fontSize(this.fontSize)
.margin({ bottom: 4 })
Row() {
Image(label)
.width(16)
.height(16)
.margin({ right: 4 })
Text(singer)
.opacity(0.38)
.fontColor('#000000')
.fontSize(this.fontSize - 4)
}
}
.alignItems(HorizontalAlign.Start)
Blank()
Image($r('app.media.ic_list_more'))
.height(24)
.width(24)
}
.height(60)
.width('100%')
}
build() {
Column() {
this.PlayAll()
Scroll() {
List() {
LazyForEach(new MyDataSource(songList), item = > {
ListItem() {
Column() {
this.SongItem(item.title, item.label, item.singer)
Divider()
.strokeWidth(0.5)
.color('#000')
.opacity(0.1)
}
.width('100%')
.height(50)
.padding({ left: 14, right: 14 })
}
}, item = > item.id.toString())
}
.width('100%')
.lanes(this.currentBreakpoint === 'lg' ? 2 : 1)
}
.height('100%')
.flexGrow(1)
.flexShrink(1)
}
.width('100%')
.height('100%')
.borderRadius({ topLeft: 20, topRight: 20 })
.backgroundColor(Color.White)
.padding({ bottom: this.currentBreakpoint === 'sm' ? this.coverHeight : 0 })
}
}
審核編輯 黃宇
-
鴻蒙
+關(guān)注
關(guān)注
57文章
2352瀏覽量
42859 -
鴻蒙OS
+關(guān)注
關(guān)注
0文章
188瀏覽量
4396
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論