background
In order to implement infinite loop scrolling of horizontal lists in the project, we naturally thought of RecyclerView, but the RecyclerView we commonly use does not support infinite loop scrolling, so we need some methods to make it loop infinitely.
Solution selection
Solution 1 Modify Adapter
Most of the solutions for blogs online are this solution, and Adapter is modified. Specific as follows
First, let Adapter's getItemCount() method return Integer.MAX_VALUE, so that the position data reaches a very large size;
Secondly, take the remainder operation of the position parameter in the onBindViewHolder() method, get the real data index corresponding to the position, and then bind the data to the itemView.
Finally, when initializing the RecyclerView, let it slide to the specified position, such as Integer.MAX_VALUE/2, so that it will not slide to the boundary. If the user is stubborn and really slide to the boundary position, add a judgment. If the current index is 0, it will be dynamically adjusted to the initial position.
This solution is quite simple, but not perfect. First, we have calculated our data and indexes, and second, if we slide to the boundary and then dynamically adjust to the middle, there will be an insignificant lag operation, making the sliding not very smooth. So, look directly at plan 2.
Scheme 2 Customize the LayoutManager and modify the layout of the RecyclerView
This can be considered a one-time solution and is also the solution I want to introduce in detail today. We all know that the data binding of the RecyclerView is handled through Adapter, and the layout method and View recycling control are implemented through LayoutManager. Therefore, we can directly modify the layout method of the itemView to achieve our goal and allow the RecyclerView to loop infinitely.
Customize LayoutManager
1. Create a custom LayoutManager
First, the custom LooperLayoutManager inherits from , and then the abstract method generateDefaultLayoutParams() is needed. The function of this method is to set the default LayoutParams for itemView, and return it directly as follows.
public class LooperLayoutManager extends { @Override public generateDefaultLayoutParams() { return new (.WRAP_CONTENT, .WRAP_CONTENT); } }
2. Turn on the scroll switch
Next, process the scrolling direction, rewrite the canScrollHorizontally() method, and turn on the horizontal scroll switch. Note that we are implementing horizontal infinite loop scrolling, so if we want to process vertical scrolling, we need to implement the canScrollVertically() method.
@Override public boolean canScrollHorizontally() { return true; }
3. Initialize the RecyclerView
Okay, the above two parts are basic work. Next, rewrite the onLayoutChildren() method and start initializing the layout of the itemView.
@Override public void onLayoutChildren( recycler, state) { if (getItemCount() <= 0) { return; } //Note 1. If the current preparation status is available, return directly if (()) { return; } //Note 2. Separate the view into the scrap cache to prepare to retype the view detachAndScrapAttachedViews(recycler); int autualWidth = 0; for (int i = 0; i < getItemCount(); i++) { //Diagram 3. Initialize, fill in the view in the screen View itemView = (i); addView(itemView); //Note 4. Measure the width and height of the itemView measureChildWithMargins(itemView, 0, 0); int width = getDecoratedMeasuredWidth(itemView); int height = getDecoratedMeasuredHeight(itemView); //Diagram 5. Layout according to the width and height of the itemView layoutDecorated(itemView, autualWidth, 0, autualWidth + width, height); autualWidth += width; //Note 6. If the sum of widths of the currently laid out itemView is greater than the width of the RecyclerView, no longer layout is done if (autualWidth > getWidth()) { break; } } }
As the name suggests, the onLayoutChildren() method is to layout all itemViews, which are generally called when initializing and calling Adapter's notifyDataSetChanged() method. The code idea has been well explained, and there are several methods that need to be briefly mentioned:
The detachAndScrapAttachedViews(recycler) method at label 2 will detach all itemViews from the View tree and then put them into the scrap cache. Students who have learned about RecyclerView should know that RecyclerView has a secondary cache. The first-level cache is the scrap cache, and the second-level cache is the recycler cache. The view detached from the View tree will be put into the scrap cache, and the View deleted by calling removeView() will be put into the recycler cache.
The (i) method will get the corresponding index itemView from the cache. This method will first fetch the itemView from the scrap cache. If not, fetch it from the recycler cache. If not, call the adapter's onCreateViewHolder() to create the itemView.
The layoutDecorated() method at 5 points will layout and layout the itemView. It can be seen here that we are sorting down to the right of the parent container in sequence according to the width until the vertex position of the next itemView exceeds the width of the RecyclerView.
4. Scroll and recycle itemView processing for RecyclerView
After typing and laying down the subitems of RecyclerView, the effect will appear after running it. However, at this time, we will find that the slide list will become blank after sliding, so it is time to process the sliding operation.
As mentioned earlier, we have turned on the horizontal scroll switch, so correspondingly, we need to rewrite the scrollHorizontallyBy() method for horizontal slide operation.
@Override public int scrollHorizontallyBy(int dx, recycler, state) { //Note 1. When sliding horizontally, fill the itemView in order on the left and right sides in order. int travl = fill(dx, recycler, state); if (travl == 0) { return 0; } //2. Slide offsetChildrenHorizontal(-travl); //3. Recycle itemView that is already invisible recyclerHideView(dx, recycler, state); return travl; }
As you can see, the sliding logic is very simple, summarized in three steps:
- When sliding horizontally, fill the itemView in order on both sides.
- Sliding itemView
- Recycle itemView that is no longer visible
The following step by step introduction:
First, the first step is to call the custom fill() method when sliding to fill the left and right sides. I haven't forgotten yet, we are here to implement loop sliding, so this step is particularly important. Let's look at the code first:
/** * When sliding left and right, fill */ private int fill(int dx, recycler, state) { if (dx > 0) { //Diagram 1. Scroll to the left View lastView = getChildAt(getChildCount() - 1); if (lastView == null) { return 0; } int lastPos = getPosition(lastView); //Note 2. The last visible itemView has completely slipped in, and new ones need to be added if (() < getWidth()) { View scrap = null; //Note 3. Judge the index of the last visible itemView. // If it is the last one, set the next itemView to the first one, otherwise set it to the next one of the current index if (lastPos == getItemCount() - 1) { if (looperEnable) { scrap = (0); } else { dx = 0; } } else { scrap = (lastPos + 1); } if (scrap == null) { return dx; } //Diagram 4. Introduce the new itemViewadd and measure and layout it addView(scrap); measureChildWithMargins(scrap, 0, 0); int width = getDecoratedMeasuredWidth(scrap); int height = getDecoratedMeasuredHeight(scrap); layoutDecorated(scrap,(), 0, () + width, height); return dx; } } else { //Scroll to the right View firstView = getChildAt(0); if (firstView == null) { return 0; } int firstPos = getPosition(firstView); if (() >= 0) { View scrap = null; if (firstPos == 0) { if (looperEnable) { scrap = (getItemCount() - 1); } else { dx = 0; } } else { scrap = (firstPos - 1); } if (scrap == null) { return 0; } addView(scrap, 0); measureChildWithMargins(scrap,0,0); int width = getDecoratedMeasuredWidth(scrap); int height = getDecoratedMeasuredHeight(scrap); layoutDecorated(scrap, () - width, 0, (), height); } } return dx; }
The code is a bit long, but the logic is clear. First, it is divided into two parts, fill to the left or fill to the right. dx is the distance to slide. If dx > 0, slide to the left, you need to judge the boundary on the right. If the last itemView is fully displayed, fill a new itemView on the right.
Look at the notation 3. When filling to the right, you need to detect the index of the last visible itemView. If the index is the last, you need to have the newly filled itemView 0th, so that you can realize infinite loop when sliding to the left. Then the newly filled itemView will be measured and layout operation, and the fill will be filled in.
Similarly, the logic of sliding to the right is similar to sliding to the left, so I won't explain it one by one.
Step 2: After filling the new itemView, you start sliding. Here you directly call the offsetChildrenHorizontal() method of LayoutManager to slide -travl distance. Travl is calculated by filling method. Usually, it is dx. It is only 0 when it slides to the last itemView and the loop scroll switch is not turned on, which means it does not scroll.
//2.Scroll offsetChildrenHorizontal(travl * -1);
Step 3: Recycle the itemView that is already invisible. Only by recycling the invisible itemView can we achieve recycling and prevent memory explosion.
/** * Recycling the view that is not visible on the interface */ private void recyclerHideView(int dx, recycler, state) { for (int i = 0; i < getChildCount(); i++) { View view = getChildAt(i); if (view == null) { continue; } if (dx > 0) { //Note 1. Scroll to the left and remove the view on the left that is not in the content. if (() < 0) { removeAndRecycleView(view, recycler); (TAG, "cycle: Remove oneview childCount=" + getChildCount()); } } else { //Note 2. Scroll to the right and remove the view on the right that is not in the content. if (() > getWidth()) { removeAndRecycleView(view, recycler); (TAG, "cycle: Remove oneview childCount=" + getChildCount()); } } } }
The code is also very simple. It goes through all items added to the RecyclerView, and then judges based on the vertex position of the itemView to remove the invisible items. Removing itemView Call removeAndRecycleView(view, recycler) method, which will recycle the removed item and then store it in the RecyclerView cache.
At this point, a LayoutManager that can implement infinite loops on the left and right is implemented. The call method is no different from the usual RrcyclerView. You only need to specify our LayoutManager when setting the LayoutManager for the RecyclerView, as follows:
(new MyAdapter()); LooperLayoutManager layoutManager = new LooperLayoutManager(); (true); (layoutManager);
Please click me to access the source code
The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.