您的位置:首页 > 其它

[置顶] 让RecyclerView更通用

2016-07-27 12:16 190 查看
-------------------------------------------------------------------

欢迎访问我的个人独立博客
ittiger.cn,原创文章,未经允许不得随意转载。

--------------------------------------------------------------------

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


setOnItemClickListener,setOnItemLongClickListener

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

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来进行区分展示,因此我这里的HeaderView和FooterView也是通过为它们设置不同的ItemViewType来进行区分展示。

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

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

分析完实现的步骤开始撸代码,下面是我实现的关键代码:

//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使用中的坑,如果RecyclerView为LinearLayoutManager时在onCreatViewHolder中生成的View都必须关联上其parent,也就是关联到RecyclerView本身。我前面的一片文章记录了我遇到的这个问题RecyclerView子View宽度不充满父容器,所以在addHeaderView和addFooterView时也需要注意这个问题

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

@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滑到底部自动加载更多

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

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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: