Android 通用 RecyclerView Adapter 实现(支持 ViewBinding + 泛型 + 点击事件)

在 Android 项目开发中,RecyclerView 是最常用的列表控件,但每次都要写 Adapter 和 ViewHolder,容易重复造轮子。本文整理一套通用 RecyclerView Adapter 实现,支持:

  • 泛型数据类型
  • ViewBinding
  • 数据操作(设置、追加、删除、更新)
  • 点击/长按事件监听
  • Payload 局部刷新支持

适合大部分简单列表场景,极大减少重复代码。

一、类说明

java 复制代码
public abstract class BaseRecyclerViewAdapter<T, VB extends ViewBinding>
        extends RecyclerView.Adapter<BaseRecyclerViewAdapter.BaseViewHolder<VB>>

泛型说明

  • T:数据类型
  • VB:ViewBinding 类型

通过泛型和 ViewBinding,减少了 findViewById,让 Adapter 更安全、简洁。

二、主要功能

1. 数据管理

提供完整的数据操作方法:

java 复制代码
// 设置数据并刷新
public void setData(List<T> list)

// 追加数据
public void addData(List<T> list)

// 插入数据到顶部
public void insertItemToTop(T data)
public void insertItemToTop(T data, RecyclerView recyclerView)

// 删除指定位置的数据
public void removeItem(int position)

// 更新指定位置数据(全量更新或局部刷新)
public void updateItem(int position, T newData)
public void updateItem(int position, T newData, Object payload)

// 清空数据
public void clearData()

// 获取数据列表或指定位置数据
public List<T> getData()
public T getItem(int position)

优势:统一数据操作,保证 RecyclerView 刷新和位置更新正确,避免因 position 错乱导致的 UI 问题。

2. 点击和长按事件

通过接口方式支持通用的点击事件:

java 复制代码
public interface OnItemClickListener<T> {
    void onItemClick(View view, T data, int position);
    void onItemLongClick(View view, T data, int position);
}

public void setOnItemClickListener(OnItemClickListener<T> listener)

使用示例:

bash 复制代码
adapter.setOnItemClickListener(new BaseRecyclerViewAdapter.OnItemClickListener<MyData>() {
    @Override
    public void onItemClick(View view, MyData data, int position) {
        Toast.makeText(context, "点击:" + data.getName(), Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onItemLongClick(View view, MyData data, int position) {
        Toast.makeText(context, "长按:" + data.getName(), Toast.LENGTH_SHORT).show();
    }
});

优势:统一处理点击事件,子类无需重复绑定。

3. ViewHolder 与 ViewBinding

bash 复制代码
public static class BaseViewHolder<VB extends ViewBinding> extends RecyclerView.ViewHolder {
    public final VB binding;
    public BaseViewHolder(@NonNull VB binding) {
        super(binding.getRoot());
        this.binding = binding;
    }
}
  • 持有 ViewBinding 对象,直接通过 binding 操作视图
  • 子类只需实现 onCreateViewBinding 和 onBindData 即可

4. 支持局部刷新(Payload)

在 onBindViewHolder 中,支持 Payload 更新:

bash 复制代码
@Override
public void onBindViewHolder(@NonNull BaseViewHolder<VB> holder,
                             int position,
                             @NonNull List<Object> payloads) {
    T mData = getItem(position);
    onBindData(holder.binding, mData, position, payloads);
}

子类可以根据 payloads 做局部更新,提升 RecyclerView 性能。

5. 插入顶部数据并滚动

支持新消息/刷新场景:

bash 复制代码
public void insertItemToTop(T data, RecyclerView recyclerView) {
    mDataList.add(0, data);
    notifyItemInserted(0);
    recyclerView.scrollToPosition(0);
}

优势:适合聊天列表、动态消息流等场景。

三、Adapter 使用示例

假设有一个简单的数据类 User:

bash 复制代码
public class User {
    public String name;
    public int age;
}

实现 Adapter:

bash 复制代码
public class UserAdapter extends BaseRecyclerViewAdapter<User, ItemUserBinding> {

    public UserAdapter(Context context) {
        super(context);
    }

    @Override
    protected ItemUserBinding onCreateViewBinding(LayoutInflater inflater, ViewGroup parent, int viewType) {
        return ItemUserBinding.inflate(inflater, parent, false);
    }

    @Override
    protected void onBindData(ItemUserBinding binding, User data, int position, @NonNull List<Object> payloads) {
        binding.tvName.setText(data.name);
        binding.tvAge.setText(String.valueOf(data.age));
    }
}

在 Activity 中使用:

bash 复制代码
UserAdapter adapter = new UserAdapter(this);
recyclerView.setAdapter(adapter);

adapter.setData(userList);

adapter.setOnItemClickListener(new BaseRecyclerViewAdapter.OnItemClickListener<User>() {
    @Override
    public void onItemClick(View view, User data, int position) {
        Toast.makeText(MainActivity.this, data.name, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onItemLongClick(View view, User data, int position) {
        Toast.makeText(MainActivity.this, "长按:" + data.name, Toast.LENGTH_SHORT).show();
    }
});

四、 RecyclerView Adapter 代码示例

bash 复制代码
package com.xxxx.xxxx;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewbinding.ViewBinding;
import java.util.ArrayList;
import java.util.List;

/**
 * Author: Su
 * Date: 2025/4/24
 * Description:
 *      通用 BaseRecyclerViewAdapter,适用于多数简单 RecyclerView 列表。
 *      支持:
 *      - 数据设置、追加、清空
 *      - 点击、长按事件
 *      - 简洁的 ViewHolder 缓存方式
 *
 *      @param <T> 数据泛型
 */

/**
 * 通用支持 ViewBinding 的 BaseRecyclerViewAdapter
 *
 * @param <T> 数据类型
 * @param <VB> ViewBinding 类型
 */
public abstract class BaseRecyclerViewAdapter<T, VB extends ViewBinding> extends RecyclerView.Adapter<BaseRecyclerViewAdapter.BaseViewHolder<VB>> {

    protected List<T> mDataList;
    protected Context mContext;
    private final LayoutInflater mInflater;
    private VB mViewBinding;
    private T mData;

    public BaseRecyclerViewAdapter(Context context) {
        this.mContext = context;
        this.mInflater = LayoutInflater.from(context);
        this.mDataList = new ArrayList<>();
    }

    /**
     * 设置数据并刷新
     */
    public void setData(List<T> list) {
        this.mDataList.clear();
        if (list != null) {
            this.mDataList.addAll(list);
        }
        notifyDataSetChanged();
    }

    /**
     * 设置数据并刷新指定position
     */
    public void setData(List<T> list, int position) {
        this.mDataList.clear();
        if (list != null) {
            this.mDataList.addAll(list);
        }
        notifyItemChanged(position);
    }

    /**
     * 追加数据
     */
    public void addData(List<T> list) {
        if (list != null) {
            int start = mDataList.size();
            mDataList.addAll(list);
            notifyItemRangeInserted(start, list.size());
        }
    }

    /**
     * 插入一条数据到顶部
     *
     * @param data 要插入的数据
     */
    public void insertItemToTop(T data) {
        if (data != null) {
            mDataList.add(0, data);
            notifyDataSetChanged(); // 🔥 必须整体刷新,保证 position 更新
        }
    }

    /**
     * 插入一条数据到顶部并刷新
     *
     * @param data 要插入的数据
     * @param recyclerView 关联的 RecyclerView
     */
    public void insertItemToTop(T data, RecyclerView recyclerView) {
        if (data != null) {
            mDataList.add(0, data);
            notifyItemInserted(0);
            recyclerView.scrollToPosition(0);
        }
    }

    /**
     * 获取当前列表中所有的数据项。
     *
     * @return 当前数据源列表,类型为 List<T>。
     */
    public List<T> getData() {
        return mDataList;
    }

    /**
     * 删除指定数据
     *
     * @param position 要删除的下标
     */
    public void removeItem(int position) {
        mDataList.remove(position);
        notifyDataSetChanged(); // 🔥 必须整体刷新,保证 position 更新
    }

    /**
     * 清空数据
     */
    public void clearData() {
        mDataList.clear();
        notifyDataSetChanged();
    }

    /**
     * 获取某个位置的数据
     */
    public T getItem(int position) {
        return position >= 0 && position < mDataList.size() ? mDataList.get(position) : null;
    }

    /**
     * 更新指定位置的数据项并刷新该项视图
     *
     * @param position 要更新的数据项的位置
     * @param newData  新的数据项
     */
    public void updateItem(int position, T newData) {
        if (position >= 0 && position < mDataList.size()) {
            mDataList.set(position, newData);
            notifyItemChanged(position);
        }
    }

    /**
     * 更新指定位置的数据项的特定部分并刷新该项视图
     *
     * @param position 要更新的数据项的位置
     * @param newData  新的数据项
     * @param payload  用于指示更新内容的标识
     */
    public void updateItem(int position, T newData, Object payload) {
        if (position >= 0 && position < mDataList.size()) {
            mDataList.set(position, newData);
            notifyItemChanged(position, payload);
        }
    }

    @NonNull
    @Override
    public BaseViewHolder<VB> onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        mViewBinding = onCreateViewBinding(mInflater, parent, viewType);
        return new BaseViewHolder<>(mViewBinding);
    }

//    @Override
//    public void onBindViewHolder(@NonNull BaseViewHolder<VB> holder, int position) {
//        mData = getItem(position);
//        onBindData(holder.binding, mData, position);
//
//        // 设置点击事件监听(如果设置了监听器)
//        if (mListener != null) {
//            holder.itemView.setOnClickListener(v -> mListener.onItemClick(v, mData, position));
//            holder.itemView.setOnLongClickListener(v -> {
//                mListener.onItemLongClick(v, mData, position);
//                return true;
//            });
//        }
//    }

    @Override
    public void onBindViewHolder(@NonNull BaseViewHolder<VB> holder, int position, @NonNull List<Object> payloads) {
        mData = getItem(position);
        onBindData(holder.binding, mData, position, payloads);

        // 设置点击事件监听(如果设置了监听器)
        if (mListener != null) {
            holder.itemView.setOnClickListener(v -> mListener.onItemClick(v, getItem(position), position));
            holder.itemView.setOnLongClickListener(v -> {
                mListener.onItemLongClick(v, getItem(position), position);
                return true;
            });
        }
    }

    @Override
    public void onBindViewHolder(@NonNull BaseViewHolder<VB> holder, int position) {

    }

    @Override
    public int getItemCount() {
        return mDataList != null ? mDataList.size() : 0;
    }

    /**
     * 子类实现:创建 ViewBinding
     */
    protected abstract VB onCreateViewBinding(LayoutInflater inflater, ViewGroup parent, int viewType);

    /**
     * 子类实现:绑定数据
     */
    protected abstract void onBindData(VB binding, T data, int position, @NonNull List<Object> payloads);

    /**
     * 通用 ViewHolder,持有 ViewBinding 对象
     */
    public static class BaseViewHolder<VB extends ViewBinding> extends RecyclerView.ViewHolder {
        public final VB binding;

        public BaseViewHolder(@NonNull VB binding) {
            super(binding.getRoot());
            this.binding = binding;
        }
    }

    /**
     * Item 点击事件接口(含点击和长按)
     */
    public interface OnItemClickListener<T> {
        void onItemClick(View view, T data, int position);
        void onItemLongClick(View view, T data, int position);
    }

    public OnItemClickListener<T> mListener;

    /**
     * 设置点击事件监听器
     */
    public void setOnItemClickListener(OnItemClickListener<T> listener) {
        this.mListener = listener;
    }

    @Override
    public void onViewRecycled(@NonNull BaseViewHolder<VB> holder) {
        super.onViewRecycled(holder);
    }

    @Override
    public int getItemViewType(int position) {
        return super.getItemViewType(position);
    }
}

五、优势总结

1. 通用性强:支持任何数据类型和 ViewBinding 布局

2. 功能完整:数据操作、点击事件、局部刷新、顶部插入等功能齐全

3. 减少重复代码:子类只需实现创建和绑定数据

4. 性能优化:支持 Payload 局部刷新,避免全量刷新

5. 适用场景广:聊天列表、消息流、商品列表、动态数据列表等

相关推荐
阿巴斯甜8 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker9 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952710 小时前
Andorid Google 登录接入文档
android
黄林晴11 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_1 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android