RecycleView的学习

1、依赖

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

2、简单实现

1、 关于Adapter
  • 继承RecyclerView.Adapter
  • 重写onCreateViewHolder:创建viewType类型的ViewHolder
  • 重写onBindViewHolder:将数据绑定到ViewHolder

其中ViewHolder将会被复用,去展示在数据集中的不同items。ViewHolder的数目最多为一屏内所能容纳的最大item个数+2

public class RecycleViewAdapter extends RecyclerView.Adapter {
   private ArrayList<String> datas;
   private Context context;
   private OnItemClickListener onItemClickListener;
   public RecycleViewAdapter(ArrayList<String> datas, Context context) {
       this.datas = datas;
       this.context = context;
   }

   @Override
   public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
       View root= LayoutInflater.from(context).inflate(R.layout.layout_item,parent,false);
       return new MyViewHolder(root);
   }

   @Override
   public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
       (((MyViewHolder)holder).info).setText(datas.get(position));
   }

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

   public class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
       public TextView info;
       public MyViewHolder(View itemView) {
           super(itemView);
           info= (TextView) itemView.findViewById(R.id.itemText);
           info.setOnClickListener(this);
       }

       @Override
       public void onClick(View v) {
           onItemClickListener.onItemClick(v,getAdapterPosition());
       }
   }

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

   public void setOnItemClickListener(OnItemClickListener onItemClickListener){
       this.onItemClickListener=onItemClickListener;
   }
}
2、 关于布局管理器(LayoutManager)

布局管理器有LinearLayoutManager、GridLayoutManager、StaggeredGridLayoutManager

   //设置布局管理器(LayoutManager)
   recyclerView.setLayoutManager(new LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false));
   //recyclerView.setLayoutManager(new GridLayoutManager(RecycleViewTestActivity.this,3));
   //recyclerView.setLayoutManager(new StaggeredGridLayoutManager(4, StaggeredGridLayoutManager.HORIZONTAL));
   //设置Adapter
   recyclerView.setAdapter(adapter);
   //设置分割线(ItemDecoration)
   recyclerView.addItemDecoration(new RecycleViewItemDecoration());
   adapter.setOnItemClickListener(new RecycleViewAdapter.OnItemClickListener() {
       @Override
       public void onItemClick(View view, int position) {
           Snackbar.make(recyclerView,"setOnItemClickListener:"+position,Snackbar.LENGTH_SHORT).show();
       }
   });
Recycleview
3、 关于ItemDecoration

ItemDecoration,用来修饰RecyclerView里的Item,可以用来制造分割线和吸顶效果。

常用的三个方法如下:

  • onDraw:设置绘制范围,而这个绘制范围可以超出在 getItemOffsets 中设置的范围,超出部分不可见;在drawChildren之前调用。
  • onDrawOver():在drawChildren之后调用,绘制出的内容是在RecyclerView的最上层,会遮挡住ItemView
  • getItemOffsets(): 可以通过outRect.set()为每个Item设置四周间距,这些值被计入了 RecyclerView 每个 item 的 padding 中。

1、绘制分割线

public class DividerGridItemDecoration extends RecyclerView.ItemDecoration {

    private Drawable mDivider;

