【Android开发】RecyclerView应用总结

一、优缺点

RecyclerView主要是和ListView进行比较,下面列举几个优缺点:

缺点:

  • listview可以通过addHeaderView()和addFooterView()添加头视图和尾视图。
  • listview可以通过android:divider设置自定义分割线。
  • listview可以setOnItemClickListener()和setOnItemLongClickListener()设置点击事件和长按事件。

优点:

  • 容易实现各种布局。
  • 默认实现了View的复用,不需要类似if(convertview==null)的实现,而且回收机制更加的完善。
  • 默认支持局部刷新。
  • 容易实现添加item,删除item的动画效果。
  • 容易实现拖拽,侧滑删除等功能
  • listview只提供notifyDataSetChanged()更新整个视图,这是很不合理的。RecyclerView提供了notifyItemInserted(),notifyItemRemove(),notifyItemChanged()等API更新单个或某个范围的Item视图。

二、四大基础部分

RecyclerView属于新增加的控件,为了让RecyclerView在所有版本上都能使用,将RecyclerView定义在support库中,因此,想要使用,首先需要在项目的build.gradle中添加相应的依赖库:
compile 'com.android.support:recyclerview-v7:24.2.0'
添加后,在布局文件中直接添加recyclerview控件。
RecyclerView需要进行四大设置:

1.Adapter(必选)

负责提供数据,一个适配器,继承自RecyclerView.Adapter。适配器需要一个构造函数将数据传入;重写三个方法,分别是onCreateViewHolder,onBindViewHolder,getItemCount方法;定义一个内部类ViewHolder,继承RecyclerView.ViewHolder。在构造函数中传入view参数,这个参数就是子项的布局。

2.LayoutManager(必选,负责布局)

LayoutManager是RecyclerView的一个抽象内部类,一般我们使用它都是使用它的子类,常用的有:

  • LinearLayoutManager(横向和纵向)
  • GridLayoutManager(网格式)
  • StaggeredGridLayoutManager(瀑布式)
纵向为默认;
横向实现方式:

layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);

网格式实现方式:

GridLayoutManager gridLayoutManager = new GridLayoutManager(this,4);
第二个参数为每一行的个数。

瀑布流式实现方式:

StaggeredGridLayoutManager layoutmanager = new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL);
除此之外还需要在adapter的onBindViewHolder中设置一个随机高度。
ViewGroup.LayoutParams layoutParams = viewHolder.linearLayout.getLayoutParams(); layoutParams.height = 300+(int)(Math.random()*100);

实现效果:

【Android开发】RecyclerView应用总结_第1张图片
瀑布流

3.Item Decoration(可选,默认为空)

负责Item之间的间隔,RecyclerView通过addItemDecoration()方法添加item之间的分割线。android并没有提供实现好的Decoration,因此任何分割样式都需要自己实现。

ItemDecoration类主要是三个方法:

public void onDraw(Canvas c, RecyclerView parent, State state)
public void onDrawOver(Canvas c, RecyclerView parent, State state)
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)

getItemOffsets实现的就是类似padding的效果;
onDraw实现类似背景绘制,内容在上面;
onDrawOver可以绘制在内容的上面,覆盖内容;

举个例子,实现分割线:
要实现分割线效果需要 getItemOffsets()和 onDraw()2个方法,首先用 getItemOffsets给item下方空出一定高度的空间(例子中是1dp),然后用onDraw绘制这个空间

 @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        outRect.bottom = dividerHeight;
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        int childCount = parent.getChildCount();
        int left = parent.getPaddingLeft();
        int right = parent.getWidth() - parent.getPaddingRight();

        for (int i = 0; i < childCount - 1; i++) {
            View view = parent.getChildAt(i);
            float top = view.getBottom();
            float bottom = view.getBottom() + dividerHeight;
            c.drawRect(left, top, right, bottom, dividerPaint);
        }
    }

实现效果:


【Android开发】RecyclerView应用总结_第2张图片
分割线

实现标签:
现在很多电商app会给商品加上一个标签,比如“推荐”,“热卖”,“秒杀”等等,可以看到这些标签都是覆盖在内容之上的,这就可以用onDrawOver()来实现,我们这里简单实现一个有趣的标签。

 @Override
    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDrawOver(c, parent, state);
        int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = parent.getChildAt(i);
            int pos = parent.getChildAdapterPosition(child);
            boolean isLeft = pos % 2 == 0;
            if (isLeft) {
                float left = child.getLeft();
                float right = left + tagWidth;
                float top = child.getTop();
                float bottom = child.getBottom();
                c.drawRect(left, top, right, bottom, leftPaint);
            } else {
                float right = child.getRight();
                float left = right - tagWidth;
                float top = child.getTop();
                float bottom = child.getBottom();
                c.drawRect(left, top, right, bottom, rightPaint);

            }
        }
    }

实现效果:


【Android开发】RecyclerView应用总结_第3张图片
覆盖在内容上

4.Item Animator(可选,默认为 DefaultItemAnimator)

