I open source a convenient RecyclerView top top Android library, welcome you to visit github.com/lizijin/Sti… , if you use this library, please give your valuable comments.

It currently supports the following features:

  1. Support single type top suction function
  2. Support multiple types of top suction function
  3. Enable or disable the top suction function
  4. Support the function of suction top in specified position
  5. Supports setting top offset
  6. Support custom RecyclerView Item top boundary custom
  7. It works seamlessly with AppBarLayout

When it comes to RecyclerView, reuse is one of the things we can talk about. The built-in ViewHolder avoids the hassle of manually creating a ViewHolder when using a ListView. Can we be sure when to recycle and reuse views? When we slide a RecyclerView, do we recycle the View and reuse the View? Or do you reuse the View first and then recycle the View? The answer is both. For details, see the following analysis:

Noun explanation

1. Recycling: The View does not need to be displayed on the screen any more and is recycled to the recycling pool

2. Reuse: Reuse in this article refers to the call to the onCreateViewHolder or onBindViewHolder methods

This article outline

1. Two scenarios of sliding RV

2. Verify the answer by DEMO

3. Explain the sliding principle

4. Source code analysis

5. Ask questions and interact

1. Two scenarios of sliding RV

1.1 a scene

Each Item in the RV is 100px high, and the last Item is 50px off the screen. The initial state of RV is shown below

Q1 assumes an upward slide of 40px

Have views been recycled or reused? If so, reuse or recycle first?


A: Neither recycling nor reuse has occurred \color{red}{return and reuse did not occur}

Q2 is assumed to slide up 60px

Have views been recycled or reused? If so, reuse or recycle first?


A: No recycling occurred. Reuse occurred \color{red}{red}

Q3 assumes an upward swipe of 120px

Have views been recycled or reused? If so, reuse or recycle first?


A: Recycling and reuse occurred. Reuse before recycling \color{red}{reclaim and reuse occurred. Reuse before recycle}

1.2 scenario 2

The first Item in the RV is 50px tall, the others are 100px, and the last Item is 95px off the screen. The initial state of the RV is as follows

Q1 assumes an upward slide of 40px

Have views been recycled or reused? If so, reuse or recycle first?


A: Neither recycling nor reuse has occurred \color{red}{return and reuse did not occur}

Q2 is assumed to slide up 60px

Have views been recycled or reused? If so, reuse or recycle first?


A: Recycling occurred, not reuse \color{red}{return;

Q3 assumes an upward swipe of 120px

Have views been recycled or reused? If so, reuse or recycle first?


A: Recycling and reuse occurred. Recycle before reuse \color{red}{reclaim and reuse occurred. Recycle before reuse}

You can see from the answer. There is no fixed answer to recycling and reuse. It varies from scene to scene. Here’s an example to verify the answer.

2. Verify the answer by DEMO

2.1 Let’s verify scenario 1

Program running diagram



The program code

class RecyclerViewActivity1 : AppCompatActivity() {
    private lateinit var mRecyclerView: RecyclerView
    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_recycler_view1)
        mRecyclerView = findViewById(R.id.recyclerview)
        mRecyclerView.setHasFixedSize(true)
        mRecyclerView.setItemViewCacheSize(0)
        mRecyclerView.layoutManager =
            LinearLayoutManager(this).apply {
                orientation = LinearLayoutManager.VERTICAL
                isItemPrefetchEnabled = false
            }
        val list: MutableList<String> =
            ArrayList()
        repeat(100) {
            list.add("item $it")
        }
        mRecyclerView.adapter = MyAdapter(list)
    }

    inner class MyAdapter(val mStrings: MutableList<String>) :
        RecyclerView.Adapter<RecyclerView.ViewHolder>() {
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
            println(RecyclerView scene onCreateViewHolder)
            val view = LayoutInflater.from(parent.context)
                .inflate(R.layout.view_item, parent, false)
            return object : RecyclerView.ViewHolder(view) {}
        }

        override fun getItemCount(a): Int {
            return mStrings.size
        }

        override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
            println(RecyclerView scene one onBindViewHolder$position ")
            val textView = holder.itemView as TextView
            textView.layoutParams.height = (resources.displayMetrics.density * 100).toInt()
            textView.text = mStrings[position]
        }

        override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
            println("Recycle occurs in RecyclerView scene one" + (holder.itemView as TextView).text)

            super.onViewRecycled(holder)
        }

    }

    fun scroll120(view: View) {
        mRecyclerView.scrollBy(0, (resources.displayMetrics.density * 120).toInt())
    }

    fun scroll60(view: View) {
        mRecyclerView.scrollBy(0, (resources.displayMetrics.density * 60).toInt())

    }

    fun scroll40(view: View) {
        mRecyclerView.scrollBy(0, (resources.displayMetrics.density * 40).toInt())

    }
}
Copy the code

