Skip to content

Reoger/StudentsRecycleView

Repository files navigation

RecyclerView基础使用

  1. 添加依赖
compile 'com.android.support:recyclerview-v7:25.3.0'
  1. 创建bean对象

这里的bean对象可以写成两个部分,一部分表示原始数据,一部分在RecycelView进行显示。 这样说可能有点抽象,举个例子说明。我需要显示如图的样式: 显示的效果

我创建两个bean对象,一个用于数据的传递,一个在adapter中用于item的显示。InfoBean用于控制数据。ItemHolder用于显示数据 代码请参考: infoBean对象如下:

package com.hut.reoger.studentsrecycleview.bean;

/**
 * Created by 24540 on 2017/3/28.
 */

public class InfoBean {
    private int id;
    private String title;
    private String content;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}

ItemHolder代码如下:


public class ItemHolder extends RecyclerView.ViewHolder{
    public ImageView imageView;
    public TextView teTitle;
    public TextView teContent;


    public ItemHolder(View itemView) {
        super(itemView);

        imageView = (ImageView) itemView.findViewById(R.id.item_image);
        teTitle = (TextView) itemView.findViewById(R.id.item_title);
        teContent = (TextView) itemView.findViewById(R.id.item_content);
    }
}

这里需要记住的是,这个类需要继承ViewHolder。当然,这个类写在adapter中也完全是ok的。

  1. 创建adapter对象 详细参照例子,这里提出要点:
  • 继承RecyclerView.Adapter
  • 实现继承的方法。
  • 利用onCreateViewHolder方法创建ViewHolder
  • 利用onBindViewHolder方法显示具体内容
  • 利用getItemCount总计数据的总数

4.创建item布局和主布局 这一点比较简单,不做解释

  1. 在主界面显示recyclerView 这一点同普通的listView实现基本相同,有一点需要注意的是:在显示之前需要为recyclerVIew设置布局。 关键代码如下:
RecyclerView.LayoutManager mManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
mRecyclerView.setLayoutManager(mManager);

基本上到这里,就差不多完成了。

在RecycleView中,并没有直接为Item开放OnItemClick等点击事件,这需要我们自己动手来完成。 下面介绍三中比较常用的方法来实现添加点击事件。

在adapter中添加点击事件

比较常见的一种方法。在使用listView时,我们有时候也会使用这种方法来实现添加点击事件。因为在 adapter中添加点击事件的可以实现最item子view点击事件的监控。 思路如下:
在adapter中新建并暴露自己定义的接口类型。

private OnRecyclerViewItemClickListener mOnItenClickListener = null;
private OnRecyclerViewItemLongClickListener mOnItemLongClickListener = null;

public void setOnItemClickListener(OnRecyclerViewItemClickListener listener){
    this.mOnItenClickListener = listener;
}

public void setOnItemLongClickListener(OnRecyclerViewItemLongClickListener listener){
    this.mOnItemLongClickListener = listener;
}

在adapter中添加对指定元素的的点击事件。

v.setOnLongClickListener(new View.OnLongClickListener() {
        @Override
        public boolean onLongClick(View v) {
            if(mOnItemLongClickListener!=null)
            mOnItemLongClickListener.OnItemLongClickListener(v, (Integer) v.getTag());
            return false;
        }
    });

在接口调用中,调用自己定义的接口来进行实现。


@Override
public void onClick(View v) {
    if(mOnItemLongClickListener !=null)
    mOnItenClickListener.OnItemClickListener(v, (Integer) v.getTag());
}

在activity中调用接口实现监听

mMyAdapter.setOnItemClickListener(new MyAdapter.OnRecyclerViewItemClickListener() {
           @Override
           public void OnItemClickListener(View view, int position) {
               Toast.makeText(MainActivity.this,"通过方法3实现的点击事件"+position,Toast.LENGTH_SHORT).show();
           }
       });
       mMyAdapter.setOnItemLongClickListener(new MyAdapter.OnRecyclerViewItemLongClickListener() {
           @Override
           public void OnItemLongClickListener(View view, int position) {
               Toast.makeText(MainActivity.this,"通过方法3实现的点击事件长安"+position,Toast.LENGTH_SHORT).show();
           }
       });

通过重写GestureDetectorCompat实现监听

虽然RecycleView没有直接实现对应的点击事件,但是在它给我们提供的api中,会发现它还是给我们预留了接口来进行实现 。通过重写GestureDetectorCompat来实现对item点击事件的监控。

 private GestureDetectorCompat gestureDetectorCompat;

    public interface OnItemClickListener{
        void onItemClick(View view, int position);
        void onItemLongClick(View view,int postion);
    }

    public ItemClickListener(final RecyclerView recyclerView,final OnItemClickListener clickListener) {
        gestureDetectorCompat = new GestureDetectorCompat(recyclerView.getContext(),
                new GestureDetector.SimpleOnGestureListener(){
                    @Override
                    public boolean onSingleTapUp(MotionEvent e) {
                        View childView = recyclerView.findChildViewUnder(e.getX(),e.getY());
                        if(childView != null && clickListener!= null){
                            clickListener.onItemClick(childView,recyclerView.getChildAdapterPosition(childView));
                        }
                        return true;
                    }

                    @Override
                    public void onLongPress(MotionEvent e) {
                        View childView = recyclerView.findChildViewUnder(e.getX(),e.getY());
                        if(childView != null && clickListener != null){
                            clickListener.onItemLongClick(childView,recyclerView.getChildAdapterPosition(childView));
                        }
                    }
                });
    }

    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        gestureDetectorCompat.onTouchEvent(e);
        return false;
    }