负责增加,删除item的动画 。
RecyclerView.setItemAnimator(new DefaultItemAnimator());
注意,这里更新数据集不是用adapter.notifyDataSetChanged()而是
notifyItemInserted(position)与notifyItemRemoved(position)
否则没有动画效果。

三、拓展功能

1.万能适配器

这里我们只针对RecyclerView,聊聊万能适配器出现的原因。为了创建一个RecyclerView的Adapter,每次我们都需要去做重复劳动,包括重写onCreateViewHolder(),getItemCount()、创建ViewHolder,并且实现过程大同小异,因此万能适配器出现了,他能通过以下方式快捷地创建一个Adapter:

public abstract class QuickAdapter extends RecyclerView.Adapter {

    private List mData;

    public QuickAdapter(List mData){
        this.mData = mData;
    }

    public abstract int getLayoutId(int viewType);

    @Override
    public VH onCreateViewHolder(ViewGroup parent,int viewType){
        return VH.get(parent,getLayoutId(viewType));
    }

    @Override
    public void onBindViewHolder(VH holder,int position){
        convert(holder,mData.get(position),position);
    }

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

    public abstract void convert(VH holder,T data,int position);

    static class VH extends RecyclerView.ViewHolder{
        private SparseArray mViews;
        private View mConvertView;

        private VH(View view){
            super(view);
            mConvertView = view;
            mViews = new SparseArray<>();
        }

        public static VH get(ViewGroup parent,int layoutid){
            View convertView = LayoutInflater.from(parent.getContext()).inflate(layoutid,parent,false);
            return new VH(convertView);
        }

        public  T getView(int id){
            View view = mViews.get(id);
            if(view == null){
                view = mConvertView.findViewById(id);
                mViews.put(id,view);
            }
            return (T)view;
        }

        public void setText(int id,String values){
            TextView textView = getView(id);
            textView.setText(values);
        }
    }
}

使用:

QuickAdapter adapter = new QuickAdapter(mData) {
            @Override
            public int getLayoutId(int viewType) {
                return R.layout.item;
            }

            @Override
            public void convert(VH holder, String data, int position) {
                holder.setText(R.id.item_text,data);

            }
        };

2.添加setOnItemClickListener接口

RecyclerView没有像ListView一样提供onItemClickListener却让人比较难过,之前都是通过给每个item添加onClickListener来模仿一个伪onItemClickListener,这种为每个item添加点击监听的解决方案不用多想也知道是浪费性能的方法,这里参照了网上的一种方法。
使用:

recyclerView.addOnItemTouchListener(new OnRecyclerItemClickListener(recyclerView) {
      @Override
      public void onItemClick(RecyclerView.ViewHolder vh) {
        //item点击事件
      }
});

OnRecyclerItemClickListener是自定义的一个触摸监听器,代码如下:

public abstract class OnRecyclerItemClickListener implements RecyclerView.OnItemTouchListener{
    private GestureDetectorCompat mGestureDetector;
    private RecyclerView recyclerView;

    public OnRecyclerItemClickListener(RecyclerView recyclerView){
        this.recyclerView = recyclerView;
        mGestureDetector = new GestureDetectorCompat(recyclerView.getContext(),new ItemTouchHelperGestureListener());
    }

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

    @Override
    public void onTouchEvent(RecyclerView rv, MotionEvent e) {
        mGestureDetector.onTouchEvent(e);
    }

    @Override
    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
    }

    private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener {
        @Override
        public boolean onSingleTapUp(MotionEvent e) {
            View child = recyclerView.findChildViewUnder(e.getX(), e.getY());
            if (child!=null) {
                RecyclerView.ViewHolder vh = recyclerView.getChildViewHolder(child);
                onItemClick(vh);
            }
            return true;
        }

        //长点击事件,本例不需要不处理
        //@Override
        //public void onLongPress(MotionEvent e) {
        //    View child = recyclerView.findChildViewUnder(e.getX(), e.getY());
        //    if (child!=null) {
        //        RecyclerView.ViewHolder vh = recyclerView.getChildViewHolder(child);
        //        onItemLongClick(vh);
        //    }
        //}

    public abstract void onItemClick(RecyclerView.ViewHolder vh);
  //public abstract void onItemLongClick(RecyclerView.ViewHolder vh);
}

查阅RecyclerView的api发现虽然没有提供onItemClickListener但是提供了addOnItemTouchListener方法:

3.添加HeaderView和FooterView

这里引入装饰器(Decorator)设计模式,该设计模式通过组合的方式,在不破话原有类代码的情况下,对原有类的功能进行扩展。

public class ExtendAdapter extends RecyclerView.Adapter {
  enum ITEM_TYPE{
      HEADER,
      NORMAL,
      FOOTER
  }
  private MyAdapter adapter;
  private View mHeaderView;
  private View mFooterView;