    public DividerGridItemDecoration(Context context) {
        mDivider = context.getResources().getDrawable(R.drawable.divider_recycle_view);
    }

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

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        int spanCount = getSpanCount(parent);
        //int childCount = parent.getAdapter().getItemCount();
        int itemPosition = parent.getChildLayoutPosition(view);
        if (isFirstRow(itemPosition, spanCount)) {
            //如果是第一行,绘制top和bottom  Offset,
            if (isFirstColumn(itemPosition, spanCount)) {
                //如果是第一列,padding、padding/2
                outRect.set(mDivider.getIntrinsicWidth(), mDivider.getIntrinsicHeight(),
                        mDivider.getIntrinsicWidth() / 2, mDivider.getIntrinsicHeight());
            } else if (isLastColumn(itemPosition, spanCount)) {
                //如果是最后一列,padding/2、padding
                outRect.set(mDivider.getIntrinsicWidth() / 2, mDivider.getIntrinsicHeight(),
                        mDivider.getIntrinsicWidth(), mDivider.getIntrinsicHeight());
            } else {
                //padding/2、padding/2
                outRect.set(mDivider.getIntrinsicWidth() / 2, mDivider.getIntrinsicHeight(),
                        mDivider.getIntrinsicWidth() / 2, mDivider.getIntrinsicHeight());
            }

        } else {
            //仅仅绘制bottom  Offset
            if (isFirstColumn(itemPosition, spanCount)) {
                //如果是第一列,padding、padding/2
                outRect.set(mDivider.getIntrinsicWidth(), 0,
                        mDivider.getIntrinsicWidth() / 2, mDivider.getIntrinsicHeight());
            } else if (isLastColumn(itemPosition, spanCount)) {
                //如果是最后一列,padding/2、padding
                outRect.set(mDivider.getIntrinsicWidth() / 2, 0,
                        mDivider.getIntrinsicWidth(), mDivider.getIntrinsicHeight());
            } else {
                //padding/2、padding/2
                outRect.set(mDivider.getIntrinsicWidth() / 2, 0,
                        mDivider.getIntrinsicWidth() / 2, mDivider.getIntrinsicHeight());
            }
        }
    }


    public void drawHorizontal(Canvas c, RecyclerView parent) {
        int childCount = parent.getChildCount();
        int spanCount = getSpanCount(parent);
        for (int i = 0; i < childCount; i++) {
            final View child = parent.getChildAt(i);
            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
                    .getLayoutParams();
            int left = child.getLeft() - params.leftMargin;
            if (isFirstColumn(i, spanCount)) {
                left = left - mDivider.getIntrinsicWidth();
            }
            int right = child.getRight() + params.rightMargin
                    + mDivider.getIntrinsicWidth();

            if (isFirstRow(i, spanCount)) {
                int bottom = child.getTop() - params.bottomMargin;
                int top = bottom - mDivider.getIntrinsicHeight();
                mDivider.setBounds(left, top, right, bottom);
                mDivider.draw(c);
            }
            int top2 = child.getBottom() + params.bottomMargin;
            int bottom2 = top2 + mDivider.getIntrinsicHeight();
            mDivider.setBounds(left, top2, right, bottom2);
            mDivider.draw(c);

        }
    }

    public void drawVertical(Canvas c, RecyclerView parent) {
        final int childCount = parent.getChildCount();
        int spanCount = getSpanCount(parent);
        for (int i = 0; i < childCount; i++) {
            View child = parent.getChildAt(i);
            RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
                    .getLayoutParams();
            int top = child.getTop() - params.topMargin;
            int bottom = child.getBottom() + params.bottomMargin;
            if (isFirstColumn(i, spanCount)) {
                int left2 = child.getRight() + params.rightMargin;
                int right2 = left2 + mDivider.getIntrinsicWidth() / 2;
                mDivider.setBounds(left2, top, right2, bottom);
                mDivider.draw(c);
                int right = child.getLeft() - params.leftMargin;
                int left = right - 20;
                mDivider.setBounds(left, top, right, bottom);
            } else if (isLastColumn(i, spanCount)) {
                final int right = child.getLeft() - params.leftMargin;
                final int left = right - mDivider.getIntrinsicWidth() / 2;
                mDivider.setBounds(left, top, right, bottom);
                mDivider.draw(c);
                final int left2 = child.getRight() + params.rightMargin;
                final int right2 = left2 + mDivider.getIntrinsicWidth();
                mDivider.setBounds(left2, top, right2, bottom);
            } else {
                final int right = child.getLeft() - params.leftMargin;
                final int left = right - mDivider.getIntrinsicWidth() / 2;
                mDivider.setBounds(left, top, right, bottom);
                mDivider.draw(c);
                final int left2 = child.getRight() + params.rightMargin;
                final int right2 = left2 + mDivider.getIntrinsicWidth() / 2;
                mDivider.setBounds(left2, top, right2, bottom);
            }
            mDivider.draw(c);
        }
    }

    private boolean isFirstRow(int itemPosition, int spanCount) {
        return (itemPosition < spanCount);
    }

    private boolean isFirstColumn(int itemPosition, int spanCount) {
        return ((itemPosition) % spanCount == 0);
    }

    private boolean isLastColumn(int itemPosition, int spanCount) {
        return ((itemPosition + 1) % spanCount == 0);
    }

    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;
    }

}

结果如图:

顶部有分割线,底部也有;第一列左侧分割线w,右侧分割线w/2,中间列分割线都是w/2,最后一列和第一列相反
outRect.set()

注意:
1、child.getLeft()得到的只是红框的left
2、setBounds()之后不要忘记draw()

2、吸顶效果
可以使用ItemDecoration的 getItemOffsets()、onDraw() 方法 为Item分类头部 留出空间, 在onDrawOver() 方法中绘制悬停的头部View。

1、利用parent,得到view的位置(在总的adapter中的位置)

  parent.getChildAdapterPosition(view);

2、得到展示在页面上的item的伪position,计算在adapter中真实的position

    for(int i=0;i!=parent.getChildCount();i++){
        View child=parent.getChildAt(i);
        int index=parent.getChildAdapterPosition(child);
    }

效果如下:

吸顶效果

大概代码如下

public class TopItemDecoration extends RecyclerView.ItemDecoration {
    //type 1 的items的容量大小
    private int firstItemSize=3;
    //显示 type1 第一个item的位置
    private int firstItemPosition=2;
    //type的高度
    private int mTopHeight=0;
    //背景
    private Paint mbgPaint;
    private TextPaint mTextPaint;
    //文字距离左侧的间距
    private int outPadding;
    //用于存放测量文字Rect
    private Rect mBounds;

    public TopItemDecoration(Context context) {
        mbgPaint=new Paint();
        mTextPaint=new TextPaint();
        mBounds=new Rect();

        mbgPaint.setColor(context.getResources().getColor(R.color.defaultBg));
        mTextPaint.setTextSize(12*context.getResources().getDisplayMetrics().scaledDensity);
        mTextPaint.setTextAlign(Paint.Align.LEFT);
        mTextPaint.setAntiAlias(true);
        mTopHeight= DeviceUtil.dp2px(context,28);
        outPadding=DeviceUtil.dp2px(context,16);
    }

    public void setItemSize(int size){
        this.firstItemSize=size;
    }

    private String getTitleName(int index){
        if(index>=firstItemSize+firstItemPosition){
            return "最新";
        }else if(index>=firstItemPosition){
            return "推荐";
        }
        return "";
    }

    private boolean isFirstOfGroup(int index){
        if(index==firstItemPosition || index==firstItemSize+firstItemPosition){
            return true;
        }
        return false;
    }

    //显示在最上方
    @Override
    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDrawOver(c, parent, state);
        int left=parent.getPaddingLeft()+outPadding;
        int right=parent.getWidth();
        //得到第一个可见item的位置
        int index= ((LinearLayoutManager)(parent.getLayoutManager())).findFirstVisibleItemPosition();
        //出现一个奇怪的bug,有时候child为空,所以将 child = parent.getChildAt(i)。-》 parent.findViewHolderForLayoutPosition(index).itemView
        View child = parent.findViewHolderForLayoutPosition(pos).itemView;
        String tag = getTitleName(index);
        //第一个类型的Item 的 header 在 position==2(firstItemPosition)处,如果index==2,则说明此item在顶部
        //index>=2,说明之后的item都有吸顶效果
        if(index-firstItemPosition>=0){
            int top=parent.getPaddingTop();
            int bottom=parent.getPaddingTop() + mTopHeight;
            //制造"最新"(下一组第一个item)和"推荐"交接时,被顶上去的效果
            //在还没开始交接的时候,"最新"的getBottom()>bottom
            if(isFirstOfGroup(index+1)){
                bottom=Math.min(child.getBottom(),bottom);
            }
            c.drawRect(0, top, right, bottom, mbgPaint);
            mTextPaint.getTextBounds(tag, 0, tag.length(), mBounds);
            c.drawText(tag, left, bottom - mTopHeight/2+mBounds.height()/2,
                    mTextPaint);
        }



    }

    /**
     * 绘制Item分类头部
     */
    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDraw(c, parent, state);
        int left=parent.getPaddingLeft()+outPadding;
        int right=parent.getWidth();
        //getChildCount:得到当前页面的child,去计算真实的position
        for(int i=0;i!=parent.getChildCount();i++){
            View child=parent.getChildAt(i);
            //计算真实的position
            int index=parent.getChildAdapterPosition(child);
            String tag = getTitleName(index);
            L.e("onDraw","i:"+i+",index:"+index);
            if(isFirstOfGroup(index)){
                int top=child.getTop()-mTopHeight;
                int bottom=child.getTop();
                c.drawRect(0,top,right,bottom,mbgPaint);
                mTextPaint.getTextBounds(tag,0,tag.length(),mBounds);
                c.drawText(getTitleName(index),left,bottom-mTopHeight/2+mBounds.height()/2,mTextPaint);
            }
        }
    }

    /**
     * 为满足添加的item,增加顶部的padding
     */
    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        //利用parent,得到view的位置(在总的adapter中的位置)
        int index=parent.getChildAdapterPosition(view);
        //使满足条件的item,顶部多出 mTopHeight 的距离
        if(isFirstOfGroup(index)){
            outRect.top=mTopHeight;
        }
    }
}

参考:使用ItemDecoration为RecyclerView打造带悬停头部的分组列表使用ItemDecoration打造列表顶部悬浮效果

4、 关于拖拽和滑动(ItemTouchHelper)

ItemTouchHelper是支持RecyclerView滑动删除和拖拽的工具类

侧滑
滑动删除
  • SimpleCallback(int dragDirs, int swipeDirs)
    dragDirs :拖拽的方向、swipeDirs:滑动的方向 (ACTION_STATE_IDLE、LEFT、RIGHT、START、END、UP、DOWN)

代码如下


   ItemTouchHelper itemTouchHelper=new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(ItemTouchHelper.DOWN|ItemTouchHelper.UP|ItemTouchHelper.LEFT|ItemTouchHelper.RIGHT,
           ItemTouchHelper.RIGHT) {
       @Override
       public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {

           int fromPosition = viewHolder.getAdapterPosition();//得到拖动ViewHolder的position
           int toPosition = target.getAdapterPosition();//得到目标ViewHolder的position
           if (fromPosition < toPosition) {
               //分别把中间所有的item的位置重新交换
               for (int i = fromPosition; i < toPosition; i++) {
                   adapter.swip(i,i+1);
               }
           } else {
               for (int i = fromPosition; i > toPosition; i--) {
                   adapter.swip(i,i-1);
               }
           }
           adapter.moved(fromPosition, toPosition);
           //返回true表示执行拖动
           return true;
       }

       @Override
       public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
           int position=viewHolder.getAdapterPosition();
           adapter.remove(position);
       }

       @Override
       public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
           super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
           if(actionState==ItemTouchHelper.ACTION_STATE_SWIPE){
               //在滑动时改变透明度
               float alpha=1-Math.abs(dX)/viewHolder.itemView.getWidth();
               viewHolder.itemView.setAlpha(alpha);
               viewHolder.itemView.setTranslationX(dX);
           }
       }
   });
   itemTouchHelper.attachToRecyclerView(recyclerView);

3、复用

</br>

参考:
Android实现RecyclerView侧滑删除和长按拖拽-ItemTouchHelper
ItemTouchHelper之SwipeDismiss
Android RecyclerView 使用完全解析 体验艺术般的控件
RecyclerView中利用GridLayoutManager实现item四周都带有分割线效果
RecyclerView剖析

推荐阅读更多精彩内容