在Activity中进行调用,可以参照下面:

mRecyclerView.addOnItemTouchListener(new ItemClickListener(mRecyclerView, new ItemClickListener.OnItemClickListener() {
   @Override
   public void onItemClick(View view, int position) {
       Toast.makeText(MainActivity.this, "通过方法1实现的点击事件" + position, Toast.LENGTH_SHORT).show();
   }

   @Override
   public void onItemLongClick(View view, int position) {
       Toast.makeText(MainActivity.this, "通过方法1实现的点击事件" + position, Toast.LENGTH_SHORT).show();
   }
}));

利用OnChildAttachStateChangeListener来实现

参照博客http://www.littlerobots.nl/blog/Handle-Android-RecyclerView-Clicks/

package com.hut.reoger.studentsrecycleview.addClickListener;

import android.support.v7.widget.RecyclerView;
import android.view.View;

import com.hut.reoger.studentsrecycleview.R;

/**
 * Created by 24540 on 2017/3/28.
 * 为Item添加点击事件:方法二:利用OnChildAttachStateChangeListener来实现
 * 同时,使用方法,可以实现对item子控件的监听,具体实现参见类:
 * 参考链接:http://www.littlerobots.nl/blog/Handle-Android-RecyclerView-Clicks/
 */

public class ItemClickSupport {
    private final RecyclerView mRecyclerView;
    private OnItemClickListener mOnItemClickListener;
    private OnItemLongClickListener mOnItemLongClickListener;

    private View.OnClickListener mOnClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (mOnItemClickListener != null) {
                RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(v);
                mOnItemClickListener.onItemClicked(mRecyclerView, holder.getAdapterPosition(), v);
            }
        }
    };

    private View.OnLongClickListener mOnLongClickListener = new View.OnLongClickListener() {
        @Override
        public boolean onLongClick(View v) {
            if (mOnItemLongClickListener != null) {
                RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(v);
                return mOnItemLongClickListener.onItemLongClicked(mRecyclerView, holder.getAdapterPosition(), v);
            }
            return false;
        }
    };

    private RecyclerView.OnChildAttachStateChangeListener mAttachListener
            = new RecyclerView.OnChildAttachStateChangeListener() {
        @Override
        public void onChildViewAttachedToWindow(View view) {
            if (mOnItemClickListener != null) {
                view.setOnClickListener(mOnClickListener);
            }
            if (mOnItemLongClickListener != null) {
                view.setOnLongClickListener(mOnLongClickListener);
            }
        }

        @Override
        public void onChildViewDetachedFromWindow(View view) {}
    };

    private ItemClickSupport(RecyclerView recyclerView) {
        mRecyclerView = recyclerView;
        mRecyclerView.setTag(R.id.item_click_support, this);
        mRecyclerView.addOnChildAttachStateChangeListener(mAttachListener);
    }

    public static ItemClickSupport addTo(RecyclerView view) {
        ItemClickSupport support = (ItemClickSupport) view.getTag(R.id.item_click_support);
        if (support == null) {
            support = new ItemClickSupport(view);
        }
        return support;
    }

    public static ItemClickSupport removeFrom(RecyclerView view) {
        ItemClickSupport support = (ItemClickSupport) view.getTag(R.id.item_click_support);
        if (support != null) {
            support.detach(view);
        }
        return support;
    }

    public ItemClickSupport setOnItemClickListener(OnItemClickListener listener) {
        mOnItemClickListener = listener;
        return this;
    }

    public ItemClickSupport setOnItemLongClickListener(OnItemLongClickListener listener) {
        mOnItemLongClickListener = listener;
        return this;
    }

    private void detach(RecyclerView view) {
        view.removeOnChildAttachStateChangeListener(mAttachListener);
        view.setTag(R.id.item_click_support, null);
    }

    public interface OnItemClickListener {
        void onItemClicked(RecyclerView recyclerView, int position, View v);
    }

    public interface OnItemLongClickListener {
        boolean onItemLongClicked(RecyclerView recyclerView, int position, View v);
    }
}

在MainActivity监听如下:

  ItemClickSupport.addTo(mRecyclerView).setOnItemClickListener(new ItemClickSupport.OnItemClickListener() {
               @Override
               public void onItemClicked(RecyclerView recyclerView, int position, View v) {
                    Toast.makeText(MainActivity.this,"通过方法2实现的点击事件"+position,Toast.LENGTH_SHORT).show();
               }
           });
           ItemClickSupport.addTo(mRecyclerView).setOnItemLongClickListener(new ItemClickSupport.OnItemLongClickListener() {
               @Override
               public boolean onItemLongClicked(RecyclerView recyclerView, int position, View v) {
                   Toast.makeText(MainActivity.this,"通过方法2实现的点击事件长安"+position,Toast.LENGTH_SHORT).show();
                   return false;
               }
           });