  public ExtendAdapter(MyAdapter adapter){
      this.adapter = adapter;
  }
  @Override
  public int getItemViewType(int position){
      if (position==0){
          return ITEM_TYPE.HEADER.ordinal();
      }else if (position == adapter.getItemCount()+1){
          return ITEM_TYPE.FOOTER.ordinal();
      }else{
          return ITEM_TYPE.NORMAL.ordinal();
      }
  }

  @Override
  public int getItemCount(){
      return adapter.getItemCount()+2;
  }
  @Override
  public void onBindViewHolder(RecyclerView.ViewHolder holder,int position){
      if (position==0){
          return;
      }else if (position==adapter.getItemCount()+1){
          return;
      }else {
          adapter.onBindViewHolder((MyAdapter.MyViewHolder) holder,position-1);
      }
  }
  @Override
  public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent,int viewType){
      if (viewType==ITEM_TYPE.HEADER.ordinal()){
          return new RecyclerView.ViewHolder(mHeaderView){};
      }else if (viewType==ITEM_TYPE.FOOTER.ordinal()){
          return new RecyclerView.ViewHolder(mFooterView) {};
      }else {
          return adapter.onCreateViewHolder(parent,viewType);
      }
  }
  public void addHeaderView(View view){
      this.mHeaderView = view;
  }
  public void addFooterView(View view){
      this.mFooterView = view;
  }
}

使用:

MyAdapter myAdapter = new MyAdapter(mData);
ExtendAdapter extendAdapter = new ExtendAdapter(myAdapter);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
View headerView = LayoutInflater.from(this).inflate(R.layout.item_header, recyclerView, false);
View footerView = LayoutInflater.from(this).inflate(R.layout.item_footer, recyclerView, false);
extendAdapter.addHeaderView(headerView);
extendAdapter.addFooterView(footerView);
recyclerView.setAdapter(myAdapter);

效果:
【Android开发】RecyclerView应用总结_第4张图片
header

【Android开发】RecyclerView应用总结_第5张图片
footer

4.拖拽、侧滑删除

Android提供了ItemTouchHelper类,使得RecyclerView能够轻易地实现滑动和拖拽,此处我们要实现上下拖拽和侧滑删除。首先创建一个继承自ItemTouchHelper.Callback的类,并重写以下方法:

  • getMovementFlags(): 设置支持的拖拽和滑动的方向,此处我们支持的拖拽方向为上下,滑动方向为从左到右和从右到左,内部通过makeMovementFlags()设置。
  • onMove(): 拖拽时回调。
  • onSwiped(): 滑动时回调。
  • onSelectedChanged(): 状态变化时回调,一共有三个状态,分别是ACTION_STATE_IDLE(空闲状态),ACTION_STATE_SWIPE(滑动状态),ACTION_STATE_DRAG(拖拽状态)。此方法中可以做一些状态变化时的处理,比如拖拽的时候修改背景色。
  • clearView(): 用户交互结束时回调。此方法可以做一些状态的清空,比如拖拽结束后还原背景色。
  • isLongPressDragEnabled(): 是否支持长按拖拽,默认为true。如果不想支持长按拖拽,则重写并返回false。
    实现:
public class MyItemTouchCallback extends ItemTouchHelper.Callback {
  private MyAdapter adapter;
  private List mData;
  public MyItemTouchCallback(MyAdapter adapter,List mData){
      this.adapter = adapter;
      this.mData = mData;
  }
  @Override
  public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder){
      int dragFlag = ItemTouchHelper.UP | ItemTouchHelper.DOWN; //s上下拖拽
      int swipeFlag = ItemTouchHelper.START | ItemTouchHelper.END; //左->右和右->左滑动
      return makeMovementFlags(dragFlag,swipeFlag);
  }
  @Override
  public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
      int from = viewHolder.getAdapterPosition();
      int to = target.getAdapterPosition();
      Collections.swap(mData, from, to);
      adapter.notifyItemMoved(from, to);
      return true;
  }

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

  @Override
  public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
      super.onSelectedChanged(viewHolder, actionState);
      if(actionState != ItemTouchHelper.ACTION_STATE_IDLE){
          MyAdapter.MyViewHolder holder = (MyAdapter.MyViewHolder)viewHolder;
          holder.itemView.setBackgroundColor(0xffbcbcbc); //设置拖拽和侧滑时的背景色
      }
  }

  @Override
  public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
      super.clearView(recyclerView, viewHolder);
      MyAdapter.MyViewHolder holder = (MyAdapter.MyViewHolder) viewHolder;
      holder.itemView.setBackgroundColor(0xffeeeeee); //背景色还原
  }
}

使用:

MyAdapter myAdapter = new MyAdapter(mData);
recyclerView.setAdapter(myAdapter);
MyItemTouchCallback itemTouchCallback = new MyItemTouchCallback(myAdapter,mData);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(itemTouchCallback);
itemTouchHelper.attachToRecyclerView(recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));

效果:


滑动效果

参考文章:

  • RecyclerView 必知必会
  • android开发游记:RecyclerView无法添加onItemClickListener最佳的高效解决方案

你可能感兴趣的:(【Android开发】RecyclerView应用总结)