(01)recyclerview的基本使用

点点圈 提交于 2020-01-29 01:44:25

1.几个重要的类

1.1 简述:首先说明他的几个重要的类

1.1.2 RecyclerView.Adapter

抽象类,为RecyclerView提供数据,一般根据不同的业务需求来编写具体的实现类。

1.1.3 RecyclerView.LayoutManager

:抽象类,主要用于测量RecyclerView的子Item,以及根据不同的布局方式来实现Item的布局效果,v 7包自带的实现类有:LinearLayoutManager、StaggeredGridLayoutManager、GridLayoutManager。

1.1.4 RecyclerView.ItemDecoration

抽象类,这个主要用于不同的Item之间添加分割线(可选)。官方没有实现类,所以如果要添加分割线,我们需要手动实现这个抽象类

.1.1.5 RecyclerView.ItemAnimator

抽象类,这个主要用于当一个item添加或者删除的时候出现的动画效果,官方提供一个默认的实现类。如果想要使我们的RecyclerView在添加、删除数据的时候有炫酷的动画,可以实现这个抽象类。

2.创建Adapter适配器

继承该类的时候,必须重写这三个方法,我们分别解释一下这三个方法是什么作用:

  1. onCreateViewHolder:创建ViewHolder,该方法会在RecyclerView需要展示一个item的时候回调。重写该方法时,应该使ViewHolder加载item view的布局。这个能发避免了不必要的findViewById操作,提高了性能。如果熟悉ListView的ViewHolder操作那么也能很容易理解这个。
  2. onBindeViewHolder:该方法在RecyclerView在特定位置展示数据时候回调,顾名思义,把数据绑定、填充到相应的item view中。
  3. getItemCount:返回数据的数量。我们注意到,继承该类的时候需要声明它的泛型类型VH,VH继承自RecyclerView.ViewHolder,同时第2个方法的返回值也是VH,由于ViewHolder是根据业务需求而变化的,不同的业务需求而需要的ViewHolder不尽相同,所以没必要单独写一个ViewHolder.java文件,因此我们可以在Adapter子类内部实现一个内部类ViewHolder,这样也符合单一职责原则
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {

    private List<String> mDataSet;
    
    //构造器,接受数据集
    public MyAdapter(List<String> data){
        mDataSet = data;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        //加载布局文件
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.itemlayout,parent,false);
        ViewHolder vh = new ViewHolder(v);
        return vh;
    }

    @Override
    public void onBindViewHolder(final ViewHolder holder, int position) {
        //将数据填充到具体的view中
        holder.mTextView.setText(mDataSet.get(position));
    }

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

    class ViewHolder extends RecyclerView.ViewHolder{
        public TextView mTextView;
        public ViewHolder(View itemView) {
            super(itemView);
            //由于itemView是item的布局文件,我们需要的是里面的textView,因此利用itemView.findViewById获      
            //取里面的textView实例,后面通过onBindViewHolder方法能直接填充数据到每一个textView了
            mTextView = (TextView) itemView.findViewById(R.id.num);
        }
    }
}

3. 更改布局管理器

3.1 GridLayoutManager

要改变布局管理器很简单,我们只需要实例化一个表格布局管理器即可,我们先看看它的构造函数:

/**
     * @param context Current context, will be used to access resources.
     * @param spanCount The number of columns or rows in the grid
     * @param orientation Layout orientation. Should be {@link #HORIZONTAL} or {@link
     *                      #VERTICAL}.
     * @param reverseLayout When set to true, layouts from end to start.
     */
    public GridLayoutManager(Context context, int spanCount, int orientation,
            boolean reverseLayout) {
        super(context, orientation, reverseLayout);
        setSpanCount(spanCount);
    }

其中第二个参数spanCount表示表格的行数或者列数;第三个参数表示是水平滑动或者是竖直方向滑动;最后一个参数表示是否从数据的尾部开始显示。
我们在MainActivity中作出如下修改:

mRecyclerView.setLayoutManager(new GridLayoutManager(this,4,VERTICAL,false));

3.2 StaggeredGridLayoutManager

瀑布流效果现在经常会见到,如果只有ListView或者GridView实现的话,有一定难度,但是如果使用RecyclerView则会简单很多,只需要使用StaggeredGridLayoutManager即可实现。
我们在MainActivity中,更改使用的布局管理器如下:

//构造器中,第一个参数表示列数或者行数,第二个参数表示滑动方向
mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(4,StaggeredGridLayoutManager.VERTICAL));

4. 处理RecyclerView的点击事件

4.1 利用View.onClickListener及onLongClickListener

利用了java的回调机制,这里我们依赖于子Item View的onClickListener及onLongClickListener。

  1. 首先新建两个接口:
public interface OnItemClickListener{
        void onItemClick(View view,int position);
    }

    public interface OnItemLongClickListener{
        void onItemLongClick(View view,int position);
    }
  1. 新建两个私有变量用于保存用户设置的监听器以及其set方法。
public interface OnItemClickListener{
        void onItemClick(View view,int position);
    }

    public interface OnItemLongClickListener{
        void onItemLongClick(View view,int position);
    }
  1. 在onBindViewHolder方法内,实现回调:
@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
    holder.mTextView.setText(mDataSet.get(position));
    //判断是否设置了监听器
    if(mOnItemClickListener != null){
        //为ItemView设置监听器
        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int position = holder.getLayoutPosition(); // 1
                mOnItemClickListener.onItemClick(holder.itemView,position); // 2
            }
        });
    }
    if(mOnItemLongClickListener != null){
        holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                int position = holder.getLayoutPosition();
                mOnItemLongClickListener.onItemLongClick(holder.itemView,position);
                //返回true 表示消耗了事件 事件不会继续传递
                return true;
            }
        });
    }
}

4.2 利用RecyclerView.OnItemTouchListener

官方虽然没有提供现成的监听器,但是提供了一个内部接口:OnItemTouchListener,我们看看官方文档对它的描述:AnOnItemTouchListener allows the application to intercept touch events in progress at the view hierarchy level of the RecyclerView before those touch events are considered for RecyclerView’s own scrolling behavior。大概意思是说该接口允许我们对RecyclerView的触摸事件进行拦截,我们看看它的几个接口方法:

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

    }
    
    @Override
    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

    }
/**
 * @author linghailong
 * @date on 2018/10/28
 * @email 105354999@qq.com
 * @describe : 对Adapter中使用到的点击事件进行优化、解耦。在实现方法二的RecyclerViewClickListener的时候,
 * 在内部对事件的实现了单击、长按的判断,但是这个长按事件不是标准的,只有松开手指的时候才会触发长按事件,
 * 这也算是一点瑕疵,同时如果要增加别的事件,比如说双击事件,则需要增加相应的逻辑,如果需要判断的事件种类变多则会给我们的代码编写带来困难,
 * 那么有没有更加简便的方法呢?其实安卓SDK为我们提供了一个手势检测类:GestureDetector来处理各种不同的手势,
 * 那么我们完全可以利用GestureDetector来对方法二进行改进。


 */
public class RecyclerViewClickListener2 implements RecyclerView.OnItemTouchListener{
    private GestureDetector mGestureDetector;
    private OnItemClickListener mListener;
    private Context mContext;

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

        void onItemLongClick(View view,int position);
    }

    public RecyclerViewClickListener2(Context context, final RecyclerView recyclerView, final OnItemClickListener listener) {
        this.mGestureDetector = mGestureDetector;
        this.mListener =listener;
        this.mContext = context;
        mGestureDetector=new GestureDetector(context,new GestureDetector.SimpleOnGestureListener(){
            /**
             * 单击事件
             * @param e
             * @return
             */
            @Override
            public boolean onSingleTapUp(MotionEvent e) {
                /** 通过手指当前的点击坐标获取当前的childView*/
                View childView = recyclerView.findChildViewUnder(e.getX(),e.getY());
                if (childView!=null&& mListener!=null){
                    mListener.onItemClick(childView,recyclerView.getChildLayoutPosition(childView));
                    return true;
                }
                return false;
            }

            /**
             * 长按事件
             * @param e
             */
            @Override
            public void onLongPress(MotionEvent e) {
                View childView=recyclerView.findChildViewUnder(e.getX(),e.getY());
                if(childView!=null&&mListener!=null){
                    mListener.onItemLongClick(childView,recyclerView.getChildLayoutPosition(childView));
                }
                super.onLongPress(e);
            }
        });

    }

    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        // 把事件交给GestureDetector处理
        if(mGestureDetector.onTouchEvent(e)){
            return true;
        }else
            return false;
    }

    @Override
    public void onTouchEvent(RecyclerView rv, MotionEvent e) {

    }

    @Override
    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

    }
}

MainActivity中的代码如下:

mRecyclerView.addOnItemTouchListener(new RecyclerViewClickListener2(this, mRecyclerView,
        new RecyclerViewClickListener2.OnItemClickListener() {
    @Override
    public void onItemClick(View view, int position) {
        Toast.makeText(MainActivity.this,"Click "+mData.get(position),Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onItemLongClick(View view, int position) {
        Toast.makeText(MainActivity.this,"Long Click "+mData.get(position),Toast.LENGTH_SHORT).show();
    }
}));

5.操作数据以及分割线

常用的api

5.1 操作数据

//该方法用于当增加一个数据的时候,position表示新增数据显示的位置
final void notifyItemInserted(int position)

//该方法用于删除一个数据的时候,position表示数据删除的位置
final void notifyItemRemoved(int position)

//该方法表示所在position对应的item位置不会改变,但是该item内容发生变化
final void notifyItemChanged(int position)

//该方法一般用于:适配器之前装载的数据大部分已经过时了,需要重新更新数据
//调用该方法的时候,recyclerView会重新计算子item及所有子item重新布局
//出于效率考虑,官方建议用更加精确的方法(比如上面三个方法)来取代这个方法
final void notifyDataSetChanged()

为了直观地看到各个方法的用法,我们对原有代码进行修改,看看具体的运行结果如何。首先对MyAdapter.java修改,新增三个方法:

//移除数据
    public void removeData(int position) {
        mDataSet.remove(position);
        notifyItemRemoved(position);
    }
    //新增数据
    public void addData(int position){
        mDataSet.add(position,"Add One");
        notifyItemInserted(position);
    }
    //更改某个位置的数据
    public void changeData(int position){
        mDataSet.set(position,"Item has changed"+ count++);
        notifyItemChanged(position);
    }

5.1 添加分割线

public class DividerItemDecoration extends RecyclerView.ItemDecoration {

    private static final int[] ATTRS = new int[]{
            android.R.attr.listDivider
    };

    public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;

    public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;

    private Drawable mDivider;

    private int mOrientation;

    public DividerItemDecoration(Context context, int orientation) {
        final TypedArray a = context.obtainStyledAttributes(ATTRS);
        mDivider = a.getDrawable(0);
        a.recycle();
        setOrientation(orientation);
    }

    public void setOrientation(int orientation) {
        if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
            throw new IllegalArgumentException("invalid orientation");
        }
        mOrientation = orientation;
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent) {
        if (mOrientation == VERTICAL_LIST) {
            drawVertical(c, parent);
        } else {
            drawHorizontal(c, parent);
        }
    }

    public void drawVertical(Canvas c, RecyclerView parent) {
        final int left = parent.getPaddingLeft();
        final int right = parent.getWidth() - parent.getPaddingRight();

        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.getBottom() + params.bottomMargin;
            final int bottom = top + mDivider.getIntrinsicHeight();
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(c);
        }
    }

    public void drawHorizontal(Canvas c, RecyclerView parent) {
        final int top = parent.getPaddingTop();
        final int bottom = parent.getHeight() - parent.getPaddingBottom();

        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 left = child.getRight() + params.rightMargin;
            final int right = left + mDivider.getIntrinsicHeight();
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(c);
        }
    }

    @Override
    public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
        if (mOrientation == VERTICAL_LIST) {
            outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
        } else {
            outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
        }
    }
}

