1# 音乐专辑页 2 3 4本小节将以音乐专辑页为例,介绍如何使用自适应布局能力和响应式布局能力适配不同尺寸窗口。 5 6 7## 页面设计 8 9音乐专辑页的页面设计如下。 10 11 | sm | md | lg | 12| -------- | -------- | -------- | 13|  |  |  | 14 15同样观察音乐专辑的页面设计,不同断点下的页面设计有较多相似的地方。 16 17据此,我们可以将页面分拆为多个组成部分。 18 191. 标题栏 20 212. 歌单封面 22 233. 歌单列表 24 254. 播放控制栏 26 27 | sm | md | lg | 28| -------- | -------- | -------- | 29|  |  |  | 30 31 32## 标题栏 33 34不同断点下,标题栏始终只显示“返回按钮”、“歌单”以及“更多按钮”,但“歌单”与“更多按钮”之间的间距不同。由于不同断点下标题栏的背景色也有较大差异,因此无法使用拉伸能力实现,此场景更适合使用栅格实现。我们可以将标题栏划分为“返回按钮及歌单”和“更多按钮”两部分,这两部分在不同断点下占据的列数如下图所示。另外,还可以借助OnBreakpointChange事件,调整不同断点下这两部分的背景色。 35 36 | | sm | md | lg | 37| -------- | -------- | -------- | -------- | 38| 效果图 |  |  |  | 39| 栅格布局图 |  |  |  | 40 41 42```ts 43@Component 44export struct Header { 45 @State moreBackgroundColor: Resource = $r('app.color.play_list_cover_background_color'); 46 build() { 47 GridRow() { 48 GridCol({span: {sm:6, md: 6, lg:4}}) { 49 Row() { 50 Image($r('app.media.ic_back')).height('24vp').width('24vp') 51 } 52 .width('100%') 53 .height('50vp') 54 .justifyContent(FlexAlign.Start) 55 .alignItems(VerticalAlign.Center) 56 .padding({left:$r('app.float.default_margin')}) 57 .backgroundColor($r('app.color.play_list_cover_background_color')) 58 } 59 GridCol({span: {sm:6, md: 6, lg:8}}) { 60 Row() { 61 Image($r('app.media.ic_add')).height('24vp').width('24vp') 62 } 63 .width('100%') 64 .height('50vp') 65 .justifyContent(FlexAlign.End) 66 .alignItems(VerticalAlign.Center) 67 .padding({right:$r('app.float.default_margin')}) 68 .backgroundColor(this.moreBackgroundColor) 69 } 70 }.onBreakpointChange((currentBreakpoint) => { 71 // 调整不同断点下返回按钮及歌单的背景色 72 if (currentBreakpoint === 'sm') { 73 this.moreBackgroundColor = $r('app.color.play_list_cover_background_color'); 74 } else { 75 this.moreBackgroundColor = $r('app.color.play_list_songs_background_color'); 76 } 77 }).height('100%').width('100%') 78 } 79} 80``` 81 82 83## 歌单封面 84 85歌单封面由封面图片、歌单介绍及常用操作三部分组成,这三部分的布局在md和lg断点下完全相同,但在sm断点下有较大差异。此场景同样可以用栅格实现。 86 87 | | sm | md/lg | 88| -------- | -------- | -------- | 89| 效果图 |  |  | 90| 栅格布局图 |  |  | 91 92 93```ts 94import { optionList } from '../model/SongList' 95 96@Component 97export default struct PlayListCover { 98 @State imgHeight: number = 0; 99 @StorageProp('coverMargin') coverMargin: number = 0; 100 @StorageProp('currentBreakpoint') currentBreakpoint: string = 'sm'; 101 @StorageProp('fontSize') fontSize: number = 0; 102 103 @Builder 104 CoverImage() { 105 Stack({ alignContent: Alignment.BottomStart }) { 106 Image($r('app.media.pic_album')) 107 .width('100%') 108 .aspectRatio(1) 109 .borderRadius(8) 110 .onAreaChange((oldArea: Area, newArea: Area) => { 111 this.imgHeight = newArea.height as number 112 }) 113 Text($r('app.string.collection_num')) 114 .letterSpacing(1) 115 .fontColor('#fff') 116 .fontSize(this.fontSize - 4) 117 .translate({ x: 10, y: '-100%' }) 118 } 119 .width('100%') 120 .height('100%') 121 .aspectRatio(1) 122 } 123 124 @Builder 125 CoverIntroduction() { 126 Column() { 127 Text($r('app.string.list_name')) 128 .opacity(0.9) 129 .fontWeight(500) 130 .fontColor('#556B89') 131 .fontSize(this.fontSize + 2) 132 .margin({ bottom: 10 }) 133 134 Text($r('app.string.playlist_Introduction')) 135 .opacity(0.6) 136 .width('100%') 137 .fontWeight(400) 138 .fontColor('#556B89') 139 .fontSize(this.fontSize - 2) 140 } 141 .width('100%') 142 .height(this.currentBreakpoint === 'sm' ? this.imgHeight : 70) 143 .alignItems(HorizontalAlign.Start) 144 .justifyContent(FlexAlign.Center) 145 .padding({ left: this.currentBreakpoint === 'sm' ? 20 : 0 }) 146 .margin({ 147 top: this.currentBreakpoint === 'sm' ? 0 : 30, 148 bottom: this.currentBreakpoint === 'sm' ? 0 : 20 149 }) 150 } 151 152 @Builder 153 CoverOptions() { 154 Row() { 155 ForEach(optionList, item => { 156 Column({ space: 4 }) { 157 Image(item.image).height(30).width(30) 158 Text(item.text) 159 .fontColor('#556B89') 160 .fontSize(this.fontSize - 1) 161 } 162 }) 163 } 164 .width('100%') 165 .height(70) 166 .padding({ 167 left: this.currentBreakpoint === 'sm' ? 20 : 0, 168 right: this.currentBreakpoint === 'sm' ? 20 : 0 169 }) 170 .margin({ 171 top: this.currentBreakpoint === 'sm' ? 15 : 0, 172 bottom: this.currentBreakpoint === 'sm' ? 15 : 0 173 }) 174 .justifyContent(FlexAlign.SpaceBetween) 175 } 176 build() { 177 Column() { 178 // 借助栅格组件实现总体布局 179 GridRow() { 180 // 歌单图片 181 GridCol({ span: { sm: 4, md: 10 }, offset: { sm: 0, md: 1, lg: 1 } }) { 182 this.CoverImage() 183 } 184 // 歌单介绍 185 GridCol({ span: { sm: 8, md: 10 }, offset: { sm: 0, md: 2, lg: 2 } }) { 186 this.CoverIntroduction() 187 } 188 // 歌单操作 189 GridCol({ span: { sm: 12, md: 10 }, offset: { sm: 0, md: 2, lg: 2 } }) { 190 this.CoverOptions() 191 }.margin({ 192 top: this.currentBreakpoint === 'sm' ? 15 : 0, 193 bottom: this.currentBreakpoint === 'sm' ? 15 : 0 194 }) 195 } 196 .margin({ left: this.coverMargin, right: this.coverMargin }) 197 } 198 .height('100%') 199 .padding({ top: this.currentBreakpoint === 'sm' ? 50 : 70 }) 200 } 201} 202``` 203 204 205## 歌单列表 206 207不同断点下,歌单列表的样式基本一致,但sm和md断点下是歌单列表是单列显示,lg断点下是双列显示。可以通过[List组件](../../reference/apis-arkui/arkui-ts/ts-container-list.md)的lanes属性实现这一效果。 208 209 210```ts 211import { songList } from '../model/SongList'; 212import MyDataSource from '../model/SongModule' 213 214@Component 215export default struct PlayList { 216 @StorageProp('currentBreakpoint') currentBreakpoint: string = 'sm'; 217 @StorageProp('fontSize') fontSize: number = 0; 218 @Consume coverHeight: number; 219 @Builder 220 PlayAll() { 221 Row() { 222 Image($r("app.media.ic_play_all")) 223 .height(23) 224 .width(23) 225 Text($r('app.string.play_all')) 226 .maxLines(1) 227 .padding({ left: 10 }) 228 .fontColor('#000000') 229 .fontSize(this.fontSize) 230 Blank() 231 Image($r('app.media.ic_order_play')) 232 .width(24) 233 .height(24) 234 .margin({ right: 16 }) 235 Image($r('app.media.ic_sort_list')) 236 .height(24) 237 .width(24) 238 } 239 .height(60) 240 .width('100%') 241 .padding({ left: 12, right: 12 }) 242 } 243 244 @Builder 245 SongItem(title: string, label: Resource, singer: string) { 246 Row() { 247 Column() { 248 Text(title) 249 .fontColor('#000000') 250 .fontSize(this.fontSize) 251 .margin({ bottom: 4 }) 252 Row() { 253 Image(label) 254 .width(16) 255 .height(16) 256 .margin({ right: 4 }) 257 Text(singer) 258 .opacity(0.38) 259 .fontColor('#000000') 260 .fontSize(this.fontSize - 4) 261 } 262 } 263 .alignItems(HorizontalAlign.Start) 264 265 Blank() 266 Image($r('app.media.ic_list_more')) 267 .height(24) 268 .width(24) 269 } 270 .height(60) 271 .width('100%') 272 } 273 build() { 274 Column() { 275 this.PlayAll() 276 Scroll() { 277 List() { 278 LazyForEach(new MyDataSource(songList), item => { 279 ListItem() { 280 this.SongItem(item.title, item.label, item.singer) 281 } 282 }) 283 } 284 .width('100%') 285 .height('100%') 286 // 配置不同断点下歌单列表的列数 287 .lanes(this.currentBreakpoint === 'lg' ? 2 : 1) 288 } 289 .backgroundColor('#fff') 290 .margin({ top: 50, bottom: this.currentBreakpoint === 'sm' ? this.coverHeight : 0 }) 291 } 292 .padding({top: 50,bottom: 48}) 293 } 294} 295``` 296 297 298## 播放控制栏 299 300在不同断点下,播放控制栏显示的内容完全一致,唯一的区别是歌曲信息与播放控制按钮之间的间距有差异,这是典型的拉伸能力的使用场景。 301 302 303```ts 304@Component 305export default struct Player { 306 @StorageProp('fontSize') fontSize: number = 0; 307 build() { 308 Row() { 309 Image($r('app.media.pic_album')).height(32).width(32).margin({right: 12}) 310 Column() { 311 Text($r('app.string.song_name')) 312 .fontColor('#000000') 313 .fontSize(this.fontSize - 1) 314 Row() { 315 Image($r('app.media.ic_vip')) 316 .height(16) 317 .width(16) 318 .margin({ right: 4 }) 319 Text($r('app.string.singer')) 320 .fontColor('#000000') 321 .fontSize(this.fontSize - 4) 322 .opacity(0.38) 323 } 324 } 325 .alignItems(HorizontalAlign.Start) 326 // 通过Blank组件实现拉伸能力 327 Blank() 328 Image($r('app.media.icon_play')).height(26).width(26).margin({right: 16}) 329 Image($r('app.media.ic_next')).height(24).width(24).margin({right: 16}) 330 Image($r('app.media.ic_Music_list')).height(24).width(24) 331 } 332 .width('100%') 333 .height(48) 334 .backgroundColor('#D8D8D8') 335 .alignItems(VerticalAlign.Center) 336 .padding({left: 16, right: 16}) 337 } 338} 339``` 340 341 342## 运行效果 343 344将页面中的四部分组合在一起,即可显示完整的页面。 345 346其中歌单封面和歌单列表这两部分的相对位置,在sm断点下是上下排布,在md和lg断点下是左右排布,也可以用栅格来实现目标效果。 347 348 | | sm | md | lg | 349| -------- | -------- | -------- | -------- | 350| 效果图 |  |  |  | 351| 栅格布局图 |  |  |  | 352 353 354```ts 355import PlayListCover from '../common/PlayListCover'; 356import PlayList from '../common/PlayList'; 357 358@Component 359export default struct Content { 360 // ... 361 build() { 362 GridRow() { 363 // 歌单封面 364 GridCol({ span: { xs: 12, sm: 12, md: 6, lg: 4 } }) { 365 PlayListCover() 366 } 367 // 歌单列表 368 GridCol({ span: { xs: 12, sm: 12, md: 6, lg: 8 } }) { 369 PlayList() 370 } 371 } 372 .height('100%') 373 } 374} 375``` 376 377最后将页面各部分组合在一起即可。 378 379 380```ts 381 382import Header from '../common/Header'; 383import Player from '../common/Player'; 384import Content from '../common/Content'; 385 386@Entry 387@Component 388struct Index { 389 build() { 390 Column() { 391 // 标题栏 392 Header() 393 // 歌单 394 Content() 395 // 播放控制栏 396 Player() 397 }.width('100%').height('100%') 398 } 399} 400``` 401 402音乐专辑页面的运行效果如下所示。 403 404 | sm | md | lg | 405| -------- | -------- | -------- | 406|  |  |  | 407 408## 相关实例 409 410针对音乐专辑应用,有以下相关实例可供参考: 411 412- [典型页面场景:音乐专辑页(ArkTS)(API9)](https://gitee.com/openharmony/applications_app_samples/tree/master/code/SuperFeature/MultiDeviceAppDev/MusicAlbum) 413 414