用这种方法,也可以实现对item的子view进行监控。具体实现可以参见我的代码。

综上,三种方法都可以实现对item点击事件的监控。 方法1和方法3都可以实现对item的子view实现监听。 方法2可以很方便实现对获取点击位置信息。 方法1附加在adapter中,代码有点耦合,不推荐使用方法1。

为RecyclerView添加header和footer

为recyclerView添加header和footer也是我们在开发过程中经常遇见的,实现起来也是比较简单的。下面介绍怎么添加header和footer

其实我们可以将header和footer看做是特殊的item,在添加他之后,我们值需要对他进行一些特殊的处理就可以达到我们想到的效果。

为了简单起见,我们直接在adapter中进行这一部分的操作。

在adapter中新建一些标识的int值,用于区分是否需要添加header和footer,如下所示:

public static final int TYPE_HEADER = 0;  //说明是带有Header的
public static final int TYPE_FOOTER = 1;  //说明是带有Footer的
public static final int TYPE_NORMAL = 2;  //说明是不带有header和footer的

对外暴露添加header和footer方法,参考如下:

private View mHeaderView;
private View mFooterView;

public void setHeaderView(View headerView) {
        mHeaderView = headerView;
        notifyItemInserted(0);
    }

    public void setFooterView(View footerView) {
        mFooterView = footerView;
        notifyItemInserted(getItemCount() - 1);
    }

覆写getItemViewType()方法,用于区分是否需要添加head和footer

 @Override
    public int getItemViewType(int position) {
        if (mHeaderView == null && mFooterView == null) {
            return TYPE_NORMAL;
        }
        if (position == 0) {
            return TYPE_HEADER;
        }
        if (position == getItemCount() - 1) {
            return TYPE_FOOTER;
        }
        return TYPE_NORMAL;
    }

在onCreateViewHolder中添加对header和footer的支持

if (mHeaderView != null && viewType == TYPE_HEADER) {
    return new ItemHolder(mHeaderView);
}
if (mFooterView != null && viewType == TYPE_FOOTER) {
    return new ItemHolder(mFooterView);
}

在onBindViewHolder增加对header和footer的处理,如果当前对象是header或者footer直接返回即可。 如果有header的话,因为header也需要占一个位子,所以显示的时候需要显示当前位子的前一个位子。

if (getItemViewType(position) == TYPE_HEADER) return;
else if (getItemViewType(position) == TYPE_FOOTER) return;
else {
    if (holder instanceof MyAdapterWith.ItemHolder) {
        position = holder.getLayoutPosition();
        position = mHeaderView == null ? position : position - 1;
        //计算当前的位置,如果添加了header的话,header也需要占用一个位置
        if (position < datas.size()) {
            holder.imageView.setImageResource(datas.get(position).getId());
            holder.teTitle.setText(datas.get(position).getTitle());
            holder.teContent.setText(datas.get(position).getContent());
        }
    }
}

然后,需要在ViewHolder中添加对header和footer的支持

  public class ItemHolder extends RecyclerView.ViewHolder {
        public ImageView imageView;
        public TextView teTitle;
        public TextView teContent;


        public ItemHolder(View itemView) {
            super(itemView);
            if (itemView == mHeaderView)
                return;
            if (itemView == mFooterView)
                return;

            imageView = (ImageView) itemView.findViewById(R.id.item_image);
            teTitle = (TextView) itemView.findViewById(R.id.item_title);
            teContent = (TextView) itemView.findViewById(R.id.item_content);
        }

最后,我们就可以直接在activity中进行添加了

View view  = LayoutInflater.from(this).inflate(R.layout.head,mRecyclerView,false);
mMyAdapter.setHeaderView(view);
View footer = LayoutInflater.from(this).inflate(R.layout.foot,mRecyclerView,false);
mMyAdapter.setFooterView(footer);

其中R.layout.headR.layout.foot是head和foot的布局。

当然。这样写好之后,我们可以观察到这样的结果: 添加header 添加foot 基本正常,但是当我们使用GridLayoutManager时,会发现这样的问题。 添加foot 可以看到,这里我们的header和footer被当作一个item,并没有实现我们想到的置顶占行的效果。 解决方案: 利用GridLayoutManager的setSpanSizeLookup方法:

gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
    @Override
    public int getSpanSize(int position) {
        if (getItemViewType(position) == TYPE_HEADER || getItemViewType(position) == TYPE_FOOTER)
            return gridLayoutManager.getSpanCount();
        else
            return 1;
    }
});

