Android Adapter 实战指南:从基础用法到性能优化,一篇吃透列表适配

目录

前言:

一、先搞懂:Adapter 到底是什么?

1.1 Adapter 的核心工作流程

1.2 Adapter 与 AdapterView 的关系

二、新手入门:3 个基础 Adapter 快速上手

2.1 ArrayAdapter:最简单的 “文本列表” 实现

核心特点

实战代码(Kotlin/Java 双版本)

布局文件(activity_array_adapter.xml)

注意事项

2.2 SimpleAdapter:快速实现 “图文混合” 列表

核心特点

实战代码:联系人列表(图文混合)

列表项布局(item_simple_adapter.xml)

注意事项

2.3 BaseAdapter:自定义程度最高的 “万能基础 Adapter”

核心方法说明

实战代码:带点赞功能的新闻列表

步骤 1:定义数据模型

步骤 2:列表项布局(item_base_adapter.xml)

步骤 3:自定义 BaseAdapter

步骤 4:Activity 中使用 Adapter

关键优化点:ViewHolder 模式

三、进阶必备:RecyclerView.Adapter 万能实战

3.1 RecyclerView.Adapter 的核心优势

3.2 基础实战:简单列表实现

步骤 1:添加依赖(AndroidX 项目)

步骤 2:Activity 布局(activity_recycler_base.xml)

步骤 3:列表项布局(item_recycler_base.xml)

步骤 4:自定义 RecyclerView.Adapter

步骤 5:Activity 中初始化 RecyclerView

3.3 高级用法 1:多类型布局(如 Feed 流、首页混合列表)

实战代码:包含 Banner、文章、Footer 的混合列表

步骤 1:定义数据模型(密封类统一管理多类型数据)

步骤 2:定义三种布局

步骤 3:自定义多类型 Adapter

步骤 4:Activity 中初始化

3.4 高级用法 2:局部刷新(避免整列表重绘)

核心工具:DiffUtil

实战代码:用 DiffUtil 实现局部刷新

步骤 1:定义数据模型(需实现 equals 和 hashCode)

步骤 2:创建 DiffUtil.Callback 实现类

步骤 3:优化 Adapter,支持局部刷新

步骤 4:Activity 中测试局部刷新

效果说明

四、性能优化:让列表滑动如丝般顺滑

4.1 必做优化:复用视图 + ViewHolder 模式

4.2 进阶优化:使用 DiffUtil 替代 notifyDataSetChanged ()

4.3 高级优化:分批加载数据(分页加载)

核心代码示例

4.4 图片优化:延迟加载 + 图片缓存

示例代码(Glide 加载优化)

4.5 布局优化:减少层级 + 避免过度绘制

五、常见问题与避坑指南

5.1 问题 1:列表滑动时数据错乱

原因

解决方案

5.2 问题 2:notifyDataSetChanged () 不生效

原因

解决方案

5.3 问题 3:RecyclerView 没有 ItemClickListener

原因

解决方案

代码示例

5.4 问题 4:刷新后 setBackgroundColor 不生效

原因

解决方案

代码示例

六、实战综合案例:完整的网络数据列表

6.1 案例需求

6.2 核心依赖

6.3 核心代码

步骤 1:网络请求相关(API 接口、数据模型)

步骤 2:Adapter 实现(支持点赞局部刷新)

步骤 3:Activity 实现(下拉刷新 + 上拉加载)

步骤 4:布局文件

七、总结与拓展

7.1 核心知识点回顾

7.2 拓展学习方向

7.3 实战建议


class 卑微码农:
    def __init__(self):
        self.技能 = ['能读懂十年前祖传代码', '擅长用Ctrl+C/V搭建世界', '信奉"能跑就别动"的玄学']
        self.发量 = 100  # 初始发量
        self.咖啡因耐受度 = '极限'
        
    def 修Bug(self, bug):
        try:
            # 试图用玄学解决问题
            if bug.严重程度 == '离谱':
                print("这一定是环境问题!")
            else:
                print("让我看看是谁又没写注释...哦,是我自己。")
        except Exception as e:
            # 如果try块都救不了,那就...
            print("重启一下试试?")
            self.发量 -= 1  # 每解决一个bug,头发-1
 
 
# 实例化一个我
我 = 卑微码农()

前言:

作为 Android 开发中连接数据与视图的核心组件,Adapter 是每个开发者绕不开的知识点。新手常被 “convertView 复用”“ViewHolder 优化”“多类型布局” 等概念劝退,老手也可能在性能优化或复杂场景中踩坑。这篇文章从实战角度出发,用通俗的语言 + 可直接运行的代码,带你从基础到高级,彻底掌握 Android Adapter 的全部核心用法。

一、先搞懂:Adapter 到底是什么?

很多新手刚接触时会觉得 Adapter 抽象,其实一句话就能说透:Adapter 是 “数据” 和 “视图” 之间的 “翻译官”+“搬运工”

数据可能是接口返回的列表、本地数据库的查询结果,视图可能是 ListView、RecyclerView 这类列表控件。数据不懂视图的 “语言”(不知道怎么展示),视图也不懂数据的 “格式”(不知道怎么读取),而 Adapter 要做两件事:一是把数据转换成视图能识别的格式,二是高效地把转换后的视图交给列表控件展示。

1.1 Adapter 的核心工作流程

用一个生活化的例子理解:把列表控件想象成超市货架,数据是仓库里的商品,Adapter 就是理货员。

  1. 货架(ListView/RecyclerView)告诉理货员(Adapter):我需要多少商品(getCount ());
  2. 理货员去仓库(数据源)取出对应商品(getItem ());
  3. 理货员把商品装进统一的包装盒(View),贴上价格标签(绑定数据);
  4. 理货员把包装好的商品放到货架指定位置(展示视图);
  5. 商品卖完或补货时,理货员及时更新货架(notifyDataSetChanged ())。

1.2 Adapter 与 AdapterView 的关系

Adapter 不能单独工作,必须配合 “容器控件” 使用,这些容器控件统称为 AdapterView,常见的有:

  • ListView:早期主流列表控件,支持线性布局;
  • GridView:网格布局列表(如九宫格);
  • Spinner:下拉选择框;
  • RecyclerView:Android 5.0 后推出的万能列表控件,支持线性、网格、瀑布流等多种布局。

