美文网首页AndroidAndroid DevAndroid UI
RecyclerView 从零开始一步步深入

RecyclerView 从零开始一步步深入

作者: 西瓜太郎123 | 来源:发表于2016-05-25 10:01 被阅读2467次

    RecyclerView作为ListView,GridView的升级版,使用起来非常灵活。并且配合动画可以实现非常赞的效果。

    基本使用步骤:
    mRecyclerView = findView(R.id.id_recyclerview);
    //设置布局管理器
    mRecyclerView.setLayoutManager(layout);
    //设置adapter
    mRecyclerView.setAdapter(adapter)
    //设置Item增加、移除动画
    mRecyclerView.setItemAnimator(new DefaultItemAnimator());
    //添加分割线
    mRecyclerView.addItemDecoration(new DividerItemDecoration(
    getActivity(), DividerItemDecoration.HORIZONTAL_LIST));

    1.基础知识点

    • LayoutManager 顾名思义 负责布局的管理,通过切换布局管理器我们可以轻松实现列表,网格,瀑布流等效果。
    • Adapter 用于适配item,这里的Adapter是继承自RecyclerView.Adapter不是BaseAdapter
    • ItemDecoration 通俗点讲就是“分割线”,类似listView中的divider,但是RecyclerView中并未提供这个属性,要实现分割线,需要通过调用addItemDecoration(),系统并未提供缺省的ItemDecoration实现类。幸运的是已经有第三方实现好了的ItemDecoration,后面介绍
    • ItemAnimator 有了它可以实现各种炫酷的动画效果,系统提供了缺省的DefaultItemAnimator

    2.知识点详解

    ⑴ LayoutManager 系统提供了LinearLayoutManager,GridLayoutManager,StaggeredGridLayoutManager,分别对应三种效果 列表网格瀑布流。示例代码:

     mNormalRecyclerView.setLayoutManager(new LinearLayoutManager(this));//设置list布局
     mNormalRecyclerView.setLayoutManager(new GridLayoutManager(this, 4));//设置网格布局
     mNormalRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL));//设置瀑布流布局
    

    可以看到想要切换效果只需要设置下LayoutManager,对于有需求要求显示多种布局效果的时候,用RecyclerView相比listView等要省力灵活很多。
    ...
    也许这还不够打动你,接着往下看
    通常我们的listView,GirdView都是竖直方向流向的,需求来了要实现横向的listView肿么办?过去还是要花点力气去实现的吧,看RecyclerView分分钟秒杀你

          LinearLayoutManager layoutManager=new LinearLayoutManager(this);
          layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
          mNormalRecyclerView.setLayoutManager(layoutManager);//设置list横向布局
    

    ⑵ Adapter 适配item布局的东东看代码,先写个最简单的Adapter

    
    public class AdapterNormal extends RecyclerView.Adapter<AdapterNormal.MyViewHolder> {
      private Context context;
      private List<String> list;
    
      public AdapterNormal(Context context, List<String> list) {
          this.context = context;
          this.list = list;
      }
    
      @Override
      public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {//产生几个可复用的ViewHolder实例
          MyViewHolder viewHolder = new MyViewHolder(LayoutInflater.from(context).inflate(R.layout.adapter_item, parent, false));
          return viewHolder;
      }
    
      @Override
      public int getItemCount() {
          return list.size();
      }
    
      @Override
      public void onBindViewHolder(final MyViewHolder holder, final int position) {//为每一项View绑定数据
          holder.mTextView.setText(list.get(position));
      }
    
    
      class MyViewHolder extends RecyclerView.ViewHolder {//ViewHolder 大家都不陌生
          private TextView mTextView;
    
          public MyViewHolder(View itemView) {
              super(itemView);
              mTextView = (TextView) itemView.findViewById(R.id.mText);
          }
      }
    }
    
    

    调用它

    adapterNormal = new AdapterNormal(getApplicationContext(), list);
    mNormalRecyclerView.setAdapter(adapterNormal);//设置适配器
    

    多个不同项布局,Adapter该怎么写?

      @Override
      public int getItemViewType(int position) {//在Adapter中重写该方法,根据条件返回不同的值例如100,101
          if (...) {
              return 100;
          }else if (...) {
              return 101;
          }else{
              return super.getItemViewType(position);
          }
      }
    
      @Override
      public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {//根据getItemViewType返回的值生成不同的ViewHolder实例
          MyViewHolder viewHolder = null;
          switch (viewType) {//示例逻辑
              case 100:
                  viewHolder=...;
                  break;
              case 101:
                  viewHolder=...;
                  break;
              default:
                  viewHolder = new MyViewHolder(LayoutInflater.from(context).inflate(R.layout.adapter_item, parent, false));
                  break;
          }
          return viewHolder;
      }
    
      @Override
      public void onBindViewHolder(final MyViewHolder holder, final int position) {//为不同的布局适配数据
          switch (holder.getItemViewType()) {
              case 100:
                  ...
                  break;
              case 101:
                  ...
                  break;
              default:
                  holder.mTextView.setText(list.get(position));
                  break;
          }
      }
    

    每次都这样写Adapter是不是觉得很累?简化它

    首先引入 compile 'com.zhy:base-adapter:2.0.0'

    Android 万能的Adapter for ListView,RecyclerView,GridView等,支持多种Item类型的情况。

    mRecyclerView.setAdapter(new CommonAdapter<String>(this, R.layout.item_list, mDatas)
    {
        @Override
        public void convert(ViewHolder holder, String s)
        {
            holder.setText(R.id.id_item_list_title, s);
        }
    });
    

    是不是相当方便,在convert方法中完成数据、事件绑定即可。还有多种ItemViewType的封装等自行研究都很方便

    ⑶ItemDecoration "分割线" 这玩意感觉没什么好说的直接看代码

      
    /**
     * ListView的分割线
     */
    public class ListItemDecoration 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 ListItemDecoration(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, RecyclerView.State state) {
            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);
                android.support.v7.widget.RecyclerView v = new android.support.v7.widget.RecyclerView(parent.getContext());
                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, View view, RecyclerView parent, RecyclerView.State state) {
            if (mOrientation == VERTICAL_LIST) {
                outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
            } else {
                outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
            }
        }
    }
    
    
    /**
     * @author 鸿洋
     * GridView的分割线
     * 因为作者瀑布流的分割线在item高度不一样的情况下有bug,所以被我去掉了
     */
    public class GridItemDecoration extends RecyclerView.ItemDecoration {
    
        private static final int[] ATTRS = new int[]{android.R.attr.listDivider};
        private Drawable mDivider;
    
        public GridItemDecoration(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();
            spanCount = ((GridLayoutManager) 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(int pos, int spanCount) {
            if ((pos + 1) % spanCount == 0)// 如果是最后一列,则不需要绘制右边
            {
                return true;
            }
            return false;
        }
    
        private boolean isLastRaw(int pos, int spanCount, int childCount) {
            childCount = childCount - childCount % spanCount;
            if (pos >= childCount)// 如果是最后一行,则不需要绘制底部
                return true;
            return false;
        }
    
        @Override
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
            int spanCount = getSpanCount(parent);
            int childCount = parent.getAdapter().getItemCount();
            if (isLastRaw(parent.getChildAdapterPosition(view), spanCount, childCount))// 如果是最后一行,则不需要绘制底部
            {
                outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
            } else if (isLastColum(parent.getChildAdapterPosition(view), spanCount))// 如果是最后一列,则不需要绘制右边
            {
                outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
            } else {
                outRect.set(0, 0, mDivider.getIntrinsicWidth(),
                        mDivider.getIntrinsicHeight());
            }
        }
    }
    
    

    ⑷ ItemAnimator 前面说过了可以产生炫酷的动画,提升逼格的神器
    https://github.com/wasabeef/recyclerview-animators

    demo3.gif
    demo2.gif
    demo.gif

    ⑸ “遗憾”。 是的RecyclerView并没有像listView,GridView那样提供itemClickListener和itemLongClickListener,我们需要自行实现,可以通过设置接口回调,好消息是上面提到的Android 万能的Adapter 已经为我们实现好了这些工作。

    ⑹ 补充。当数据发生变化时我们需要更新数据集

    adapterNormal.notifyDataSetChanged(); //无动画效果
    adapterNormal.notifyItemInserted(0);//有动画效果
    

    以上就是RecyclerView的基本使用,华丽的分割线,提升。。。


    自定义RecyclerView,实现下拉刷新,上拉加载更多
    https://github.com/jianghejie/XRecyclerView
    看效果先

    default.gif

    源码浅析:

    public class XRecyclerView extends RecyclerView {
      @Override
      public void setAdapter(Adapter adapter) {//重写适配器方法
          mWrapAdapter = new WrapAdapter(adapter);//一个包装类
          super.setAdapter(mWrapAdapter);//设置这个包装后的适配器作为适配器
          adapter.registerAdapterDataObserver(mDataObserver);//注册自定义的数据观察者,因为适配器是包装后的适配器
          mDataObserver.onChanged();
      }
    
      @Override
      public void onScrollStateChanged(int state) {//通过判断最后一个可见item的位置和项数量的大小关系实现上拉加载更多
          super.onScrollStateChanged(state);
    
          if (state == RecyclerView.SCROLL_STATE_IDLE && mLoadingListener != null && !isLoadingData && loadingMoreEnabled) {
              LayoutManager layoutManager = getLayoutManager();
              int lastVisibleItemPosition;
              if (layoutManager instanceof GridLayoutManager) {
                  lastVisibleItemPosition = ((GridLayoutManager) layoutManager).findLastVisibleItemPosition();
              } else if (layoutManager instanceof StaggeredGridLayoutManager) {
                  int[] into = new int[((StaggeredGridLayoutManager) layoutManager).getSpanCount()];
                  ((StaggeredGridLayoutManager) layoutManager).findLastVisibleItemPositions(into);
                  lastVisibleItemPosition = findMax(into);
              } else {
                  lastVisibleItemPosition = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
              }
              if (layoutManager.getChildCount() > 0
                      && lastVisibleItemPosition >= layoutManager.getItemCount() - 1 && layoutManager.getItemCount() > layoutManager.getChildCount() && !isNoMore && mRefreshHeader.getState() < ArrowRefreshHeader.STATE_REFRESHING) {
    
                  View footView = mFootViews.get(0);
                  isLoadingData = true;
                  if (footView instanceof LoadingMoreFooter) {
                      ((LoadingMoreFooter) footView).setState(LoadingMoreFooter.STATE_LOADING);
                  } else {
                      footView.setVisibility(View.VISIBLE);
                  }
                  mLoadingListener.onLoadMore();
              }
          }
      }
    
      @Override
      public boolean onTouchEvent(MotionEvent ev) {//通过重写触摸事件实现下拉刷新
          if (mLastY == -1) {
              mLastY = ev.getRawY();
          }
          switch (ev.getAction()) {
              case MotionEvent.ACTION_DOWN:
                  mLastY = ev.getRawY();
                  break;
              case MotionEvent.ACTION_MOVE:
                  final float deltaY = ev.getRawY() - mLastY;
                  mLastY = ev.getRawY();
                  if (isOnTop() && pullRefreshEnabled) {
                      mRefreshHeader.onMove(deltaY / DRAG_RATE);
                      if (mRefreshHeader.getVisibleHeight() > 0 && mRefreshHeader.getState() < ArrowRefreshHeader.STATE_REFRESHING) {
                          return false;
                      }
                  }
                  break;
              default:
                  mLastY = -1; // reset
                  if (isOnTop() && pullRefreshEnabled) {
                      if (mRefreshHeader.releaseAction()) {
                          if (mLoadingListener != null) {
                              mLoadingListener.onRefresh();
                          }
                      }
                  }
                  break;
          }
          return super.onTouchEvent(ev);
      }
    }
    
       private class WrapAdapter extends RecyclerView.Adapter<ViewHolder> {//定义一个包装类,将外部自定义的适配器和内部适配器逻辑关联起来
    
          private RecyclerView.Adapter adapter;//持有我们使用的时候自定义的适配器
    
    
          public WrapAdapter(RecyclerView.Adapter adapter) {
              this.adapter = adapter;
          }
    
          @Override
          public void onAttachedToRecyclerView(RecyclerView recyclerView) {//只执行一次
              super.onAttachedToRecyclerView(recyclerView);
              RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
              if (manager instanceof GridLayoutManager) {
                  final GridLayoutManager gridManager = ((GridLayoutManager) manager);
                  gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {//设置监听回调只需要设置一次
                      @Override
                      public int getSpanSize(int position) {//SpanSize 代表占几列
                          return (isHeader(position) || isFooter(position))//头部或者尾部占满全行或者全列
                                  ? gridManager.getSpanCount() : 1;
                      }
                  });
              }
          }
    
          @Override
          public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {//被多次调用
              super.onViewAttachedToWindow(holder);
              ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
              if (lp != null
                      && lp instanceof StaggeredGridLayoutManager.LayoutParams
                      && (isHeader(holder.getLayoutPosition()) || isFooter(holder.getLayoutPosition()))) {
                  StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp;
                  p.setFullSpan(true);//头部或者尾部占满全行或者全列
              }
          }
    
          @Override
          public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
              if (viewType == TYPE_REFRESH_HEADER) {//根据返回的类型生成不同的ViewHolder实例
                  mCurrentPosition++;
                  return new SimpleViewHolder(mHeaderViews.get(0));
              } else if (isContentHeader(mCurrentPosition)) {
                  if (viewType == sHeaderTypes.get(mCurrentPosition - 1)) {
                      mCurrentPosition++;
                      return new SimpleViewHolder(mHeaderViews.get(headerPosition++));
                  }
              } else if (viewType == TYPE_FOOTER) {
                  return new SimpleViewHolder(mFootViews.get(0));
              }
              return adapter.onCreateViewHolder(parent, viewType);
          }
    
          private int mCurrentPosition;
    
          @Override
          public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {//为我们自定义的item类型绑定数据
              if (isHeader(position)) {
                  return;
              }
              int adjPosition = position - getHeadersCount();
              int adapterCount;
              if (adapter != null) {
                  adapterCount = adapter.getItemCount();
                  if (adjPosition < adapterCount) {
                      adapter.onBindViewHolder(holder, adjPosition);
                      return;
                  }
              }
          }
    
    
          @Override
          public int getItemViewType(int position) {//返回刷新头类型,普通头类型,尾部类型,我们自定义的类型
              if (isRefreshHeader(position)) {
                  return TYPE_REFRESH_HEADER;
              }
              if (isHeader(position)) {
                  position = position - 1;
                  return sHeaderTypes.get(position);
              }
              if (isFooter(position)) {
                  return TYPE_FOOTER;
              }
              int adjPosition = position - getHeadersCount();
              int adapterCount;
              if (adapter != null) {
                  adapterCount = adapter.getItemCount();
                  if (adjPosition < adapterCount) {
                      return adapter.getItemViewType(adjPosition);
                  }
              }
              return TYPE_NORMAL;
          }
     }
    
       private class DataObserver extends RecyclerView.AdapterDataObserver {
          @Override
          public void onChanged() {//数据改变的时候通过比较item数量显示隐藏EmptyView
              Adapter<?> adapter = getAdapter();
              if (adapter != null && mEmptyView != null) {
                  int emptyCount = 0;
                  if (pullRefreshEnabled) {
                      emptyCount++;
                  }
                  if (loadingMoreEnabled) {
                      emptyCount++;
                  }
                  if (adapter.getItemCount() == emptyCount) {
                      mEmptyView.setVisibility(View.VISIBLE);
                      XRecyclerView.this.setVisibility(View.GONE);
                  } else {
                      mEmptyView.setVisibility(View.GONE);
                      XRecyclerView.this.setVisibility(View.VISIBLE);
                  }
              }
              if (mWrapAdapter != null) {
                  mWrapAdapter.notifyDataSetChanged();
              }
          }
    
          @Override
          public void onItemRangeInserted(int positionStart, int itemCount) {
              mWrapAdapter.notifyItemRangeInserted(positionStart, itemCount);
          }
    
          @Override
          public void onItemRangeChanged(int positionStart, int itemCount) {
              mWrapAdapter.notifyItemRangeChanged(positionStart, itemCount);
          }
    
          @Override
          public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
              mWrapAdapter.notifyItemRangeChanged(positionStart, itemCount, payload);
          }
    
          @Override
          public void onItemRangeRemoved(int positionStart, int itemCount) {
              mWrapAdapter.notifyItemRangeRemoved(positionStart, itemCount);
          }
    
          @Override
          public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
              mWrapAdapter.notifyItemMoved(fromPosition, toPosition);
          }
      };
    

    大致思路:定义一个包装适配器,通过不同的item类型判断生成刷新头,普通头部,尾部,我们自定义的itemView并绑定item数据→监听滚动和触摸事件来显示隐藏刷新头和加载更多尾部视图并通过接口抛出刷新和加载更多抽象方法

    相关文章

      网友评论

      • 代码咖啡:想请问一下,如何自定义刷新头部的布局样式?
      • 菲利柯斯:学习了,最近正打算还recycleview
      • 069432f24e5d:张鸿洋大神的万能adapter_(:_」∠)_
        西瓜太郎123:@丶假象 是的
      • yangyirunning: 打开简书首页就看到了,这正是我需要的,今后想把列表逐步换成recyclerView,看到博主的文章,果断马!!!
      • 西门鱼:very good.
        西瓜太郎123:@木月师兄9527 谢谢支持

      本文标题:RecyclerView 从零开始一步步深入

      本文链接:https://www.haomeiwen.com/subject/hpezrttx.html