RecyclerView系列(8)—自定义LayoutManager(下) ,回收复用及优化

转载请注明出处:
http://blog.csdn.net/user11223344abc/article/details/78080671
出自【蛟-blog】

0.前言

本文分为俩个Step来研究如何自定义一个合格的LinearlayoutMnager

  • Step 1:视觉上定义一个具备上下边界的RecyclerView.layoutMnager
    这里边又分为几个小步。
    传送门:https://blog.csdn.net/user11223344abc/article/details/78080671
  • Step 2:item回收,以及性能的验证
    当然我们不能满足于视觉上,条目的离屏回收和复用是一个合格Rv的基本标准。
    传送门:https://blog.csdn.net/user11223344abc/article/details/79168157

内容涉及到部分原理,更多是代码层面的讲解,就是说,代码为什么这样写

Ps:有什么问题请直接指出,欢迎拍砖。

1.初始化的优化方案

上篇(见title)初步认识了自定义layoutManager是怎么回事,也定义了一个可以滑动的VerticalLinlearLayoutManager,建议先看下之前的代码。在这里,我提出问题,然后分析如何解决。

1.1 初始化的问题所在

之前我们初始化,是按照这么一个模式进行的。

forI(getItemCount){
addView...
layoutView...
}

这么做的问题,就是假设有十万条数据,那么就直接炸了。

1.2 优化方案

  • 算出可见范围
  • 算出条目均高(若条目均高不一样,复杂度翻倍,这里我们默认认为每个item都一样高)
  • 算出可见范围内能放多少条数数据
  • 初始化add和layout可见的条数
  • 其他扩展(为回收做准备)

思路已经理出来了,但要落实到代码上,还需要费一番功夫。

1.3 代码落地

1.3.1 算出可见范围

   /**
     * 获取可见的区域Rect
     * @return
     */
    private Rect getVisibleArea() {
        Rect result = new Rect(getPaddingLeft(), getPaddingTop() + mTheMoveDistance, getWidth() + getPaddingRight(), getVerticalVisibleHeight() + mTheMoveDistance);
        return result;
    }

由于可见范围随着滑动而改变,所以这里用了一个变量mTheMoveDistance,上一篇博客有关于它的详细讲解。

1.3.2 算出条目均高

    private int initStepHeight(RecyclerView.Recycler recycler) {
        View Adam = recycler.getViewForPosition(0);
        addView(Adam);
        int result = -1;
        measureChildWithMargins(Adam, 0, 0);
        result = getDecoratedMeasuredHeight(Adam);
        removeAndRecycleAllViews(recycler);
        return result;
    }

这个Adam为首个条目(意为亚当),有了这个均高,我们才能算出可见屏幕能容纳多少条item。

1.3.3 算出屏幕内的可容纳条目数

   /**
     * 初始化可见条目数,addview以及layout
     */
    private int initVisibleCounts(int stepHeight) {
        int verticalVisibleHeight = getVerticalVisibleHeight();
        return verticalVisibleHeight / stepHeight;
    }

1.3.4 初始化add和layout可见的条数

   /**
     * addView * layoutDecorated
     *
     * @param recycler
     * @param visibleCount
     */
    private void layoutChildren(RecyclerView.Recycler recycler, int visibleCount) {

        detachAndScrapAttachedViews(recycler);

        for (int i = 0; i < visibleCount; i++) {
            View viewForPositionChild = recycler.getViewForPosition(i);
            measureChildWithMargins(viewForPositionChild, 0, 0);
            addView(viewForPositionChild);
            //TODO 可以判空
            layoutDecorated(viewForPositionChild, itemRects.get(i).left, itemRects.get(i).top, itemRects.get(i).right, itemRects.get(i).bottom);
        }

    }

这方法的意思就是,在可见范围内填充应该填充条目的数量。(基于上面几个步骤运算得出的结果)。

1.3.5 其他扩展(为回收做准备)

这里我先不贴代码,等分析完回收复用的逻辑之后在后面贴出代码,这里只是表示我们需要再初始化的时候初始化一个缓存容器。

2.回收复用逻辑

2.1 回收

回收的api我们在上一篇就已经过了一遍,这里我再贴出来,方便记忆:

回收API

scrap >> detachAndScrapView()
Recycle >> removeAndRecycleView()

那么它应该写在哪呢?
首先因为我们初始化已经进行过优化,所以回收逻辑我们只需要写在滑动的方法内,也就是像verticalscrollby这种方法。并且也可以这么理解,离屏回收嘛,肯定是需要我们滑动的时候,条目离开了可见范围的时候才进行回收。

2.2.复用

  • 复用 recycler.getViewForPosition(i)
    关于这个api我简单的将它的源码调用链贴出来,有兴趣的可以去追下。
getViewForPosition
↓
tryGetViewHolderForPositionByDeadline
↓
tryBindViewHolderByDeadline$Recycler
↓
bindViewHolder$Adapter
↓
onBindViewHolder$Adapter

这里在onCreateViewHolder了少数条目之后,多数是调了onBindViewHolder,那就说明是复用了。

2.3.回收复用的整体逻辑补充

滑动的时候,判断,如果条目没有在可见范围内(可见范围是我们初始化的时候就已经计算出来的了),就进行一个回收。若在可见范围内就从从RecyclerView二级缓存内去取出条目填充到列表之中。

需要注意的是,我们填充一个条目到recyclerView的时候是需要穿参一个Rect,也就是需要知道每个条目的位置信息,而这个位置信息我们需要再初始化的时候根据条目均高来计算并放入到一个SparseArray之内,还记得刚才我初始化的代码有一个没贴代码么?【1.3.5 其他扩展(为回收做准备)】,这里我把代码贴出来。

    /**
     * 记录所有条目的位置
     * 以及totalHeight
     */
    private void initItemRectSparse(int stepItemHeight) {
        itemRects = new SparseArray<>();
        int offsetY = getPaddingTop();
        for (int i = 0; i < getItemCount(); i++) {
            Log.e("initItemRectSparse", "SparseArray item>>" + i + "__上:" + offsetY + "__下:" + (offsetY + stepItemHeight));
            itemRects.put(i, new Rect(getPaddingLeft(), offsetY, getWidth() + getPaddingRight(), offsetY + stepItemHeight));
            offsetY += stepItemHeight;
            mTotalHeight = offsetY;

        }
    }

接下来我把整个回收复用的逻辑贴出来。

2.4.代码落地

  /**
     * 处理离屏回收
     *
     * @param recycler
     * @param state
     */
    private void handleRecycle(RecyclerView.Recycler recycler, RecyclerView.State state) {


        if (cut(state)) return;

        detachAndScrapAttachedViews(recycler);

        int childCount = getChildCount();
        Log.e("handleRecycle", childCount + "");
        Rect visibleRect = getVisibleArea();

        for (int i = 0; i < getItemCount(); i++) {
            View viewForPosition = recycler.getViewForPosition(i);
            Rect rect = itemRects.get(i);
            if (Rect.intersects(visibleRect, rect)) {
                addView(viewForPosition);
                measureChildWithMargins(viewForPosition, 0, 0);
                layoutDecorated(viewForPosition, rect.left, rect.top - mTheMoveDistance, rect.right, rect.bottom - mTheMoveDistance);
            } else {
                removeAndRecycleView(viewForPosition, recycler);
                Log.e("handleRecycle","回收喽");
            }
        }
    }

调用:


    @Override
    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
        dy = handleScroll(dy, recycler, state);
        handleRecycle(recycler, state);
        return dy;
    }

3.Demo

有问题请直接指出,demo地址下载

你可能感兴趣的:(android,列表)