任务四 实现歌曲列表显示
接下来把歌曲列表项接入页面:RecyclerView 管理滚动列表,适配器负责把 Song 对象绑定到条目控件;这一步先用本地测试数据验证显示流程。
一、任务目标
做完这个任务,应用就能:
- 使用 RecyclerView 展示可滚动的歌曲列表。
- 通过适配器绑定歌曲名称、歌手和操作图标。
- 使用本地测试数据验证列表布局和数据绑定。
二、添加 RecyclerView 依赖
打开 app/build.gradle.kts,在 dependencies 中加入 RecyclerView 依赖。
dependencies { // ... 省略已有依赖。 implementation("androidx.recyclerview:recyclerview:1.3.2")}保存文件并等待 Gradle 同步。RecyclerView 会回收并复用离开屏幕的条目视图,适合显示数量不固定的歌曲列表。
三、配置歌曲列表容器
打开 app/src/main/res/layout/fragment_music.xml,在 FrameLayout 中加入 RecyclerView,并为根布局补充 tools 命名空间。
<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。这一步先声明一个普通类,保存歌曲集合,并预留两个点击回调。
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 下方加入它,同时补充用到的导入。
import android.view.Viewimport android.widget.TextViewimport androidx.recyclerview.widget.RecyclerViewimport 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。
import android.view.LayoutInflaterimport 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 能将歌曲列表传给适配器。
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。
import android.os.Bundleimport android.view.LayoutInflaterimport android.view.Viewimport android.view.ViewGroupimport com.sziit.musicplayer.Rimport com.sziit.musicplayer.databinding.FragmentMusicBindingimport 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()。
import androidx.recyclerview.widget.DividerItemDecorationimport 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() 末尾将测试歌曲交给适配器。
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() 只用于验证列表显示,下一任务会由在线接口替换。
六、运行验证
运行应用后,标题栏下方显示三条测试歌曲,每条包含歌曲名称、歌手和播放图标,条目之间显示分隔线;底部控制区仍保持静态状态。
将应用运行到设备,打开后检查:
- 是否显示“晴天”“起风了”“海阔天空”三条歌曲。
- 歌曲名称和歌手是否分别显示在两行。
- 条目之间是否显示分隔线。
- 点击歌曲或图标时暂时没有播放动作,这是预期结果。
如果列表为空,检查 recycler ID、setData() 调用和 sampleSongs() 返回值;如果应用无法编译,检查 RecyclerView 依赖是否已经同步。
任务小结
RecyclerView、布局管理器和适配器已经协同工作,Song 对象可以转换为可滚动的列表项。当前数据来自 Fragment 内的测试列表,下一任务将把它替换为服务器返回的在线歌曲。