RecyclerView的基本使用、RecyclerView万能adapter及多条目的实现

RecyclerView是android5.0以后出现的一个Material Design风格的控件,可以用来实现Listview、GridView、瀑布流等效果;

RecyclerView的基本使用
1、引入依赖库

compile 'com.android.support:recyclerview-v7:25.3.1'

2、xml文件中使用

.support.v7.widget.RecyclerView
    android:id="@+id/rv"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

3、获取控件并设置布局管理器和适配器

recyclerView = (RecyclerView) findViewById(R.id.rv);
//设置布局管理器
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(adapter);

这里需要注意在setAdapter之前必须要先setLayoutManager,否则会没有效果;系统提供了下面几种布局管理器:

//线性管理器,支持横向、纵向。
recyclerView.setLayoutManager(new LinearLayoutManager(this)); 
//网格布局管理器
recyclerView.setLayoutManager(new GridLayoutManager(this, 4)); 
//瀑布就式布局管理器
recyclerView.setLayoutManager(new StaggeredGridLayoutManager());

3.1、setAdapter设置适配器

class ViewAdapter extends RecyclerView.Adapter<ViewAdapter.Holder>{

    @Override
    public ViewAdapter.Holder onCreateViewHolder(ViewGroup parent, int viewType) {
        //加载条目布局
        View view = LayoutInflater.from(BaseUserActivity.this).inflate(R.layout.recycler_item, parent, false);
        return new Holder(view);
    }

    @Override
    public void onBindViewHolder(final ViewAdapter.Holder holder, final int position) {
        //设定数据
        holder.tv.setText(datas.get(position));     
    }

    @Override
    public int getItemCount() {
        //返回整个RecyclerView条目数
        return datas.size();
    }
    class Holder extends RecyclerView.ViewHolder{
        TextView tv;
        public Holder(View itemView) {
            super(itemView);
            tv = (TextView) itemView.findViewById(R.id.tv);
        }
    }
}

会发现和ListView的适配器不一样,RecyclerView的适配器必须有一个ViewHolder,简单的线性列表效果就实现了,但是会发现并没有分割线,还有就是每个条目没有点击和长按事件;是的RecyclerView只负责复用和释放,其他的效果需要自己去实现,可以看出其高度的解耦;

4、添加分割线

//添加分割线
recyclerView.addItemDecoration();

需要传入一个ItemDecoration对象,而ItemDecoration是一个abstract类,实际上传入的是子类对象,就写一个类去extend ItemDecoration,

public class DividerGridItemDecoration extends RecyclerView.ItemDecoration{
    private Drawable mDivider;
    private int[] attrs = new int[]{
            android.R.attr.listDivider
    };

    public DividerGridItemDecoration(Context context, int drawableResourceId) {
//        TypedArray typedArray = context.obtainStyledAttributes(attrs);
//        mDivider = typedArray.getDrawable(0);
//        typedArray.recycle();
        //解析Drawable
        mDivider = ContextCompat.getDrawable(context, drawableResourceId);
    }

}

ItemDecoration里面有下面这些方法,不过有些是已经过时的,extends ItemDecoration的时候需要去复写onDraw(),getItemOffsets();方法;

 public static abstract class ItemDecoration {

    public void onDraw(Canvas c, RecyclerView parent, State state) {
        onDraw(c, parent);
    }


    @Deprecated
    public void onDraw(Canvas c, RecyclerView parent) {
    }


    public void onDrawOver(Canvas c, RecyclerView parent, State state) {
        onDrawOver(c, parent);
    }


    @Deprecated
    public void onDrawOver(Canvas c, RecyclerView parent) {
    }


    @Deprecated
    public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
        outRect.set(0, 0, 0, 0);
    }


    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
        getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
            parent);
    }
}

4.1、复写getItemOffsets()得到偏移量

@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
    // 四个方向的偏移值
    int right = mDivider.getIntrinsicWidth();
    int bottom = mDivider.getIntrinsicHeight();
    int position = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
    if (isLastColumn(position, parent)) {// 是否是最后一列
        right = 0;
    }
    if (isLastRow(position, parent)) {// 是否是最后一行
        bottom = 0;
    }
    outRect.set(0, 0, right, bottom);
}