这里的getSpanSize()方法返回的值决定了每个position上item占据的单元格个数。为了简单起见,可以将这个方法放在adapter中。代码如下:

 @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
        if(manager==null)       Log.d("TAG", "manager=null");

        if (manager instanceof GridLayoutManager) {

            final GridLayoutManager gridLayoutManager = (GridLayoutManager) manager;
            gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                @Override
                public int getSpanSize(int position) {
                    if (getItemViewType(position) == TYPE_HEADER || getItemViewType(position) == TYPE_FOOTER)
                        return gridLayoutManager.getSpanCount();
                    else
                        return 1;
                }
            });
        }
    }

最后,再为StaggeredGridLayoutManager做一些处理。

 @Override
    public void onViewAttachedToWindow(ItemHolder holder) {
        super.onViewAttachedToWindow(holder);
        ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
        if (lp != null
                && lp instanceof StaggeredGridLayoutManager.LayoutParams) {
            StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp;
            if (mHeaderView != null)
                p.setFullSpan(holder.getLayoutPosition() == 0);
//            if(mFooterView!=null)
//                 p.setFullSpan(holder.getLayoutPosition() ==datas.size());
        }
    }

到此,添加header和footer的基本操作就完成了。不过在日常使用中,我们肯定会对每一次都需要编写一个这么复杂的header类感觉很麻烦,这里可以考虑封装一下。 对了,在使用的过程中,如果需要适配GridLayoutManager的话,在activity的recycler的setAdapter方法之前,需要先调用 mRecyclerView.setLayoutManager(mManager); 参见代码:

RecyclerView.LayoutManager mManager = new new GridLayoutManager(this,2);
mRecyclerView.setLayoutManager(mManager);

 mRecyclerView.setAdapter(mMyAdapter);

如果这样写:

RecyclerView.LayoutManager mManager = new new GridLayoutManager(this,2);
 mRecyclerView.setAdapter(mMyAdapter);
 mRecyclerView.setLayoutManager(mManager);

那么,我们在adapter中编写的对GridLayoutManager的适配就无法获取到recycler而无法起作用。

为RecyclerView添加item的分隔线


在listView中,添加分隔先的方法很简单,可以直接使用setDivider(Drawable divider)方法来设置分隔线。但是在recyceler中 google并没有为我们提供这么简单的方法。因为recycle提倡高度的自定义话。google给我们提供了addItemDecoration(RecyclerView.ItemDecoration decor) 这个方法设置分隔线,但是里面的RecyclerView.ItemDecoration确要我们自己去实现。 接下来我们就实现这样的一个类,查看一下ItemDecoration类给我们提供了什么接口:

public static abstract class ItemDecoration {

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


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

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

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

通过这个源码我们可以知道:

  • 我们需要通过重写onDraw来实现绘制分割线
  • 通过getItemOffsets或者getItemOffsets方法来为item设置偏移量
  • onDrawOver方法在onDraw之后,一般覆写onDraw方法即可 下面是一般的实现:
package com.hut.reoger.studentsrecycleview.myDecoraltion;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;

/**
 * Created by 24540 on 2017/3/30.
 * 为Recycler每个item之间添加间隔
 * 参考博客:http://blog.csdn.net/lmj623565791/article/details/45059587
 */

public class DividerItemDecoration extends RecyclerView.ItemDecoration {
    private Context mContext;
    private Drawable mDivider;
    private int mOrientation;

    public static final int HORIZONAL = LinearLayoutManager.HORIZONTAL;
    public static final int VERTICAL = LinearLayoutManager.VERTICAL;
    public static final int[] ATRRS = new int[]{android.R.attr.listDivider};

    public DividerItemDecoration(Context mContext, int mOrientation) {
        this.mContext = mContext;
        this.mOrientation = mOrientation;
        final TypedArray typedArray = mContext.obtainStyledAttributes(ATRRS);
        this.mDivider = typedArray.getDrawable(0);

    }

    //设置屏幕方向
    public void setOrientation(int orientation) {
        if (orientation != HORIZONAL && orientation != VERTICAL) {
            throw new IllegalArgumentException("未知的屏幕方向");
        }
        mOrientation = orientation;
    }


    //需要重写这个方法,实现绘制item之间的间隔
    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        if (mOrientation == HORIZONAL) {
            drawHorizonalLine(c, parent, state);
        } else {
            drawVerticalLine(c, parent, state);
        }
    }

    //绘制竖线
    private void drawVerticalLine(Canvas canvas, RecyclerView recyclerView, RecyclerView.State state) {
        int top = recyclerView.getPaddingTop();
        int bottom = recyclerView.getBottom();
        final int childCount = recyclerView.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = recyclerView.getChildAt(i);

            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
            final int left = params.rightMargin + child.getRight();
            final int right = left+mDivider.getIntrinsicHeight();
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(canvas);
        }
    }

    /**
     * 绘制横线
     *
     * @param canvas
     * @param recyclerView
     * @param state
     */
    private void drawHorizonalLine(Canvas canvas, RecyclerView recyclerView, RecyclerView.State state) {
        int left = recyclerView.getPaddingLeft();
        int right = recyclerView.getWidth() - recyclerView.getPaddingRight();
        final int childCount = recyclerView.getChildCount();
        for (int i = 0; i < childCount; i++){
            final View child = recyclerView.getChildAt(i);

            //获得child的布局信息
            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)child.getLayoutParams();
            final int top = child.getBottom() + params.bottomMargin;
            final int bottom = top + mDivider.getIntrinsicHeight();
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(canvas);
        }

    }

    //如果item之间设置了间隔,那么每个item就需要向下移动一定的位置
    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {

        if(mOrientation ==HORIZONAL){
            outRect.set(0,0,0,mDivider.getIntrinsicHeight());
        }else{
            outRect.set(0,0,0,mDivider.getIntrinsicWidth());
        }
    }
}

里面的注释还算是比较多,这里主要讲解一些mDivider.setBounds(int left,int top,int right,int bottom);四个参数的含义 img 在activity中使用就比较简单了:

 mRecyclerView.addItemDecoration(new DividerItemDecoration(this,DividerItemDecoration.VERTICAL));

另外,如果需要适配对GridLayoutManager布局的话,可以参考下面的代码:


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

    public DividerGridItemDecoration(Context context)
    {
        final TypedArray a = context.obtainStyledAttributes(ATTRS);
        mDivider = a.getDrawable(0);
        a.recycle();
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state)
    {

        drawHorizontal(c, parent);
        drawVertical(c, parent);

    }

    private int getSpanCount(RecyclerView parent)
    {
        // 列数
        int spanCount = -1;
        RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
        if (layoutManager instanceof GridLayoutManager)
        {

            spanCount = ((GridLayoutManager) layoutManager).getSpanCount();
        } else if (layoutManager instanceof StaggeredGridLayoutManager)
        {
            spanCount = ((StaggeredGridLayoutManager) layoutManager)
                    .getSpanCount();
        }
        return spanCount;
    }

    public void drawHorizontal(Canvas c, RecyclerView parent)
    {
        int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++)
        {
            final View child = parent.getChildAt(i);
            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
                    .getLayoutParams();
            final int left = child.getLeft() - params.leftMargin;
            final int right = child.getRight() + params.rightMargin
                    + mDivider.getIntrinsicWidth();
            final int top = child.getBottom() + params.bottomMargin;
            final int bottom = top + mDivider.getIntrinsicHeight();
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(c);
        }
    }

    public void drawVertical(Canvas c, RecyclerView parent)
    {
        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++)
        {
            final View child = parent.getChildAt(i);

            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
                    .getLayoutParams();
            final int top = child.getTop() - params.topMargin;
            final int bottom = child.getBottom() + params.bottomMargin;
            final int left = child.getRight() + params.rightMargin;
            final int right = left + mDivider.getIntrinsicWidth();

            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(c);
        }
    }

    private boolean isLastColum(RecyclerView parent, int pos, int spanCount,
                                int childCount)
    {
        RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
        if (layoutManager instanceof GridLayoutManager)
        {
            if ((pos + 1) % spanCount == 0)// 如果是最后一列,则不需要绘制右边
            {
                return true;
            }
        } else if (layoutManager instanceof StaggeredGridLayoutManager)
        {
            int orientation = ((StaggeredGridLayoutManager) layoutManager)
                    .getOrientation();
            if (orientation == StaggeredGridLayoutManager.VERTICAL)
            {
                if ((pos + 1) % spanCount == 0)// 如果是最后一列,则不需要绘制右边
                {
                    return true;
                }
            } else
            {
                childCount = childCount - childCount % spanCount;
                if (pos >= childCount)// 如果是最后一列,则不需要绘制右边
                    return true;
            }
        }
        return false;
    }

    private boolean isLastRaw(RecyclerView parent, int pos, int spanCount,
                              int childCount)
    {
        RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
        if (layoutManager instanceof GridLayoutManager)
        {
            childCount = childCount - childCount % spanCount;
            if (pos >= childCount)// 如果是最后一行,则不需要绘制底部
                return true;
        } else if (layoutManager instanceof StaggeredGridLayoutManager)
        {
            int orientation = ((StaggeredGridLayoutManager) layoutManager)
                    .getOrientation();
            // StaggeredGridLayoutManager 且纵向滚动
            if (orientation == StaggeredGridLayoutManager.VERTICAL)
            {
                childCount = childCount - childCount % spanCount;
                // 如果是最后一行,则不需要绘制底部
                if (pos >= childCount)
                    return true;
            } else
            // StaggeredGridLayoutManager 且横向滚动
            {
                // 如果是最后一行,则不需要绘制底部
                if ((pos + 1) % spanCount == 0)
                {
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public void getItemOffsets(Rect outRect, int itemPosition,
                               RecyclerView parent)
    {
        int spanCount = getSpanCount(parent);
        int childCount = parent.getAdapter().getItemCount();
        if (isLastRaw(parent, itemPosition, spanCount, childCount))// 如果是最后一行,则不需要绘制底部
        {
            outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
        } else if (isLastColum(parent, itemPosition, spanCount, childCount))// 如果是最后一列,则不需要绘制右边
        {
            outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
        } else
        {
            outRect.set(0, 0, mDivider.getIntrinsicWidth(),
                    mDivider.getIntrinsicHeight());
        }
    }

}

运行效果如图: img6

对了,这个样式是可以自己进行修改了:在styles.xml文件中进行如下修改:

<resources>
    <!-- Base application theme. -->
	<style name = "AppTheme" parent = "Theme.AppCompat.Light.DarkActionBar">
		<item name="android:listDivider">@drawable/divider</item>
    </style>
</resources>

通过这里,我们可以实现对分割线样式的控制。到此,分隔线就结束了。

为Recycler添加下拉刷新,上拉加载更多功能

当然,完全可以自己动手通过自定义view来实现下拉刷新的功能,但是具体实现起来还是比较麻烦的。如果有兴趣,可以参考慕课网上的相关视屏 传送门 但是目前我们有更加方便的方法来实现这样的功能,google已经为我们提供了一个上拉刷新与下拉加载更多的控件。 SwipeRefreshLayout。现在就通过这个控件为recyclerView添加上拉刷新与下载功能。 SwipeRefreshLayout包含在V4的jar包中,不需要特殊的导入,更多细节请参考:官方参考文档 接下来就学习怎么使用:

<android.support.v4.widget.SwipeRefreshLayout
		android:id="@+id/swipeRefreshLayout"
		android:layout_width="368dp"
		android:layout_height="495dp"
		android:scrollbars="vertical"
		tools:layout_editor_absoluteY="8dp"
		tools:layout_editor_absoluteX="8dp">
<android.support.v7.widget.RecyclerView
	android:id="@+id/recycler"
	android:layout_width="368dp"
	android:layout_height="495dp"
	android:background="#ccc"
	tools:layout_editor_absoluteX="8dp"
	tools:layout_editor_absoluteY="8dp" />

</android.support.v4.widget.SwipeRefreshLayout>

在MainActivity中添加监听事件即可实现: 主要代码如下:

 mSwipRefreshLayout.setProgressBackgroundColorSchemeResource(android.R.color.white);
        mSwipRefreshLayout.setColorSchemeResources(android.R.color.holo_blue_light,
                android.R.color.holo_red_light,android.R.color.holo_orange_light,
                android.R.color.holo_green_light);
        mSwipRefreshLayout.setProgressViewOffset(false, 0, (int) TypedValue
                .applyDimension(TypedValue.COMPLEX_UNIT_DIP, 24, getResources()
                        .getDisplayMetrics()));
                        //设置进度条的颜色
        mSwipRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener(){

            @Override
            public void onRefresh() {
                //模仿加载数据
                //加载更多的逻辑在这里进行实现
               new Handler().postDelayed(new Runnable() {
                   @Override
                   public void run() {
                     loadMoreData();//加载数据
                   }
               },2000);
               //加载完毕后,记得将设置运行状态为false,隐藏进度条的显示
                mSwipRefreshLayout.setRefreshing(false);
            }
        });

到此,只需要实现加载数据和通知数据更新即可,这里不是我们的重点。我们就实现了下拉刷新的功能。、 然后,我们在来实现当我们拉到底的时候,加载更多。 参考链接:https://www.easydone.cn/2015/10/26/ 为了代码的独立性,我们单独将其主要实现的方法封装在一个抽象类中: 要实现下拉刷新,我们首先要考虑的是,我们什么时候开始刷新、依据是什么。

public abstract class DropDownListener extends RecyclerView.OnScrollListener {

    //声明一个LinearLayoutManager
    private LinearLayoutManager mLinearLayoutManager;

    //当前页,从0开始
    private int currentPage = 0;
    //已经加载出来的Item的数量
    private int totalItemCount;

    //主要用来存储上一个totalItemCount
    private int previousTotal = 0;

    //在屏幕上可见的item数量
    private int visibleItemCount;

    //在屏幕可见的Item中的第一个
    private int firstVisibleItem;

    //是否正在上拉数据
    private boolean loading = true;

    public DropDownListener(LinearLayoutManager linearLayoutManager) {
        this.mLinearLayoutManager = linearLayoutManager;
    }

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);

        visibleItemCount = recyclerView.getChildCount();
        totalItemCount = mLinearLayoutManager.getItemCount();
        firstVisibleItem = mLinearLayoutManager.findFirstVisibleItemPosition();
        if (loading) {
            if (totalItemCount > previousTotal) {
                //说明数据已经加载结束
                loading = false;
                previousTotal = totalItemCount;
            }
        }
       //当没有在加载且 已经加载出来的item数量-屏幕上可见的item数量<=屏幕上可见的第一个item的序号(即已经滑倒底的时候)
       //执行相应的操作
        if (!loading && totalItemCount - visibleItemCount <= firstVisibleItem) {
            currentPage++;
            onLoadMore(currentPage);
            loading = true;
        }
    }

    /**
     * 提供一个抽闲方法,在Activity中监听到这个EndLessOnScrollListener
     * 并且实现这个方法
     */
    public abstract void onLoadMore(int currentPage);
}

