RecyclerView series three custom LayoutManager

As mentioned in the first article, LayoutManager is mainly used to layout the items in it. In LayoutManager, we can change the size and position of each item and place it where we want. Among many excellent effects, They are all realized by customizing LayoutManager. For example,
insert image description here insert image description here
insert image description here
you can see that these effects are very good. Through the study of this section, everyone will understand the method of customizing LayoutManager, and then it will not be difficult to understand the code of these controls. up.

In this section, we first make a LinearLayoutManager ourselves to see how to customize the LayoutManager. In the next section, we will customize the LayoutManager to create the first scroll wheel page turning effect.

1. Initialize the display interface

1.1 Customize CustomLayoutManager

Mr. Generate a class CustomLayoutManager, derived from LayoutManager:

public class CustomLayoutManager extends LayoutManager {
    
    
    @Override
    public LayoutParams generateDefaultLayoutParams() {
    
    
        return null;
    }
}

When we derive from LayoutManager, it will force us to generate a method generateDefaultLayoutParams.
This method is the layout parameters of the RecyclerView Item. In other words, it is the LayoutParameters of the RecyclerView sub-item. If you want to modify the layout parameters of the sub-Item (such as: width/height/margin/padding, etc.), you can set them in this method .
Generally speaking, if there is no special requirement, you can directly let the sub-item decide its own width and height (wrap_content).
So, our general way of writing is:

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

If at this time, we replace the LinearLayoutManager in the demo in the previous section:

public class LinearActivity extends AppCompatActivity {
    
    
    private ArrayList<String> mDatas = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_linear);

        …………
        RecyclerView mRecyclerView = (RecyclerView) findViewById(R.id.linear_recycler_view);
        
        mRecyclerView.setLayoutManager(new CustomLayoutManager());

        RecyclerAdapter adapter = new RecyclerAdapter(this, mDatas);
        mRecyclerView.setAdapter(adapter);
    }
    …………
}

Run it and find that the page is completely blank:
insert image description here

We said that the layout of all Items is handled in LayoutManager. Obviously, we currently do not layout any Items in CustomLayoutManager. Of course no Item appeared.

1.2 onLayoutChildren()

In LayoutManager, the layout of all Items is handled in the onLayoutChildren() function, so we add the onLayoutChildren() function in CustomLayoutItem:

@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    
    
    //定义竖直方向的偏移量
    int offsetY = 0;
    for (int i = 0; i < getItemCount(); i++) {
    
    
        View view = recycler.getViewForPosition(i);
        addView(view);
        measureChildWithMargins(view, 0, 0);
        int width = getDecoratedMeasuredWidth(view);
        int height = getDecoratedMeasuredHeight(view);
        layoutDecorated(view, 0, offsetY, width, offsetY + height);
        offsetY += height;
    }
}

In this function, I mainly do two things:
first: add the views corresponding to all items:

for (int i = 0; i < getItemCount(); i++) {
    
    
    View view = recycler.getViewForPosition(i);
    addView(view);
    …………
}

Second: Put all the Items where they should be:

public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    
    
    //定义竖直方向的偏移量
    int offsetY = 0;
    for (int i = 0; i < getItemCount(); i++) {
    
    
        …………
        measureChildWithMargins(view, 0, 0);
        int width = getDecoratedMeasuredWidth(view);
        int height = getDecoratedMeasuredHeight(view);
        layoutDecorated(view, 0, offsetY, width, offsetY + height);
        offsetY += height;
    }
}

First, we measureChildWithMargins(view, 0, 0);measure the view through the function, and getDecoratedMeasuredWidth(view)get the measured width. It should be noted that getDecoratedMeasuredWidth(view)the total width of the item+decoration is obtained. If you only want to get the measured width of the view, you can get it through view.getMeasuredWidth()

Then use layoutDecorated();the function to place each item in the corresponding position. The left and right positions of each item are the same, starting from x=0 on the left side, but the y point needs to be calculated. So here is a variable offsetY, which is used to accumulate the height of all items before the current Item. So as to calculate the position of the current item. This part is not difficult, so I won't go into details.

After that, if we run the program again, we will find that the item is now displayed:
insert image description here

2. Add scrolling effect

2.1 Add scrolling effect

However, it is not possible to slide now. If we want to add a slide to it, we need to modify two places:

@Override
public boolean canScrollVertically() {
    
    
    return true;
}

@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
    
    
    // 平移容器内的item
    offsetChildrenVertical(-dy);
    return dy;
}

First, we enable the LayoutManager to scroll vertically by returning true in canScrollVertically(). Then receive the distance dy of each scroll in scrollVerticallyBy.
If you want the LayoutManager to have the function of horizontal scrolling, you can return true in canScrollHorizontally();

It should be noted here that in scrollVerticallyBy, dy represents the displacement of each finger sliding on the screen.

  • When the finger slides from bottom to top, dy>0
  • When the finger slides from top to bottom, dy<0

Obviously, when the finger slides up, we need to move all sub-items up, and moving up obviously needs to subtract dy. Therefore, after testing, you can also find that moving the item in the container by -dy distance is in line with life Habit. In LayoutManager, we can public void offsetChildrenVertical(int dy)move all items in RecycerView through functions.

Now let's run it again:
insert image description here

It can be seen from the effect diagram that although scrolling is realized here, there are two problems. After the Item reaches the top and bottom, it can still scroll. This is obviously wrong. We need to add judgment when scrolling. If it is at the end or at the end Don't let it scroll.

2.2 Add exception judgment

(1) Judging to the top
It is relatively easy to judge to the top, we only need to add all the dy, if it is less than 0, it means we have reached the top. Just don't let it move anymore, the code is as follows:

private int mSumDy = 0;
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
    
    
    int travel = dy;
    //如果滑动到最顶部
    if (mSumDy + dy < 0) {
    
    
        travel = -mSumDy;
    }
    mSumDy += travel;
    // 平移容器内的item
    offsetChildrenVertical(-travel);
    return dy;
}

In this code, all the moved dy are saved by the variable mSumDy. If the current moving distance is <0, then the dy will no longer be accumulated, and it will be moved directly to the position of y=0, because the previously moved distance is mSumdy ;
So the calculation method is:
travel+mSumdy = 0;
=> travel = -mSumdy
So ​​to move it to the position of y=0, the moving distance is -mSumdy. The effect is shown in the figure below: You
insert image description here
can see from the effect picture Now, when it reaches the top, it will no longer move. Let's take a look at the problem in the end.

(2)
The method of judging to the end is actually that we need to know the total height of all items, and subtract the height of the last screen from the total height, which is the offset value at the end. If it is greater than this offset value, it means It's over the bottom.

Therefore, we first need to get the total height of all items. We know that all items will be measured in onLayoutChildren and each item will be laid out, so we only need to add the heights of all items in onLayoutChildren to get the total of all items. high.

private int mTotalHeight = 0;
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    
    
    //定义竖直方向的偏移量
    int offsetY = 0;
    for (int i = 0; i < getItemCount(); i++) {
    
    
        View view = recycler.getViewForPosition(i);
        addView(view);
        measureChildWithMargins(view, 0, 0);
        int width = getDecoratedMeasuredWidth(view);
        int height = getDecoratedMeasuredHeight(view);
        layoutDecorated(view, 0, offsetY, width, offsetY + height);
        offsetY += height;
    }
    //如果所有子View的高度和没有填满RecyclerView的高度,
    // 则将高度设置为RecyclerView的高度
    mTotalHeight = Math.max(offsetY, getVerticalSpace());
}
private int getVerticalSpace() {
    
    
    return getHeight() - getPaddingBottom() - getPaddingTop();
}

The getVerticalSpace() function can get the real height of the RecyclerView used to display the item. Compared with the above onLayoutChildren, only one sentence of code is added here: mTotalHeight = Math.max(offsetY, getVerticalSpace());here only the maximum value of offsetY and getVerticalSpace() is taken because offsetY is the total height of all items, and when the item does not fill the RecyclerView, offsetY should be larger than The real height of RecyclerView is small, and the real height at this time should be the height set by RecyclerView itself.

The next step is to judge and process in scrollVerticallyBy:

public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
    
    
    int travel = dy;
    //如果滑动到最顶部
    if (mSumDy + dy < 0) {
    
    
        travel = -mSumDy;
    } else if (mSumDy + dy > mTotalHeight - getVerticalSpace()) {
    
    
        travel = mTotalHeight - getVerticalSpace() - mSumDy;
    }

    mSumDy += travel;
    // 平移容器内的item
    offsetChildrenVertical(-travel);
    return dy;
}

mSumDy + dy > mTotalHeight - getVerticalSpace()Middle:
mSumDy + dy represents the current moving distance, mTotalHeight - getVerticalSpace() represents the total distance when sliding to the bottom;

When sliding to the end, how to calculate the moving distance this time?
The algorithm is as follows:
travel + mSumDy = mTotalHeight - getVerticalSpace();
that is, the distance to be moved plus the previous total moving distance should be the bottom distance.
=> travel = mTotalHeight - getVerticalSpace() - mSumDy;

Now run the code again, and you can see that the vertical sliding list is completed at this time: as
insert image description here
can be seen from the list, the problem of continuing to slide to the top and bottom is now solved. The complete CustomLayoutManager code is posted below for your reference:

public class CustomLayoutManager extends LayoutManager {
    
    
    private int mSumDy = 0;
    private int mTotalHeight = 0;

    @Override
    public LayoutParams generateDefaultLayoutParams() {
    
    
        return new RecyclerView.LayoutParams(RecyclerView.LayoutParams.WRAP_CONTENT,
                RecyclerView.LayoutParams.WRAP_CONTENT);
    }

    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    
    
        //定义竖直方向的偏移量
        int offsetY = 0;
        for (int i = 0; i < getItemCount(); i++) {
    
    
            View view = recycler.getViewForPosition(i);
            addView(view);
            measureChildWithMargins(view, 0, 0);
            int width = getDecoratedMeasuredWidth(view);
            int height = getDecoratedMeasuredHeight(view);
            layoutDecorated(view, 0, offsetY, width, offsetY + height);
            offsetY += height;
        }

        //如果所有子View的高度和没有填满RecyclerView的高度,
        // 则将高度设置为RecyclerView的高度
        mTotalHeight = Math.max(offsetY, getVerticalSpace());
    }

    private int getVerticalSpace() {
    
    
        return getHeight() - getPaddingBottom() - getPaddingTop();
    }

    @Override
    public boolean canScrollVertically() {
    
    
        return true;
    }
    
    @Override
    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
    
    
        int travel = dy;
        //如果滑动到最顶部
        if (mSumDy + dy < 0) {
    
    
            travel = -mSumDy;
        } else if (mSumDy + dy > mTotalHeight - getVerticalSpace()) {
    
    
        //如果滑动到最底部
            travel = mTotalHeight - getVerticalSpace() - mSumDy;
        }

        mSumDy += travel;
        // 平移容器内的item
        offsetChildrenVertical(-travel);
        return dy;
    }
}

github code address: https://github.com/harvic/harvic_blg_share located in RecylcerView (3)
reprinted: https://blog.csdn.net/harvic880925/article/details/84789602

Guess you like

Origin blog.csdn.net/gqg_guan/article/details/130288958