4.2、onDraw方法进行绘制

@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
    //绘制垂直间隔线(垂直的矩形)
    drawVertical(c, parent);
    //绘制水平间隔线
    drawHorizontal(c, parent);
}

4.2.1、绘制垂直间隔线(垂直的矩形)

private void drawVertical(Canvas c, RecyclerView parent) {
    //绘制垂直间隔线(垂直的矩形)
    int childCount = parent.getChildCount();
    for (int i = 0; i < childCount; i++) {
        View child = parent.getChildAt(i);
        RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
        int left = child.getRight() + params.rightMargin;
        int right = left + mDivider.getIntrinsicWidth();
        int top = child.getTop() - params.topMargin;
        int bottom = child.getBottom() + params.bottomMargin;
        mDivider.setBounds(left, top, right, bottom);
        mDivider.draw(c);
    }
}

4.2.2、绘制水平间隔线

private void drawHorizontal(Canvas c, RecyclerView parent) {
    // 绘制水平间隔线
    //获取RecyclerView的条目数
    int childCount = parent.getChildCount();
    //遍历条目数进行绘制
    for (int i = 0; i < childCount; i++) {
        //得到每一个条目view
        View child = parent.getChildAt(i);
        //利用RecyclerView.LayoutParams获取Margin值
        RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
        //计算  left  right  top  bottom值
        int left = child.getLeft() - params.leftMargin;
        //mDivider.getIntrinsicWidth()获取Drawable的宽度
        int right = child.getRight() + params.rightMargin + mDivider.getIntrinsicWidth();
        int top = child.getBottom() + params.bottomMargin;
        //mDivider.getIntrinsicHeight()获取Drawable的高度
        int bottom = top + mDivider.getIntrinsicHeight();
        //设定 left  right  top  bottom值
        mDivider.setBounds(left, top, right, bottom);
        //对Drawable进行绘制
        mDivider.draw(c);
    }
}

5、添加条目点击和长按事件
条目点击和长按事件这里是采用接口回调的方式实现的。

/**
 * Created by Administrator on 2017/5/28.
 * 点击事件接口
 */

public interface OnItemClickListener {
    void onItemClick(Object obj, int position);
}
/**
 * Created by Administrator on 2017/5/28.
 * 长按点击事件接口
 */

public interface OnLongClickListener {
    boolean onItemLongClick(Object obj, int position);
}

然后在adapter里面设置条目点击和长按事件的监听并提供set相应方法进行回调监听;

//条目点击事件
if(mItemClickListener!=null){
    holder.itemView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            mItemClickListener.onItemClick(datas.get(position),position);
                }
    });
}
//长按点击事件
if(mItemLoogClickListener!=null){
    holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
        @Override
        public boolean onLongClick(View v) {
            return mItemLoogClickListener.onItemLongClick(datas.get(position),position);
        }
    });
}

提供外界调用的方法;

//使用接口回调点击事件
private OnItemClickListener mItemClickListener;
public void setOnItemClickListener(OnItemClickListener itemClickListener){
    this.mItemClickListener=itemClickListener;
}
//使用接口回调点击事件
private OnLongClickListener mItemLoogClickListener;
public void setOnItemLongClickListener(OnLongClickListener itemLongClickListener){
    this.mItemLoogClickListener=itemLongClickListener;
}

这样子条目分割线、条目点击和长按事件就添加上去了;
效果如下:
RecyclerView的基本使用、RecyclerView万能adapter及多条目的实现_第1张图片

RecyclerView万能adapter的封装
RecyclerView使用一次就需求去写一个adapter,去实现getItemCount、onBindViewHolder、onCreateViewHolder方法,这样子重复写的代码还是挺过的,所以就对RecyclerView的adapter进行封装,封装成一个万能的adapter,主要涉及到ViewHolder和adapter封装;

ViewHolder的封装

/**
 * Created by Administrator on 2017/5/28.
 * RecyclerView通用ViewHolder
 */

