background

In the project to realize the horizontal list of infinite cycle rolling, naturally thought of RecyclerView, but we commonly used RecyclerView is not support infinite cycle rolling, so we need some ways to make it infinite cycle.

Scheme selection

Solution 1 Modify the Adapter

Most blogs on the web have solutions like this, modifying Adapter. Specific as follows

First, let the Adapter getItemCount() method return integer.max_value, making the position data very, very large;

Second, mod the position argument in onBindViewHolder(), retrieve the actual data index of position, and bind the data to the itemView

Finally, in the initialization of RecyclerView, let it slide to the specified position, such as integer. MAX_VALUE/2, so it will not slide to the boundary, if the user is a single-minded, really slide to the boundary position, plus a judgment, if the current index is 0, then dynamically adjust to the initial position

The plan is simple, but it’s not perfect. First, we have calculated our data and index. Second, if we slide to the boundary and then dynamically adjust to the middle, there will be an insignificant stuck operation, which makes the slide not very smooth. So, let’s go to plan two.

Scheme 2 user-defined LayoutManager, modify the RecyclerView layout

This is a one-size-fits-all solution, and it’s the one I’m going to talk about in detail today. As we all know, the data binding of RecyclerView is handled by Adapter, while the typesetting method and View recycling control are realized by LayoutManager. Therefore, we can directly modify the typesetting method of itemView to achieve our goal. Make RecyclerView infinite cycle.

Custom LayoutManager

1. Create a custom LayoutManager

. First of all, the custom LooperLayoutManager inherited from RecyclerView LayoutManager, then need to implement the abstract methods generateDefaultLayoutParams (), This method sets the default LayoutParams for itemView and returns the following.

public class LooperLayoutManager extends RecyclerView.LayoutManager {
        @Override
    public RecyclerView.LayoutParams generateDefaultLayoutParams() {
        returnnew RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); }}Copy the code
2. Turn on the scroll switch

Next, the rolling direction is processed and the canScrollHorizontally switch is turned on by rewriting the canScrollHorizontally() method. Note that we’re doing horizontal infinite loop scrolling, so I’m implementing this method, but if I’m dealing with vertical scrolling, I’m going to implement the canScrollVertically() method.

    @Override
    public boolean canScrollHorizontally() {
        return true;
    }
Copy the code
3. Initialize the RecyclerView layout

Ok, so that’s the basics. Next, rewrite the onLayoutChildren() method to initialize the layout of itemView.

    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        if (getItemCount() <= 0) {
            return; } // Annotate 1. If the current ready state, directly returnif (state.isPreLayout()) {
            return; } / / mark 2. Separate the view into the scrap in the cache, to prepare for back to typesetting detachAndScrapAttachedViews view (recycler); int autualWidth = 0;for(int i = 0; i < getItemCount(); I++) {/ / mark 3. Initialization, fill of the view in the screen view itemView = recycler. GetViewForPosition (I); addView(itemView); MeasureChildWithMargins (itemView, 0, 0); int width = getDecoratedMeasuredWidth(itemView); int height = getDecoratedMeasuredHeight(itemView); 5. Arrange layoutDecorated(itemView, autualWidth, 0, autualWidth + Width, height) according to the width and height of itemView; autualWidth += width; 6. If the total width of the current layout itemView is greater than the width of RecyclerView, the layout will not be usedif (autualWidth > getWidth()) {
                break; }}}Copy the code

The onLayoutChildren() method, as its name implies, lays out all itemViews and is called when the Adapter’s notifyDataSetChanged() method is initialized and called. The code thread is clearly commented out, and there are a few methods that need to be mentioned briefly:

With two detachAndScrapAttachedViews (recycler) method will all itemView all detach from the tree of the View, and then into the scrap in the cache. For those of you who have RecyclerView, RecyclerView has a tier 2 cache, tier 1 cache is scrap cache, tier 2 cache is recycler cache, where views detach from the View tree go into scrap cache, Calls to removeView() remove views that can be put into the recycler cache.

Note 3 recycler. GetViewForPosition (I) method will get the corresponding itemView of index from the cache and this method will first itemView from scrap cache, if not from the recycler cache, If not, call the Adapter onCreateViewHolder() to create the itemView.

The layoutDecorated() method is used to arrange the layout of the itemView. As you can see here, we arrange it to the right of the parent according to its width until the vertex of the next itemView exceeds the width of the RecyclerView.

4. Recycle RecyclerView and recycle itemView

After the RecyclerView sub-item typeset layout, run the effect will appear, but at this time we will find that the slide list after sliding into blank, so it is time to handle the slide operation.

As mentioned earlier, we turned on horizontal scrolling, so we’ll override the scrollHorizontallyBy() method for horizontal scrolling.

@override public int scrollHorizontallyBy(int dx, recyclerView.recycler Recycler, recyclerview.state State) {// recycle 1. When scrolling, fill the left and right itemView int Travl = Fill (dx, recycler, state);if (travl == 0) {
            return0; } //2. Slide offsetChildrenHorizontal(-travl); //3. Recycle recyclhideView (dx, recycler, state);return travl;
    }
Copy the code

As you can see, the sliding logic is simple, summed up in three steps:

  • As you swipe sideways, fill the Left and right itemView in order
  • Sliding itemView
  • Reclaim an itemView that is no longer visible

Step by step: First, when sliding, call the custom fill() method to fill the left and right sides. Not forgetting, we are going to implement the looping slide, so this step is especially important, first look at the code:

/** * private int fill(int dx, RecyclerView. RecyclerView.State State) {/** * private int fill(int dx, RecyclerView. RecyclerView.if(dx > 0) {lastView = getChildAt() -1);if (lastView == null) {
                return0; } int lastPos = getPosition(lastView); 2. The last visible itemView is completely slid in and needs to be updatedif(lastView.getRight() < getWidth()) { View scrap = null; 3. Determine the index of the last visible itemView, if it is the last, set the next itemView to the first, otherwise set the next itemView to the current indexif (lastPos == getItemCount() - 1) {
                    if (looperEnable) {
                        scrap = recycler.getViewForPosition(0);
                    } else{ dx = 0; }}else {
                    scrap = recycler.getViewForPosition(lastPos + 1);
                }
                if (scrap == null) {
                    returndx; } // add new ItemViewin and measure and layout addView(scrap); measureChildWithMargins(scrap, 0, 0); int width = getDecoratedMeasuredWidth(scrap); int height = getDecoratedMeasuredHeight(scrap); layoutDecorated(scrap,lastView.getRight(), 0, lastView.getRight() + width, height);returndx; }}elseFirstView = getChildAt(0);if (firstView == null) {
                return 0;
            }
            int firstPos = getPosition(firstView);

            if (firstView.getLeft() >= 0) {
                View scrap = null;
                if (firstPos == 0) {
                    if (looperEnable) {
                        scrap = recycler.getViewForPosition(getItemCount() - 1);
                    } else{ dx = 0; }}else {
                    scrap = recycler.getViewForPosition(firstPos - 1);
                }
                if (scrap == null) {
                    return0; } addView(scrap, 0); MeasureChildWithMargins (scrap, 0, 0); int width = getDecoratedMeasuredWidth(scrap); int height = getDecoratedMeasuredHeight(scrap); layoutDecorated(scrap, firstView.getLeft() - width, 0, firstView.getLeft(), height); }}return dx;
    }
Copy the code

The code is a bit long, but the logic is clear. Dx is the distance to slide. If dx > 0, it slides to the left. Then you need to determine the boundary on the right. If the index is the last visible itemView, then the newly populated itemView will be the 0th one. This will enable an infinite loop when sliding to the left. Then measure the layout of the itemView that needs to be filled, and fill it in.

Similarly, the logic of swiping to the right is similar to that of swiping to the left.

The second step: After the new itemView is filled, the slide is performed by calling LayoutManager’s offsetChildrenHorizontal() method to slide the travl distance. The travl is calculated by fill and is usually dx. 0 only if you slide to the last itemView and the loop scroll switch is not turned on, i.e. no scrolling.

//2. Scroll offsetChildrenHorizontal(travl * -1);Copy the code

Step 3: Reclaim the itemView that is no longer visible. Only the invisible itemView can be reclaimed to prevent memory explosion.

Private void recyclerHideView(int dx, recyclerview.recycler Recycler, recyclerview.state State) {for (int i = 0; i < getChildCount(); i++) {
            View view = getChildAt(i);
            if (view == null) {
                continue;
            }
            if1. Scroll left to remove the left view that is not in the contentif (view.getRight() < 0) {
                    removeAndRecycleView(view, recycler);
                    Log.d(TAG, "Loop: Remove a view childCount="+ getChildCount()); }}else{// annotation 2. Scroll right to remove the view on the right that is not in the contentif (view.getLeft() > getWidth()) {
                    removeAndRecycleView(view, recycler);
                    Log.d(TAG, "Loop: Remove a view childCount="+ getChildCount()); }}}}Copy the code

The code is also very simple, iterate over all items added into RecyclerView, and then judge according to the vertex position of itemView, remove invisible items. RemoveAndRecycleView (View, Recycleer) can recycle items that are removed and then stored in the RecyclerView cache.

At this point, a LayoutManager can be achieved left or right infinite loop is realized, call the way with usually we use RrcyclerView there is no difference, just need to give RecyclerView set LayoutManager to specify our LayoutManager, As follows:

        recyclerView.setAdapter(new MyAdapter());
        LooperLayoutManager layoutManager = new LooperLayoutManager();
        layoutManager.setLooperEnable(true);
        recyclerView.setLayoutManager(layoutManager);
Copy the code

To access the source code please click me

Ok, Dragon Boat Festival holiday post, not easy. Thumbs up for anything helpful!