Android RecyclerView实现吸附效果

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

Android RecyclerView 使用ItemDecoration实现吸附效果,和业务代码完全解耦,即插即用

前言

最近在项目开发当中遇到一个记录列表的需求,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 {

  @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。


猜你喜欢

转载自blog.csdn.net/qq_36467463/article/details/79707501