然后,我们调用的时候就很简单了。关键代码如下:

 mRecyclerView.addOnScrollListener(new DropDownListener((LinearLayoutManager) mManager) {
            @Override
            public void onLoadMore(int currentPage) {
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        loadMoreData();
                    }
                },2000);
            }
        });

到此,我们就实现了简单的下拉刷新、上拉加载的功能。如图;


可以看到,我们的确是实现了上拉加载、下拉刷新的功能。但是在下拉加载的时候,并没有任何提示, 这一点是非常不友好的。于是、我们继续来改进。

因为我们之前实现了添加header和footer的功能。所以,比较简单的一种思想就是 直接添加footer当作我们下拉刷新的提示。当刷新完成的时候,隐藏footer就ok了。 下面我们来实现代码: 将footer改造成我们想要的效果即可,譬如,我将footer设置呈这样:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
             android:layout_width="match_parent"
             android:layout_height="80dp"
             android:background="#fff"
             android:orientation="vertical">

<TextView
	android:layout_width="match_parent"
	android:layout_height="wrap_content"
	android:gravity="center"
	android:paddingTop="10dp"
	android:textColor="#F00"
	android:text="正在加载" />
<ProgressBar
	android:id="@+id/progressBar"
	android:layout_width="match_parent"
	android:layout_height="wrap_content" />

