效果图
简述
这一篇我们继续扩展上一篇的SwipeAdapter
,增加粘性头部的支持,我们命名为PinnedAdapter
,在第一篇的demo截图中有一张模拟了慕课网中Swift课程的实现就是用了本篇的扩展进行实现的。下文中的item header为粘性头部控件。
这里我们用最简单的一种方式实现,我们采取的实现方式是这样的:每一个item view都带一个item header,在需要的时候设置其可见性。
接下来还是定义下我们的需求:
a. 在item支持滑动菜单的情况下,我们不允许item header滑动
b. item header当前没有提供监听器
经过上面的描述我们已经知道,客户端设置的最原始的item view可能经过SwipeLayout的包装,到这里后我们还需要对其进行一次LinearLayout包装,命名为PinnedLayout,我们将用它创建新的viewHolder。
1. 创建viewHolder
@Override
final
public BaseViewHolder onCreateHolder(ViewGroup parent, int viewType) {
BaseViewHolder holder = super.onCreateHolder(parent, viewType);
View headerView = inflater.inflate(getPinnedItemViewLayout(), parent, false);
PinnedLayout pinnedLayout = new PinnedLayout(context);
pinnedLayout.setUpView(parent, holder.itemView, headerView);
//说明客户端设置了支持滑动菜单
if (holder.itemView instanceof SwipeLayout) {
View itemView = ((SwipeLayout) holder.itemView).getItemView();
itemView.setClickable(true);
return new BaseViewHolder(pinnedLayout, itemView);
}else {
return new BaseViewHolder(pinnedLayout);
}
}
2. 数据绑定
在这部分的处理里面我们需要完成以下几点:
a. 在原有的功能上我们需要扩展一个设置粘性头部数据的方法
/**
* 设置粘性控件数据
* @param holder
* @param position 数据域中的索引
* @param type 数据所在的组
*/
void convertPinnedHolder(BaseViewHolder holder, int position, int type);
b. 需要为item设置一个标识,用来标识在这一组中该item位于哪个位置
这个是用于在滑动处理中用来处理每个item头部可见性以及粘性头部
private interface PinnedItemRange {
int FIRST = 1;
int OTHER = 2;
}
c. 为item设置关联数据的索引位置
这个是为了在回调给客户端设置头部数据的时候用的。
关键的点已经描述清楚,下面看具体逻辑
代码如下:
@Override
final
protected void onBindHolder(BaseViewHolder holder, int position) {
PinnedLayout pinnedLayout = (PinnedLayout) holder.itemView;
//每个组的第一条需要设置header可见
if (0 == position || getItem(position).type != getItem(position - 1).type) {
pinnedLayout.pinnedView.setVisibility(View.VISIBLE);
holder.itemView.setTag(R.string.pinned_item_status, PinnedItemRange.FIRST);
} else {
pinnedLayout.pinnedView.setVisibility(View.GONE);
holder.itemView.setTag(R.string.pinned_item_status, PinnedItemRange.OTHER);
}
final int type = getItem(position).type;
holder.itemView.setTag(R.string.pinned_item_data_type, type);
holder.itemView.setTag(R.string.item_index_in_datasection, position);
super.onBindHolder(holder, position);
this.convertPinnedHolder(holder, position, type);
}
3. 滑动处理
数据处理完毕之后,我们需要处理滑动中数据的操作。在上面数据绑定的时候我们已经设置了在处理过程中需要的标识数据,在滑动过程中我们需要知道以下几点:
- 滑动时粘性头部控件
pinnedView
所在位置上方和下方的控件以及附带的数据- 滑动时
pinnedView
数据绑定- 滑动时
pinnedView
动画
以上3点具体的逻辑都在下面的监听器里
private RecyclerView.OnScrollListener scrollListener = new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
final int pinnedViewWidth = pinnedView.getMeasuredWidth();
final int pinnedViewHeight = pinnedView.getMeasuredHeight();
//获取Adapter中粘性头部位置上方的控件
View headerView = recyclerView.findChildViewUnder(pinnedViewWidth / 2, 5);
Object obj = headerView.getTag(R.string.pinned_item_data_type);
if (headerView != null && null != obj && obj instanceof Integer) {
int position = (int) headerView.getTag(R.string.item_index_in_datasection);
convertPinnedHolder(pinnedViewHolder, position, (int) obj);
}
//获取Adapter中粘性头部位置下方的控件
headerView = recyclerView.findChildViewUnder(pinnedViewWidth / 2, pinnedViewHeight + 1);
obj = headerView.getTag(R.string.pinned_item_status);
if (null == headerView || null == obj || !(obj instanceof Integer)) {
return;
}
int transViewStatus = (int) obj;
int translationY = headerView.getTop() - pinnedViewHeight;
if (transViewStatus == PinnedItemRange.FIRST) {
if (headerView.getTop() > 0) {
pinnedView.setTranslationY(translationY);
} else {
pinnedView.setTranslationY(0);
}
} else if (transViewStatus == PinnedItemRange.OTHER) {
pinnedView.setTranslationY(0);
}
}
};
4. 设置粘性头部及滑动监听
经过上面的描述,所有的处理过程以ok,剩下最后一点就是为RecyclerView
添加滚动监听,同时希望粘性头部也让我们的库自动加上去,不必让客户端很罗嗦的修改xml文件等,这样就对界面的布局有点要求,我们这里限定为RecyclerView
要有一个FrameLayout
的父容器包裹。
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
try {
//父容器是FrameLayout,否则让粘性功能失效
FrameLayout parent = (FrameLayout) recyclerView.getParent();
if (null == this.pinnedViewHolder) {
this.initPinnedView(parent, recyclerView);
}
recyclerView.addOnScrollListener(scrollListener);
} catch (Exception e) {
Log.e("DEBUG", "RecyclerView parent must be FrameLayout");
e.printStackTrace();
Toast.makeText(context, "RecyclerView parent must be FrameLayout", Toast.LENGTH_SHORT).show();
}
}
5. ItemWrapper包装器
在我这种粘性头部实现的方式中,从逻辑中也能看出来是将数据进行了分组,将每一个实体数据都关联到组上,这一实现是通过将实体数据进行ItemWrapper
包装,为每个实体数据设置type组属性实现的,所以这一篇的PinnedAdapter
将数据通过泛型的方式进行了限定public class PinnedAdapter<T extends ItemWrapper>
。
到这里为止,为Adapter添加粘性头部支持就结束了,Adapter的整个扩展系列也结束了,如果感觉有帮助的话欢迎到github star以下或者fork都行,有问题也可以提出来,也可以帮助我更好的完善。
后面会抽出点时间基于这个扩展库再封装一个支持下拉、上拉的分页处理库。