【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);

实现效果:

瀑布流

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

实现效果:


分割线

实现标签:
现在很多电商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);

            }
        }
    }

实现效果:


覆盖在内容上

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<T> extends RecyclerView.Adapter<QuickAdapter.VH> {

    private List<T> mData;

    public QuickAdapter(List<T> 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<View> 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 extends View> 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<String> adapter = new QuickAdapter<String>(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<RecyclerView.ViewHolder> {
  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);

效果:
header

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<String> mData;
  public MyItemTouchCallback(MyAdapter adapter,List<String> 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));

效果:


滑动效果

参考文章:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容