让RecyclerView更通用

何为通用

用过RecyclerView的都知道它没有为我们提供像ListView中类似addHeaderView,addFooterView,setOnItemClickListener,setOnItemLongClickListener的接口,另外还有底部自动加载更多,这些都是列表使用中很常见的功能,所以如果我们使用RecyclerView来实现列表的话就需要自己实现这些功能。本文主要介绍这些通用功能的实现方式,使得RecyclerView使用起来和ListView一样方便。

点击事件

setOnItemClickListener,setOnItemLongClickListener

RecyclerView中虽然没有提供上面这两个接口,但是给我们提供了另外一个接口:OnItemTouchListener看这个接口的文档描述我们知道此接口可以对RecyclerView中的手势进行监听处理,因此我们可以采用OnItemTouchListener+GestureDetector来实现RecyclerViewOnItemClickOnItemLongClick。实现方式也比较简单,还是上代码吧…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
private OnItemClickListener mOnItemClickListener;
private OnItemLongClickListener mItemLongClickListener;
mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
@Override
public void onLongPress(MotionEvent e) {
super.onLongPress(e);
if(mItemLongClickListener != null) {
View childView = findChildViewUnder(e.getX(), e.getY());
if(childView != null) {
int position = getChildLayoutPosition(childView);
mItemLongClickListener.onItemLongClick(position, childView);
}
}
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
if(mOnItemClickListener != null) {
View childView = findChildViewUnder(e.getX(),e.getY());
if(childView != null){
int position = getChildLayoutPosition(childView);
mOnItemClickListener.onItemClick(position, childView);
return true;
}
}
return super.onSingleTapUp(e);
}
});
addOnItemTouchListener(new SimpleOnItemTouchListener() {
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
if (mGestureDetector.onTouchEvent(e)) {//交由手势处理
return true;
}
return false;
}
});
/**
* Item项点击事件
*/
public interface OnItemClickListener {
void onItemClick(int position, View itemView);
}
/**
* Item项长按点击事件
*/
public interface OnItemLongClickListener {
void onItemLongClick(int position, View itemView);
}

addHeaderView,addFooterView

实现原理

前面写过一篇文章RecyclerView下拉刷新上拉加载 介绍过RecyclerView的上拉加载的实现方式,里面的上拉加载进度条其实也是RecyclerView的一个FooterView,其实现方式就是为LoadMoreView设置了一个特殊的ItemViewType来进行区分展示,因此我这里的HeaderViewFooterView也是通过为它们设置不同的ItemViewType来进行区分展示。

我们知道ListView中的addHeaderViewaddFooterView都是可以添加多个View的,也就是说RecyclerView中也会出现添加多个完全不同的HeaderViewFooterView,所以我们必须为添加的每个HeaderViewFooterView都设置一个ItemViewType从而达到添加多个不同的HeaderViewFooterView的目的(如果所有的HeaderViewFooterView都设置同一个ItemViewType的话只能显示一种ViewHeaderViewFooterView)。

实现步骤