</FrameLayout>

然后,设置recyclerView添加footer,设置效果如下:

效果基本达到我们的预想。但是这种方法也有一定的缺点:

  1. 不能实现自己回弹(即松开手指自动隐藏)
  2. 不能横好的独立出来,这就造成不是很好封装成一个jar
  3. 当我们需要一个footer的时候,就无法满足需求。 正对上面的三点,我们需要需找新的方案来解决这个问题。但是这里就不做介绍了。

为recyclerView添加Item移动、删除

先看看效果

看样子很不错把。下面我们来进行实现: 在RecyclerView中,为我们提供了ItemTouchHelper这么一个类来帮助我们实现这个功能。我们先来看看这个类给我们的注释把:

  • This is a utility class to add swipe to dismiss and drag & drop support to RecyclerView.
  • It works with a RecyclerView and a Callback class, which configures what type of interactions
  • are enabled and also receives events when user performs these actions.
  • Depending on which functionality you support, you should override
  • {@link Callback#onMove(RecyclerView, ViewHolder, ViewHolder)} and / or
  • {@link Callback#onSwiped(ViewHolder, int)}.
  • This class is designed to work with any LayoutManager but for certain situations, it can be
  • optimized for your custom LayoutManager by extending methods in the
  • {@link ItemTouchHelper.Callback} class or implementing {@link ItemTouchHelper.ViewDropHandler}
  • interface in your LayoutManager.
  • By default, ItemTouchHelper moves the items' translateX/Y properties to reposition them. On
  • platforms older than Honeycomb, ItemTouchHelper uses canvas translations and View's visibility
  • property to move items in response to touch events. You can customize these behaviors by
  • overriding {@link Callback#onChildDraw(Canvas, RecyclerView, ViewHolder, float, float, int,
  • boolean)}
  • or {@link Callback#onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int,
  • boolean)}.
  • Most of the time, you only need to override onChildDraw but due to limitations of
  • platform prior to Honeycomb, you may need to implement onChildDrawOver as well.

