任务四 实现歌曲列表显示

接下来把歌曲列表项接入页面:RecyclerView 管理滚动列表,适配器负责把 Song 对象绑定到条目控件;这一步先用本地测试数据验证显示流程。

一、任务目标

做完这个任务,应用就能:

  • 使用 RecyclerView 展示可滚动的歌曲列表。
  • 通过适配器绑定歌曲名称、歌手和操作图标。
  • 使用本地测试数据验证列表布局和数据绑定。

二、添加 RecyclerView 依赖

打开 app/build.gradle.kts,在 dependencies 中加入 RecyclerView 依赖。

app/build.gradle.kts
dependencies {
// ... 省略已有依赖。
implementation("androidx.recyclerview:recyclerview:1.3.2")
}

保存文件并等待 Gradle 同步。RecyclerView 会回收并复用离开屏幕的条目视图,适合显示数量不固定的歌曲列表。

三、配置歌曲列表容器

打开 app/src/main/res/layout/fragment_music.xml,在 FrameLayout 中加入 RecyclerView,并为根布局补充 tools 命名空间。

app/src/main/res/layout/fragment_music.xml
<FrameLayout
xmlns:tools="http://schemas.android.com/tools"
...>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="@layout/recycler_view_item" />
</FrameLayout>

RecyclerView 填满 Fragment 的可用空间。tools:listitem 只用于 Android Studio 预览,运行时显示的条目由适配器决定。本步骤完成后,切换到预览视图即可看到重复排列的歌曲条目,如下图所示:

歌曲列表容器配置完成后的预览效果

四、编写列表适配器

1. 创建适配器和数据集合

在 app/src/main/java/com/sziit/musicplayer/ui 下创建 adapter 包,再创建 MusicListAdapter.kt。这一步先声明一个普通类,保存歌曲集合,并预留两个点击回调。

app/src/main/java/com/sziit/musicplayer/ui/adapter/MusicListAdapter.kt
package com.sziit.musicplayer.ui.adapter
import com.sziit.musicplayer.model.Song
class MusicListAdapter(
private val onMusicClick: (Song) -> Unit, // 点击歌曲条目时的回调
private val onMvClick: (Song) -> Unit // 点击 MV 图标时的回调
) {
// 当前展示的歌曲,数据更新时整体替换
private val musicList = mutableListOf<Song>()
}

两个回调分别负责歌曲和 MV 的点击处理;下一节创建适配器时先传入空实现,单元三任务三再接入播放逻辑。

2. 添加 ViewHolder

列表的每一行都需要一个容器来持有控件并绑定数据,这就是 ViewHolder。在 musicList 下方加入它,同时补充用到的导入。

app/src/main/java/com/sziit/musicplayer/ui/adapter/MusicListAdapter.kt
import android.view.View
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.sziit.musicplayer.R
class MusicListAdapter(
private val onMusicClick: (Song) -> Unit,
private val onMvClick: (Song) -> Unit
) {
private val musicList = mutableListOf<Song>()
// 一行列表项对应一个 ViewHolder:持有控件,并把歌曲写入控件
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val tvSong = itemView.findViewById<TextView>(R.id.tv_song)
private val tvSinger = itemView.findViewById<TextView>(R.id.tv_singer)
private val imgMv = itemView.findViewById<View>(R.id.img_mv)
// 显示歌曲信息,并设置点击监听。
fun bind(song: Song) {
tvSong.text = song.name
tvSinger.text = song.singer
itemView.setOnClickListener { onMusicClick(song) }
// 没有 MV 时隐藏图标,避免出现无效入口
imgMv.visibility = if (song.mvPath.isNullOrEmpty()) View.GONE else View.VISIBLE
imgMv.setOnClickListener { onMvClick(song) }
}
}
}

ViewHolder 构造时用 findViewById() 取出条目布局中的控件,bind() 把歌曲名称和歌手写入条目。没有 mvPath 时隐藏视频图标,避免向用户提供无效入口。

3. 继承 RecyclerView.Adapter 并实现三个方法

条目容器就绪,现在让 MusicListAdapter 继承 RecyclerView.Adapter。继承同时带来三个必须实现的方法,在这一步一并写出;泛型参数 <MusicListAdapter.ViewHolder> 告诉 RecyclerView 使用刚定义的 ViewHolder。

app/src/main/java/com/sziit/musicplayer/ui/adapter/MusicListAdapter.kt
import android.view.LayoutInflater
import android.view.ViewGroup
class MusicListAdapter(
private val onMusicClick: (Song) -> Unit,
private val onMvClick: (Song) -> Unit
) {
) : RecyclerView.Adapter<MusicListAdapter.ViewHolder>() {
private val musicList = mutableListOf<Song>()
// ... 省略已经完成的 ViewHolder。
// 创建新条目时加载布局,并返回持有控件的 ViewHolder。
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.recycler_view_item, parent, false)
return ViewHolder(view)
}
// 将当前位置的歌曲信息显示到条目中。
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(musicList[position])
}
// 返回列表条目总数。
override fun getItemCount() = musicList.size
}

onCreateViewHolder() 创建任务二设计的列表项,onBindViewHolder() 取出当前位置的歌曲并调用刚写好的 bind(),getItemCount() 返回歌曲数量。

4. 添加数据更新方法

在 musicList 下方添加 setData(),让 Fragment 能将歌曲列表传给适配器。

app/src/main/java/com/sziit/musicplayer/ui/adapter/MusicListAdapter.kt
class MusicListAdapter(
...
) : RecyclerView.Adapter<MusicListAdapter.ViewHolder>() {
private val musicList = mutableListOf<Song>()
// 用新一批歌曲替换当前数据,并通知列表刷新。
fun setData(list: List<Song>) {
musicList.clear()
musicList.addAll(list)
notifyDataSetChanged() // 继承自 RecyclerView.Adapter,触发列表重新绑定
}
// ... 省略已经完成的 ViewHolder 和三个适配器方法。
}

setData() 用新数据替换当前歌曲列表,再调用 notifyDataSetChanged() 通知 RecyclerView 重新显示条目。

五、在 Fragment 中显示测试数据

1. 改用 ViewBinding 加载布局

打开 MusicListFragment.kt,将任务一中的简写 Fragment 改为使用 FragmentMusicBinding。

app/src/main/java/com/sziit/musicplayer/ui/fragment/MusicListFragment.kt
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.sziit.musicplayer.R
import com.sziit.musicplayer.databinding.FragmentMusicBinding
import com.sziit.musicplayer.ui.adapter.MusicListAdapter
class MusicListFragment : Fragment(R.layout.fragment_music)
class MusicListFragment : Fragment() {
// 视图绑定和适配器,都在视图创建过程中完成赋值
private lateinit var binding: FragmentMusicBinding
private lateinit var adapter: MusicListAdapter
// 加载 Fragment 布局,返回根视图
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentMusicBinding.inflate(inflater, container, false)
return binding.root
}
}

FragmentMusicBinding 负责加载并访问列表布局,adapter 保存 RecyclerView 使用的适配器对象。

2. 初始化 RecyclerView

在 onCreateView() 后添加 onViewCreated()。

app/src/main/java/com/sziit/musicplayer/ui/fragment/MusicListFragment.kt
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
class MusicListFragment : Fragment() {
// ... 省略已有字段和 onCreateView()。
// 视图创建后初始化歌曲列表。
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 点击回调暂时传空实现,单元三任务三再接入播放逻辑
adapter = MusicListAdapter(onMusicClick = {}, onMvClick = {})
binding.recycler.layoutManager = LinearLayoutManager(requireContext())
binding.recycler.adapter = adapter
binding.recycler.addItemDecoration(
DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL)
)
}
}

布局管理器让条目纵向排列,分隔器在相邻歌曲之间绘制横线。列表初始化后还没有数据,接下来添加三首测试歌曲,检查条目能否显示。

3. 添加测试歌曲

在 MusicListFragment 中添加 sampleSongs(),并在 onViewCreated() 末尾将测试歌曲交给适配器。

app/src/main/java/com/sziit/musicplayer/ui/fragment/MusicListFragment.kt
import com.sziit.musicplayer.model.Song
class MusicListFragment : Fragment() {
// ... 省略已有字段和其他方法。
// 用于验证列表显示的测试数据。
private fun sampleSongs() = listOf(
Song(name = "晴天", singer = "周杰伦"),
Song(name = "起风了", singer = "买辣椒也用券"),
Song(name = "海阔天空", singer = "Beyond")
)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// ... 省略 RecyclerView 初始化代码。
adapter.setData(sampleSongs())
}
}

sampleSongs() 只用于验证列表显示,下一任务会由在线接口替换。

六、运行验证

运行应用后,标题栏下方显示三条测试歌曲,每条包含歌曲名称、歌手和播放图标,条目之间显示分隔线;底部控制区仍保持静态状态。

歌曲列表显示效果

将应用运行到设备,打开后检查:

  1. 是否显示“晴天”“起风了”“海阔天空”三条歌曲。
  2. 歌曲名称和歌手是否分别显示在两行。
  3. 条目之间是否显示分隔线。
  4. 点击歌曲或图标时暂时没有播放动作,这是预期结果。

如果列表为空,检查 recycler ID、setData() 调用和 sampleSongs() 返回值;如果应用无法编译,检查 RecyclerView 依赖是否已经同步。

任务小结

RecyclerView、布局管理器和适配器已经协同工作,Song 对象可以转换为可滚动的列表项。当前数据来自 Fragment 内的测试列表,下一任务将把它替换为服务器返回的在线歌曲。