其中 RecyclerView 凭借模块化设计和优秀性能,现已成为开发首选,后续实战部分会重点围绕它展开。


二、新手入门:3 个基础 Adapter 快速上手

Android 系统提供了多个现成的基础 Adapter,无需自定义就能实现简单列表需求。新手可以从这几个开始,感受 Adapter 的工作逻辑。

2.1 ArrayAdapter:最简单的 “文本列表” 实现

ArrayAdapter 是最基础的 Adapter,专门用于展示单一文本类型的列表,无需自定义布局(也可自定义),一行代码就能搞定。

核心特点
  • 支持字符串数组、List<String> 等简单数据源;
  • 默认使用系统自带的文本布局(android.R.layout.simple_list_item_1);
  • 适合快速实现纯文本列表(如设置选项、菜单列表)。
实战代码(Kotlin/Java 双版本)
// Kotlin 版本
class ArrayAdapterActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_array_adapter)
        
        val listView = findViewById<ListView>(R.id.lv_array)
        // 1. 数据源:字符串列表
        val dataList = listOf("Android 基础", "Java 核心", "Kotlin 进阶", "RecyclerView 实战", "性能优化")
        
        // 2. 创建 ArrayAdapter:参数依次是上下文、布局资源、数据源
        val adapter = ArrayAdapter(
            this,
            android.R.layout.simple_list_item_1, // 系统默认文本布局
            dataList
        )
        
        // 3. 给 ListView 设置 Adapter
        listView.adapter = adapter
        
        // 4. 列表项点击事件
        listView.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ ->
            Toast.makeText(this, "选中了:${dataList[position]}", Toast.LENGTH_SHORT).show()
        }
    }
}
// Java 版本
public class ArrayAdapterActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_array_adapter);
        
        ListView listView = findViewById(R.id.lv_array);
        // 1. 数据源
        List<String> dataList = new ArrayList<>();
        dataList.add("Android 基础");
        dataList.add("Java 核心");
        dataList.add("Kotlin 进阶");
        dataList.add("RecyclerView 实战");
        dataList.add("性能优化");
        
        // 2. 创建 ArrayAdapter
        ArrayAdapter<String> adapter = new ArrayAdapter<>(
            this,
            android.R.layout.simple_list_item_1,
            dataList
        );
        
        // 3. 设置 Adapter
        listView.setAdapter(adapter);
        
        // 4. 点击事件
        listView.setOnItemClickListener((parent, view, position, id) -> {
            Toast.makeText(ArrayAdapterActivity.this, "选中了:" + dataList.get(position), Toast.LENGTH_SHORT).show();
        });
    }
}
布局文件(activity_array_adapter.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ListView
        android:id="@+id/lv_array"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>
注意事项
  • 系统默认布局仅支持文本,如果需要展示图片 + 文本,需自定义布局并使用 ArrayAdapter 的重载构造;
  • 数据源变化时,需调用 adapter.notifyDataSetChanged() 通知视图更新(后续所有 Adapter 均适用)。

2.2 SimpleAdapter:快速实现 “图文混合” 列表

如果需要展示图文混合的列表(如新闻标题 + 缩略图、联系人头像 + 姓名),SimpleAdapter 是比 ArrayAdapter 更合适的选择。它支持将 Map 类型的数据源映射到 XML 布局的控件中,无需自定义 Adapter。

核心特点
  • 支持多控件展示(TextView、ImageView 等);
  • 数据源为 List<Map<String, Object>>,键值对形式便于映射;
  • 无需重写 Adapter,仅需配置映射关系即可。
实战代码:联系人列表(图文混合)
// Kotlin 版本
class SimpleAdapterActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_simple_adapter)
        
        val listView = findViewById<ListView>(R.id.lv_simple)
        // 1. 数据源:List<Map> 形式,每个 Map 对应一个列表项
        val dataList = mutableListOf<Map<String, Any>>()
        dataList.add(mapOf(
            "avatar" to R.drawable.avatar1, // 头像资源ID
            "name" to "张三",
            "phone" to "13800138000"
        ))
        dataList.add(mapOf(
            "avatar" to R.drawable.avatar2,
            "name" to "李四",
            "phone" to "13900139000"
        ))
        dataList.add(mapOf(
            "avatar" to R.drawable.avatar3,
            "name" to "王五",
            "phone" to "13700137000"
        ))
        
        // 2. 定义布局控件的 key(与 Map 的 key 对应)
        val from = arrayOf("avatar", "name", "phone")
        // 3. 定义布局控件的 ID(与 from 数组顺序对应)
        val to = intArrayOf(R.id.iv_avatar, R.id.tv_name, R.id.tv_phone)
        
        // 4. 创建 SimpleAdapter
        val adapter = SimpleAdapter(
            this,
            dataList,
            R.layout.item_simple_adapter, // 自定义列表项布局
            from,
            to
        )
        
        // 5. 设置 Adapter
        listView.adapter = adapter
    }
}
列表项布局(item_simple_adapter.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="80dp"
    android:gravity="center_vertical"
    android:paddingHorizontal="16dp"
    android:orientation="horizontal">

    <!-- 头像 -->
    <ImageView
        android:id="@+id/iv_avatar"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:scaleType="centerCrop"
        android:background="@drawable/shape_circle" />

    <!-- 姓名和电话容器 -->
    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:layout_marginStart="16dp"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tv_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:textStyle="bold" />

        <TextView
            android:id="@+id/tv_phone"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="4dp"
            android:textSize="14sp"
            android:textColor="@color/gray" />
    </LinearLayout>
</LinearLayout>
注意事项
  • SimpleAdapter 仅支持静态数据映射,不适合动态修改控件样式(如根据状态改变颜色);
  • ImageView 仅支持本地资源(drawable/mipmap),若需加载网络图片,需自定义 Adapter 或使用图片加载库(如 Glide)。

2.3 BaseAdapter:自定义程度最高的 “万能基础 Adapter”

ArrayAdapter 和 SimpleAdapter 适合简单场景,但实际开发中,列表项往往包含复杂布局、动态样式或交互逻辑(如按钮点击、状态切换),这时就需要自定义 BaseAdapter。

BaseAdapter 是所有 Adapter 的基类,需要重写 4 个核心方法,灵活性最高,也是新手必须掌握的基础技能。

核心方法说明
方法名作用
getCount()返回数据源的长度(列表项数量)
getItem(position: Int)返回指定位置的数据源对象
getItemId(position: Int)返回指定位置的列表项 ID(通常返回 position)
getView(position: Int, convertView: View?, parent: ViewGroup)创建 / 复用列表项视图,并绑定数据
实战代码:带点赞功能的新闻列表
步骤 1:定义数据模型
// 新闻数据模型
data class NewsModel(
    val title: String, // 新闻标题
    val content: String, // 新闻摘要
    val likeCount: Int, // 点赞数
    var isLiked: Boolean // 是否已点赞(可变状态)
)
步骤 2:列表项布局(item_base_adapter.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="16dp"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tv_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="18sp"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/tv_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:textSize="14sp"
        android:textColor="@color/gray"
        android:maxLines="2"
        android:ellipsize="end" />

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="12dp"
        android:gravity="center_vertical"
        android:orientation="horizontal">

        <ImageView
            android:id="@+id/iv_like"
            android:layout_width="20dp"
            android:layout_height="20dp"
            android:src="@drawable/ic_like_normal" />

        <TextView
            android:id="@+id/tv_like_count"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:textSize="14sp" />
    </LinearLayout>
</LinearLayout>
步骤 3:自定义 BaseAdapter
class NewsBaseAdapter(
    private val context: Context,
    private val dataList: MutableList<NewsModel>
) : BaseAdapter() {

    // 1. 返回列表项数量
    override fun getCount(): Int = dataList.size

    // 2. 返回指定位置的数据源
    override fun getItem(position: Int): NewsModel = dataList[position]

    // 3. 返回列表项 ID
    override fun getItemId(position: Int): Long = position.toLong()

    // 4. 创建/复用视图并绑定数据(核心方法)
    override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
        // ViewHolder 用于缓存控件,避免重复 findViewById
        val holder: ViewHolder
        val view: View

        if (convertView == null) {
            // 第一次创建视图:加载布局、初始化控件、绑定 ViewHolder
            view = LayoutInflater.from(context).inflate(R.layout.item_base_adapter, parent, false)
            holder = ViewHolder()
            holder.tvTitle = view.findViewById(R.id.tv_title)
            holder.tvContent = view.findViewById(R.id.tv_content)
            holder.ivLike = view.findViewById(R.id.iv_like)
            holder.tvLikeCount = view.findViewById(R.id.tv_like_count)
            // 将 ViewHolder 存储到 View 中
            view.tag = holder
        } else {
            // 复用已有视图:直接从 View 中获取 ViewHolder
            view = convertView
            holder = view.tag as ViewHolder
        }

        // 绑定数据
        val news = getItem(position)
        holder.tvTitle?.text = news.title
        holder.tvContent?.text = news.content
        holder.tvLikeCount?.text = news.likeCount.toString()

        // 设置点赞状态
        if (news.isLiked) {
            holder.ivLike?.setImageResource(R.drawable.ic_like_selected)
            holder.tvLikeCount?.setTextColor(ContextCompat.getColor(context, R.color.red))
        } else {
            holder.ivLike?.setImageResource(R.drawable.ic_like_normal)
            holder.tvLikeCount?.setTextColor(ContextCompat.getColor(context, R.color.gray))
        }

        // 点赞点击事件
        holder.ivLike?.setOnClickListener {
            news.isLiked = !news.isLiked
            val newCount = if (news.isLiked) news.likeCount + 1 else news.likeCount - 1
            news.likeCount = newCount
            // 通知 Adapter 数据变化,更新视图
            notifyDataSetChanged()
        }

        return view
    }

    // ViewHolder 静态内部类:缓存列表项控件
    private class ViewHolder {
        var tvTitle: TextView? = null
        var tvContent: TextView? = null
        var ivLike: ImageView? = null
        var tvLikeCount: TextView? = null
    }
}
步骤 4:Activity 中使用 Adapter
class BaseAdapterActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_base_adapter)
        
        val listView = findViewById<ListView>(R.id.lv_base)
        // 初始化数据源
        val dataList = mutableListOf<NewsModel>().apply {
            add(NewsModel("Android 14 正式发布,这些新特性值得关注", "谷歌推出的 Android 14 带来了更灵活的权限管理、更好的性能优化...", 128, false))
            add(NewsModel("RecyclerView 性能优化实战", "掌握这 5 个技巧,让你的列表滑动如丝般顺滑...", 89, false))
            add(NewsModel("Kotlin 协程入门到精通", "协程是 Kotlin 中处理异步任务的核心工具,本文带你快速上手...", 215, true))
        }
        
        // 创建 Adapter 并设置
        val adapter = NewsBaseAdapter(this, dataList)
        listView.adapter = adapter
    }
}
关键优化点:ViewHolder 模式

