头图

Encapsulate the universal RecyclerView adapter

Maenj_Ba_lah
中文

Traditional adapter

In Android projects, there will basically be a list function, and the current list function is implemented through RecyclerView. When there are more list functions in the project, each RecyclerView needs an Adapter adapter, which will make the Adapter in the project There are so many categories. Therefore, encapsulating a universal RecyclerView adapter can improve our development efficiency. Before that, let's take a look at how the traditional adapter works with RecyclerView.

Let's first take a look at what the example we want to implement looks like, as shown below:
image.png

The code of the Adapter is as follows:

public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.MyViewHolder> {

    private OnItemClickListener onItemClickListener;
    private RecyclerView mRv;
    private List<String> dataSource;
    private Context mContext;
    private int addDataPosition = -1;

    public MyRecyclerViewAdapter(Context context, RecyclerView recyclerView) {
        this.mContext = context;
        this.dataSource = new ArrayList<>();
        this.mRv = recyclerView;
    }

    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
        this.onItemClickListener = onItemClickListener;
    }

    public void setDataSource(List<String> dataSource) {
        this.dataSource = dataSource;
        notifyDataSetChanged();
    }

    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int position) {
        return new MyViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item_layout, viewGroup, false));
    }

    @Override
    public void onBindViewHolder(@NonNull MyViewHolder myViewHolder, final int position) {
        myViewHolder.mIv.setImageResource(getIcon(position));
        myViewHolder.mTv.setText(dataSource.get(position));
        
        // 只在瀑布流布局中使用随机高度
        if (mRv.getLayoutManager().getClass() == StaggeredGridLayoutManager.class) {
            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getRandomHeight());
            myViewHolder.mTv.setLayoutParams(params);
        } else {
            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
            myViewHolder.mTv.setLayoutParams(params);
        }

        // 改变ItemView背景颜色
        if (addDataPosition == position) {
            myViewHolder.mItemView.setBackgroundColor(Color.RED);
        } else {
            myViewHolder.mItemView.setBackgroundColor(Color.parseColor("#A4D3EE"));
        }

        myViewHolder.mItemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 调用接口的回调方法
                if (onItemClickListener != null) {
                    onItemClickListener.onItemClick(position);
                }
            }
        });
    }

    @Override
    public int getItemCount() {
        return dataSource.size();
    }

    private int getIcon (int position) {
        switch (position % 5) {
            case 0:
                return R.mipmap.a;
            case 1:
                return R.mipmap.b;
            case 2:
                return R.mipmap.c;
            case 3:
                return R.mipmap.d;
            case 4:
                return R.mipmap.e;
        }
        return 0;
    }

    private int getRandomHeight () {
        return (int)(Math.random() * 1000);
    }

    public void addData (int position) {
        addDataPosition = position;
        dataSource.add(position, "插入的数据");
        notifyItemInserted(position);

        // 刷新ItemView
        notifyItemRangeChanged(position, dataSource.size() - position);
    }

    public void removeData (int position) {
        addDataPosition = -1;
        dataSource.remove(position);
        notifyItemRemoved(position);

        // 刷新ItemView
        notifyItemRangeChanged(position, dataSource.size() - position);
    }

    class MyViewHolder extends RecyclerView.ViewHolder {

        View mItemView;
        ImageView mIv;
        TextView mTv;

        public MyViewHolder(@NonNull View itemView) {
            super(itemView);

            mIv = itemView.findViewById(R.id.iv);
            mTv = itemView.findViewById(R.id.tv);
            mItemView = itemView;
        }
    }

    public interface OnItemClickListener {
        void onItemClick(int position);
    }
}

Although the above adapter adds functions such as click events and inserting data, it is still relatively simple. The implementation of the Adapter adapter mainly consists of three steps:

  1. Inherit RecyclerView.Adapter
  2. Create ViewHolder
  3. Bind data

With this adapter, RecyclerView can display the list as long as it is bound to this adapter. The specific code is as follows:

mRecyclerView = findViewById(R.id.recycler_view);

// 线性布局
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(linearLayoutManager);
mAdapter = new MyRecyclerViewAdapter(this, mRecyclerView);
mRecyclerView.setAdapter(mAdapter);

// itemView点击事件监听
mAdapter.setOnItemClickListener(new MyRecyclerViewAdapter.OnItemClickListener() {
    @Override
    public void onItemClick(int position) {
        Toast.makeText(MainActivity2.this, "第" + position + "条数据被点击", Toast.LENGTH_SHORT).show();
    }
});

Such a list function is realized, but the above Adapter can only adapt to this list, and cannot be universal. Now let's implement the universal RecyclerView adapter.

Encapsulate the universal Adapter

Since the Adapter of RecyclerView must inherit from RecyclerView.Adapter, and specify that the ViewHolder we write is generic, in order to achieve the universal effect, we directly use a generic T to refer to the Java Bean properties that need to be passed in. Here I use the interface to encapsulate, without using abstract classes, and also implement the encapsulation of multi-type adapters. The specific code is as follows:

public class CommonAdapter<T> extends RecyclerView.Adapter<CommonViewHolder> {

    private final List<T> mList;

    private OnBindDataListener<T> onBindDataListener;
    private OnMoreBindDataListener<T> onMoreBindDataListener;

    // 单类型
    public CommonAdapter(List<T> mList, OnBindDataListener<T> onBindDataListener) {
        this.mList = mList;
        this.onBindDataListener = onBindDataListener;
    }

    // 多类型
    public CommonAdapter(List<T> mList, OnMoreBindDataListener<T> onMoreBindDataListener) {
        this.mList = mList;
        this.onBindDataListener = onMoreBindDataListener;
        this.onMoreBindDataListener = onMoreBindDataListener;
    }

    //绑定数据
    public interface OnBindDataListener<T> {
        void onBindViewHolder(T model, CommonViewHolder viewHolder, int type, int position);

        int getLayoutId(int type);
    }

    //绑定多类型的数据
    public interface OnMoreBindDataListener<T> extends OnBindDataListener<T> {
        int getItemType(int position);
    }

    @Override
    public int getItemViewType(int position) {
        if (onMoreBindDataListener != null) {
            return onMoreBindDataListener.getItemType(position);
        }
        return 0;
    }

    @Override
    public CommonViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        int layoutId = onBindDataListener.getLayoutId(viewType);
        View view = LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false);
        return CommonViewHolder.getCommonViewHolder(parent.getContext(), view);
    }

    @Override
    public void onBindViewHolder(CommonViewHolder holder, int position) {
        onBindDataListener.onBindViewHolder(mList.get(position), holder, getItemViewType(position), position);
    }

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

    // 插入一项数据
    public void insert(T item, int position) {
        mList.add(position, item);
        notifyItemInserted(position);
    }

    // 删除一项数据
    public void remove(int position) {
        mList.remove(position);
        notifyItemRemoved(position);
    }
}

Such a universal Adapter is written. The is to pass onCreateViewHolder() and onBindViewHolder() to the caller to process through the interface.

Package general ViewHolder

We all know that ViewHolder in ListView needs to be customized. In RecyclerView, ViewHolder is already encapsulated, so we also need to encapsulate a general ViewHolder. The specific code is as follows:

public class CommonViewHolder extends RecyclerView.ViewHolder {

    // 子View的集合
    private SparseArray<View> mViews;
    private Context context;

    public CommonViewHolder(Context context, View itemView) {
        super(itemView);
        this.context = context;
        mViews = new SparseArray<>();
    }

    public static CommonViewHolder getCommonViewHolder(Context context, View itemView) {
        return new CommonViewHolder(context, itemView);
    }

    /**
     * 提供给外部访问 View 的方法
     *
     * @param viewId id
     * @param <T>    泛型
     * @return
     */
    public <T extends View> T getView(int viewId) {
        View view = mViews.get(viewId);
        if (view == null) {
            view = itemView.findViewById(viewId);
            mViews.put(viewId, view);
        }
        return (T) view;
    }

    /**
     * 设置文本
     *
     * @param viewId id
     * @param text   文本
     * @return this
     */
    public CommonViewHolder setText(int viewId, String text) {
        TextView tv = getView(viewId);
        tv.setText(text);
        return this;
    }

    /**
     * 设置图片
     *
     * @param viewId id
     * @param resId  图片id
     * @return this
     */
    public CommonViewHolder setImageResource(int viewId, int resId) {
        ImageView iv = getView(viewId);
        iv.setImageResource(resId);
        return this;
    }
}

Use the universal RecyclerView adapter

We have already encapsulated the universal Adapter and the universal ViewHolder. Now let's take a look at how to use it. The specific code is as follows:

mRecyclerView = findViewById(R.id.recycler_view);

// 线性布局
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(linearLayoutManager);
mCommonAdapter = new CommonAdapter<>(mList, new CommonAdapter.OnBindDataListener<String>() {
    @Override
    public void onBindViewHolder(String model, CommonViewHolder viewHolder, int type, final int position) {
        viewHolder.setText(R.id.tv, model);
    }

    @Override
    public int getLayoutId(int type) {
        return R.layout.item_layout;
    }
});

mRecyclerView.setAdapter(mCommonAdapter);

This use is very simple, pass in the layout of the Item through getLayoutId(), and set the data through the onBindViewHolder() method. In the future, the adapter of RecyclerView in our project only needs the above two classes.

Source code

source code of has been uploaded to github. Since all the code is not posted above, you can download it on github.

阅读 2.6k

Android技术学习
一个分享Android相关知识的专栏,有时候也会写一些Kotlin相关技术博客。

真正的大师,永远怀着一颗学徒的心。

25 声望
3 粉丝
0 条评论

真正的大师,永远怀着一颗学徒的心。

25 声望
3 粉丝
文章目录
宣传栏