前言:这两天借鉴了几位大牛的文章,自己撸了一个极度简便的RecyclerView的控件,包括了HeaderView、EmptyView、FootView三个功能,核心类只有一个,如下。按照普通的RecyclerView使用即可.
改版:1、头部、脚部的计数方式改为按可见状态下的计算。
2、增加了内部类WrapContentLinearLayoutManager。
修改:增加Item点击事件
一、核心类介绍
package com.zuji.entrance.widget;
import android.content.Context;
import android.support.v4.util.SparseArrayCompat;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
/**
* Created by fangyc on 2018/7/9.
*/
public class CRecyclerView extends RecyclerView {
HeaderAndFooterWrapAdapter mHeaderAndFooterWrapper;//与头部、脚部相关
private View emptyView; //与空布局相关
//数据观察者,与空布局相关
final private AdapterDataObserver observer = new AdapterDataObserver() {
@Override
public void onChanged() {
checkIfEmpty();
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
checkIfEmpty();
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
checkIfEmpty();
}
};
public CRecyclerView(Context context) {
this(context, null);
}
public CRecyclerView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
/**
* 添加头部
*
* @param view
*/
public void addHeaderView(View view) {
if (mHeaderAndFooterWrapper == null)
throw new UnsupportedOperationException("设置头部前,请先setAdapter!");
mHeaderAndFooterWrapper.addHeaderView(view);
}
/**
* 添加底部
*
* @param view
*/
public void addFootView(View view) {
if (mHeaderAndFooterWrapper == null)
throw new UnsupportedOperationException("设置底部前,请先setAdapter!");
mHeaderAndFooterWrapper.addFootView(view);
}
/**
* 移除头部,0<=position<=头部的个数
*
* @param position
*/
public void removeHeaderView(int position) {
mHeaderAndFooterWrapper.removeHeaderView(position);
}
/**
* 移除底部,0<=position<=底部的个数
*
* @param position
*/
public void removeFootView(int position) {
mHeaderAndFooterWrapper.removeFootView(position);
}
/**
* 获取头部
*
* @param position
* @return
*/
public View getHeaderView(int position) {
return mHeaderAndFooterWrapper.getHeaderView(position);
}
/**
* 获取脚部
*
* @param position
* @return
*/
public View getFootView(int position) {
return mHeaderAndFooterWrapper.getFootView(position);
}
/**
* 设置头部可见状态
*
* @param position
* @param visibility
*/
public void setHeaderViewVisibility(int position, int visibility) {
mHeaderAndFooterWrapper.setHeaderViewVisibility(position, visibility);
}
/**
* 设置脚部可见状态
*
* @param position
* @param visibility
*/
public void setFootViewVisibility(int position, int visibility) {
mHeaderAndFooterWrapper.setFootViewVisibility(position, visibility);
}
/*----以上为关于headerView和footView的部分--------------------------------------------------------------------------------------*/
/*----以下为关于emptyView的部分--------------------------------------------------------------------------------------*/
/**
* 检查数据是否为空,是否展示emptyView
*/
private void checkIfEmpty() {
if (emptyView != null && getAdapter() != null) {
final boolean emptyViewVisible = getAdapter().getItemCount() == 0;
emptyView.setVisibility(emptyViewVisible ? VISIBLE : GONE);
setVisibility(emptyViewVisible ? GONE : VISIBLE);
}
}
//设置没有内容时,提示用户的空布局
public void setEmptyView(View emptyView) {
this.emptyView = emptyView;
// checkIfEmpty();
}
/**
* 返回空布局
*
* @return
*/
public View getEmptyView() {
return this.emptyView;
}
@Override
public void setAdapter(Adapter adapter) {
final Adapter oldAdapter = getAdapter();
if (oldAdapter != null) {
oldAdapter.unregisterAdapterDataObserver(observer);
}
mHeaderAndFooterWrapper = new HeaderAndFooterWrapAdapter(adapter);
super.setAdapter(mHeaderAndFooterWrapper);
if (adapter != null) {
// adapter.registerAdapterDataObserver(observer);
mHeaderAndFooterWrapper.registerAdapterDataObserver(observer);
}
checkIfEmpty();
// observer.onChanged();
}
public void notifyDataSetChanged(){
final Adapter adapter = getAdapter();
if (adapter != null) {
adapter.notifyDataSetChanged();
}
}
/**
* 装饰者模式,
* 用于处理headerView和FootView,
* 内部类,也可以将该类独立出来.
*
* @param <T>
*/
private class HeaderAndFooterWrapAdapter<T> extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int BASE_ITEM_TYPE_HEADER = 100000;
private static final int BASE_ITEM_TYPE_FOOTER = 200000;
private SparseArrayCompat<View> mHeaderViews = new SparseArrayCompat<>();
private SparseArrayCompat<View> mFootViews = new SparseArrayCompat<>();
private RecyclerView.Adapter mInnerAdapter;
public HeaderAndFooterWrapAdapter(RecyclerView.Adapter adapter) {
mInnerAdapter = adapter;
}
private boolean isHeaderViewPos(int position) {
return position < getVisibleHeadersCount();
}
private boolean isFooterViewPos(int position) {
return position >= getVisibleHeadersCount() + getRealItemCount();
}
public void addHeaderView(View view) {
mHeaderViews.put(mHeaderViews.size() + BASE_ITEM_TYPE_HEADER, view);
}
public void addFootView(View view) {
mFootViews.put(mFootViews.size() + BASE_ITEM_TYPE_FOOTER, view);
}
public void removeHeaderView(int position) {
mHeaderViews.remove(position + BASE_ITEM_TYPE_HEADER);
}
public void removeFootView(int position) {
mFootViews.remove(position + BASE_ITEM_TYPE_FOOTER);
}
public View getHeaderView(int position) {
return mHeaderViews.get(position + BASE_ITEM_TYPE_HEADER, null);
}
public View getFootView(int position) {
return mFootViews.get(position + BASE_ITEM_TYPE_FOOTER, null);
}
public void setHeaderViewVisibility(int position, int visibility) {
if (mFootViews.get(position + BASE_ITEM_TYPE_HEADER) != null) {
mFootViews.get(position + BASE_ITEM_TYPE_HEADER).setVisibility(visibility);
}
}
public void setFootViewVisibility(int position, int visibility) {
if (mFootViews.get(position + BASE_ITEM_TYPE_FOOTER) != null) {
mFootViews.get(position + BASE_ITEM_TYPE_FOOTER).setVisibility(visibility);
}
}
// public int getHeadersCount() {
// return mHeaderViews.size();
// }
//
// public int getFootersCount() {
// return mFootViews.size();
// }
/**
* 获取可见的头部条数
*
* @return
*/
private int getVisibleHeadersCount() {
int size = mHeaderViews.size();
if (size <= 0) return size;
int visiSize = 0;
for (int i = 0; i < size; i++) {
View view = mHeaderViews.get(i + BASE_ITEM_TYPE_HEADER);
if (view.getVisibility() == View.VISIBLE) visiSize++;
}
return visiSize;
}
/**
* 获取可见的脚部条数
*
* @return
*/
private int getVisibleFootersCount() {
int size = mFootViews.size();
if (size <= 0) return size;
int visiSize = 0;
for (int i = 0; i < size; i++) {
View view = mFootViews.get(i + BASE_ITEM_TYPE_FOOTER);
if (view.getVisibility() == View.VISIBLE) visiSize++;
}
return visiSize;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (mHeaderViews.get(viewType) != null) {
return new SimpleViewHolder(mHeaderViews.get(viewType));
} else if (mFootViews.get(viewType) != null) {
return new SimpleViewHolder(mFootViews.get(viewType));
}
return mInnerAdapter.onCreateViewHolder(parent, viewType);
}
@Override
public int getItemViewType(int position) {
if (isHeaderViewPos(position)) {
return mHeaderViews.keyAt(position);
} else if (isFooterViewPos(position)) {
return mFootViews.keyAt(position - getVisibleHeadersCount() - getRealItemCount());
}
return mInnerAdapter.getItemViewType(position - getVisibleHeadersCount());
}
private int getRealItemCount() {
return mInnerAdapter.getItemCount();
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (isHeaderViewPos(position)) {
return;
}
if (isFooterViewPos(position)) {
return;
}
mInnerAdapter.onBindViewHolder(holder, position - getVisibleHeadersCount());
}
@Override
public int getItemCount() {
// return getHeadersCount() + getFootersCount() + getRealItemCount();
return getVisibleHeadersCount() + getVisibleFootersCount() + getRealItemCount();
}
private class SimpleViewHolder extends RecyclerView.ViewHolder {
public SimpleViewHolder(View itemView) {
super(itemView);
}
}
/**
* 针对GridLayoutManager
*
* @param recyclerView
*/
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
mInnerAdapter.onAttachedToRecyclerView(recyclerView);
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {
final GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
final GridLayoutManager.SpanSizeLookup spanSizeLookup = gridLayoutManager.getSpanSizeLookup();
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
int viewType = getItemViewType(position);
if (mHeaderViews.get(viewType) != null) {
return gridLayoutManager.getSpanCount();
} else if (mFootViews.get(viewType) != null) {
return gridLayoutManager.getSpanCount();
}
if (spanSizeLookup != null)
return spanSizeLookup.getSpanSize(position);
return 1;
}
});
gridLayoutManager.setSpanCount(gridLayoutManager.getSpanCount());
}
}
/**
* 对于StaggeredGridLayoutManager
*
* @param holder
*/
@Override
public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
mInnerAdapter.onViewAttachedToWindow(holder);
int position = holder.getLayoutPosition();
if (isHeaderViewPos(position) || isFooterViewPos(position)) {
ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
if (lp != null && lp instanceof StaggeredGridLayoutManager.LayoutParams) {
StaggeredGridLayoutManager.LayoutParams p =
(StaggeredGridLayoutManager.LayoutParams) lp;
p.setFullSpan(true);
}
}
}
}
/**
* 据说
* RecyclerView Bug:IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter的解决方案
*/
public static class WrapContentLinearLayoutManager extends LinearLayoutManager {
public WrapContentLinearLayoutManager(Context context) {
super(context);
}
public WrapContentLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
public WrapContentLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
try {
super.onLayoutChildren(recycler, state);
} catch (IndexOutOfBoundsException e) {
e.printStackTrace();
}
}
}
/**
* Created by Administrator on 2017/5/5.
*/
public static class RecyclerViewClickListener implements RecyclerView.OnItemTouchListener {
private GestureDetector mGestureDetector;
private OnItemClickListener mListener;
//内部接口,定义点击方法以及长按方法
public interface OnItemClickListener {
void onItemClick(View view, int position);
void onItemLongClick(View view, int position);
}
public RecyclerViewClickListener(Context context, final RecyclerView recyclerView, OnItemClickListener listener) {
mListener = listener;
mGestureDetector = new GestureDetector(context,
new GestureDetector.SimpleOnGestureListener() { //这里选择SimpleOnGestureListener实现类,可以根据需要选择重写的方法
//单击事件
@Override
public boolean onSingleTapUp(MotionEvent e) {
View childView = recyclerView.findChildViewUnder(e.getX(), e.getY());
if (childView != null && mListener != null) {
mListener.onItemClick(childView, recyclerView.getChildLayoutPosition(childView));
return true;
}
return false;
}
//长按事件
@Override
public void onLongPress(MotionEvent e) {
View childView = recyclerView.findChildViewUnder(e.getX(), e.getY());
if (childView != null && mListener != null) {
mListener.onItemLongClick(childView, recyclerView.getChildLayoutPosition(childView));
}
}
});
}
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
//把事件交给GestureDetector处理
if (mGestureDetector.onTouchEvent(e)) {
return true;
} else
return false;
}
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
}
}
二、使用介绍:
1,布局,(emptyView可以自己定义,也可以是简单的TextView、ImageView等)
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/transparent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<com.csair.staffservice.view.CRecyclerView
android:id="@+id/msgRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#f5f5f5"
android:overScrollMode="never" />
<com.csair.staffservice.view.EmptyView
android:id="@+id/msgEmpty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#f5f5f5"
android:overScrollMode="never" />
</LinearLayout>
2,代码使用
mRecyclerView = (CRecyclerView) view.findViewById(R.id.msgRecyclerView);
mEmptyView = (View) view.findViewById(R.id.msgEmpty);
//设置layoutManager
mRecyclerView.setLayoutManager(mRecyclerView.new WrapContentLinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false));
//设置空布局
mRecyclerView.setEmptyView(mEmptyView);
//设置头部
mRecyclerView.addHeaderView(new HeaderView(getContext()));
//设置脚部
mRecyclerView.addFootView(new NoDataFooterView(getContext()));
这几个布局都可以自由发挥
添加Item点击事件
crv_hometown.addOnItemTouchListener(new CRecyclerView.RecyclerViewClickListener(getContext(), crv_hometown, new CRecyclerView.RecyclerViewClickListener.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
String region;
// RegionsBean.DataBean bean = regionsBean.getData().get(position);
ZujiRegionSelectedDM.setSelectedBean(regionsBean.getData().get(position));
if (LanguageChecker.isZH()) {
region = regionsBean.getData().get(position).getRegion_cn();
} else {
region = regionsBean.getData().get(position).getRegion_en();
}
tv_regoin_selected_name.setText(region);
// mAdapter.setSelectedBean(selectedBean);
crv_hometown.notifyDataSetChanged();
}
@Override
public void onItemLongClick(View view, int position) {
}
}));
三、bug介绍
删除元素出现的bug及解决方案:
1、(亲测,不可靠) RecyclerView 删除元素后,点击报 IndexOutOfBoundsException 解决方法,
onBindViewHolder() 方法中的位置参数 position 不是实时更新的,所以在我们删除元素后,item 的 position 没有改变。为了实时获取元素的位置,RecyclerView 为我们提供了 ViewHolder.getAdapterPosition() 方法。
当把上面奔溃的代码中的 position 换成 holder.getAdapterPosition() 就解决了问题。
2、(亲测,不可靠)IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter的解决方案,
其实也不是什么解决方法,只是把这个异常捕获了,不让他奔溃了,这个问题的终极解决方案还是得让google去修复。
a、创建一个类LinearLayoutManagerWrapper继承LinearLayoutManager,重写onLayoutChildren方法
public class WrapContentLinearLayoutManager extends LinearLayoutManager {
public WrapContentLinearLayoutManager(Context context) {
super(context);
}
public WrapContentLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
public WrapContentLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
try {
super.onLayoutChildren(recycler, state);
} catch (IndexOutOfBoundsException e) {
e.printStackTrace();
}
}
}
b、设置RecyclerView的布局管理为WrapContentLinearLayoutManager对象
mRecyclerView.setLayoutManager(new WrapContentLinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
3、(亲测,相当可靠)positionViewHolder{a1bbfa3 position=2 id=-1, oldPos=-1, pLpos:-1 no parent}
网上查阅下说是原生bug,自定义了线性layout,重写LinearLayoutManagerWrapper.结果发现还是会报红,干脆在clear数据时,recycler_user.removeAllViews();就没遇到报的问题了.在删除元素时,我的真正有效的解决方法是:
try {
StaffMsgBean.DataBean data = datas.remove(position);
if (datas.size() <= 0) {
crv.setFootViewVisibility(0, View.GONE);
}
//解决bug的核心
crv.removeAllViews();//移除所有views
notifyDataSetChanged();//刷新数据,会触发CRecyclerView中observer#onChanged()
crv.scrollToPosition(position);//滚动至具体条目位置
StaffRepo.getInstance().init(act).delete(data.getId());
} catch (Exception e) {
// e.printStackTrace();
ToastUtil.showShort(act, "删除失败!");
}
最后附上大牛文章
Android 优雅的为RecyclerView添加HeaderView和FooterView
RecyclerView添加EmptyView(空布局)