最近在项目开发当中遇到一个记录列表的需求,UED设计稿要求有吸附效果,原本想偷懒在网上找个抄一下,可是简单的看了一下网上的方案都跟业务耦合比较大,不是很想用,就本身写了一个和业务解耦,即插即用的。git
废话不说,先看东西github
实现的效果仍是不错的。缓存
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.rv);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
//StickyItemDecoration 实现吸附效果
recyclerView.addItemDecoration(new StickyItemDecoration());
复制代码
咱们就从这行代码开始分析bash
recyclerView.addItemDecoration(new StickyItemDecoration());
复制代码
首先进入StickyItemDecoration的构造方法ide
public StickyItemDecoration() {
mStickyView = new ExampleStickyView();
initPaint();
}
复制代码
咦,第一行代码是什么意思? 让咱们点击进去看看布局
public class ExampleStickyView implements StickyView {
@Override
public boolean isStickyView(View view) {
return (Boolean) view.getTag();
}
@Override
public int getStickViewType() {
return 11;
}
}
复制代码
ExampleStickyView 实现了一个叫作StickyView 的接口,而且须要去实现它的两个方法,那这两个方法是作什么的呢?ui
isStickyView方法 是用来判断传递进来的View是不是须要吸附的View,由于我在适配器当中给须要吸附的View设置了一个tag是true,因此这边代码判断若是tag是true就是须要吸附的View。this
getStickViewType方法,由于须要吸附效果的列表通常都会有2个item type,getStickViewType方法就是返回须要吸附View的type是多少。spa
这两个方法会在ItemDecoration的绘制方onDrawOver法当中用到。设计
接下来就是初始化绘制参数 initPaint();
构造方法结束之后,就要进入到重要的绘制onDrawOver方法
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
//获得当前RecyclerView的布局管理器
mLayoutManager = (LinearLayoutManager) parent.getLayoutManager();
mCurrentUIFindStickView = false;
for (int m = 0, size = parent.getChildCount(); m < size; m++) {
View view = parent.getChildAt(m);
/**
* 这里就用到了ExampleStickyView的isStickyView方法
用来判断是不是须要吸附效果的View
是的话才会进入到if逻辑当中
*/
if (mStickyView.isStickyView(view)) {
//当前UI当中是否找到了须要吸附的View,此时设置为true
mCurrentUIFindStickView = true;
//这个方法是获得吸附View的viewHolder
getStickyViewHolder(parent);
//缓存须要吸附的View在列表当中的下标position
cacheStickyViewPosition(m);
//若是当前吸附的view距离 顶部小于等于0,而后给吸附的View绑定数据,计算View的宽高
if (view.getTop() <= 0) {
bindDataForStickyView(mLayoutManager.findFirstVisibleItemPosition(), parent.getMeasuredWidth());
} else {
//若是大于0,从position缓存中取得当前的position,而后绑定数据,计算View的宽高
if (mStickyPositionList.size() > 0) {
if (mStickyPositionList.size() == 1) {
bindDataForStickyView(mStickyPositionList.get(0), parent.getMeasuredWidth());
} else {
int currentPosition = getStickyViewPositionOfRecyclerView(m);
int indexOfCurrentPosition = mStickyPositionList.lastIndexOf(currentPosition);
bindDataForStickyView(mStickyPositionList.get(indexOfCurrentPosition - 1), parent.getMeasuredWidth());
}
}
}
//计算吸附的View距离顶部的高度
if (view.getTop() > 0 && view.getTop() <= mStickyItemViewHeight) {
mStickyItemViewMarginTop = mStickyItemViewHeight - view.getTop();
} else {
mStickyItemViewMarginTop = 0;
}
//绘制吸附的View
drawStickyItemView(c);
break;
}
}
//若是在当前的列表视图中没有找到须要吸附的View
if (!mCurrentUIFindStickView) {
mStickyItemViewMarginTop = 0;
//若是已经滑动到底部了,就绑定最后一个缓存的position的View,这种状况通常出如今快速滑动列表的时候吸附View出现错乱,因此须要绑定一下
if (mLayoutManager.findFirstVisibleItemPosition() + parent.getChildCount() == parent.getAdapter().getItemCount()) {
bindDataForStickyView(mStickyPositionList.get(mStickyPositionList.size() - 1), parent.getMeasuredWidth());
}
//绘制View
drawStickyItemView(c);
}
}
复制代码
上面代码每一行都有注释具体是什么意思,下面我来简单的阐述一下代码的思路和具体逻辑。
大体思路就是:
在列表滚动的时候会进入onDrawOver方法,而后循环当前列表的ItemView,若是遇到是吸附的Item View, 经过适配器再根据itemType来建立一个ViewHolder,而且获得这个ViewHolder的itemView;
循环的时候须要不断去缓存吸附View所在RecyclerView中的下标位置position,根据View距离顶部的高度来获得当前吸附View的position;
接下来经过adapter的onBindViewHolder来给ViewHolder的itemView绑定数据,而后计算itemView的宽高,z这样吸附的View拿到了,数据也绑定好了;
而后再计算距离顶部的高度,把itemView绘制到屏幕上便可。
若是由于在当前列表中没有找到吸附的itemView(mCurrentUIFindStickView=false),就直接绘制上一个便可。
介绍到这里,整理流程就通了,上面贴的并不是所有代码,下面附上源码的连接
https://github.com/chenpengfei88/StickyItemDecoration
有兴趣的朋友能够看看,也欢迎你们star。