[HarmonyOS NEXT 实战案例十三] 音乐播放器网格布局(上)

2025-06-08 15:04:35
107次阅读
0个评论
最后修改时间:2025-06-08 15:13:56

[HarmonyOS NEXT 实战案例十三] 音乐播放器网格布局(上)

项目已开源,开源地址: https://gitcode.com/nutpi/HarmonyosNextCaseStudyTutorial , 欢迎fork & star

效果演示

image.png

1. 概述

本教程将详细讲解如何使用HarmonyOS NEXT的GridRow和GridCol组件实现一个音乐播放器的网格布局。音乐播放器是移动应用中常见的功能,通过网格布局可以实现清晰、美观的界面结构,提升用户体验。

本教程将涵盖以下内容:

  • 音乐播放器的整体布局设计
  • 专辑封面区域实现
  • 歌曲信息区域实现
  • 进度条区域实现
  • 控制按钮区域实现
  • GridRow和GridCol组件的灵活应用

2. 状态管理设计

在实现音乐播放器之前,我们首先定义几个状态变量来管理播放器的状态:

@State isPlaying: boolean = false  // 是否正在播放
@State currentTime: number = 0     // 当前播放时间(秒)
@State duration: number = 180      // 歌曲总时长(秒,这里设为3分钟)

这些状态变量将用于控制播放/暂停按钮的显示状态,以及进度条的当前值和最大值。

3. 整体布局结构

音乐播放器的整体布局采用Column容器,内部包含四个主要部分,每个部分都使用GridRow和GridCol组件进行布局:

build() {
    Column() {
        // 专辑封面
        GridRow({ columns: 1 }) {
            // ...
        }

        // 歌曲信息
        GridRow({ columns: 1 }) {
            // ...
        }

        // 进度条
        GridRow({ columns: 1 }) {
            // ...
        }

        // 控制按钮
        GridRow({ columns: 5 }) {
            // ...
        }
    }
    .width('100%')
    .height('100%')
}

这种结构将播放器界面分为四个垂直排列的部分,每个部分内部又使用GridRow和GridCol进行更细致的布局。

4. 专辑封面区域实现

专辑封面区域使用一个单列的GridRow,内部包含一个GridCol,用于居中显示专辑封面图片:

// 专辑封面
GridRow({ columns: 1 }) {
    GridCol({ span: 1 }) {
        Image($r("app.media.big14"))
            .width(200)
            .height(200)
            .borderRadius(100)
            .margin({ top: 32, bottom: 32 })
    }
}