The log output is as follows

Let’s go to the initial state

RecyclerView Scenario 1 onCreateViewHolder

RecyclerView Scenario 1 onBindViewHolder 0

RecyclerView Scenario 1 onCreateViewHolder

RecyclerView Scenario 1 onBindViewHolder 1

RecyclerView Scenario 1 onCreateViewHolder

RecyclerView Scenario 1 onBindViewHolder 2

RecyclerView Scenario 1 onCreateViewHolder

RecyclerView Scenario 1 onBindViewHolder 3

RecyclerView Scenario 1 onCreateViewHolder

RecyclerView Scenario 1 onBindViewHolder 4

RecyclerView Scenario 1 onCreateViewHolder

RecyclerView Scenario 1 onBindViewHolder 5

Click and slide up 40px. The printed logs remain unchanged. Prove that neither recycling nor reuse has occurred

Click and slide up 60px. The following logs are displayed: Prove that no recycling has occurred and that reuse has occurred

RecyclerView Scenario 1 onCreateViewHolder

RecyclerView Scenario 1 onBindViewHolder 0

RecyclerView Scenario 1 onCreateViewHolder

RecyclerView Scenario 1 onBindViewHolder 1

RecyclerView Scenario 1 onCreateViewHolder

RecyclerView Scenario 1 onBindViewHolder 2

RecyclerView Scenario 1 onCreateViewHolder

RecyclerView Scenario 1 onBindViewHolder 3

RecyclerView Scenario 1 onCreateViewHolder

RecyclerView Scenario 1 onBindViewHolder 4

RecyclerView Scenario 1 onCreateViewHolder

RecyclerView Scenario 1 onBindViewHolder 5

RecyclerView onCreateViewHolder // Only RecyclerView onBindViewHolder 6 occurred

Click and swipe up 120px. The following logs are displayed: Demonstrate that recycling and reuse have occurred. Reuse before recycling

RecyclerView Scenario 1 onCreateViewHolder

RecyclerView Scenario 1 onBindViewHolder 0

RecyclerView Scenario 1 onCreateViewHolder

RecyclerView Scenario 1 onBindViewHolder 1

RecyclerView Scenario 1 onCreateViewHolder

RecyclerView Scenario 1 onBindViewHolder 2

RecyclerView Scenario 1 onCreateViewHolder

RecyclerView Scenario 1 onBindViewHolder 3

RecyclerView Scenario 1 onCreateViewHolder

RecyclerView Scenario 1 onBindViewHolder 4

RecyclerView Scenario 1 onCreateViewHolder

RecyclerView Scenario 1 onBindViewHolder 5

RecyclerView Scenario 1 onCreateViewHolder // Reuse

RecyclerView Scenario 1 onBindViewHolder 6

RecyclerView Scenario 1 Recycle item 0 // Recycle item

2.2 Let’s verify scenario 2

Program running diagram

The program code

class RecyclerViewActivity2 : AppCompatActivity() {
    private lateinit var mRecyclerView: RecyclerView
    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_recycler_view2)
        mRecyclerView = findViewById(R.id.recyclerview)
        mRecyclerView.setHasFixedSize(true)
        mRecyclerView.setItemViewCacheSize(0)
        mRecyclerView.layoutManager =
            LinearLayoutManager(this).apply {
                orientation = LinearLayoutManager.VERTICAL
                isItemPrefetchEnabled = false
            }
        val list: MutableList<String> =
            ArrayList()
        repeat(100) {
            list.add("item $it")
        }
        mRecyclerView.adapter = MyAdapter(list)
    }

    inner class MyAdapter(val mStrings: MutableList<String>) :
        RecyclerView.Adapter<RecyclerView.ViewHolder>() {
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
            println(RecyclerView scenario 2 onCreateViewHolder)
            val view = LayoutInflater.from(parent.context)
                .inflate(R.layout.view_item, parent, false)
            return object : RecyclerView.ViewHolder(view) {}
        }

        override fun getItemCount(a): Int {
            return mStrings.size
        }

        override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
            println(RecyclerView scene two onBindViewHolder$position ")
            val textView = holder.itemView as TextView
            textView.layoutParams.height =
                if (position == 0) (resources.displayMetrics.density * 50).toInt() else (resources.displayMetrics.density * 100).toInt()
            textView.text = mStrings[position]
        }

        override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
            println("Recycle occurs in RecyclerView scenario 2" + (holder.itemView as TextView).text)
            super.onViewRecycled(holder)
        }

    }

    fun scroll120(view: View) {
        mRecyclerView.scrollBy(0, (resources.displayMetrics.density * 120).toInt())
    }

    fun scroll60(view: View) {
        mRecyclerView.scrollBy(0, (resources.displayMetrics.density * 60).toInt())

    }

    fun scroll40(view: View) {
        mRecyclerView.scrollBy(0, (resources.displayMetrics.density * 40).toInt())

    }
}
Copy the code

