Android DemoAndroidandroid

优雅封装RecyclerView通用Adapter

2018-04-26  本文已影响308人  鸡汤程序员

一、 概述

看了鸿洋大神的 为RecyclerView打造通用Adapter ,感觉随着 item 类型的增多以及 item 功能复杂度的增加,用该 Adapter 的实现会增加不少麻烦,所以想自己封装一个更加解耦的通用 Adapter。实现思想是用一个实体类来承载 item 的功能和数据。

功能特点:

二、 使用及效果

(1) 单 item 情况

新建一个实体类继承自 RecyclerItem

public class SingleItem implements RecyclerItem {
    
    int num;
    
    public SingleItem(int num){
        this.num = num;
    }
    
    @Override
    public int getType() {
        return 0;
    }

    @Override
    public View getView(Context context, ViewGroup parent) {
        return LayoutInflater.from(context).inflate(R.layout.item1,parent,false);
    }

    @Override
    public void convert(final RecyclerViewHolder holder) {
        TextView text = holder.getViewById(R.id.text);
        text.setText(String.valueOf(num));
        text.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(holder.getView().getContext(),"被点击",Toast.LENGTH_SHORT).show();
            }
        });
    }

convert方法中调用该对象的数据,不需要额外的类作为数据载体。接着用此 item 作为数据源传给 RecyclerAdapter

//MainActivity#onCreate()

for (int i=0;i<10;i++){
    RecyclerItem item = new SingleItem(i);
    dataList.add(item);
}
recyclerView.setAdapter(new RecyclerAdapter(this,dataList));

贴个运行结果


图1

(2)多 item 类型

多 item 跟单 item 类似,为每一个 item 都创建一个实体类实现 RecyclerItem 接口,然后根据具体情况进行相关逻辑操作。接着将 item 的实例列表传到 RecyclerAdapter 中。如:

for (int i = 0;i < 10;i++){
    RecyclerItem item;
    if(i % 2 == 0){
        item = new MutiItem1();
    }else{
        item = new MutiItem2();
    }
    dataList.add(item);
}

但需要注意的是,多 item 情况必须保证每个 RecyclerItem 的实现类的 int getType() 方法返回不同的整形数据,简单的方法是返回当前类名的 hashCode()

(3)保存及恢复 item 状态

平时开发中,经常需要在 RecyclerView 中进行 item 状态的保存及恢复。例如,多选功能中 item 的选中状态保存及恢复。下面我们来用此 Adapter 来实现。
先上个效果:


图2
实现过程:

跟上面的过程一样,首先新建 SendMsgItemReceiveMsgItem 两个实体类,并实现 RecyclerItem 接口。下面是 SendMsgItem 类的部分代码:

public class SendMsgItem implements RecyclerItem, CheckObservable.Observer {

    boolean isChecked = false;
    boolean isVisible = false;
    private String mMsg;

    public SendMsgItem(String msg){
        mMsg =msg;
        CheckObservable.getInstance().registerObserver(this);
    }

    @Override
    public void convert(RecyclerViewHolder holder) {
        TextView textView = holder.getViewById(R.id.msg_send);
        textView.setText(mMsg);
        final CheckBox checkBox = holder.getViewById(R.id.check_box);
        View view = holder.getView();
        if (isVisible) {
            checkBox.setVisibility(View.VISIBLE);
            checkBox.setChecked(isChecked);
            view.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    isChecked = !isChecked;
                    checkBox.setChecked(isChecked);
                }
            });
        } else {
            checkBox.setVisibility(View.GONE);
            view.setOnClickListener(null);
            isChecked = false;
        }
    }

    @Override
    public void changed() {
        isVisible = !isVisible;
    }
}
/**省略部分代码**/

convert() 方法中进行了状态的判断显示以及保存。基本所有的逻辑和数据都在当前类中实现,有良好的扩展性,便于日后更改维护。CheckObservable 类与 Observer 接口是为了后面显示选择框用的,用了观察者模式,代码如下:

public class CheckObservable extends Observable<CheckObservable.Observer> {
    private static CheckObservable mInstance;

    private CheckObservable() {
    }

    public static CheckObservable getInstance() {
        if (mInstance == null) {
            synchronized (CheckObservable.class) {
                if (mInstance == null) {
                    mInstance = new CheckObservable();
                }
            }
        }
        return mInstance;
    }

    public void notifyChanged() {
        for (int i = 0; i < mObservers.size(); i++) {
            mObservers.get(i).changed();
        }
    }

    public interface Observer {
        void changed();
    }
}

接着是 MainActivity

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        /**省略部分代码**/
        final RecyclerAdapter adapter = new RecyclerAdapter(this, dataList);
        initData();
        adapter.setOnItemLongClickListener(new RecyclerAdapter.OnItemLongClickListener() {
            @Override
            public boolean onLongClick(View v , int position) {
                CheckObservable.getInstance().notifyChanged();
                adapter.notifyDataSetChanged();
                return false;
            }
        });
        recyclerView.setAdapter(adapter);
    }
    private void initData() {
        RecyclerItem item;
        for (int i = 0; i < 20; i++) {
            if (i % 2 == 0) {
                item = new SendMsgItem("Nice to meet you. "+i);
            } else {
                item = new ReceiveMsgItem("Nice to meet you too. "+i);
            }
            dataList.add(item);
        }
    }

Adapter 中也封装了 setOnItemClickListenersetOnItemLongClickListener ,方便在 Activity 和 Fragment 中使用。如果需要例子 Demo 源码的话请拉至文末。

三、 源码实现

经过上面的例子和说明,相信你已大概了解该封装的用法了,应该也知道此 Adapter 需要配套使用。即 RecyclerAdapter+RecyclerItem+RecyclerViewHolder,缺一不可。下面我们就来一一分析一下这三个类具体的实现。

(1) RecyclerItem 接口:

public interface RecyclerItem {

    /**
     *@return 返回item的type,当使用多item时必须保证每个item返回不同的type
     */
    int getType();

    /**
     * 此方法在{@link RecyclerAdapter#onCreateViewHolder(ViewGroup, int)}
     * 内调用,你应该在此方法的实现中返回item的view对象
     * @return item view
     */
    View getView(Context context, ViewGroup parent);

    /**
     * 此方法在{@link RecyclerAdapter#onBindViewHolder(RecyclerViewHolder, int)}
     * 内调用,你应该在此方法的实现中进行item的view与数据的绑定
     * @param holder 当前item的{@link RecyclerViewHolder}
     */
    void convert(RecyclerViewHolder holder);
}

各个方法的说明在注释中已经解释的很清楚了。
在使用过程中,应该在每个 RecyclerItem 实现类里面进行数据的存取以及数据和视图的绑定。

(2)RecyclerViewHolder

public class RecyclerViewHolder extends RecyclerView.ViewHolder {

    private View mView;
    private SparseArray<View> mViewMap = new SparseArray<>();

    public <T extends RecyclerItem> RecyclerViewHolder(Context context, T item, ViewGroup parent) {
        super(item.getView(context, parent));
        mView = itemView;
        mView.requestFocusFromTouch();
    }

    //返回根View
    public View getView() {
        return mView;
    }

    /**
     * 根据View的id来返回view实例
     */
    public <T extends View> T getViewById(int ResId) {
        View view = mViewMap.get(ResId);
        if (view == null) {
            view = mView.findViewById(ResId);
            mViewMap.put(ResId, view);
        }
        return (T) view;
    }
}

该类的实现参考了鸿洋大神的通用 ViewHolder 中的部分代码,用了 SparseArray 来保存 View 对象,避免每次都要 findViewById。

(3)RecyclerAdapter

public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerViewHolder> {

    private List<RecyclerItem> mItemList;
    private Context mContext;
    //保存当前 ViewHolder 在 Adapter 中的位置
    private int mPosition;
    private OnItemClickListener mClickListener;
    private OnItemLongClickListener mLongClickListener;

    public RecyclerAdapter(Context context, List<RecyclerItem> itemList) {
        mContext = context;
        mItemList = itemList;
    }

    @Override
    public RecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new RecyclerViewHolder(mContext, mItemList.get(mPosition), parent);
    }

    @Override
    public void onBindViewHolder(RecyclerViewHolder holder, int position) {
        //保存view的position
        holder.getView().setTag(position);
        setListener(holder);
        mItemList.get(position).convert(holder);
    }

    @Override
    public int getItemViewType(int position) {
        mPosition = position;
        return mItemList.get(position).getType();
    }


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

    public Context getContext() {
        return mContext;
    }

    protected void setListener(final RecyclerViewHolder holder) {
        if (mClickListener != null && !holder.getView().hasOnClickListeners()) {
            holder.getView().setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    //从tag中取出position
                    mClickListener.onClick(v,(int)v.getTag());
                }
            });
        }
        if (mLongClickListener != null && !holder.getView().hasOnClickListeners()) {
            holder.getView().setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    return mLongClickListener.onLongClick(v,(int)v.getTag());
                }
            });
        }
    }

    public void setOnItemClickListener(OnItemClickListener l) {
        mClickListener = l;
    }

    public void setOnItemLongClickListener(OnItemLongClickListener l) {
        mLongClickListener = l;
    }

    public interface OnItemClickListener {
        void onClick(View view,int position);
    }

    public interface OnItemLongClickListener {
        boolean onLongClick(View view,int position);
    }
}

此类继承了 RecyclerVIew.Adapter 抽象类,然后实现了四个抽象方法

总结

  为了学习 RecyclerView 与设计模式的使用,本猿封装了此 Adapter 进行日常偷懒。
  该 Adapter 需要与 RecyclerItem 以及RecylerViewHoler 配合使用。在RecylerItem实例中的convert方法内调用对象的数据,不需要额外的类作为数据载体,每一个 RecyclerItem 都对应一个 Adapter 内的数据源。
  为了使各个 Item 的功能逻辑分离开来,应该尽量在具体的 RecyclerItem类中进行数据和功能的实现和整合。后面维护过程中需要更改哪个 item 只需要打开与修改该 item 的实现类即可。

下一篇文章将会带来用此封装扩展的 RecyclerView,支持下拉刷新,上拉加载,HeaderView,FooterView...

Demo地址: https://github.com/gminibird/RecyclerViewTest

本文同步发送到本人公众号,欢迎关注:
扫一扫关注
上一篇 下一篇

猜你喜欢

热点阅读