查看原文
其他

RecyclerView 使用ItemDecoration 巧妙实现吸附效果

伪文艺大叔 鸿洋 2019-04-05

本文作者


作者:伪文艺大叔

链接:

https://juejin.im/post/5a70139ff265da3e274574cb

本文由作者投稿发布。


1前言


最近在项目开发当中遇到一个记录列表的需求,UED设计稿要求有吸附效果,本来想偷懒在网上找个抄一下,但是简单的看了一下网上的方案都跟业务耦合比较大,不是很想用,就自己写了一个和业务解耦,即插即用的。


效果图




实现的效果还是不错的。


使用

RecyclerView recyclerView = (RecyclerView) findViewById(R.id.rv);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
//StickyItemDecoration 实现吸附效果
recyclerView.addItemDecoration(new StickyItemDecoration());


2实现思路


构造方法


我们就从这行代码开始分析

recyclerView.addItemDecoration(new StickyItemDecoration());


首先进入StickyItemDecoration的构造方法

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 的接口,并且需要去实现它的两个方法,那这两个方法是做什么的呢?


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。


推荐阅读

玩Android 快应用已经开源啦~

app拆分,多产品打包实录



如果你想要跟大家分享你的文章,欢迎投稿~

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存