这是 BaseAdapter 中最核心的优化手段,作用是缓存列表项的控件实例,避免每次调用 getView () 时都执行 findViewById ()

  • 没有 ViewHolder 时:每次滑动列表都会重新查找控件,findViewById () 是耗时操作,会导致滑动卡顿;
  • 有 ViewHolder 时:控件实例被缓存到 ViewHolder 中,复用视图时直接取出,大幅提升性能。

三、进阶必备:RecyclerView.Adapter 万能实战

RecyclerView 是 Android 官方推荐的列表控件,它的 Adapter 设计更先进、性能更优,支持线性、网格、瀑布流等多种布局,是目前开发的首选。

3.1 RecyclerView.Adapter 的核心优势

相比 BaseAdapter,RecyclerView.Adapter 有 3 个关键升级:

  1. 强制 ViewHolder 模式:无需手动判断 convertView 是否为 null,系统自动管理视图复用;
  2. 职责分离:布局排列(LayoutManager)、分割线(ItemDecoration)、动画(ItemAnimator)等功能独立,灵活扩展;
  3. 分级缓存机制:设计了三级缓存池,视图复用效率远超 ListView+BaseAdapter。

3.2 基础实战:简单列表实现

步骤 1:添加依赖(AndroidX 项目)

RecyclerView 属于 AndroidX 库,需在 build.gradle(Module 级)中添加依赖:

