任务二 搭建歌曲列表项与播放栏
任务一已经划分出歌曲列表区和底部控制区,本任务为两个区域完成静态布局:设计列表中的列表项,以及底部的播放栏。列表项就是歌曲列表中的一行,本任务里也简称”条目”。
一、任务目标
任务完成后,页面将具备以下静态控件:
- 列表项能够展示歌曲名称、歌手和操作图标。
- 播放控制区能够展示封面、歌曲名称、时间、进度条和三个控制按钮。
- 所有控件完成尺寸和约束配置,但暂不加载数据或响应点击。
二、设计列表项布局
1. 建立条目布局骨架
在 app/src/main/res/layout 目录中创建 recycler_view_item.xml,先使用 ConstraintLayout 建立条目容器。
<?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。
<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 图标和播放图标。
<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。
<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. 添加封面和歌曲名称
在根布局中加入默认封面和歌曲名称。
<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,先让它从封面右侧横跨到容器右缘。
<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>第二步,在进度条左侧加入当前时间、右侧加入总时长,两者都与进度条上下对齐。
<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 添加底部约束。
<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. 添加播放控制按钮
第一步,在进度条下方加入”上一首”按钮,左侧贴住封面。
<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 样式。
<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>第三步,在”播放”右侧加入”下一首”按钮,右端贴住容器边缘,并把”播放”的右侧改为贴住”下一首”。
<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”、进度条和三个控制按钮。
任务小结
列表项和底部播放控制区已经具备完整的静态结构。文字、图标和进度控件目前只承担显示作用,下一任务将建立歌曲数据模型,为这些界面提供统一的数据来源。