Log output enters the initial state first

RecyclerView Scenario 2 onCreateViewHolder

RecyclerView Scenario 2 onBindViewHolder 0

RecyclerView Scenario 2 onCreateViewHolder

RecyclerView Scenario 2 onBindViewHolder 1

RecyclerView Scenario 2 onCreateViewHolder

RecyclerView Scenario 2 onBindViewHolder 2

RecyclerView Scenario 2 onCreateViewHolder

RecyclerView Scenario 2 onBindViewHolder 3

RecyclerView Scenario 2 onCreateViewHolder

RecyclerView Scenario 2 onBindViewHolder 4

RecyclerView Scenario 2 onCreateViewHolder

RecyclerView Scenario 2 onBindViewHolder 5

RecyclerView Scenario 2 onCreateViewHolder

RecyclerView Scenario 2 onBindViewHolder 6

Click and slide up 40px. The printed logs remain unchanged. Prove that neither recycling nor reuse has occurred

Click and slide up 60px. The following logs are displayed: Prove that recycling has occurred and no reuse has occurred

RecyclerView Scenario 2 onCreateViewHolder

RecyclerView Scenario 2 onBindViewHolder 0

RecyclerView Scenario 2 onCreateViewHolder

RecyclerView Scenario 2 onBindViewHolder 1

RecyclerView Scenario 2 onCreateViewHolder

RecyclerView Scenario 2 onBindViewHolder 2

RecyclerView Scenario 2 onCreateViewHolder

RecyclerView Scenario 2 onBindViewHolder 3

RecyclerView Scenario 2 onCreateViewHolder

RecyclerView Scenario 2 onBindViewHolder 4

RecyclerView Scenario 2 onCreateViewHolder

RecyclerView Scenario 2 onBindViewHolder 5

RecyclerView Scenario 2 onCreateViewHolder

RecyclerView Scenario 2 onBindViewHolder 6

RecyclerView Scenario 2 Recycle Item 0 // Recycle only

Click and swipe up 120px. The following logs are displayed: Demonstrate that recycling and reuse have occurred. Recycle before reuse

RecyclerView Scenario 2 onCreateViewHolder

RecyclerView Scenario 2 onBindViewHolder 0

RecyclerView Scenario 2 onCreateViewHolder

RecyclerView Scenario 2 onBindViewHolder 1

RecyclerView Scenario 2 onCreateViewHolder

RecyclerView Scenario 2 onBindViewHolder 2

RecyclerView Scenario 2 onCreateViewHolder

RecyclerView Scenario 2 onBindViewHolder 3

RecyclerView Scenario 2 onCreateViewHolder

RecyclerView Scenario 2 onBindViewHolder 4

RecyclerView Scenario 2 onCreateViewHolder

RecyclerView Scenario 2 onBindViewHolder 5

RecyclerView Scenario 2 onCreateViewHolder

RecyclerView Scenario 2 onBindViewHolder 6

RecyclerView Scenario 2 Recycle item 0 // Recycle item first

RecyclerView Scenario 2 onBindViewHolder 7 // Reuse

3. Sliding principle analysis

As shown, introduce some parameters about coordinates

  1. Delta: finger slide distance 120px.

  2. MOffset: RV The Bottom of the last subview is 600px Y in the screen coordinate system. The next View(Item7) of the RV is laid out from the mOffset.

  3. MScrollingOffset: RV The distance between the Bottom of the last subview and the RV Bottom is 50px. Slide up not to exceed this distance. If it does, create a new View fill.

  4. MVailable: delta – mScrollingOffset. You can fill the View space. If it is greater than 0, there is room to fill the new View

  5. If delta<mScrollingOffset, mScrollingOffset=delta, mVailable<0