dependencies {
    implementation 'androidx.recyclerview:recyclerview:1.3.2'
}
步骤 2:Activity 布局(activity_recycler_base.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_base"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingHorizontal="16dp" />
</LinearLayout>
步骤 3:列表项布局(item_recycler_base.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="60dp"
    android:gravity="center_vertical"
    android:borderBottomWidth="1dp"
    android:borderBottomColor="@color/gray_light">

    <TextView
        android:id="@+id/tv_item"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="16sp" />
</LinearLayout>
步骤 4:自定义 RecyclerView.Adapter
class BaseRecyclerAdapter(
    private val dataList: List<String>
) : RecyclerView.Adapter<BaseRecyclerAdapter.BaseViewHolder>() {

    // 1. 创建 ViewHolder(对应 BaseAdapter 的 convertView == null 时)
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_recycler_base, parent, false)
        return BaseViewHolder(view)
    }

    // 2. 绑定数据(对应 BaseAdapter 的 getView 中绑定数据部分)
    override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
        holder.tvItem.text = dataList[position]
        // 列表项点击事件
        holder.itemView.setOnClickListener {
            Toast.makeText(holder.itemView.context, "选中:${dataList[position]}", Toast.LENGTH_SHORT).show()
        }
    }

    // 3. 返回列表项数量
    override fun getItemCount(): Int = dataList.size

    // 自定义 ViewHolder(与 BaseAdapter 的 ViewHolder 作用一致)
    class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val tvItem: TextView = itemView.findViewById(R.id.tv_item)
    }
}
步骤 5:Activity 中初始化 RecyclerView
class RecyclerBaseActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_recycler_base)
        
        val recyclerView = findViewById<RecyclerView>(R.id.rv_base)
        // 1. 设置布局管理器(线性布局,垂直方向)
        recyclerView.layoutManager = LinearLayoutManager(this)
        // 2. 初始化数据源
        val dataList = listOf("首页", "分类", "发现", "我的", "收藏", "历史", "设置")
        // 3. 创建并设置 Adapter
        val adapter = BaseRecyclerAdapter(dataList)
        recyclerView.adapter = adapter
    }
}

3.3 高级用法 1:多类型布局(如 Feed 流、首页混合列表)

实际开发中常遇到 “一个列表包含多种布局” 的场景(如首页的 Banner 轮播 + 文章列表 + 推荐卡片),这就需要通过 getItemViewType() 实现多类型布局。

实战代码:包含 Banner、文章、Footer 的混合列表
步骤 1:定义数据模型(密封类统一管理多类型数据)
// 密封类:统一多类型数据模型
sealed class FeedItem {
    // Banner 类型:包含轮播图图片列表
    data class BannerItem(val images: List<String>) : FeedItem()
    // 文章类型:包含标题、作者、阅读数
    data class ArticleItem(val title: String, val author: String, val readCount: Int) : FeedItem()
    // Footer 类型:包含底部提示文本
    data class FooterItem(val text: String) : FeedItem()
}
步骤 2:定义三种布局
  • Banner 布局(item_feed_banner.xml):
<ImageView
    android:id="@+id/iv_banner"
    android:layout_width="match_parent"
    android:layout_height="180dp"
    android:scaleType="centerCrop" />
  • 文章布局(item_feed_article.xml):
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="16dp"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tv_article_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="18sp"
        android:textStyle="bold" />

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/tv_article_author"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="14sp"
            android:textColor="@color/gray" />

        <TextView
            android:id="@+id/tv_article_read"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:textSize="14sp"
            android:textColor="@color/gray" />
    </LinearLayout>
</LinearLayout>
  • Footer 布局(item_feed_footer.xml):
<TextView
    android:id="@+id/tv_footer"
    android:layout_width="match_parent"
    android:layout_height="60dp"
    android:gravity="center"
    android:textSize="14sp"
    android:textColor="@color/gray" />
步骤 3:自定义多类型 Adapter
class FeedMultiTypeAdapter(
    private val dataList: List<FeedItem>
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    // 定义布局类型常量
    companion object {
        private const val TYPE_BANNER = 1
        private const val TYPE_ARTICLE = 2
        private const val TYPE_FOOTER = 3
    }

    // 1. 返回当前位置的布局类型
    override fun getItemViewType(position: Int): Int {
        return when (dataList[position]) {
            is FeedItem.BannerItem -> TYPE_BANNER
            is FeedItem.ArticleItem -> TYPE_ARTICLE
            is FeedItem.FooterItem -> TYPE_FOOTER
        }
    }

    // 2. 根据布局类型创建对应的 ViewHolder
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return when (viewType) {
            TYPE_BANNER -> {
                val view = LayoutInflater.from(parent.context)
                    .inflate(R.layout.item_feed_banner, parent, false)
                BannerViewHolder(view)
            }
            TYPE_ARTICLE -> {
                val view = LayoutInflater.from(parent.context)
                    .inflate(R.layout.item_feed_article, parent, false)
                ArticleViewHolder(view)
            }
            TYPE_FOOTER -> {
                val view = LayoutInflater.from(parent.context)
                    .inflate(R.layout.item_feed_footer, parent, false)
                FooterViewHolder(view)
            }
            else -> throw IllegalArgumentException("未知布局类型")
        }
    }

    // 3. 根据 ViewHolder 类型绑定对应数据
    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val item = dataList[position]
        when (holder) {
            is BannerViewHolder -> {
                val bannerItem = item as FeedItem.BannerItem
                // 这里用 Glide 加载网络图片(需添加 Glide 依赖)
                Glide.with(holder.itemView.context)
                    .load(bannerItem.images[0]) // 简化:加载第一张图
                    .into(holder.ivBanner)
            }
            is ArticleViewHolder -> {
                val articleItem = item as FeedItem.ArticleItem
                holder.tvTitle.text = articleItem.title
                holder.tvAuthor.text = articleItem.author
                holder.tvRead.text = "阅读 ${articleItem.readCount}"
            }
            is FooterViewHolder -> {
                val footerItem = item as FeedItem.FooterItem
                holder.tvFooter.text = footerItem.text
            }
        }
    }

    // 4. 返回列表项数量
    override fun getItemCount(): Int = dataList.size

    // Banner 对应的 ViewHolder
    class BannerViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val ivBanner: ImageView = itemView.findViewById(R.id.iv_banner)
    }

    // 文章对应的 ViewHolder
    class ArticleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val tvTitle: TextView = itemView.findViewById(R.id.tv_article_title)
        val tvAuthor: TextView = itemView.findViewById(R.id.tv_article_author)
        val tvRead: TextView = itemView.findViewById(R.id.tv_article_read)
    }

    // Footer 对应的 ViewHolder
    class FooterViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val tvFooter: TextView = itemView.findViewById(R.id.tv_footer)
    }
}
步骤 4:Activity 中初始化
class FeedMultiTypeActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_feed_multi_type)
        
        val recyclerView = findViewById<RecyclerView>(R.id.rv_feed)
        recyclerView.layoutManager = LinearLayoutManager(this)
        
        // 初始化多类型数据源
        val dataList = listOf<FeedItem>(
            FeedItem.BannerItem(listOf(
                "https://picsum.photos/800/400?1",
                "https://picsum.photos/800/400?2",
                "https://picsum.photos/800/400?3"
            )),
            FeedItem.ArticleItem("Android Adapter 完全指南", "技术干货君", 1234),
            FeedItem.ArticleItem("RecyclerView 性能优化实战", "Android 开发笔记", 897),
            FeedItem.ArticleItem("Kotlin 密封类在多类型布局中的应用", "编程小助手", 562),
            FeedItem.FooterItem("已加载全部内容")
        )
        
        val adapter = FeedMultiTypeAdapter(dataList)
        recyclerView.adapter = adapter
    }
}