这个注释写的已经很明白了,这个类就是就是帮助我们添加滑动和删除效果到RecyclerView中的。我们只需要继承CallBack,然后实现 {@link Callback#onMove(RecyclerView, ViewHolder, ViewHolder)} and / or{@link Callback#onSwiped(ViewHolder, int)}. 这两个方法就可以了。下面是具体实现过程。

public class MyItemTouchHelpCallback extends ItemTouchHelper.Callback {

    /**Item操作的回调*/
    private OnItemTouchCallbackListener onItemTouchCallbackListener;
    /**是否可以拖拽**/
    private boolean isCanDrag = false;
    /***是否可以滑动*/
    private boolean isCanSwipe = false;

    public MyItemTouchHelpCallback(OnItemTouchCallbackListener onItemTouchCallbackListener) {
        this.onItemTouchCallbackListener = onItemTouchCallbackListener;
    }

    public void setOnItemTouchCallbackListener(OnItemTouchCallbackListener onItemTouchCallbackListener){
        this.onItemTouchCallbackListener = onItemTouchCallbackListener;
    }

    /**
     * 设置是否可以被拖拽
     * */
    public void setDragEnable(boolean CanDrag){
        this.isCanDrag = CanDrag;
    }

    /**
     * 设置是否可以滑动
     * @param canSwipe
     */
    public void setSwipeEnable(boolean canSwipe){
        this.isCanSwipe = canSwipe;
    }

    /**
     * 当Item被长安的时候是否可以拖动
     * @return
     */
    @Override
    public boolean isLongPressDragEnabled() {
        return isCanDrag;
    }

    /**
     * Item是否可以被滑动(H:左右滑动,V:上下滑动)
     * @return
     */
    @Override
    public boolean isItemViewSwipeEnabled() {
        return isCanSwipe;
    }


    /**
     * 当用户拖拽或者滑动Item的时候需要我们告诉系统滑动或者拖拽的方向
     * @param recyclerView
     * @param viewHolder
     * @return
     */
    @Override
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
        if(layoutManager instanceof LinearLayoutManager) {
            LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
            int orientation = linearLayoutManager.getOrientation();

            int dragFlag = 0;
            int swipFlag = 0;

            if(orientation== LinearLayoutManager.HORIZONTAL){
                swipFlag = ItemTouchHelper.UP |ItemTouchHelper.DOWN;
                dragFlag = ItemTouchHelper.LEFT |ItemTouchHelper.RIGHT;
            }else if(orientation== LinearLayoutManager.VERTICAL){
                dragFlag = ItemTouchHelper.UP |ItemTouchHelper.DOWN;
                swipFlag = ItemTouchHelper.LEFT |ItemTouchHelper.RIGHT;
            }
            return makeMovementFlags(dragFlag,swipFlag);
        }
        return 0;
    }


    /**
     * 当Item被拖拽的时候回调
     * @param recyclerView
     * @param viewHolder 拖拽的vieholder
     * @param target 目的拖拽viewHolder
     * @return
     */
    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
        if(onItemTouchCallbackListener!=null)
            return onItemTouchCallbackListener.onMove(viewHolder.getAdapterPosition(),target.getAdapterPosition());
        return false;
    }

    /**
     * 当Item被删除的时候回调
     * @param viewHolder 要删除的item
     * @param direction
     */
    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
        if(onItemTouchCallbackListener!=null){
            onItemTouchCallbackListener.onSwiped(viewHolder.getAdapterPosition());
        }
    }

    public interface OnItemTouchCallbackListener{
        /**
         * 当某个item被滑动删除的时候
         * @param position
         */
        void onSwiped(int position);

        /**
         * 当连个Item位置互相的时候
         * @param srcPosition
         * @param targerPostion
         * @return
         */
        boolean onMove(int srcPosition,int targerPostion);
    }
}

上面的代码添加了不少的注释,相信理解起来很简单。 然后,在MainActivity中就可以进行调用了,但是为了方便起见,我们还是将其封装一次。 帮助类可以这么写:

public class MyItemTouchHelper extends YolandaItemTouchHelper {

    private  MyItemTouchHelpCallback myItemTouchHelpCallback;


    public MyItemTouchHelper(MyItemTouchHelpCallback.OnItemTouchCallbackListener callback) {
        super(new MyItemTouchHelpCallback(callback));
        myItemTouchHelpCallback = (MyItemTouchHelpCallback) getCallback();
    }


    /**
     * 设置是否可以拖动
     * @param canDrag
     */
    public void setDragEnable(boolean canDrag){
        myItemTouchHelpCallback.setDragEnable(canDrag);
    }

    public void setSwipeEnable(boolean canSwipe){
        myItemTouchHelpCallback.setSwipeEnable(canSwipe);
    }

对了,为了方便获取到CallBack(),我们在新建了android.support.v7.widget.helper包,然后在该包下新建了 一个类YolandaItemTouchHelper,代码如下:

public class YolandaItemTouchHelper extends ItemTouchHelper {
    /**
     * Creates an ItemTouchHelper that will work with the given Callback.
     * <p>
     * You can attach ItemTouchHelper to a RecyclerView via
     * {@link #attachToRecyclerView(RecyclerView)}. Upon attaching, it will add an item decoration,
     * an onItemTouchListener and a Child attach / detach listener to the RecyclerView.
     *
     * @param callback The Callback which controls the behavior of this touch helper.
     */
    public YolandaItemTouchHelper(Callback callback) {
        super(callback);
    }
    public Callback getCallback(){
        return mCallback;//返回了CallBack
    }
}

最后,在MainActivity中进行调用就非常的简单了:代码如下:

 itemTouchHelper = new MyItemTouchHelper(onItemTouchCallbackListener);
        itemTouchHelper.attachToRecyclerView(mRecyclerView);
        itemTouchHelper.setSwipeEnable(true);
        itemTouchHelper.setDragEnable(true);

当然,onItemTouchCallbackListener也是很重要的,这里我们还需要先申明:

 private MyItemTouchHelpCallback.OnItemTouchCallbackListener onItemTouchCallbackListener = new MyItemTouchHelpCallback.OnItemTouchCallbackListener(){

        @Override
        public void onSwiped(int position) {
            if(datas !=null){
                datas.remove(position);
                adpater.notifyItemRemoved(position);
            }
        }

        @Override
        public boolean onMove(int srcPosition, int targerPostion) {
            if(datas != null){
                Collections.swap(datas,srcPosition,targerPostion);
                adpater.notifyItemMoved(srcPosition,targerPostion);
            }

            return true;
        }
    };

大功告成!赶快去试试把~

About

--recycle基本使用

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages