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. 适用场景广:聊天列表、消息流、商品列表、动态数据列表等

相关推荐
oMcLin2 小时前
如何在Ubuntu 22.04 LTS上配置并优化MySQL 8.0分区表,提高大规模数据集查询的效率与性能?
android·mysql·ubuntu
幸福的达哥3 小时前
安卓APP代码覆盖率测试方案
android·代码覆盖率
佛系打工仔3 小时前
绘制K线入门
android
川石课堂软件测试4 小时前
Android和iOS APP平台测试的区别
android·数据库·ios·oracle·单元测试·测试用例·cocoa
花卷HJ5 小时前
Android 通用 BaseDialog 实现:支持 ViewBinding + 全屏布局 + 加载弹窗
android
生产队队长5 小时前
Linux:awk进行行列转换操作
android·linux·运维
叶羽西5 小时前
Android15 EVS HAL中使用Camera HAL Provider接口
android
2501_915918415 小时前
除了 Perfdog,如何在 Windows 环境中完成 iOS App 的性能测试工作
android·ios·小程序·https·uni-app·iphone·webview
泓博5 小时前
Android状态栏文字图标设置失效
android·composer