知道了实现原理,我们再来理一下实现步骤:

  1. 因为每个HeaderViewFooterView都需要对应一个ItemViewType,所以我们需要分别为它们建立一个映射关系,我采用SparseArray实现映射
  2. 我们需要在添加HeaderViewFooterView的时候生成对应的ItemViewType值,也就是我们需要定义一个ItemViewType的生成规则,我采用了基准值+视图个数的方式生成ItemViewType
  3. 自定义一个Adapter继承自RecyclerView.Adapter,重写里面的几个方法:onCreateViewHolder,onBindViewHolder,getItemViewType,getItemCount
  4. getItemCount方法中返回的数据总数显然是:HeaderView总数+FooterView总数+List列表展示的数据总数
  5. 重写onBindViewHoldergetItemViewType这两个方法时,显然需要根据position判断当前位置是否为HeaderView或是FooterView,而根据展示顺序来看当0<=position<HeaderView总数 时是HeaderView,而当position>=(HeaderView总数+List总数)时则是FooterView,其余位置则是List数据对应的View
  6. 而重写onCreateViewHolder方法时,则可用直接根据其方法参数viewType在SparseArray映射中查找是否存在该类型的HeaderView或是FooterView,有则返回,没有则返回List数据展示的View

    关键代码

    分析完实现的步骤开始撸代码,下面是我实现的关键代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    //HeaderView的ItemViewType的生成基准值,生成规则为基准值+当前HeaderView的个数
    private static final int TYPE_HEADER = 100000;
    //FooterView的ItemViewType的生成基准值,生成规则为基准值+当前的FooterView的个数
    private static final int TYPE_FOOTER = 200000;
    //存储HeaderView,key值作为对应HeaderView的ItemViewType
    private SparseArray<View> mHeaderViews = new SparseArray<>(0);
    //存储FooterView,key值作为对应HeaderView的ItemViewType
    private SparseArray<View> mFooterViews = new SparseArray<>(0);
    @Override
    public final ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    if(isHeaderViewEnable() && mHeaderViews.get(viewType) != null) {
    return new ViewHolder(mHeaderViews.get(viewType));
    } else if(isFooterViewEnable() && mFooterViews.get(viewType) != null) {
    return new ViewHolder(mFooterViews.get(viewType));
    }
    return onCreateItemViewHolder(parent, viewType);
    }
    @Override
    public final void onBindViewHolder(ViewHolder holder, int position) {
    if(isFooterView(position) || isHeaderView(position)) {
    return;
    }
    T item = getItem(position - getHeaderViewCount());
    onBindItemViewHolder(holder, position, item);
    }
    @Override
    public final int getItemViewType(int position) {
    if(isHeaderView(position)) {//FooterView
    return mHeaderViews.keyAt(position);
    }
    if(isFooterView(position)){//HeaderView
    return mFooterViews.keyAt(position - getHeaderViewCount() - getItemDataCount());
    }
    return getItemViewTypeForData(position);
    }
    /**
    * 展示的总数据数(包括HeaderView和FooterView)
    *
    * @return
    */
    @Override
    public final int getItemCount() {
    //从写此方法,数据总数需要包括HeaderView总数和FooterView总数
    return getItemDataCount() + getHeaderViewCount() + getFooterViewCount();
    }
    /**
    * 要展示的有效数据数(不包括HeaderView和FooterView)
    *
    * @return
    */
    public int getItemDataCount() {
    return mList == null ? 0 : mList.size();
    }
    /**
    * 获取HeaderView的总数
    *
    * @return
    */
    public int getHeaderViewCount() {
    return isHeaderViewEnable() ? mHeaderViews.size() : 0;
    }
    /**
    * 获取FooterView的总数
    *
    * @return
    */
    public int getFooterViewCount() {
    return isFooterViewEnable() ? mFooterViews.size() : 0;
    }
    /**
    * 判断position位置是否为FooterView
    *
    * @param position
    * @return
    */
    public boolean isFooterView(int position) {
    return isFooterViewEnable() && isFooterViewPosition(position);
    }
    /**
    * 判断position位置是否为HeaderView
    *
    * @param position
    * @return
    */
    public boolean isHeaderView(int position) {
    return isHeaderViewEnable() && isHeaderViewPosition(position);
    }
    /**
    * 判断position位置是否为FooterView的索引
    *
    * @param position
    * @return
    */
    public boolean isFooterViewPosition(int position) {
    return position >= getItemDataCount() + getHeaderViewCount();
    }
    /**
    * 判断position位置是否为HeaderView的索引
    *
    * @param position
    * @return
    */
    public boolean isHeaderViewPosition(int position) {
    return position < getHeaderViewCount();
    }
    /**
    * 添加一个HeaderView
    *
    * @param headerView
    */
    public void addHeaderView(View headerView) {
    if(headerView == null) {
    throw new NullPointerException("headerView is null");
    }
    mHeaderViews.put(TYPE_HEADER + getHeaderViewCount(), headerView);
    notifyItemInserted(getHeaderViewCount() - 1);
    }
    /**
    * 添加一个FooterView
    *
    * @param footerView
    */
    public void addFooterView(View footerView) {
    if(footerView == null) {
    throw new NullPointerException("footerView is null");
    }
    mFooterViews.put(TYPE_FOOTER + getFooterViewCount(), footerView);
    notifyItemInserted(getHeaderViewCount() + getItemDataCount() + getFooterViewCount() - 1);
    }

RecyclerView使用注意

  1. 这里需要注明一点RecyclerView使用中的坑,如果RecyclerViewLinearLayoutManager时在onCreatViewHolder中生成的View都必须关联上其parent,也就是关联到RecyclerView本身。我前面的一片文章记录了我遇到的这个问题RecyclerViewView宽度不充满父容器,所以在addHeaderViewaddFooterView时也需要注意这个问题

  2. 如果你的RecyclerViewLayoutManagerGridLayoutManagerStaggeredGridLayoutManager时,如果就这样添加HeaderViewFooterView,会发现HeaderViewFooterView不会独立的占据一行。这是因为设置了SpanSize的缘故,所以我们需要针对这两种LayoutManager进行处理,处理方式如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
    super.onAttachedToRecyclerView(recyclerView);
    final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
    if(layoutManager instanceof GridLayoutManager) {
    ((GridLayoutManager) layoutManager).setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
    @Override
    public int getSpanSize(int position) {
    return getNewSpanSize(((GridLayoutManager) layoutManager).getSpanCount(), position);
    }
    });
    }
    }
    @Override
    public void onViewAttachedToWindow(ViewHolder holder) {
    super.onViewAttachedToWindow(holder);
    int position = holder.getLayoutPosition();
    if(isHeaderView(position) || isFooterView(position)) {
    final ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
    if(layoutParams != null && layoutParams instanceof StaggeredGridLayoutManager.LayoutParams) {
    StaggeredGridLayoutManager.LayoutParams lp = (StaggeredGridLayoutManager.LayoutParams) layoutParams;
    lp.setFullSpan(true);
    }
    }
    }
    private int getNewSpanSize(int spanCount, int position) {
    if(isHeaderView(position) || isFooterView(position)) {
    return spanCount;
    }
    return 1;
    }

自动加载更多

自动加载更多也是列表显示中比较常见的一个功能,我们可以为RecyclerView设置ScrollListener监听来进行实现,具体实现的关键代码如下;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
super.setOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if(newState == SCROLL_STATE_IDLE && mIsAutoLoadMore && mLoadMoreListener != null) {
if(mLastVisiblePosition + 1 == getAdapter().getItemCount()) {
mLoadMoreListener.onLoadMore();
}
}
if(mOnScrollListener != null) {
mOnScrollListener.onScrollStateChanged(recyclerView, newState);
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if(mIsAutoLoadMore && mLoadMoreListener != null) {
mLastVisiblePosition = getLastVisiblePosition();
}
if(mOnScrollListener != null) {
mOnScrollListener.onScrolled(recyclerView, dx, dy);
}
}
});

文章相关的完整代码和Demo:https://github.com/huyongli/AndroidDemo


原创文章,本文采用知识共享署名 2.5(中国大陆许可协议)进行许可,欢迎转载,但转载请注明来自ittiger.cn,并保证转载后文章内容的完整性。本人(laohu)保留所有版权相关权利。



评论