The sliding logic is as follows

  1. RecyclerView from the 0 View start traversal, until the View Bottom>mScrollingOffset, and record the View index, recycle [0,index) interval View,index is open interval, If index>=1, the View in the range [0,index) will be removed from the screen and put into the recycling pool according to the recycling algorithm. For the specific recycling algorithm, press no table first.

  2. If mVailable>0, fill it with a new View from mOffset. MOffset += New View height, mVailable-= New View height, mScrollingOffset+= New View height, if mVailable<0, mScrollingOffset+=mVailable. After the layout is complete, recycle the View as required using the algorithm of Step 1.

  3. Repeat Step 2

  4. Move the RV as a whole up the delta or consumed distance (generally delta distance, but the specific consumed distance when there is no Item under RecyclerView)

Based on this slide logic, we analyzed the slide up 120px in scene 1

mOffset = 600px

mScrollingOffset = 50px

mAvailable = 70px

Item1 height 100 px

  1. Start by traversing Bottom>50px from the 0th View. Find Item1. bottom=100px and record index=0. Because the index < 1. So no collection takes place

  2. MAvailable >0, add View Item7 height 100px, mOffset=700px, mAvailable=-30 from the bottom of Item6 MScrollingOffset = mScrollingOffset + 100-30 = 120 px. Then check for recycling. Start by traversing the Bottom>120px from the 0th View. Find Item2. bottom=200px and record index=1. Recycle View in range [0,1), i.e. recycle Item1

  3. MAvailable =-30<0, exit the filling logic

  4. Overall move up 120px

We saw that we created Item7 and then reclaimed Item1. It matches the log

RecyclerView onCreateViewHolder // Reuse RecyclerView onBindViewHolder 6

RecyclerView Scenario 1 Recycle item 0 // Recycle item

The same logic can be used to analyze the slide up 120px in scenario 2. In scenario 2, recycling occurs before reuse. Readers can find out for themselves.

4. Source code analysis

The RV slide ends up calling the scrollBy method of LayoutManager. We use a linear Layer outManager.

//LinearLayoutManager.java
int scrollBy(int delta, RecyclerView.Recycler recycler, RecyclerView.State state) {
        if (getChildCount() == 0 || delta == 0) {
            return 0;
        }
        ensureLayoutState();
        mLayoutState.mRecycle = true;
        final int layoutDirection = delta > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
        final intabsDelta = Math.abs(delta); # # code1The updateLayoutState method is used to calculate parameters such as mOffset. updateLayoutState(layoutDirection, absDelta,true, state); # # code2The fill method fills the View according to the remaining spacefinal int consumed = mLayoutState.mScrollingOffset
                + 
                fill(recycler, mLayoutState, state, false);
        if (consumed < 0) {
            if (DEBUG) {
                Log.d(TAG, "Don't have any more elements to scroll");
            }
            return 0;
        }
        final intscrolled = absDelta > consumed ? layoutDirection * consumed : delta; # # code3OffsetChildren, whole mobile RV child View mOrientationHelper. OffsetChildren (- scrolled);if (DEBUG) {
            Log.d(TAG, "scroll req: " + delta + " scrolled: " + scrolled);
        }
        mLayoutState.mLastScrollDelta = scrolled;
        return scrolled;
    }
Copy the code

## code 1 updateLayoutState (mOffset)

Fill method, fill the View according to the remaining space

## Code 3 offsetChildren, moving the RV subview as a whole

