任务二 搭建歌曲列表项与播放栏

任务一已经划分出歌曲列表区和底部控制区,本任务为两个区域完成静态布局:设计列表中的列表项,以及底部的播放栏。列表项就是歌曲列表中的一行,本任务里也简称”条目”。

一、任务目标

任务完成后,页面将具备以下静态控件:

  • 列表项能够展示歌曲名称、歌手和操作图标。
  • 播放控制区能够展示封面、歌曲名称、时间、进度条和三个控制按钮。
  • 所有控件完成尺寸和约束配置,但暂不加载数据或响应点击。

二、设计列表项布局

1. 建立条目布局骨架

在 app/src/main/res/layout 目录中创建 recycler_view_item.xml,先使用 ConstraintLayout 建立条目容器。

app/src/main/res/layout/recycler_view_item.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingVertical="8dp">
</androidx.constraintlayout.widget.ConstraintLayout>

条目宽度占满列表,垂直内边距用于分隔相邻歌曲。

2. 添加歌曲名称和歌手

在 ConstraintLayout 中加入两个 TextView。

app/src/main/res/layout/recycler_view_item.xml
<androidx.constraintlayout.widget.ConstraintLayout
...>
<TextView
android:id="@+id/tv_song"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLength="24"
android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="唯一" />
<TextView
android:id="@+id/tv_singer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:maxLength="24"
android:textSize="14sp"
app:layout_constraintStart_toStartOf="@id/tv_song"
app:layout_constraintTop_toBottomOf="@id/tv_song"
tools:text="邓紫棋" />
</androidx.constraintlayout.widget.ConstraintLayout>

tools:text 只用于 Android Studio 布局预览,运行时的文字由歌曲数据提供。maxLength="24" 将每行文字限制为最多 24 个字符。完成布局后,还需检查长歌名和歌手名称是否与右侧图标重叠。

3. 添加歌曲操作图标

继续在 ConstraintLayout 中加入 MV 图标和播放图标。

app/src/main/res/layout/recycler_view_item.xml
<androidx.constraintlayout.widget.ConstraintLayout
...>
<!-- ... 省略已经完成的歌曲名称和歌手。 -->
<ImageView
android:id="@+id/img_play"
android:layout_width="25dp"
android:layout_height="25dp"
android:background="@drawable/ic_start"
android:contentDescription="播放歌曲"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/img_mv"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginEnd="16dp"
android:background="@drawable/ic_mv"
android:contentDescription="查看 MV"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/img_play"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

两个图标靠右排列。img_mv 用于进入 MV 页面,img_play 只用来提示该歌曲可以播放,不需要单独设置点击事件。任务四会把播放的点击监听器加在整行条目上,点到播放图标所在位置同样会播放这首歌曲。

列表项的三个子步骤都完成后,点击预览面板顶部的预览按钮切换到预览视图,即可查看整体布局效果,如下图所示。

列表项布局完成后的预览效果

三、完善底部播放控制布局

1. 替换根布局

打开 app/src/main/res/layout/fragment_player.xml,将原来的 FrameLayout 替换为 ConstraintLayout。

app/src/main/res/layout/fragment_player.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="72dp"
android:background="#F6F0FB" />
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#F6F0FB"
android:paddingHorizontal="16dp"
android:paddingVertical="8dp">
</androidx.constraintlayout.widget.ConstraintLayout>

ConstraintLayout 便于按相互位置排列封面、进度条和按钮,同时减少嵌套层级。

2. 添加封面和歌曲名称

在根布局中加入默认封面和歌曲名称。

app/src/main/res/layout/fragment_player.xml
<androidx.constraintlayout.widget.ConstraintLayout
...>
<ImageView
android:id="@+id/img_pic"
android:layout_width="64dp"
android:layout_height="64dp"
android:src="@drawable/ic_launcher_foreground"
android:contentDescription="专辑封面"
android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_song_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:text="--"
android:textColor="@android:color/black"
android:textSize="18sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/img_pic"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

当前还没有歌曲数据,因此使用“—”作为歌曲名称占位文字。歌曲名称此刻只约束了顶部和左右两侧,底部约束要等下一步的进度条出现后再补。

3. 添加播放进度区

第一步,在歌曲名称下方加入 SeekBar,先让它从封面右侧横跨到容器右缘。

app/src/main/res/layout/fragment_player.xml
<androidx.constraintlayout.widget.ConstraintLayout
...>
<!-- ... 省略已经完成的封面和歌曲名称。 -->
<SeekBar
android:id="@+id/progress_bar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="6dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/img_pic"
app:layout_constraintTop_toBottomOf="@id/tv_song_title" />
</androidx.constraintlayout.widget.ConstraintLayout>

