一多音樂專輯主頁
介紹
本示例展示了音樂專輯主頁。
- 頭部返回欄: 因元素單一、位置固定在頂部,因此適合采用自適應拉伸,充分利用頂部區(qū)域。
- 專輯封面: 使用柵格組件控制占比,在小尺寸屏幕下封面圖與歌單描述在同一行。
- 歌曲列表: 使用柵格組件控制寬度,在小尺寸屏幕下寬度為屏幕的100%,中尺寸屏幕下寬度為屏幕的50%,大尺寸屏幕下寬度為屏幕的75%。
- 播放器: 采用自適應拉伸,充分使用底部區(qū)域。
本示例使用[一次開發(fā)多端部署]中介紹的自適應布局能力和響應式布局能力進行多設備(或多窗口尺寸)適配,保證應用在不同設備或不同窗口尺寸下可以正常顯示。
用到了媒體查詢接口[@ohos.mediaquery]。
效果預覽
本示例在預覽器中的效果:
本示例在開發(fā)板上運行的效果:
使用說明:
1.啟動應用,查看本應用在全屏狀態(tài)下的效果。
2.在應用頂部,下滑出現(xiàn)窗口操作按鈕。(建議通過外接鼠標操作,接入鼠標只需要將鼠標移動至頂部即可出現(xiàn)窗口)
3.點擊懸浮圖標,將應用懸浮在其他界面上顯示。
4.拖動應用懸浮窗口的四個頂角,改變窗口尺寸,觸發(fā)應用顯示刷新。改變窗口尺寸的過程中,窗口尺寸可能超出屏幕尺寸,此時在屏幕中只能看到應用部分區(qū)域的顯示。可以通過移動窗口位置,查看應用其它區(qū)域的顯示。
開發(fā)前請熟悉鴻蒙開發(fā)指導文檔 :gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md
點擊或者復制轉(zhuǎn)到。
工程目錄
AppMarket/entry/src/main/ets/
|---model
| |---MediaData.ets // 主頁用到的圖片資源
| |---SongList.ets // 歌曲數(shù)據(jù)
| |---SongModule.ets // 事件監(jiān)聽函數(shù)模塊
|---pages
| |---index.ets // 首頁
|---common
| |---Content.ets // 內(nèi)容組件
| |---Header.ets // 標題欄
| |---Player.ets // app模塊(包含安裝,展示圖片,更多功能)
| |---PlayList.ets // 歌單列表
| |---PlayListCover.ets // 歌單封面
具體實現(xiàn)
本示例介紹如何使用自適應布局能力和響應式布局能力適配不同尺寸窗口,將頁面分拆為4個部分。
標題欄
由于在不同斷點下,標題欄始終只顯示“返回按鈕”、“歌單”以及“更多按鈕”,但“歌單”與“更多按鈕”之間的間距不同。
通過柵格實現(xiàn):將標題欄劃分為“返回按鈕及歌單”和“更多按鈕”兩部分,這兩部分在不同斷點下占據(jù)的列數(shù)不同。
歌單封面
通過柵格實現(xiàn)歌單封面,它由封面圖片、歌單介紹及常用操作三部分組成這三部分的布局在md和lg斷點下完全相同,但在sm斷點下有較大差異,[源碼參考]。
/*
* 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斷點下,封面圖片和歌單介紹占滿12列,常用操作此時會自動換行顯示。
2、在lg和md斷點下,封面圖片,歌單和常用操作各占一行中顯示。
歌單列表
通過List組件的lanes屬性實現(xiàn):在不同斷點下,歌單列表的樣式一致,但sm和md斷點下是歌單列表是單列顯示,lg斷點下是雙列顯示,[源碼參考]。
/*
* 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 })
}
}
播放控制欄
通過Blank組件實現(xiàn)拉伸能力:在不同斷點下,播放控制欄顯示的內(nèi)容完全一致,唯一的區(qū)別是歌曲信息與播放控制按鈕之間的間距有差異。
總體運行效果
通過在首頁Column()中引用上述各組件后,可實現(xiàn)首頁的組件整合渲染,即可完成整體頁面開發(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 })
}
}
`HarmonyOS與OpenHarmony鴻蒙文檔籽料:mau123789是v直接拿`
-
移動開發(fā)
+關(guān)注
關(guān)注
0文章
52瀏覽量
9696 -
鴻蒙系統(tǒng)
+關(guān)注
關(guān)注
183文章
2634瀏覽量
66224 -
HarmonyOS
+關(guān)注
關(guān)注
79文章
1967瀏覽量
30033 -
OpenHarmony
+關(guān)注
關(guān)注
25文章
3665瀏覽量
16161 -
鴻蒙OS
+關(guān)注
關(guān)注
0文章
188瀏覽量
4371
發(fā)布評論請先 登錄
相關(guān)推薦
評論