简介
众所周知,RecyclerView 是Android L版本中新添加的一个用来取代ListView的SDK,它的灵活性与可替代性比listview更好。我们可以在此基础上开发出丰富的效果。RecycleView之所以如此方便与受欢迎,主要是它近乎于一种插拔式的开发模式,也可以说是类装饰装者模式。
例如:
1,Recycle.setLayoutMnager();
可以设置GridLayoutManger,LinearLayoutManger(横向,纵向),StaggeredGridLayoutManager
2,Recycle.addItenDecoration();
可以添加装饰,针对item的间隔线或者是一些悬浮分组的设计都可以通过它来完成
3,设置适配器,类似于ListView,可以更换不一样的适配器,显示不一样的布局
以上只是对RecycleView的简单介绍,本文的重点是XRecycleView,在GitHub上是一个非常受欢迎的RecycleView封装框架XRecycleView链接。该框架对RecycleView的加heardView,footView和上拉刷新,下拉加载都做了很好的封装,非常好用,由于我在开发中有需要做一个分组列表的需求,本身网络上有很多实现分组的适配器,可直接给RecycleView设置。虽然XRecycleView 是基于RecycleView的封装,但测试结果是不可以直接给XRecycleView设置带有分组效果的适配器设置的。原因和XRecycleView的封装有关,具体的可以查看源码理解分析,这里不深入探讨。
思路
分析XRcycleView中添加headerView或者footerView的思路,其实HeaderView,footView都是Item的一种,只不过显示在顶部或者底部的位置,他通过为其设置ItemType来完成的。而且他的下拉刷新本身也是一个头的位置。
上面的代码是判断适配器的位置是不是下拉刷新头,我们可以看到他直接返回position == 0,也就是说0的位置默认为下拉刷新头。
而真正heardView的位置就得position>=1.
那么,我们的分组,其实也可以类似于addHeardView()一样addTitleView();思路基本差不多,但有一些难点。
难点:
1,title的位置是变动的,如果加入多个头,要如何处理他的位置和判断该位置是不是title
2,要如何在title的位置create不一样的布局
3,要如何不打乱展示数据的位置前提下加入title
addTitleView()的处理过程
1,创建必要的几个集合存储操作数据
//title类型type的基数
private static final int TYPE_TITLE_POSITION = 10003;
//存放titleView的集合,每一个都是不一样的
private SparseArray<View> mTitleViews = new SparseArray<>();
//每个title必须有不同的type,不然滚动的时候顺序会变化(TYPE_TITLE_POSITION+(1,··))
private List<Integer> sTitleTypes = new ArrayList<>();
//存放title位置的集合,就是在适配器的哪个指定位置插入title
private SparseIntArray titleIndex = new SparseIntArray();
如上面的代码所示,每一个titleView需要不一样的itemType,指定加入的位置。
2,添加TitleView的方法和判断titleType
/**
* 添加title的方法
*
* @param view 加入的view
* @param key view对应的key 和 组成view类型的基数
* @param titlePosition 位置
*/
public void addTitleView(View view, int key, int titlePosition) {
sTitleTypes.add(TYPE_TITLE_POSITION + key);
titleIndex.put(mTitleViews.size(), titlePosition);
mTitleViews.put(key, view);
if (mWrapAdapter != null) {
mWrapAdapter.notifyDataSetChanged();
}
}
/**
* 数据变化时,需要清空集合数据
*/
public void clearTitleView() {
titleIndex.clear();
sTitleTypes.clear();
mTitleViews.clear();
}
/**
* 根据Title的ViewType判断是哪个titleView
*
* @param itemType
* @return
*/
private View getTitleViewByType(int itemType) {
if (!isTitleType(itemType)) {
return null;
}
return mTitleViews.get(itemType - TYPE_TITLE_POSITION);
}
/**
* 判断一个type是否为TitleType
*
* @param itemViewType
* @return
*/
private boolean isTitleType(int itemViewType) {
return mTitleViews.size() > 0 && sTitleTypes.contains(itemViewType);
}
简单的理解上面的代码,就是
(1),为每一个titleView,指定titleViewType。(TYPE_TITLE_POSITION+key)
(2),为每一个titleView的集合指定唯一的存放位置。(key,View)
(3),为每一个titleView应该被加入到适配器总的具体位置指定集合存放位置(mTitleViews.size(),titlePosition)
之后就是,每次加入需要清除集合数据(位置变动)。根据titleViewType判断是不是titleView的。根据titleViewType返回对应的view
3,关键步骤,在XRcycleView 的内部适配器WrapAdapter中做处理
/**
* 根据传入的位置判断是不是加入title的位置
* @param position
* @return
*/
public boolean isTitlePosition(int position) {
boolean flag = false;
for (int i = 0; i < titleIndex.size(); i++) {
flag = (position == titleIndex.get(i));
if (flag) {
break;
}
}
return flag;
}
/**
* 获取title的总数
* @return
*/
public int getTitlesCount() {
return mTitleViews.size();
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_REFRESH_HEADER) {
return new SimpleViewHolder(mRefreshHeader);
} else if (isHeaderType(viewType)) {
return new SimpleViewHolder(getHeaderViewByType(viewType));
} else if (viewType == TYPE_FOOTER) {
return new SimpleViewHolder(mFootView);
} else if (isTitleType(viewType)) {
return new SimpleViewHolder(getTitleViewByType(viewType));
}
return adapter.onCreateViewHolder(parent, viewType);
}
oncreateViewHolder最关键的方法,一定要弄清楚它的调用条件:它是在viewType改变的情况下才会调用。所以上面的添加titleView之前需要指定每一个view的唯一key和清理所有集合数据。然后就是根据不一样的titleViewType,创建对应不一样的view。
/**
* some times we need to override this
* 为了不让适配器适配的数据混乱和丢失,需要对头部view,下拉刷新view和title的view的位置进行处理
*
* @param holder
* @param position
* @param payloads
*/
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List<Object> payloads) {
if (isHeader(position) || isRefreshHeader(position) || isTitlePosition(position)) {
return;
}
final int tempPosition = position - (getHeadersCount() + 1);
final int titleCount = titleIndex.size();
int adjPosition = tempPosition;
//处理title加入的位置不影响适配器中真实数据的位置排列
for (int i = 0; i < titleCount; i++) {
if (position >= titleIndex.get(i) + 1) {
adjPosition = tempPosition - i - 1;
}
}
int adapterCount;
if (adapter != null) {
adapterCount = adapter.getItemCount();
if (adjPosition < adapterCount) {
if (payloads.isEmpty()) {
adapter.onBindViewHolder(holder, adjPosition);
} else {
adapter.onBindViewHolder(holder, adjPosition, payloads);
}
}
}
}
这个是绑定viewHolder的方法,要记住它是每一次滑动展示position上的view时都会被调用,所以对应的加入titleView的位置我们需要返回,因为它要填充的view是不一样的,而真实数据的排列位置受到了它的影响,需要对数据的position做处理。
@Override
public int getItemCount() {
if (loadingMoreEnabled) {
if (adapter != null) {
return getHeadersCount() + getTitlesCount() + adapter.getItemCount() + 2;
} else {
return getHeadersCount() + getTitlesCount() + 2;
}
} else {
if (adapter != null) {
return getHeadersCount() + getTitlesCount() + adapter.getItemCount() + 1;
} else {
return getHeadersCount() + getTitlesCount() + 1;
}
}
}
这是适配器中真实的item数量,当有数据的是后,总的数量等于头的数量+itemcount的数量+title的数量+1(下拉刷新头)或者+2(下拉加载foot的位置减了1).
@Override
public int getItemViewType(int position) {
int adjPosition = position - (getHeadersCount() + getTitlesCount() + 1);
if (isRefreshHeader(position)) {
return TYPE_REFRESH_HEADER;
}
if (isHeader(position)) {
position = position - 1;
return sHeaderTypes.get(position);
}
if (isFooter(position)) {
return TYPE_FOOTER;
}
if (isTitlePosition(position)) {
int titleCount = titleIndex.size();
for (int i = 0; i < titleCount; i++) {
if (position == titleIndex.get(i)) {
return sTitleTypes.get(i);
}
}
}
int adapterCount;
if (adapter != null) {
adapterCount = adapter.getItemCount();
if (adjPosition < adapterCount) {
int type = adapter.getItemViewType(adjPosition);
if (isReservedItemViewType(type)) {
throw new IllegalStateException("XRecyclerView require itemViewType in adapter should be less than 10000 ");
}
return type;
}
}
return 0;
}
@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) {
return (isHeader(position) || isFooter(position) || isRefreshHeader(position)) || isTitlePosition(position)
? gridManager.getSpanCount() : 1;
}
});
}
adapter.onAttachedToRecyclerView(recyclerView);
}
这个方法处理的是GridView的问题,在titleView的位置布局是不一样的,需要合并成一行。
还有一些其他的地方,需要一些对titleView的位置和个数判断,这里就不贴出来了,具体的可在我的百度网盘XrecycleViewExpand 链接:https://pan.baidu.com/s/1Y7iVEr30w87L8XFmbfT6QQ 密码:krg6 下载demo详解。本文主要是在XrecycleView上拓展功能,以此分享和复用,方便以后开发理解。非常诚挚感谢XrecycleView原作者的奉献精神。