6. 滑动监听

ItemTouchHelper是一个处理RecyclerView的滑动删除和拖拽的辅助类,RecyclerView 的item拖拽移动和滑动删除就靠它来实现。
ItemTouchHelper 是系统为我们提供的一个用于滑动和删除 RecyclerView 条目的工具类,用起来也是非常简单的,大致两步:

  • 创建 ItemTouchHelper 实例,同时实现 ItemTouchHelper.SimpleCallback 中的抽象方法,用于初始化 ItemTouchHelper
  • 调用 ItemTouchHelper 的 attachToRecyclerView 方法关联上 RecyclerView 即可

ItemTouchHelper的源码如下:

itemTouchHelper=new ItemTouchHelper(new ItemTouchHelper.Callback() {

        //用于设置拖拽和滑动的方向
        @Override
        public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
            int dragFlags=0,swipeFlags=0;
            if(recyclerView.getLayoutManager() instanceof StaggeredGridLayoutManager||recyclerView.getLayoutManager() instanceof GridLayoutManager){
               //网格式布局有4个方向
               dragFlags=ItemTouchHelper.UP|ItemTouchHelper.DOWN|ItemTouchHelper.LEFT|ItemTouchHelper.RIGHT;
            }else if(recyclerView.getLayoutManager() instanceof LinearLayoutManager){
               //线性式布局有2个方向
               dragFlags=ItemTouchHelper.UP|ItemTouchHelper.DOWN;
               
               swipeFlags = ItemTouchHelper.START|ItemTouchHelper.END; //设置侧滑方向为从两个方向都可以
            }
            return makeMovementFlags(dragFlags,swipeFlags);//swipeFlags 为0的话item不滑动
        }

        //长摁item拖拽时会回调这个方法
        @Override
        public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
            int from=viewHolder.getAdapterPosition();
            int to=target.getAdapterPosition();

            Meizi moveItem=meizis.get(from);
            meizis.remove(from);
            meizis.add(to,moveItem);//交换数据链表中数据的位置

            mAdapter.notifyItemMoved(from,to);//更新适配器中item的位置
            return true;
        }

        @Override
        public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
        //这里处理滑动删除
        }

        @Override
        public boolean isLongPressDragEnabled() {
            return false;//返回true则为所有item都设置可以拖拽
        }
    });

itemTouchHelper需要与recyclerView绑定才有效果,在recyclerView初始化的时候调用

itemTouchHelper.attachToRecyclerView(recyclerview);

如果你想为item设置拖拽和滑动时的响应动画效果,可以利用ItemTouchHelper的下面三个方法。用线性布局示例:

//当item拖拽开始时调用
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
super.onSelectedChanged(viewHolder, actionState);
 if(actionState==ItemTouchHelper.ACTION_STATE_DRAG){
       viewHolder.itemView.setBackgroundColor(Color.LTGRAY);//拖拽时设置背景色为灰色
    }
}

//当item拖拽完成时调用
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
 viewHolder.itemView.setBackgroundColor(Color.WHITE);//拖拽停止时设置背景色为白色
}

 //当item视图变化时调用
 @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);
  //根据item滑动偏移的值修改item透明度。screenwidth是我提前获得的屏幕宽度
  viewHolder.itemView.setAlpha(1-Math.abs(dX)/screenwidth);
 }

参考文献

  1. 丶蓝天白云梦de简书
  2. 作者:简名
  3. csdn
  4. 自己添加hrader和footer
  5. brvah 多类型布局
  6. recyclerview 源码分析
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!