3.4 高级用法 2:局部刷新(避免整列表重绘)

传统的 notifyDataSetChanged() 会刷新整个列表,导致不必要的性能消耗,还可能出现视图闪烁。RecyclerView 提供了 notifyItemChanged() 等精准刷新方法,结合 DiffUtil 可以实现 “只刷新变化的数据”。

核心工具:DiffUtil

DiffUtil 是 Android 支持库提供的工具类,能自动计算新旧数据集的差异,只更新变化的项,无需手动判断哪些数据变了。

实战代码:用 DiffUtil 实现局部刷新
步骤 1:定义数据模型(需实现 equals 和 hashCode)
data class UserModel(
    val id: Long, // 唯一标识(关键)
    val name: String,
    val age: Int
)
步骤 2:创建 DiffUtil.Callback 实现类
class UserDiffCallback(
    private val oldList: List<UserModel>,
    private val newList: List<UserModel>
) : DiffUtil.Callback() {

    // 1. 判断两个 item 是否是同一个对象(通过唯一标识 id)
    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return oldList[oldItemPosition].id == newList[newItemPosition].id
    }

    // 2. 判断两个 item 的内容是否相同
    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return oldList[oldItemPosition] == newList[newItemPosition]
    }

    // 3. 返回变化的 payload(可选,用于局部刷新特定字段)
    override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
        val oldItem = oldList[oldItemPosition]
        val newItem = newList[newItemPosition]
        // 只返回变化的字段(如姓名或年龄)
        val payload = mutableMapOf<String, Any>()
        if (oldItem.name != newItem.name) {
            payload["name"] = newItem.name
        }
        if (oldItem.age != newItem.age) {
            payload["age"] = newItem.age
        }
        return if (payload.isEmpty()) null else payload
    }

    // 4. 返回旧列表长度
    override fun getOldListSize(): Int = oldList.size

    // 5. 返回新列表长度
    override fun getNewListSize(): Int = newList.size
}
步骤 3:优化 Adapter,支持局部刷新
class UserAdapter : RecyclerView.Adapter<UserAdapter.UserViewHolder>() {

    private var dataList: List<UserModel> = emptyList()

    // 更新数据:使用 DiffUtil 计算差异并刷新
    fun submitList(newList: List<UserModel>) {
        val diffResult = DiffUtil.calculateDiff(UserDiffCallback(dataList, newList))
        dataList = newList
        diffResult.dispatchUpdatesTo(this) // 只刷新变化的项
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_user, parent, false)
        return UserViewHolder(view)
    }

    // 重载 onBindViewHolder,支持 payload 局部刷新
    override fun onBindViewHolder(holder: UserViewHolder, position: Int, payloads: MutableList<Any>) {
        if (payloads.isEmpty()) {
            // payload 为空:完整绑定数据
            super.onBindViewHolder(holder, position, payloads)
        } else {
            // payload 不为空:只刷新变化的字段
            val user = dataList[position]
            val payload = payloads[0] as Map<String, Any>
            payload.forEach { (key, value) ->
                when (key) {
                    "name" -> holder.tvName.text = value as String
                    "age" -> holder.tvAge.text = "年龄:${value as Int}"
                }
            }
        }
    }

    override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
        val user = dataList[position]
        holder.tvId.text = "ID:${user.id}"
        holder.tvName.text = user.name
        holder.tvAge.text = "年龄:${user.age}"
    }

    override fun getItemCount(): Int = dataList.size

    class UserViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val tvId: TextView = itemView.findViewById(R.id.tv_id)
        val tvName: TextView = itemView.findViewById(R.id.tv_name)
        val tvAge: TextView = itemView.findViewById(R.id.tv_age)
    }
}
步骤 4:Activity 中测试局部刷新
class DiffUtilActivity : AppCompatActivity() {
    private lateinit var adapter: UserAdapter
    private val initialList = listOf(
        UserModel(1, "张三", 25),
        UserModel(2, "李四", 28),
        UserModel(3, "王五", 30)
    )

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_diff_util)
        
        val recyclerView = findViewById<RecyclerView>(R.id.rv_user)
        recyclerView.layoutManager = LinearLayoutManager(this)
        adapter = UserAdapter()
        adapter.submitList(initialList)
        recyclerView.adapter = adapter
        
        // 点击按钮更新数据(只修改张三的年龄和李四的姓名)
        findViewById<Button>(R.id.btn_update).setOnClickListener {
            val newList = listOf(
                UserModel(1, "张三", 26), // 年龄变化
                UserModel(2, "李四同学", 28), // 姓名变化
                UserModel(3, "王五", 30) // 无变化
            )
            adapter.submitList(newList)
        }
    }
}
效果说明