public class ViewHolder extends RecyclerView.ViewHolder {
    //用于缓存已找的界面
    private SparseArray mView;
    public ViewHolder(View itemView) {
        super(itemView);
        mView=new SparseArray<>();
    }
    public  T getView(int viewId){
        //对已有的view做缓存
        View view=mView.get(viewId);
        //使用缓存的方式减少findViewById的次数
        if(view==null){
            view=itemView.findViewById(viewId);
            mView.put(viewId,view);
        }
        return (T) view;
    }
    //通用的功能进行封装  设置文本 设置条目点击事件  设置图片
    public ViewHolder setText(int viewId ,CharSequence text){
        TextView view = getView(viewId);
        view.setText(""+text);
        //希望可以链式调用
        return this;
    }

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

    /**
     * 加载图片资源路径
     * @param viewId
     * @param imageLoader
     * @return
     */
    public ViewHolder setImagePath(int viewId,HolderImageLoader imageLoader){
        ImageView iv=getView(viewId);
        imageLoader.loadImage(iv,imageLoader.getPath());
        return this;
    }
    public abstract static class HolderImageLoader{
        private String mPath;
        public HolderImageLoader(String path){
            this.mPath=path;
        }

        /**
         * 需要去复写这个方法加载图片
         * @param iv
         * @param path
         */
        public abstract void loadImage(ImageView iv,String path);
        public String getPath(){
            return mPath;
        }
    }
}

adapter的封装

public abstract class RecyclerCommonAdapter extends RecyclerView.Adapter{
    //条目布局
    private int mLayoutId;
    private List mData;
    private Context mContext;
    private LayoutInflater mInflater;
    private MulitiType mTypeSupport;
    public RecyclerCommonAdapter(Context context,List data,int layoutId){
        this.mContext=context;
        mInflater=LayoutInflater.from(mContext);
        this.mData=data;
        this.mLayoutId=layoutId;
    }
    //需要多布局
    public RecyclerCommonAdapter(Context context,List data,MulitiType typeSupport){
        this(context,data,-1);
        this.mTypeSupport=typeSupport;
    }
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if(mTypeSupport!=null){
            //需要多布局
            mLayoutId=viewType;
        }
        //创建view
        View view = mInflater.inflate(mLayoutId, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public int getItemViewType(int position) {
        //多布局问题
        if(mTypeSupport!=null){
            return mTypeSupport.getLayoutId(mData.get(position));
        }
        return super.getItemViewType(position);
    }

    @Override
    public void onBindViewHolder(final ViewHolder holder, final int position) {
        //绑定数据
        bindData(holder,mData.get(position),position);
        //条目点击事件
        if(mItemClickListener!=null){
            holder.itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    mItemClickListener.onItemClick(mData.get(position),position);
                }
            });
        }
        //长按点击事件
        if(mItemLoogClickListener!=null){
            holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    return mItemLoogClickListener.onItemLongClick(mData.get(position),position);
                }
            });
        }
    }
    /**
     * 将必要参数传递出去
     * @param holder
     * @param data
     * @param position
     */
    protected abstract void bindData(ViewHolder holder, DATA data, int position);

    @Override
    public int getItemCount() {
        return mData.size();
    }
    //使用接口回调点击事件
    private OnItemClickListener mItemClickListener;
    public void setOnItemClickListener(OnItemClickListener itemClickListener){
        this.mItemClickListener=itemClickListener;
    }
    //使用接口回调点击事件
    private OnLongClickListener mItemLoogClickListener;
    public void setOnItemLongClickListener(OnLongClickListener itemLongClickListener){
        this.mItemLoogClickListener=itemLongClickListener;
    }
}

这样子在写adapter的时候,extends RecyclerCommonAdapter,就只需要去复写一个构造方法和bindData()方法就可以了;

public class RecycleAdapter extends RecyclerCommonAdapter<String> {

    public RecycleAdapter(Context context, List data, int layoutId) {
        super(context, data, layoutId);
    }