// Mostly calculations
private void updateLayoutState(int layoutDirection, int requiredSpace,
            boolean canUseExistingSpace, RecyclerView.State state) {
        // If parent provides a hint, don't measure unlimited.
        mLayoutState.mInfinite = resolveIsInfinite();
        mLayoutState.mLayoutDirection = layoutDirection;
        mReusableIntPair[0] = 0;
        mReusableIntPair[1] = 0;
        calculateExtraLayoutSpace(state, mReusableIntPair);
        int extraForStart = Math.max(0, mReusableIntPair[0]);
        int extraForEnd = Math.max(0, mReusableIntPair[1]);
        boolean layoutToEnd = layoutDirection == LayoutState.LAYOUT_END;
        mLayoutState.mExtraFillSpace = layoutToEnd ? extraForEnd : extraForStart;
        mLayoutState.mNoRecycleSpace = layoutToEnd ? extraForStart : extraForEnd;
        int scrollingOffset;
        if (layoutToEnd) {
            mLayoutState.mExtraFillSpace += mOrientationHelper.getEndPadding();
            // get the first child in the direction we are going
            final View child = getChildClosestToEnd();
            // the direction in which we are traversing children
            mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
                    : LayoutState.ITEM_DIRECTION_TAIL;
            mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection;
            mLayoutState.mOffset = mOrientationHelper.getDecoratedEnd(child);
            // calculate how much we can scroll without adding new children (independent of layout)
            scrollingOffset = mOrientationHelper.getDecoratedEnd(child)
                    - mOrientationHelper.getEndAfterPadding();

        } else {
            final View child = getChildClosestToStart();
            mLayoutState.mExtraFillSpace += mOrientationHelper.getStartAfterPadding();
            mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL
                    : LayoutState.ITEM_DIRECTION_HEAD;
            mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection;
            mLayoutState.mOffset = mOrientationHelper.getDecoratedStart(child);
            scrollingOffset = -mOrientationHelper.getDecoratedStart(child)
                    + mOrientationHelper.getStartAfterPadding();
        }
        mLayoutState.mAvailable = requiredSpace;
        if (canUseExistingSpace) {
            mLayoutState.mAvailable -= scrollingOffset;
        }
        mLayoutState.mScrollingOffset = scrollingOffset;
    }
Copy the code
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
            RecyclerView.State state, boolean stopOnFocusable) {
        // max offset we should set is mFastScroll + available
        final int start = layoutState.mAvailable;
        if(layoutState.mScrollingOffset ! = LayoutState.SCROLLING_OFFSET_NaN) {// TODO ugly bug fix. should not happen
            if (layoutState.mAvailable < 0) { layoutState.mScrollingOffset += layoutState.mAvailable; } # # code1First determine whether you need to recycleView
            recycleByLayoutState(recycler, layoutState);
        }
        intremainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace; LayoutChunkResult layoutChunkResult = mLayoutChunkResult; # # code2Determine whether to fill the remaining spaceView
        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
            layoutChunkResult.resetInternal();
            if (RecyclerView.VERBOSE_TRACING) {
                TraceCompat.beginSection("LLM LayoutChunk"); } # # code3Is the specific layout method layoutChunk(Recycler, State, layoutState, layoutChunkResult);if (RecyclerView.VERBOSE_TRACING) {
                TraceCompat.endSection();
            }
            if (layoutChunkResult.mFinished) {
                break;
            }
            layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
            /** * Consume the available space if: * * layoutChunk did not request to be ignored * * OR we are laying out scrap children * * OR we are not doing pre-layout * /
            if(! layoutChunkResult.mIgnoreConsumed || layoutState.mScrapList ! =null| |! state.isPreLayout()) { layoutState.mAvailable -= layoutChunkResult.mConsumed;// we keep a separate remaining space because mAvailable is important for recycling
                remainingSpace -= layoutChunkResult.mConsumed;
            }

            if(layoutState.mScrollingOffset ! = LayoutState.SCROLLING_OFFSET_NaN) { layoutState.mScrollingOffset += layoutChunkResult.mConsumed;if (layoutState.mAvailable < 0) { layoutState.mScrollingOffset += layoutState.mAvailable; } # # code4Check whether recycling is required after the layout is completedView
                recycleByLayoutState(recycler, layoutState);
            }
            if (stopOnFocusable && layoutChunkResult.mFocusable) {
                break; }}if (DEBUG) {
            validateChildOrder();
        }
        return start - layoutState.mAvailable;
    }
Copy the code

## Code 1, first determine whether the View needs to be recycled

## Code 2, based on the remaining space, determine whether the View needs to be filled

## Code 3 is the specific layout method

## code 4 is to determine whether the View needs to be recycled after the layout is completed

This article focuses on the logic of recycling and reuse when sliding. How to recycle and reuse. RecyclerView three level cache is how to achieve. Listen to the breakdown next time.

5. Ask questions and interact

Finally, to reinforce your understanding, ask a question, please write your answer in the comments section.

In case3 of scenario 1, swipe up 120px and 120px is 100px higher than the height of the first Item. Why not recycle Item1 first?

Question 2: What might be the reason for Google’s design?

If you’re afraid you won’t find this post for a long time, please like it and it will appear in your likes list.