An overview of the

In the process of daily development, students have encountered the need for RecyclerView infinite loop, but in the official provided several LayoutManager does not support infinite loop.

The usual solution to this problem is:

Return the Integer.MAX_VALUE in adapter and let RecyclerView slide to a large enough size. 2, choose custom LayoutManager, realize the RecyclerView of the cycle.

Customizing LayoutManager can be difficult, so this article will walk you through the implementation of this custom LayoutManager, as shown in the figure below. At the same time, in the familiar with the custom LayoutManager, but also according to the need to adjust the RecyclerView display effect.

A preliminary LayoutManager

Similar to the custom ViewGroup, all the custom LayoutManager does is “add”, “measure”, “layout” of the ItemView. But LayoutManager has an extra “recycle” job compared to custom ViewGroups.

Before you can customize LayoutManager, you need to understand the apis it provides for measurement, layout, and recycling.

measure

First, the measurement method is introduced. Similar to the custom ViewGroup, the measurement is usually fixed logic and does not need to be implemented. The developer does not need to copy the measurement method, just need to call the measurement function before the layout to get the “width” of the View to be laid out.

LayoutManager provides two methods for measuring subviews:

// Measure the subview
public void measureChild(@NonNull View child, int widthUsed, int heightUsed)

// Measure the subview and take the Margin of the subview into account
public void measureChildWithMargins(@NonNull View child, int widthUsed, int heightUsed)
Copy the code

GetMeasuredWidth (); getMeasuredHeight(); So you need to use the following two apis to get the size of the measured View:

// Get the width of the child and take ItemDecoration into account
public int getDecoratedMeasuredWidth(@NonNull View child) {
    final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets;
    return child.getMeasuredWidth() + insets.left + insets.right;
}
// Get the height of the child and take ItemDecoration into account
public int getDecoratedMeasuredHeight(@NonNull View child) {
    final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets;
    return child.getMeasuredHeight() + insets.top + insets.bottom;
}
Copy the code

layout

The layout method is introduced. The same as the custom ViewGroup, the LayoutManager completes the measurement of the ItemView. In the LayoutManager, not by direct call ItemView layout function of the layout of the View, but instead USES layoutDecorated and layoutDecoratedWithMargins, the difference between the two is the latter considers the Margins:

public void layoutDecorated(@NonNull View child, int left, int top, int right, int bottom) {
    final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets;
    child.layout(left + insets.left, top + insets.top, right - insets.right,
                bottom - insets.bottom);
}

public void layoutDecoratedWithMargins(@NonNull View child, int left, int top, int right,
                int bottom) {
    final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    final Rect insets = lp.mDecorInsets;
    child.layout(left + insets.left + lp.leftMargin, top + insets.top + lp.topMargin,
            right - insets.right - lp.rightMargin,
            bottom - insets.bottom - lp.bottomMargin);
}
Copy the code

recycle

Recycling is the soul of RecyclerView and the difference between RecyclerView and ordinary ViewGroup. As we all know, there are four types of caches in RecyclerView, each of which has its own use in the layout process:

1, AttachedScrap: store visible ViewHolder that does not need to be rebound 2, CachedViews: Store invisible ViewHoler that does not need to be rebound 3, ViewCacheExtension: 4. RecyclerPool: Stores ViewHolder that is not visible and needs to be rebound

Multiple collection methods are provided in LayoutManager:

// Reclaim the specified View directly to ecyclerPool
public void removeAndRecycleView(@NonNull View child, @NonNull Recycler recycler) {
    removeView(child);
    recycler.recycleView(child);
}
// Append the View directly to ecyclerPool
public void removeAndRecycleViewAt(int index, @NonNull Recycler recycler) {
    final View view = getChildAt(index);
    removeViewAt(index);
    recycler.recycleView(view);
}
Copy the code

LayoutManager create

1, implement abstraction method, and letRecyclerViewTransversely sliding

public class RepeatLayoutManager extends RecyclerView.LayoutManager {
    @Override
    public RecyclerView.LayoutParams generateDefaultLayoutParams(a) {
        return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT);
    }

    @Override
    public boolean canScrollHorizontally(a) {
        return true; }}Copy the code

2. Define the initial layout

Itemviews are added, measured, and laid out in the onLayoutChildren(RecyclerView. Recyclerview.state State) method.

