base-adapter-helper的RecyclerView版

前几天我写了一篇分析base-adapter-helper的文章,文章中提到了尝试用和base-adapter-helper相同的api实现RecyclerView适配器。现在已经实现了。

在这期间也看了简书作者轻微 的一篇文章:RecyclerView适配器的超省写法 。发现他的实现原理其实和base-adapter-helper有很大的相似之处,不知道这是英雄所见略同的巧合,还是借鉴了base-adapter-helper,我想还是前者的可能性略大,因为代码相似度很低。

好了,回到简化RecyclerView适配器这个话题上来。

其实总的来说要比ListView实现起来更简单。思路也和ListView版本很相似。就三点:

  1. 列表数据要使用泛型;

  2. 原本ViewHolder中的View成员变量转而通过view数组来实现(比如SparseArray);

  3. 把数据绑定通过实现抽象方法来实现。

我更改了一下base-adapter-helper的项目结构:

D7E06BB4-6089-44E1-975D-3CD7D8E91CCC.png

如果你想用RecyclerView的适配器,import recyclerview包下面的QuickAdapter。

首先BaseQuickAdapter

package com.joanzapata.android.recyclerview;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.List;
/**
 * Created by jianghejie on 15/8/8.
 */
public abstract class BaseQuickAdapter <T, H extends BaseAdapterHelper> extends RecyclerView.Adapter<BaseAdapterHelper> implements View.OnClickListener{
    protected static final String TAG = BaseQuickAdapter.class.getSimpleName();
    protected final Context context;
    protected final int layoutResId;
    protected final List<T> data;
    protected boolean displayIndeterminateProgress = false;
    private OnItemClickListener mOnItemClickListener = null;
    //define interface
    public static interface OnItemClickListener {
        void onItemClick(View view , int position);
    }
    /**
     * Create a QuickAdapter.
     * @param context     The context.
     * @param layoutResId The layout resource id of each item.
     */
    public BaseQuickAdapter(Context context, int layoutResId) {
        this(context, layoutResId, null);
    }
    /**
     * Same as QuickAdapter#QuickAdapter(Context,int) but with
     * some initialization data.
     * @param context     The context.
     * @param layoutResId The layout resource id of each item.
     * @param data        A new list is created out of this one to avoid mutable list
     */
    public BaseQuickAdapter(Context context, int layoutResId, List<T> data) {
        this.data = data == null ? new ArrayList<T>() : data;
        this.context = context;
        this.layoutResId = layoutResId;
    }
    @Override
    public int getItemCount() {
        return data.size();
    }
    public T getItem(int position) {
        if (position >= data.size()) return null;
        return data.get(position);
    }
    @Override
    public BaseAdapterHelper onCreateViewHolder(ViewGroup viewGroup,  int viewType) {
        View view = LayoutInflater.from(viewGroup.getContext()).inflate(layoutResId, viewGroup, false);
        view.setOnClickListener(this);
        BaseAdapterHelper vh = new BaseAdapterHelper(view);
        return vh;
    }
    @Override
    public void onBindViewHolder(BaseAdapterHelper helper,  int position) {
        helper.itemView.setTag(position);
        T item = getItem(position);
        convert((H)helper, item);
    }
    /**
     * Implement this method and use the helper to adapt the view to the given item.
     * @param helper A fully initialized helper.
     * @param item   The item that needs to be displayed.
     */
    protected abstract void convert(H helper, T item);
    @Override
    public void onClick(View v) {
        if (mOnItemClickListener != null) {
            mOnItemClickListener.onItemClick(v,(int)v.getTag());
        }
    }
    public void setOnItemClickListener(OnItemClickListener listener) {
        this.mOnItemClickListener = listener;
    }
}

然后是BaseAdapterHelper

在这里BaseAdapterHelper就是一个RecyclerView.ViewHolder,为了一致性,我取名为BaseAdapterHelper。

package com.joanzapata.android.recyclerview;
import android.support.v7.widget.RecyclerView;
import android.util.SparseArray;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
/**
 * Created by jianghejie on 15/8/8.
 */
