通过自定义LayoutManager来实现RecyclerView布局扩展

1 RecyclerView机制

RecyclerView之所以性能优秀,被广泛使用,是因为他的优秀的回收复用机制,查看源代码知道,recyclerview在layoutmanager里面帮我们完成了相关操作,当我们需要自定义LayoutManager时,我们需要将不用的View回收掉;在需要获取新的View时直接申请,即通过getViewForPosition()方法,返回的View可能是之前回收的垃圾View,也可能是new出来的新View,这些都是RecyclerView帮我们做的。那么RecyclerView内部的垃圾View缓存是什么样子的呢?我们接下来看看~

1.1RecyclerView的二级缓存

RecyclerView中,有两个缓存:ScrapRecycleScrap中文就是废料的意思,Recycle对应是回收的意思。这两个缓存有什么作用呢?首先Scrap缓存是指里面缓存的View是接下来需要用到的,即里面的绑定的数据无需更改,可以直接拿来用的,是一个轻量级的缓存集合;而Recycle的缓存的View为里面的数据需要重新绑定,即需要通过Adapter重新绑定数据。

当我们去获取一个新的View时,RecyclerView首先去检查Scrap缓存是否有对应的positionView,如果有,则直接拿出来可以直接用,不用去重新绑定数据;如果没有,则从Recycle缓存中取,并且会回调AdapteronBindViewHolder方法(当然了,如果Recycle缓存为空,还会调用onCreateViewHolder方法),最后再将绑定好新数据的View返回。

1.2 将View缓存的两种方式

前面我们了解到,RecyclerView中有二级缓存,我们可以自己选择将View缓存到哪里。我们有两种选择的方式:DetachRemoveDetachView放在Scrap缓存中,Remove掉的View放在Recycle缓存中;那我们应该如何去选择呢?

在什么样的场景中使用Detach呢?主要是在我们的代码执行结束之前,我们需要反复去将View移除并且马上又要添加进去时,选择Datach方式,比如:当我们对View进行重新排序的时候,可以选择Detach,因为屏幕上显示的就是这些position对应的View,我们并不需要重新去绑定数据,这明显可以提高效率。使用Detach方式可以通过函数detachAndScrapView()实现。

而使用Remove的方式,是当View不在屏幕中有任何显示的时候,你需要将它Remove掉,以备后面循环利用。可以通过函数removeAndRecycleView()实现。

2 开始自定义LayoutManager

新建一个类继承RecyclerView.LayoutManager,实现它的方法generateDefaultLayoutParams();

然后重写onLayoutChildren()方法,在这个方法下按照自己的想法去对itemview进行布局

   //摆放子布局
    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        if(getItemCount() <=0){
            return;
        }

        // 先把所有的View先从RecyclerView中detach掉,然后标记为"Scrap"状态,表示这些View处于可被重用状态(非显示中)。
        // 实际就是把View放到了Recycler中的一个集合中。
        detachAndScrapAttachedViews(recycler);
        //开始摆放
        int offsetY = 0;
        int offsetX = 0;
        int viewH = 0;
        for (int i=0;i<getItemCount();i++) {
            View view = recycler.getViewForPosition(i);
            addView(view);//因为进行了detach操作,所以现在要重新添加
            measureChildWithMargins(view, 0, 0);//通知测量itemView

            int w=getDecoratedMeasuredWidth(view);//获取itemVIEW的实际大小,包括measureChildWithMargins方法中设置的大小
            int h=getDecoratedMeasuredHeight(view);
            viewH = h;
            Rect fram = allItemframs.get(i);
            if (fram == null){
                fram = new Rect();
            }
//            需要换行
            if (offsetX + w > getWidth()) {
//                换行的View的值
                offsetY += h;
                offsetX=w;
                fram.set(0, offsetY, w, offsetY + h);
            }else {
//                不需要换行
                fram.set(offsetX, offsetY, offsetX + w, offsetY + h);
                offsetX += w;
            }
//            要 针对于当前View   生成对应的Rect  然后放到allItemframs数组
            allItemframs.put(i, fram);
            layoutDecorated(view, fram.left, fram.top, fram.right, fram.bottom);
        }

    }

看看效果

添加滑动效果,对应方法canScrollVertically和scrollVerticallyBy方法(以垂直方向为例)


    @Override
    public boolean canScrollVertically() {
        return true;
    }

    @Override
    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
        //实际滑动距离  dx
        int trval = dy;

        if (verticalScrollOffset + dy < 0) {//如果滑动到最顶部
            trval = -verticalScrollOffset;
        } else if(verticalScrollOffset+dy>totalHeight-getHeight()){
//            如果滑动到最底部  往上滑   verticalScrollOffset   +
            trval = totalHeight - getHeight() - verticalScrollOffset;
        }

        verticalScrollOffset += trval;
        //平移容器内的item
        offsetChildrenVertical(trval);
        recyclerViewFillView(recycler,state);//回收滑出去的itemview,并从缓存中拿出进行复用

        return trval;
    }

回收和复用(关键)

    private void recyclerViewFillView(RecyclerView.Recycler recycler, RecyclerView.State state) {
        //        清空RecyclerView的子View
        detachAndScrapAttachedViews(recycler);//将当前getchildcount数量的子View放入到scrap缓存池去
        Rect phoneFrame = new Rect(0, verticalScrollOffset, getWidth(), verticalScrollOffset + getHeight());//当前可见区域
        //将滑出屏幕的view进行回收
        for (int i=0;i<getChildCount();i++){
            View childView = getChildAt(i);
            Rect child=allItemframs.get(i);
            if (!Rect.intersects(phoneFrame, child)) {
                removeAndRecycleView(childView, recycler);//recyclerview移除当前childview,并将之放入到recycler缓存池
            }
        }
        //可见区域出现在屏幕上的子view
        for (int j = 0;j<getItemCount();j++){
            if (Rect.intersects(phoneFrame,allItemframs.get(j))){
//                scrap回收池里面拿的
                View scrap = recycler.getViewForPosition(j);
                measureChildWithMargins(scrap,0,0);
                addView(scrap);
                Rect frame = allItemframs.get(j);
                layoutDecorated(scrap, frame.left, frame.top - verticalScrollOffset,
                        frame.right, frame.bottom - verticalScrollOffset);//给每一个itemview进行布局
            }
        }
    }

看看现在的效果

至此就完成了一个自动布局的自定义Layoutmanager

代码地址:https://github.com/games2sven/RecyClerViewLayoutManager

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值