The specific steps are as follows:

  • userecycler.getViewForPosition(int pos)Get the subview from the cache
  • When there is extra space in the layable area, passaddView(View view)The subview will be added by theRecyclerViewTo add a child View, and measure and layout the child View until the child View exceedsRecyclerViewOf the layout width.
    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        if (getItemCount() <= 0) {
            return;
        }
        if (state.isPreLayout()) {
            return;
        }
        // Separate all items into Scrap
        detachAndScrapAttachedViews(recycler);
        int itemLeft = getPaddingLeft();
        for (int i = 0; ; i++) {
            if (itemLeft >= getWidth() - getPaddingRight()) {
                break;
            }
            View itemView = recycler.getViewForPosition(i % getItemCount());
            // Add a child View
            addView(itemView);
            // Measure the subview
            measureChildWithMargins(itemView, 0.0);

            int right = itemLeft + getDecoratedMeasuredWidth(itemView);
            int top = getPaddingTop();
            int bottom = top + getDecoratedMeasuredHeight(itemView) - getPaddingBottom();
            // Layout the child ViewlayoutDecorated(itemView, itemLeft, top, right, bottom); itemLeft = right; }}Copy the code

3. Slide and fill

OffsetChildrenHorizontal (int X) is used for whole left/right movement of subviews in RecyclerView. In order to have the effect of moving subviews when sliding RecyclerView, you need to copy the scrollHorizontallyBy function and call offsetChildrenHorizontal(int x) in it.

When the child View is moved left after the left slide,RecyclerViewThe visible unfilled area will appear on the right ofRecyclerViewNew subviews are added and laid out on the right until there are no visible unfilled areas.Similarly, the left unfilled area needs to be filled after the right swipe.

The specific code is as follows:

    @Override
    public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
        fill(recycler, dx > 0); OffsetChildrenHorizontal (dx);return dx;
    }
    
    /** * When sliding, fill the visible unfilled area */
    private void fill(RecyclerView.Recycler recycler, boolean fillEnd) {
        if (getChildCount() == 0) return;
        if (fillEnd) {
            // Fill the tail
            View anchorView = getChildAt(getChildCount() - 1);
            int anchorPosition = getPosition(anchorView);
            for (; anchorView.getRight() < getWidth() - getPaddingRight(); ) {
                int position = (anchorPosition + 1) % getItemCount();
                if (position < 0) position += getItemCount();

                View scrapItem = recycler.getViewForPosition(position);
                addView(scrapItem);
                measureChildWithMargins(scrapItem, 0.0);
                
                int left = anchorView.getRight();
                int top = getPaddingTop();
                int right = left + getDecoratedMeasuredWidth(scrapItem);
                intbottom = top + getDecoratedMeasuredHeight(scrapItem) - getPaddingBottom(); layoutDecorated(scrapItem, left, top, right, bottom); anchorView = scrapItem; }}else {
            // Fill the header
            View anchorView = getChildAt(0);
            int anchorPosition = getPosition(anchorView);
            for (; anchorView.getLeft() > getPaddingLeft(); ) {
                int position = (anchorPosition - 1) % getItemCount();
                if (position < 0) position += getItemCount();

                View scrapItem = recycler.getViewForPosition(position);
                addView(scrapItem, 0);
                measureChildWithMargins(scrapItem, 0.0);
                int right = anchorView.getLeft();
                int top = getPaddingTop();
                int left = right - getDecoratedMeasuredWidth(scrapItem);
                intbottom = top + getDecoratedMeasuredHeight(scrapItem) - getPaddingBottom(); layoutDecorated(scrapItem, left, top, right, bottom); anchorView = scrapItem; }}return;
    }
Copy the code

recycling

As mentioned earlier, when the RecyclerView is sliding, the visible unfilled area needs to be filled. However, padding does not recycle the Item, which is not much different from a normal ViewGroup.

In RecyclerView, need to slide, fill the visible area at the same time, the invisible area of the sub-view for recycling, so as to reflect the advantages of RecyclerView.

The direction of recycling is the opposite of the direction of filling. So how exactly does the recycling code work? The code is as follows:

    @Override
    public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
        fill(recycler, dx > 0);
        offsetChildrenHorizontal(-dx);
        recyclerChildView(dx > 0, recycler);
        return dx;
    }
    
    /** * Recycle invisible child View */
    private void recyclerChildView(boolean fillEnd, RecyclerView.Recycler recycler) {
        if (fillEnd) {
            // Retrieve the header
            for (int i = 0; ; i++) {
                View view = getChildAt(i);
                booleanneedRecycler = view ! =null && view.getRight() < getPaddingLeft();
                if (needRecycler) {
                    removeAndRecycleView(view, recycler);
                } else {
                    return; }}}else {
            // Retrieve the tail
            for (int i = getChildCount() - 1; ; i--) {
                View view = getChildAt(i);
                booleanneedRecycler = view ! =null && view.getLeft() > getWidth() - getPaddingRight();
                if (needRecycler) {
                    removeAndRecycleView(view, recycler);
                } else {
                    return; }}}}Copy the code

use

  • Add the dependent
 implementation 'cn. Student0. Manager: repeatmanager: 1.0.3'
Copy the code
  • Use in code
  RecyclerView recyclerView = findViewById(R.id.rv_demo);
  recyclerView.setAdapter(new DemoAdapter());
  recyclerView.setLayoutManager(new RepeatLayoutManager
Copy the code

conclusion

At this point, the implementation of the LayoutManager for infinite loops is complete. The insufficiency of the article also please point out, thank you.

Github: github.com/jiarWang/Re… Welcome to Star.

reference

Android Custom LayoutManager Style 11: Dragon in the sky

【Android】 Learn how to customize LayoutManager(1