RecyclerView学习----自定义通用RecyclerCommonAdapter

默认的RecyclerView.Adapter创建时是如下方式的.
public class RecyclerViewAdapter extends RecyclerView.Adapter {
    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return null;
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {

    }

    @Override
    public int getItemCount() {
        return 0;
    }
}

我们可以发现,我们需要复写这三个方法,每次都这样大家会觉得比较麻烦,接下来我们来一步步实现.

首先我们先创建一个默认的ViewHolder

public class ViewHolder extends RecyclerView.ViewHolder{
	public ViewHolder(@NonNull View itemView) {
        super(itemView);
    }
}

接下来我们创建一个RecyclerCommonAdapter继承自RecyclerView.Adapter,ViewHolder用我们自定义的这个.
代码如下:

public class RecyclerCommonAdapterextends RecyclerView.Adapter<ViewHolder> {
    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return null;
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {

    }

    @Override
    public int getItemCount() {
        return 0;
    }
}

下一步我们分析一下构造方法我们需要传入什么参数,首先第一个就是上下文对象Context,这个在LayoutInflater创建时需要用到,
第二个我们需要传入我们的数据集合,这里我们用List泛型,因为我们不知道用户会传入什么类型的数据,第三个我们需要传入的是布局文件资源.
代码如下.

protected Context mContext;
    protected int mLayoutId;
    protected List<T> mDatas;
    private LayoutInflater mLayoutInflater;
    public RecyclerCommonAdapter(Context context, List<T> mDatas, int mLayoutId) {
        mLayoutInflater = LayoutInflater.from(context);
        this.mContext = context;
        this.mLayoutId = mLayoutId;
        this.mDatas = mDatas;
    }

在这里我们先把LayoutInflater初始化一下,后面需要用到.
下一步我们需要复写onCreateViewHolder和getItemCount方法
这里比较简单,我们只需要把contentView初始化一下.并放到ViewHolder中

View itemView = mLayoutInflater.inflate(mLayoutId,parent,false);
return new ViewHolder(itemView);

getItemCount就只是返回条目的数量

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

下一步是比较关键的,我们需要复写onBindViewHolder,在这里我们需要定义一个abstract方法,保证使用我们这个工具的人都会复写这个方法.

@Override
    public void onBindViewHolder(@NonNull ViewHolder holder, final int position) {
        convert(holder,mDatas.get(position),position);
    }
protected abstract void convert(ViewHolder holder, T t, int position);

在这里我们将当前的ViewHolder,当前条目的数据以及当前条目的position传出去.

写到这里,我们先来定义一个TestAdapter来测试一下这个Adapter

public class TestAdapter extends RecyclerCommonAdapter<String> {

    public TestAdapter(Context context, List<String> data){
        super(context,data,R.layout.item_common_adapter);
    }
    @Override
    protected void convert(ViewHolder holder, String s, int position) {
    
    }
}

在这里我们直接继承自RecyclerViewAdapter,数据类型我们直接用String,我们只是一个测试,就不搞那么复杂了.
在这个构造方法里面我们传入上下文对象和我们的数据集合,Layout资源我们可以从外面传过来,也可以直接在里面写,我们这里就直接在里面写了,接下来我们还需要实现convert方法.
item_common_adapter布局我里面就放了两个TextView和一个ImageView.

<?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">
    <ImageView
        android:id="@+id/icon"
        android:scaleType="centerInside"
        android:adjustViewBounds="true"
        android:layout_width="80dp"
        android:layout_height="80dp"/>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:orientation="vertical">
        <TextView
            android:id="@+id/text1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:paddingStart="?android:attr/listPreferredItemPaddingStart" />
        <TextView
            android:id="@+id/text2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:paddingStart="?android:attr/listPreferredItemPaddingStart" />
    </LinearLayout>

</LinearLayout>

接下来我们来写convert方法里面的内容.这里我们直接用findViewById的方式去找到我们的控件

		TextView text1 = holder.itemView.findViewById(R.id.text1);
        TextView text2 = holder.itemView.findViewById(R.id.text2);
        ImageView imageView = holder.itemView.findViewById(R.id.icon);

        text1.setText(s);
        text2.setText(s+s);
        imageView.setImageResource(R.mipmap.ic_launcher);

我们来测试一下,我们先在布局文件activity_main中放一个RecyclerView

<?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/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#FFFFFF" />
</LinearLayout>

下一步在MainActivity中给这个RecyclerView设置Adapter

public class MainActivity extends AppCompatActivity {
    private RecyclerView mRecyclerView;
    private List<String> mDatas;

    private TestAdapter mAdapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initData();

        mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        mAdapter = new TestAdapter(this, mDatas);

        mRecyclerView.setAdapter(mAdapter);

        // 设置显示分割 ListView样式
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
    }

    protected void initData() {
        mDatas = new ArrayList<String>();
        for (int i = 'A'; i < 'Z'+1; i++) {
            mDatas.add("" + (char) i);
        }
    }
}

我们运行下看下效果
在这里插入图片描述
现在我们发现,我们的布局已经出来了,效果没问题

下面我们优化一下我们的ViewHolder;
在TestAdapter里面,我们可以看到,我们是通过findViewById的方式找到我们的View,如果有多个TextView,我们需要写多个findViewById.所以我们可以在ViewHolder里面来实现我们的setText方法.

首先我们定义一个getView方法来获取我们的控件

// 用来存放子View减少findViewById的次数
    private SparseArray<View> mViews;
    
    public ViewHolder(@NonNull View itemView) {
        super(itemView);
        mViews = new SparseArray<>();
    }
/**
     * 通过id获取view
     */
    public <T extends View> T getView(int viewId){
        // 先从缓存中找
        View view = mViews.get(viewId);
        if (null == view){
            // 直接从ItemView中找
            view = itemView.findViewById(viewId);
            mViews.put(viewId,view);
        }
        return (T)view;
    }

在这里我们可以通过缓存的方式将我们的控件存起来,这样可以减少findViewById的次数.我们使用SparseArray来存放我们的View.

接下来我们来写setText方法.

/**
     * 设置TextView文本
     */
    public ViewHolder setText(int viewId, String text){
        View view = getView(viewId);
        if (view instanceof TextView){
            ((TextView) view).setText(text);
        }
        return this;
    }

这里我们返回this是为了可以链式调用,这个在Builder模式中使用的比较多,多次调用有优势.

这样,我们前面的

		TextView text1 = holder.itemView.findViewById(R.id.text1);
        TextView text2 = holder.itemView.findViewById(R.id.text2);
        text1.setText(s);
        text2.setText(s+s);

就可以修改为

holder.setText(R.id.text1,s).setText(R.id.text2,s+s);

这样是不是简单多了

同样的,ImageView我们也可以这么做

/**
     * 设置ImageView的资源
     */
    public ViewHolder setImageResource(int viewId,int resourceId){
        View view = getView(viewId);
        if (view instanceof ImageView){
            ((ImageView) view).setImageResource(resourceId);
        }
        return this;
    }

这样imageView就可以通过

holder.setImageResource(R.id.icon,R.mipmap.ic_launcher);

来设置了

接下来我们看看如何加载网络中的图片我们要怎么做呢.

因为我们不能指定用户用什么框架来加载图片,所以我们需要回调给用户自己去处理

/**
     * 图片加载,这里稍微处理得复杂一些,因为考虑加载图片的第三方可能不太一样
     * 也可以不写这个类
     */
    public abstract static class HolderImageLoader{
        private String url;
        public HolderImageLoader(String url){
            this.url = url;
        }

        /**
         * 要去复写这个方法加载图片
         * @param imageView
         * @param path
         */
        public abstract void loadImage(ImageView imageView, String path);

        public String getUrl(){
            return url;
        }
    }

我们先定义一个HolderImageLoader,然后在setImageByUrl传入我们要的viewId和HolderImageLoader

/**
     * 设置图片通过路径,这里稍微处理得复杂一些,因为考虑加载图片的第三方可能不太一样
     * 也可以直接写死
     */
    public ViewHolder setImageByUrl(int viewId,HolderImageLoader imageLoader){
        View view = getView(viewId);
        if (view instanceof ImageView){
            imageLoader.loadImage((ImageView) view,imageLoader.getUrl());
        }
        return this;
    }

这样,在我们写的TestAdapter中就需要定义一个ImageLoader.
当然,大家也可以将这个ImageLoader类定义在外面,这样整个工程都可以使用,这里我们用Glide来加载图片

public class ImageLoader extends ViewHolder.HolderImageLoader {
    public ImageLoader(String url){
        super(url);
    }
    @Override
    public void loadImage(ImageView imageView, String url) {
        Glide.with(imageView.getContext()).load(url).placeholder(R.drawable.ic_discovery_default_channel).into(imageView);
    }
}

在TestAdapter里面这么用就行了.

holder.setImageByUrl(R.id.icon,new ImageLoader("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1563269254640&di=3ba62579d87d361cd93ab10cdecea49f&imgtype=0&src=http%3A%2F%2Fp2.bahamut.com.tw%2FM%2F2KU%2F37%2F0001126337.JPG%3F_%3D1421643125179"));

这样,TextView和ImageView的设置方法我们就写好了,最后我们来加上ItemClick和ItemLongClick监听.
首先,自定义ItemClickListener和ItemLongClickListener接口

public interface ItemClickListener {
    public void onItemClick(int position);
}
public interface ItemLongClickListener {
    public boolean onLongClick(int position);
}

接下来在ViewHolder里面增加两个方法

/**
     * 设置条目点击事件
     */
    public void setOnItemClickListener(View.OnClickListener listener) {
        itemView.setOnClickListener(listener);
    }

    /**
     * 设置条目长按事件
     */
    public void setOnItemLongClickListener(View.OnLongClickListener listener) {
        itemView.setOnLongClickListener(listener);
    }

然后在RecyclerCommonAdapter中增加

private ItemClickListener mItemClickListener;

    private ItemLongClickListener mItemLongClickListener;

    /***************
     * 给条目设置点击和长按事件
     *********************/
    public void setOnItemClickListener(ItemClickListener itemClickListener){
        this.mItemClickListener = itemClickListener;
    }

    public void setOnItemLongClickListener(ItemLongClickListener itemLongClickListener){
        this.mItemLongClickListener = itemLongClickListener;
    }

在RecyclerCommonAdapter.onBindViewHolder中增加

// 设置点击和长按事件
        if (null != mItemClickListener){
            holder.setOnItemClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    mItemClickListener.onItemClick(position);
                }
            });
        }

        if (null != mItemLongClickListener){
            holder.setOnItemLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    return mItemLongClickListener.onLongClick(position);
                }
            });
        }

最后,在MainAActivity中使用

mAdapter.setOnItemClickListener(new ItemClickListener() {
            @Override
            public void onItemClick(int position) {
                Toast.makeText(MainActivity.this, "点击--"+position, Toast.LENGTH_SHORT).show();
            }
        });

        mAdapter.setOnItemLongClickListener(new ItemLongClickListener() {
            @Override
            public boolean onLongClick(int position) {
                Toast.makeText(MainActivity.this, "长按--"+position, Toast.LENGTH_SHORT).show();
                return true;
            }
        });

注意,onLongClick是需要返回true的,不然长按时onItemClick也会被调用.

至此,我们的自定义Adapter就基本完成了,大家可以自己试下
谢谢大家

源码地址 源码

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
通过封装BaseAdapterRecyclerView.Adapter得到的通用的,简易的Adapter。项目地址:https://github.com/tianzhijiexian/CommonAdapter 效果图:已解决的问题 提升item的独立性,完美支持item被多处复用 item会根据type来做自动复用 支持多种类型的item 一个item仅会调用一次setViews(),避免重复建立监听器 一个item仅会触发一次绑定视图的操作,提示效率 支持dataBinding和其他第三方注入框架 提供了getView()方法来简化findViewById 支持通过item的构造方法来传入Activity对象 支持通过item的构造方法来传入item中事件的回调 提供了getConvertedData(data, type)方法来对item传入的数据做转换,方便拆包和提升item的复用性 支持viewpager的正常加载模式和懒加载 支持快速将listview的适配器切换为recyclerView的适配器 viewpager的notifyDataSetChanged可以正常更新界面 支持recyclerView的添加头部和底部 支持适配器的数据自动绑定,只用操作数据便可,adapter会自动notify界面零、重要接口adapter的item必须实现此接口,接口源码如下:public interface AdapterItem<T> {     /**      * @return item布局文件的layoutId      */     @LayoutRes     int getLayoutResId();     /**      * 初始化views      */     void bindViews(final View root);     /**      * 设置view的参数      */     void setViews();     /**      * 根据数据来设置item的内部views      *      * @param model    数据list内部的model      * @param position 当前adapter调用item的位置      */     void handleData(T model, int position); }例子:public class TextItem implements AdapterItem<DemoModel> {     @Override     public int getLayoutResId() {         return R.layout.demo_item_text;     }     TextView textView;     @Override     public void bindViews(View root) {         textView = (TextView) root.findViewById(R.id.textView);     }     @Override     public void setViews() { }     @Override     public void handleData(DemoModel model, int position) {         textView.setText(model.content);     } }一、ListView GridView的通用适配器——CommonAdapter只需继承CommonAdapter便可实现适配器:listView.setAdapter(new CommonAdapter<DemoModel>(data, 1) {     public AdapterItem<DemoModel> createItem(Object type) {         return new TextItem();     } });二、RecyclerView通用适配器——CommonRcvAdapter通过继承CommonRcvAdapter来实现适配器:mAdapter = new CommonRcvAdapter<DemoModel>(data) {  public AdapterItem createItem(Object type) {         return new TextItem();   } };三、ViewPager的通用适配器——CommonPagerAdapter通过继承CommonPagerAdapter来实现适配器:viewPager.setAdapter(new CommonPagerAdapter<DemoModel>() {     public AdapterItem createItem(Object type) {         return new TextItem();     } });设计思路1. Adapter如果用adapter常规写法,你会发现代码量很大,可读性低。如果adapter中有多个类型的Item,我们还得在getView()中写很多if-else语句,很乱。 而现在我让adapter的代码量减少到一个8行的内部类,如果你需要更换item只需要动一行代码,真正实现了可插拔化。最关键的是item现在作为了一个独立的对象,可以方便的进行复用。2. AdapterItem和原来方式最为不同的一点就是我把adapter的item作为了一个实体,这种方式借鉴了RecyclerView中ViewHolder的设计。把item作为实体的好处有很多,比如复用啊,封装啊,其余的就不细说了。3. 分层在使用过程中,我发现如果adapter放在view层,那就会影响到view层的独立性。此外adapter中经常有很多数据处理的操作,比如通过type选择item,数据的拆包、转换等操作。于是我还是推荐把adapter放在mvp的p层,或者是mvvm的m层。通过在实际的项目中使用来看,放在m或p层的效果较好,view的复用也比较好做。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值