第二步,在进度条左侧加入当前时间、右侧加入总时长,两者都与进度条上下对齐。

app/src/main/res/layout/fragment_player.xml
<androidx.constraintlayout.widget.ConstraintLayout
...>
<!-- ... 省略已经完成的封面、歌曲名称和进度条。 -->
<TextView
android:id="@+id/tv_bg_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:text="00:00"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@id/progress_bar"
app:layout_constraintStart_toEndOf="@id/img_pic"
app:layout_constraintTop_toTopOf="@id/progress_bar" />
<TextView
android:id="@+id/tv_end_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="00:00"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@id/progress_bar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/progress_bar" />
</androidx.constraintlayout.widget.ConstraintLayout>

第三步,回补两处约束:进度条的左右两端改贴两个时间文本,同时为 tv_song_title 添加底部约束。

app/src/main/res/layout/fragment_player.xml
<androidx.constraintlayout.widget.ConstraintLayout
...>
<TextView
android:id="@+id/tv_song_title"
...
app:layout_constraintBottom_toTopOf="@id/progress_bar"
/>
<!-- ... 省略已经完成的封面和时间文本。 -->
<SeekBar
android:id="@+id/progress_bar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="6dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintEnd_toStartOf="@id/tv_end_time"
app:layout_constraintStart_toEndOf="@id/img_pic"
app:layout_constraintStart_toEndOf="@id/tv_bg_time"
app:layout_constraintTop_toBottomOf="@id/tv_song_title" />
</androidx.constraintlayout.widget.ConstraintLayout>

两个时间控件分别位于进度条左右两侧,初始状态都显示“00:00”。

4. 添加播放控制按钮

第一步,在进度条下方加入”上一首”按钮,左侧贴住封面。

app/src/main/res/layout/fragment_player.xml
<androidx.constraintlayout.widget.ConstraintLayout
...>
<!-- ... 省略已经完成的封面、歌曲名称和进度区。 -->
<ImageButton
android:id="@+id/btn_pre"
android:layout_width="36dp"
android:layout_height="36dp"
android:background="@drawable/ic_last"
android:contentDescription="上一首"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/img_pic"
app:layout_constraintTop_toBottomOf="@id/progress_bar" />
</androidx.constraintlayout.widget.ConstraintLayout>

第二步,在”上一首”右侧加入”播放”按钮,并把”上一首”的右侧改为贴住”播放”,同时为这条横向链设置 packed 样式。

app/src/main/res/layout/fragment_player.xml
<androidx.constraintlayout.widget.ConstraintLayout
...>
<ImageButton
android:id="@+id/btn_pre"
...
app:layout_constraintEnd_toStartOf="@id/btn_play"
app:layout_constraintHorizontal_chainStyle="packed"
/>
<ImageButton
android:id="@+id/btn_play"
android:layout_width="36dp"
android:layout_height="36dp"
android:background="@drawable/ic_start"
android:contentDescription="播放或暂停"
app:layout_constraintBottom_toBottomOf="@id/btn_pre"
app:layout_constraintStart_toEndOf="@id/btn_pre"
app:layout_constraintTop_toTopOf="@id/btn_pre" />
</androidx.constraintlayout.widget.ConstraintLayout>

第三步,在”播放”右侧加入”下一首”按钮,右端贴住容器边缘,并把”播放”的右侧改为贴住”下一首”。

app/src/main/res/layout/fragment_player.xml
<androidx.constraintlayout.widget.ConstraintLayout
...>
<!-- ... 省略已经完成的"上一首"按钮。 -->
<ImageButton
android:id="@+id/btn_play"
...
app:layout_constraintEnd_toStartOf="@id/btn_next"
/>
<ImageButton
android:id="@+id/btn_next"
android:layout_width="36dp"
android:layout_height="36dp"
android:background="@drawable/ic_next"
android:contentDescription="下一首"
app:layout_constraintBottom_toBottomOf="@id/btn_play"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/btn_play"
app:layout_constraintTop_toTopOf="@id/btn_play" />
</androidx.constraintlayout.widget.ConstraintLayout>

三个按钮通过水平链居中排列。此时只显示图标,不处理点击事件。布局预览效果如下图所示:

底部播放控制布局完成后的预览效果

四、运行验证

运行应用后,底部控制区显示默认封面、歌曲占位文字“—”、起止时间“00:00”、进度条和三个控制按钮。

静态播放控制界面运行效果

任务小结

列表项和底部播放控制区已经具备完整的静态结构。文字、图标和进度控件目前只承担显示作用,下一任务将建立歌曲数据模型,为这些界面提供统一的数据来源。