RecyclerView 使用ItemDecoration 巧妙实现吸附效果
本文作者
作者:伪文艺大叔
链接:
https://juejin.im/post/5a70139ff265da3e274574cb
本文由作者投稿发布。
最近在项目开发当中遇到一个记录列表的需求,UED设计稿要求有吸附效果,本来想偷懒在网上找个抄一下,但是简单的看了一下网上的方案都跟业务耦合比较大,不是很想用,就自己写了一个和业务解耦,即插即用的。
效果图
实现的效果还是不错的。
使用
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.rv);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
//StickyItemDecoration 实现吸附效果
recyclerView.addItemDecoration(new StickyItemDecoration());
构造方法
我们就从这行代码开始分析
recyclerView.addItemDecoration(new StickyItemDecoration());
首先进入StickyItemDecoration的构造方法
public StickyItemDecoration() {
mStickyView = new ExampleStickyView();
initPaint();
}
咦,第一行代码是什么意思? 让我们点击进去看看
public class ExampleStickyView implements StickyView {
public boolean isStickyView(View view) {
return (Boolean) view.getTag();
}
public int getStickViewType() {
return 11;
}
}
ExampleStickyView 实现了一个叫做StickyView 的接口,并且需要去实现它的两个方法,那这两个方法是做什么的呢?
isStickyView方法 是用来判断传递进来的View是否是需要吸附的View,因为我在适配器当中给需要吸附的View设置了一个tag是true,所以这边代码判断如果tag是true就是需要吸附的View。
getStickViewType方法,因为需要吸附效果的列表一般都会有2个item type,getStickViewType方法就是返回需要吸附View的type是多少。
这两个方法会在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。
推荐阅读:
如果你想要跟大家分享你的文章,欢迎投稿~