    @Override
    protected void bindData(ViewHolder holder, String s, int position) {
        //绑定数据
        holder.setText(R.id.id_num,s);
    }
}

实现起来的效果和上面是一样的,但是是不是感觉代码少了很多,不需要每次都去复写getItemCount、onBindViewHolder、onCreateViewHolder方法,也不需要每次都去写一个ViewHolder,这样可以大大提高开发速度。

RecyclerView多条目的实现
RecyclerView多条目的实现其实在RecyclerView万能adapter封装的时候就已经将其封装进去了,不过不管是ListView的多条目效果还是RecyclerView多条目效果去复写下面这个方法;

//获取条目的类型
@Override
public int getItemViewType(int position) {
    //多布局问题
    if(mTypeSupport!=null){
        return mTypeSupport.getLayoutId(mData.get(position));
    }
    return super.getItemViewType(position);
}

在extends RecyclerCommonAdapter的时候使用多布局的构造方法

//需要多布局
public RecyclerCommonAdapter(Context context,List data,MulitiType typeSupport){
    this(context,data,-1);
    this.mTypeSupport=typeSupport;
}

根据数据类型去判断加载相应的布局

private class CommonAdapter extends RecyclerCommonAdapter<ChatData> {

    public CommonAdapter(Context context, List data) {
        super(context, data, new MulitiType() {
        @Override
        public int getLayoutId(ChatData item) {
            if(item.isMe==1){
                return R.layout.item_chat_me;

            }
                return R.layout.item_chat_friend;
            }
        });
    }

    @Override
    protected void bindData(ViewHolder holder, ChatData chatData, int position) {
        holder.setText(R.id.chat_text,chatData.chatContent);
    }
}

运行效果:
RecyclerView的基本使用、RecyclerView万能adapter及多条目的实现_第2张图片

ScrollView和RecyclerView嵌套使用
都知道ScrollView和ListView嵌套使用,如果不做处理的话,会导致ListView只能显示一个条目,原因和解决在 ScrollView嵌套 ListView显示不全(http://blog.csdn.net/wangwo1991/article/details/73662365)播客中有;但是ScrollView和RecyclerView嵌套使用就不会出现RecyclerView显示不全的现象,系统已经做了很好的处理;
找到ScrollView源码里面的onMeasure()方法,

//mLayout为空会对根据宽高模式进行测量
if (mLayout == null) {
    defaultOnMeasure(widthSpec, heightSpec);
    return;
}
/**
 * Used when onMeasure is called before layout manager is set
 */
void defaultOnMeasure(int widthSpec, int heightSpec) {
    // calling LayoutManager here is not pretty but that API is already public and it is better
    // than creating another method since this is internal.
    final int width = LayoutManager.chooseSize(widthSpec,
        getPaddingLeft() + getPaddingRight(),
        ViewCompat.getMinimumWidth(this));
    final int height = LayoutManager.chooseSize(heightSpec,
        getPaddingTop() + getPaddingBottom(),
        ViewCompat.getMinimumHeight(this));

    setMeasuredDimension(width, height);
}
/**
  * Chooses a size from the given specs and parameters that is closest to the desired size
  * and also complies with the spec.
  *
  * @param spec The measureSpec
  * @param desired The preferred measurement
  * @param min The minimum value
  *
  * @return A size that fits to the given specs
  */
public static int chooseSize(int spec, int desired, int min) {
    final int mode = View.MeasureSpec.getMode(spec);
    final int size = View.MeasureSpec.getSize(spec);
        switch (mode) {
            case View.MeasureSpec.EXACTLY:
            //返回widthSpec或者heightSpec  也就是宽或者高
                return size;
            case View.MeasureSpec.AT_MOST:
            //
                return Math.min(size, Math.max(desired, min));
            case View.MeasureSpec.UNSPECIFIED:
            default:
                return Math.max(desired, min);
    }
}

通过上面这些处理ScrollView和RecyclerView嵌套使用的时候不会出现RecyclerView条目不全。

源码地址:
http://download.csdn.net/download/wangwo1991/9940634

你可能感兴趣的:(MaterialDesign)