public class BaseAdapterHelper extends RecyclerView.ViewHolder{
    private SparseArray<View> views;
    public BaseAdapterHelper(View itemView){
        super(itemView);
        this.views = new SparseArray<View>();
    }
    public TextView getTextView(int viewId) {
        return retrieveView(viewId);
    }
    public Button getButton(int viewId) {
        return retrieveView(viewId);
    }
    public ImageView getImageView(int viewId) {
        return retrieveView(viewId);
    }
    public View getView(int viewId) {
        return retrieveView(viewId);
    }
    
    @SuppressWarnings("unchecked")
    protected <T extends View> T retrieveView(int viewId) {
        View view = views.get(viewId);
        if (view == null) {
            view = itemView.findViewById(viewId);
            views.put(viewId, view);
        }
        return (T) view;
    }
}

在前面一片文章中,我们解决base-adapter-helper的缺点是采用抛弃了诸多setXXX方法,直接调用retrieveView,然而感觉那样会多一行代码,看起来不够精炼,在RecyclerView适配器的超省写法 中我学到了一点,那就是将setXXX改成getXXX,在getXXX方法中做好强制转换。

而且一般来说,我们只需要很有限的getXXX方法,基本有这三个就可以满足90%的需求了:

    public TextView getTextView(int viewId) {
        return retrieveView(viewId);
    }
    public Button getButton(int viewId) {
        return retrieveView(viewId);
    }
    public ImageView getImageView(int viewId) {
        return retrieveView(viewId);
    }

但是有些时候也许我们并不需要知道view的具体类型,比如在设置透明度,可见状态的时候,所以最好还是增加一个获得普通view的方法:

public TextView getTextView(int viewId) {
    return retrieveView(viewId);
}
public Button getButton(int viewId) {
    return retrieveView(viewId);
}
public ImageView getImageView(int viewId) {
    return retrieveView(viewId);
}
public View getView(int viewId) {
    return retrieveView(viewId);
}

最后,需要提醒大家一点,就是这种实现方式和传统的RecyclerView.Adapter在流程上稍微有一点点不同。

传统RecyclerView的ViewHolder在onCreateViewHolder的时候就已经找到了所有要绑定的view,如下面的ViewHolder

public static class ViewHolder extends RecyclerView.ViewHolder {
    public TextView title;
    public TextView author;
    public TextView description;
    public TextView date;
    public TextView count;
    public ImageView litPic;
    public ViewHolder(View view){
        super(view);
        title = (TextView) view
                .findViewById(R.id.blog_listitem_title);
        author = (TextView) view
                .findViewById(R.id.blog_listitem_author);
        date = (TextView) view
                .findViewById(R.id.blog_listitem_date);
        litPic = (ImageView) view
                .findViewById(R.id.blog_listitem_litpic);
        description = (TextView) view
                .findViewById(R.id.description);
    }
}

这些View都是成员变量,在调用构造函数的时候已经初始化了。

而我们的实现中,这些View是在绑定的时候被初始化的,我们必须这样做,因为我们无法预知绑定的item的布局以及数据模型是怎样的。

不过这并不会带来丝毫的性能损失,我们做的只是把findViewById放在了onBindViewHolder中,并且只有第一次绑定的时候才会去执行findViewById。

使用


mRecyclerView = (RecyclerView) mPageView.findViewById(R.id.my_recycler_view);
mRecyclerView.enableLoadmore();
LinearLayoutManager layoutManager = new LinearLayoutManager(appContext);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
layoutManager.scrollToPosition(0);
mRecyclerView.setLayoutManager(layoutManager);
//init the  listview
mAdapter = new QuickAdapter<Blog>(appContext, R.layout.item_blog_list, blogData){
   @Override
   protected void convert(BaseAdapterHelper helper, Blog blog) {
      helper.getTextView(R.id.blog_listitem_title).setText(Html.fromHtml(blog.getTitle()));
      helper.getTextView(R.id.blog_listitem_author).setText(blog.getAuthor());
      helper.getTextView(R.id.description).setText(blog.getDescription());
      Picasso.with(context).load(blog.getImageUrl()).into(helper.getImageView(R.id.blog_listitem_litpic));
      }
   }
};
mRecyclerView.setAdapter(mAdapter);

是不是和ListView版本非常一致。

源码下载

现在只提供网盘的下载地址,完善之后再提交到github。

http://pan.baidu.com/s/1kT9wBHH