点击按钮后,只有张三的年龄和李四的姓名会刷新,王五的视图不会重新绘制,相比 notifyDataSetChanged() 大幅提升性能,尤其适合大数据量列表。


四、性能优化:让列表滑动如丝般顺滑

Adapter 的性能直接影响列表的滑动体验,以下是 5 个实战性极强的优化技巧,覆盖从基础到高级的全场景。

4.1 必做优化:复用视图 + ViewHolder 模式

这是最基础也是最重要的优化,前面的示例中已经用到,核心原则是:

  • 避免重复创建 View:通过 convertView(BaseAdapter)或 RecyclerView 自带的复用机制,重复使用已创建的视图;
  • 避免重复 findViewById:用 ViewHolder 缓存控件实例,一次查找多次使用。

4.2 进阶优化:使用 DiffUtil 替代 notifyDataSetChanged ()

如 3.4 节所示,notifyDataSetChanged() 会强制刷新整个列表,而 DiffUtil 只刷新变化的项,减少 UI 重绘和数据绑定的开销。

4.3 高级优化:分批加载数据(分页加载)

当列表数据量较大(如几百条、几千条)时,一次性加载所有数据会导致初始化缓慢、内存占用过高。分批加载(分页)是解决这个问题的关键:

  1. 首次加载前 N 条数据(如 20 条);
  2. 列表滑动到底部时,加载下一页数据;
  3. 用 notifyItemRangeInserted() 刷新新增数据(避免整列表刷新)。
核心代码示例
class PagingAdapter : RecyclerView.Adapter<PagingAdapter.PagingViewHolder>() {
    private val dataList = mutableListOf<String>()
    private var isLoading = false // 避免重复请求

    // 添加分页数据
    fun addData(newData: List<String>) {
        val startPosition = dataList.size
        dataList.addAll(newData)
        notifyItemRangeInserted(startPosition, newData.size)
        isLoading = false
    }

    // 标记加载中状态
    fun setLoading(loading: Boolean) {
        isLoading = loading
    }

    // ... 其他方法(onCreateViewHolder、onBindViewHolder 等)

    class PagingViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val tvContent: TextView = itemView.findViewById(R.id.tv_content)
    }
}

// Activity 中监听滑动到底部
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
        super.onScrolled(recyclerView, dx, dy)
        val layoutManager = recyclerView.layoutManager as LinearLayoutManager
        val visibleItemCount = layoutManager.childCount
        val totalItemCount = layoutManager.itemCount
        val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()

        // 滑动到底部且不在加载中,加载下一页
        if (!adapter.isLoading && visibleItemCount + firstVisibleItemPosition >= totalItemCount - 5) {
            adapter.setLoading(true)
            loadNextPage() // 加载下一页数据(网络请求/数据库查询)
        }
    }
})

// 模拟网络请求加载下一页
private fun loadNextPage() {
    Handler(Looper.getMainLooper()).postDelayed({
        val newData = List(20) { "第 ${dataList.size + it + 1} 条数据" }
        adapter.addData(newData)
    }, 1000)
}

4.4 图片优化:延迟加载 + 图片缓存

列表中的图片是性能消耗的重灾区,优化方案:

  1. 使用图片加载库:Glide、Picasso 等库自带延迟加载、图片压缩、缓存功能;
  2. 压缩图片尺寸:根据列表项大小加载对应分辨率的图片,避免加载过大图片;
  3. 占位图 + 渐入效果:提升用户体验,避免图片加载完成后突然闪现。
示例代码(Glide 加载优化)
Glide.with(holder.itemView.context)
    .load(imageUrl)
    .placeholder(R.drawable.ic_placeholder) // 占位图
    .error(R.drawable.ic_error) // 加载失败图
    .override(300, 200) // 压缩图片尺寸
    .centerCrop() // 缩放模式
    .diskCacheStrategy(DiskCacheStrategy.ALL) // 缓存策略
    .into(holder.ivImage)

4.5 布局优化:减少层级 + 避免过度绘制

  1. 减少布局层级:用 ConstraintLayout 替代多层 LinearLayout,避免嵌套过深;
  2. 移除不必要的背景:如果父布局和子布局的背景重叠,移除子布局的背景;
  3. 使用 merge 标签:减少根布局层级(如列表项布局的根标签用 merge,复用父布局的布局参数)。

五、常见问题与避坑指南

开发中遇到的 Adapter 问题,90% 都是以下几种情况,提前掌握能少踩很多坑。

5.1 问题 1:列表滑动时数据错乱

原因

视图复用导致的状态混乱(如点赞状态、选中状态未正确保存)。

解决方案
  • 状态存储在数据源中:不要把状态存在 ViewHolder 或 View 中,要存在数据模型里;
  • 每次绑定数据时重置状态:在 getView() 或 onBindViewHolder() 中,无论视图是否复用,都明确设置状态(如点赞图标、选中颜色)。

5.2 问题 2:notifyDataSetChanged () 不生效

原因
  • 数据源未真正修改(如用 val 定义的列表,重新赋值后未通知 Adapter);
  • Adapter 引用的数据源对象未更新(如修改了列表元素但未调用 notify)。
解决方案
  • 用可变集合(MutableList)存储数据,修改后调用 notify 方法;
  • 如果替换整个数据源,确保 Adapter 引用的是新的列表对象,再调用 notify。

5.3 问题 3:RecyclerView 没有 ItemClickListener

原因

RecyclerView 没有像 ListView 那样的 setOnItemClickListener 方法,需要手动实现。

解决方案
  • 在 ViewHolder 中给 itemView 设置点击事件;
  • 通过接口回调将点击事件暴露给 Activity/Fragment。
代码示例
// 定义点击事件接口
interface OnItemClickListener {
    fun onItemClick(position: Int)
}

class MyAdapter(
    private val dataList: List<String>,
    private val listener: OnItemClickListener
) : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {

    // ...

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        holder.itemView.setOnClickListener {
            listener.onItemClick(position)
        }
    }

    // ...
}

// Activity 中使用
adapter = MyAdapter(dataList, object : OnItemClickListener {
    override fun onItemClick(position: Int) {
        Toast.makeText(this, "选中:${dataList[position]}", Toast.LENGTH_SHORT).show()
    }
})

5.4 问题 4:刷新后 setBackgroundColor 不生效

原因

视图复用导致旧的背景色未被覆盖,或样式设置顺序错误。

解决方案
  • 每次绑定数据时先重置背景色(如设置为透明),再设置目标颜色;
  • 避免在 onCreateViewHolder() 中设置动态变化的样式,统一在 onBindViewHolder() 中处理。
代码示例
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
    // 先重置背景色
    holder.itemView.setBackgroundColor(Color.TRANSPARENT)
    // 再设置目标颜色
    if (dataList[position].isSelected) {
        holder.itemView.setBackgroundColor(ContextCompat.getColor(context, R.color.blue))
    }
}

六、实战综合案例:完整的网络数据列表

整合前面的知识点,实现一个 “网络请求 + 分页加载 + 下拉刷新 + 局部刷新” 的完整列表,模拟真实开发场景。

6.1 案例需求

  1. 调用公开 API 获取文章列表数据;
  2. 支持下拉刷新(刷新第一页);
  3. 支持上拉加载更多(分页加载);
  4. 点击列表项进入详情页;
  5. 支持点赞功能(局部刷新点赞数)。

6.2 核心依赖

// 网络请求
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
// 图片加载
implementation 'com.github.bumptech.glide:glide:4.16.0'
kapt 'com.github.bumptech.glide:compiler:4.16.0'
// 下拉刷新
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'

6.3 核心代码

步骤 1:网络请求相关(API 接口、数据模型)
// 文章数据模型
data class ArticleResponse(
    val data: List<ArticleModel>
)

data class ArticleModel(
    val id: Long,
    val title: String,
    val author: String,
    val cover: String,
    val likeCount: Int,
    var isLiked: Boolean
)

// Retrofit API 接口
interface ApiService {
    @GET("article/list")
    suspend fun getArticleList(@Query("page") page: Int, @Query("size") size: Int): Response<ArticleResponse>
}

// 网络请求工具类
object RetrofitClient {
    private const val BASE_URL = "https://api.example.com/" // 替换为真实 API 地址

    val apiService: ApiService by lazy {
        Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(ApiService::class.java)
    }
}
步骤 2:Adapter 实现(支持点赞局部刷新)
class ArticleAdapter(
    private val onLikeClick: (Int, Boolean) -> Unit // 点赞点击回调
) : RecyclerView.Adapter<ArticleAdapter.ArticleViewHolder>() {

    private var dataList: List<ArticleModel> = emptyList()

    fun submitList(newList: List<ArticleModel>) {
        val diffResult = DiffUtil.calculateDiff(ArticleDiffCallback(dataList, newList))
        dataList = newList
        diffResult.dispatchUpdatesTo(this)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArticleViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_article, parent, false)
        return ArticleViewHolder(view)
    }

    override fun onBindViewHolder(holder: ArticleViewHolder, position: Int) {
        val article = dataList[position]
        holder.tvTitle.text = article.title
        holder.tvAuthor.text = article.author
        holder.tvLikeCount.text = article.likeCount.toString()

        // 加载封面图
        Glide.with(holder.itemView.context)
            .load(article.cover)
            .placeholder(R.drawable.ic_cover_placeholder)
            .error(R.drawable.ic_cover_error)
            .centerCrop()
            .into(holder.ivCover)

        // 设置点赞状态
        if (article.isLiked) {
            holder.ivLike.setImageResource(R.drawable.ic_like_selected)
            holder.tvLikeCount.setTextColor(ContextCompat.getColor(holder.itemView.context, R.color.red))
        } else {
            holder.ivLike.setImageResource(R.drawable.ic_like_normal)
            holder.tvLikeCount.setTextColor(ContextCompat.getColor(holder.itemView.context, R.color.gray))
        }

        // 点赞点击事件
        holder.ivLike.setOnClickListener {
            val newLiked = !article.isLiked
            val newLikeCount = if (newLiked) article.likeCount + 1 else article.likeCount - 1
            // 局部刷新点赞状态和数量
            val newArticle = article.copy(isLiked = newLiked, likeCount = newLikeCount)
            val newList = dataList.toMutableList().apply {
                set(position, newArticle)
            }
            submitList(newList)
            // 回调给 Activity 发送网络请求(实际开发中)
            onLikeClick(article.id.toInt(), newLiked)
        }

        // 列表项点击事件(进入详情页)
        holder.itemView.setOnClickListener {
            val intent = Intent(holder.itemView.context, ArticleDetailActivity::class.java)
            intent.putExtra("article_id", article.id)
            holder.itemView.context.startActivity(intent)
        }
    }

    override fun getItemCount(): Int = dataList.size

    class ArticleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val ivCover: ImageView = itemView.findViewById(R.id.iv_cover)
        val tvTitle: TextView = itemView.findViewById(R.id.tv_title)
        val tvAuthor: TextView = itemView.findViewById(R.id.tv_author)
        val ivLike: ImageView = itemView.findViewById(R.id.iv_like)
        val tvLikeCount: TextView = itemView.findViewById(R.id.tv_like_count)
    }

    // DiffUtil 回调
    class ArticleDiffCallback(
        oldList: List<ArticleModel>,
        newList: List<ArticleModel>
    ) : DiffUtil.Callback() {
        private val oldList = oldList
        private val newList = newList

        override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
            return oldList[oldItemPosition].id == newList[newItemPosition].id
        }

        override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
            return oldList[oldItemPosition] == newList[newItemPosition]
        }

        override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
            val oldItem = oldList[oldItemPosition]
            val newItem = newList[newItemPosition]
            val payload = mutableMapOf<String, Any>()
            if (oldItem.isLiked != newItem.isLiked) {
                payload["isLiked"] = newItem.isLiked
            }
            if (oldItem.likeCount != newItem.likeCount) {
                payload["likeCount"] = newItem.likeCount
            }
            return if (payload.isEmpty()) null else payload
        }

        override fun getOldListSize(): Int = oldList.size
        override fun getNewListSize(): Int = newList.size
    }
}
步骤 3:Activity 实现(下拉刷新 + 上拉加载)
class ArticleListActivity : AppCompatActivity() {
    private lateinit var adapter: ArticleAdapter
    private lateinit var swipeRefreshLayout: SwipeRefreshLayout
    private lateinit var recyclerView: RecyclerView
    private var currentPage = 1
    private val pageSize = 20
    private var isLoading = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_article_list)
        
        // 初始化控件
        swipeRefreshLayout = findViewById(R.id.srl_refresh)
        recyclerView = findViewById(R.id.rv_article)
        recyclerView.layoutManager = LinearLayoutManager(this)
        
        // 初始化 Adapter
        adapter = ArticleAdapter { articleId, isLiked ->
            // 点赞回调:发送网络请求(实际开发中实现)
            Toast.makeText(this, "点赞状态:$isLiked", Toast.LENGTH_SHORT).show()
        }
        recyclerView.adapter = adapter
        
        // 下拉刷新
        swipeRefreshLayout.setOnRefreshListener {
            currentPage = 1
            loadArticleList()
        }
        
        // 上拉加载更多
        recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                super.onScrolled(recyclerView, dx, dy)
                val layoutManager = recyclerView.layoutManager as LinearLayoutManager
                val visibleItemCount = layoutManager.childCount
                val totalItemCount = layoutManager.itemCount
                val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()

                if (!isLoading && !swipeRefreshLayout.isRefreshing 
                    && visibleItemCount + firstVisibleItemPosition >= totalItemCount - 5) {
                    currentPage++
                    loadArticleList()
                }
            }
        })
        
        // 首次加载数据
        loadArticleList()
    }

    // 加载文章列表
    private fun loadArticleList() {
        isLoading = true
        if (currentPage == 1) {
            swipeRefreshLayout.isRefreshing = true
        }

        // 协程发起网络请求
        lifecycleScope.launch {
            try {
                val response = RetrofitClient.apiService.getArticleList(currentPage, pageSize)
                if (response.isSuccessful) {
                    val articleList = response.body()?.data ?: emptyList()
                    if (currentPage == 1) {
                        // 第一页:替换数据
                        adapter.submitList(articleList)
                    } else {
                        // 分页:追加数据
                        val oldList = (adapter as ArticleAdapter).dataList.toMutableList()
                        oldList.addAll(articleList)
                        adapter.submitList(oldList)
                    }
                } else {
                    Toast.makeText(this@ArticleListActivity, "加载失败", Toast.LENGTH_SHORT).show()
                }
            } catch (e: Exception) {
                e.printStackTrace()
                Toast.makeText(this@ArticleListActivity, "网络错误", Toast.LENGTH_SHORT).show()
            } finally {
                isLoading = false
                swipeRefreshLayout.isRefreshing = false
            }
        }
    }
}
步骤 4:布局文件
  • Activity 布局(activity_article_list.xml):
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/srl_refresh"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_article"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingHorizontal="16dp"
        android:paddingTop="8dp" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
  • 列表项布局(item_article.xml):
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:marginBottom="16dp"
    android:background="@drawable/shape_rounded_corner"
    android:elevation="2dp">

    <!-- 封面图 -->
    <ImageView
        android:id="@+id/iv_cover"
        android:layout_width="match_parent"
        android:layout_height="160dp"
        android:scaleType="centerCrop"
        android:background="@color/gray_light" />

    <!-- 标题和作者 -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="16dp"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tv_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:textStyle="bold"
            android:maxLines="2"
            android:ellipsize="end" />

        <TextView
            android:id="@+id/tv_author"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:textSize="14sp"
            android:textColor="@color/gray" />
    </LinearLayout>

    <!-- 点赞区域 -->
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginBottom="16dp"
        android:gravity="center_vertical"
        android:orientation="horizontal">

        <ImageView
            android:id="@+id/iv_like"
            android:layout_width="24dp"
            android:layout_height="24dp"
            android:src="@drawable/ic_like_normal" />

        <TextView
            android:id="@+id/tv_like_count"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:textSize="14sp"
            android:textColor="@color/gray" />
    </LinearLayout>
</LinearLayout>

七、总结与拓展

7.1 核心知识点回顾

  1. Adapter 的核心作用:连接数据与视图,负责数据转换和视图复用;
  2. 基础 Adapter:ArrayAdapter(纯文本)、SimpleAdapter(简单图文)、BaseAdapter(自定义);
  3. 主流 Adapter:RecyclerView.Adapter(支持多布局、性能优、可扩展);
  4. 高级用法:多类型布局、局部刷新(DiffUtil)、分页加载;
  5. 性能优化:ViewHolder、DiffUtil、图片优化、布局优化;
  6. 避坑指南:数据错乱、刷新不生效、点击事件、样式问题。

7.2 拓展学习方向

  1. 更多 RecyclerView 高级功能:ItemDecoration(自定义分割线)、ItemAnimator(动画)、侧滑删除;
  2. 数据状态管理:结合 ViewModel、LiveData 实现数据与 UI 分离;
  3. 第三方 Adapter 框架:如 BRVAH(快速开发列表)、Epoxy(复杂多类型列表);
  4. Jetpack Compose 中的列表适配:LazyColumn 替代 RecyclerView,无需 Adapter。

7.3 实战建议

  1. 新手从 BaseAdapter 入手,理解视图复用和 ViewHolder 原理;
  2. 实际开发优先使用 RecyclerView.Adapter,配合 DiffUtil 和分页加载;
  3. 代码复用:封装通用 Adapter 模板,减少重复开发;
  4. 注重性能:列表滑动卡顿是常见问题,提前做好优化。

这篇文章从基础到高级,覆盖了 Android Adapter 的全部核心用法,所有示例代码均可直接复制运行。如果需要进一步学习,可以结合官方文档和实际项目反复练习,真正掌握列表适配的精髓。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值