在这个部分:

  • GridRow设置为单列布局(columns: 1),使内容在水平方向上居中
  • GridCol占据整个行宽(span: 1
  • Image组件显示专辑封面,设置宽高为200像素,边框圆角为100像素(使其呈现为圆形),上下边距为32像素

5. 歌曲信息区域实现

歌曲信息区域同样使用单列的GridRow,显示歌曲名称和歌手信息:

// 歌曲信息
GridRow({ columns: 1 }) {
    GridCol({ span: 1 }) {
        Column() {
            Text('夜曲')
                .fontSize(24)
                .fontWeight(FontWeight.Bold)

            Text('周杰伦')
                .fontSize(16)
                .fontColor('#9E9E9E')
                .margin({ top: 8 })
        }
    }
}

在这个部分:

  • GridRow和GridCol的配置与专辑封面区域相同
  • 内部使用Column容器垂直排列两个Text组件
  • 第一个Text显示歌曲名称,使用较大的粗体字体
  • 第二个Text显示歌手名称,使用较小的灰色字体,顶部边距为8像素

6. 进度条区域实现

进度条区域使用单列的GridRow,内部包含播放时间、进度条和总时长:

// 进度条
GridRow({ columns: 1 }) {
    GridCol({ span: 1 }) {
        Row() {
            Text(this.formatTime(this.currentTime))
                .fontSize(12)
                .fontColor('#9E9E9E')

            Slider({
                value: this.currentTime,
                min: 0,
                max: this.duration,
                step: 1,
                style: SliderStyle.OutSet
            })
                .width('70%')
                .onChange((value) => {
                    this.currentTime = value
                })
                .margin({ left: 8, right: 8 })

            Text(this.formatTime(this.duration))
                .fontSize(12)
                .fontColor('#9E9E9E')
        }
        .width('100%')
        .margin({ top: 24, bottom: 24 })
    }
}

在这个部分:

  • GridRow和GridCol的配置与前两个区域相同
  • 内部使用Row容器水平排列三个组件
  • 左侧Text显示当前播放时间,使用formatTime方法格式化时间
  • 中间Slider组件作为进度条,宽度为70%,最小值为0,最大值为歌曲总时长
  • 右侧Text显示歌曲总时长
  • 整个Row设置上下边距为24像素

7. 控制按钮区域实现

控制按钮区域使用五列的GridRow,每个按钮占据一列:

// 控制按钮
GridRow({ columns: 5 }) {
    GridCol({ span: 1 }) {
        Image($r('app.media.01'))
            .width(24)
            .height(24)
    }

    GridCol({ span: 1 }) {
        Image($r('app.media.02'))
            .width(24)
            .height(24)
    }

    GridCol({ span: 1 }) {
        Image(this.isPlaying ? $r('app.media.03') : $r('app.media.01'))
            .width(32)
            .height(32)
            .onClick(() => {
                this.isPlaying = !this.isPlaying
            })
    }

    GridCol({ span: 1 }) {
        Image($r('app.media.04'))
            .width(24)
            .height(24)
    }

    GridCol({ span: 1 }) {
        Image($r('app.media.05'))
            .width(24)
            .height(24)
    }
}
.margin({ top: 16, bottom: 32 })

在这个部分:

  • GridRow设置为五列布局(columns: 5),使五个控制按钮均匀分布
  • 每个GridCol占据一列(span: 1
  • 每个GridCol内部包含一个Image组件,显示不同的控制图标
  • 中间的播放/暂停按钮稍大(32x32像素),其他按钮较小(24x24像素)
  • 播放/暂停按钮根据isPlaying状态显示不同的图标,并添加点击事件处理
  • 整个GridRow设置上边距为16像素,下边距为32像素

8. 辅助方法实现

为了格式化时间显示,我们实现了一个辅助方法:

private formatTime(seconds: number): string {
    const mins = Math.floor(seconds / 60)
    const secs = seconds % 60
    return `${mins}:${secs < 10 ? '0' + secs : secs}`
}

这个方法将秒数转换为"分:秒"格式,并确保秒数小于10时前面添加0,如"3:05"。

9. GridRow和GridCol配置详解

在本案例中,我们使用了不同配置的GridRow和GridCol组件:

9.1 单列布局

对于专辑封面、歌曲信息和进度条区域,我们使用了单列布局:

GridRow({ columns: 1 }) {
    GridCol({ span: 1 }) {
        // 内容
    }
}

这种配置使内容在水平方向上居中,占据整个可用宽度。

9.2 多列布局

对于控制按钮区域,我们使用了五列布局:

GridRow({ columns: 5 }) {
    // 五个GridCol,每个span: 1
}

这种配置使五个按钮均匀分布在一行中,每个按钮占据总宽度的1/5。

10. 完整代码

下面是音乐播放器网格布局的完整代码:

// 音乐播放器网格布局

@Component
export struct MusicPlayerGrid {
    @State isPlaying: boolean = false
    @State currentTime: number = 0
    @State duration: number = 180 // 3分钟

    build() {
        Column() {
            // 专辑封面
            GridRow({ columns: 1 }) {
                GridCol({ span: 1 }) {
                    Image($r("app.media.big14"))
                        .width(200)
                        .height(200)
                        .borderRadius(100)
                        .margin({ top: 32, bottom: 32 })
                }
            }

            // 歌曲信息
            GridRow({ columns: 1 }) {
                GridCol({ span: 1 }) {
                    Column() {
                        Text('夜曲')
                            .fontSize(24)
                            .fontWeight(FontWeight.Bold)

                        Text('周杰伦')
                            .fontSize(16)
                            .fontColor('#9E9E9E')
                            .margin({ top: 8 })
                    }
                }
            }

            // 进度条
            GridRow({ columns: 1 }) {
                GridCol({ span: 1 }) {
                    Row() {
                        Text(this.formatTime(this.currentTime))
                            .fontSize(12)
                            .fontColor('#9E9E9E')

                        Slider({
                            value: this.currentTime,
                            min: 0,
                            max: this.duration,
                            step: 1,
                            style: SliderStyle.OutSet
                        })
                            .width('70%')
                            .onChange((value) => {
                                this.currentTime = value
                            })
                            .margin({ left: 8, right: 8 })

                        Text(this.formatTime(this.duration))
                            .fontSize(12)
                            .fontColor('#9E9E9E')
                    }
                    .width('100%')
                    .margin({ top: 24, bottom: 24 })
                }
            }

            // 控制按钮
            GridRow({ columns: 5 }) {
                GridCol({ span: 1 }) {
                    Image($r('app.media.01'))
                        .width(24)
                        .height(24)
                }

                GridCol({ span: 1 }) {
                    Image($r('app.media.02'))
                        .width(24)
                        .height(24)
                }

                GridCol({ span: 1 }) {
                    Image(this.isPlaying ? $r('app.media.03') : $r('app.media.01'))
                        .width(32)
                        .height(32)
                        .onClick(() => {
                            this.isPlaying = !this.isPlaying
                        })
                }

                GridCol({ span: 1 }) {
                    Image($r('app.media.04'))
                        .width(24)
                        .height(24)
                }

                GridCol({ span: 1 }) {
                    Image($r('app.media.05'))
                        .width(24)
                        .height(24)
                }
            }
            .margin({ top: 16, bottom: 32 })
        }
        .width('100%')
        .height('100%')
    }

    private formatTime(seconds: number): string {
        const mins = Math.floor(seconds / 60)
        const secs = seconds % 60
        return `${mins}:${secs < 10 ? '0' + secs : secs}`
    }
}

11. GridRow和GridCol组件详解

11.1 GridRow组件

GridRow是HarmonyOS NEXT提供的网格行容器组件,用于创建网格布局。它具有以下主要属性:

属性 类型 描述
columns number | { xs?: number, sm?: number, md?: number, lg?: number, xl?: number, xxl?: number } 设置布局列数
gutter number | { x?: number, y?: number } 栅格间隔
breakpoints { value: string[], reference: BreakpointsReference } 设置断点值的断点数列以及基于窗口或容器尺寸的相应参照
direction GridRowDirection 栅格布局排列方向

11.2 GridCol组件

GridCol是HarmonyOS NEXT提供的网格列容器组件,用于在GridRow中创建网格列。它具有以下主要属性:

属性 类型 描述
span number | { xs?: number, sm?: number, md?: number, lg?: number, xl?: number, xxl?: number } 列宽度
offset number | { xs?: number, sm?: number, md?: number, lg?: number, xl?: number, xxl?: number } 列偏移量
order number | { xs?: number, sm?: number, md?: number, lg?: number, xl?: number, xxl?: number } 列顺序

12. 布局效果分析

本案例中的音乐播放器布局具有以下特点:

  1. 垂直分区明确:整体布局分为四个垂直区域(专辑封面、歌曲信息、进度条、控制按钮),结构清晰
  2. 水平对齐一致:专辑封面、歌曲信息和进度条区域都采用单列布局,保持水平居中对齐
  3. 控制按钮均匀分布:控制按钮区域采用五列布局,使五个按钮均匀分布,视觉平衡
  4. 层次感强:通过不同的字体大小、颜色和间距,创造出良好的视觉层次
  5. 交互元素明确:播放/暂停按钮和进度条作为主要交互元素,尺寸较大且位置突出

这种布局设计使音乐播放器界面既美观又实用,用户可以轻松地查看歌曲信息、控制播放进度和操作播放控制。

13. 总结

本教程详细讲解了如何使用HarmonyOS NEXT的GridRow和GridCol组件实现音乐播放器的网格布局。通过合理的状态管理和布局设计,我们创建了一个功能完整、界面美观的音乐播放器界面。

主要内容包括:

  • 音乐播放器的状态管理设计
  • 整体布局结构的实现
  • 专辑封面、歌曲信息、进度条和控制按钮区域的详细实现
  • GridRow和GridCol组件的不同配置方式
  • 时间格式化辅助方法的实现

通过本教程,你应该已经掌握了如何使用GridRow和GridCol组件实现复杂的界面布局,以及如何管理组件状态和处理用户交互。这些技能可以应用到各种需要网格布局的场景中,如媒体播放器、照片浏览器、控制面板等。

在下一篇教程中,我们将进一步探讨如何优化音乐播放器,添加更多功能和交互效果,使其更加实用和吸引人。

收藏00

登录 后评论。没有帐号? 注册 一个。