Nested scrolling parent2

Keywords: Android github Fragment REST

Article directory

I wrote one before Nested slide – NestedScroll - project instance (Taobao homepage defect), and the principle of CoordinatorLayout and AppbarLayout linkage , compared the sliding effect of Taobao and Jingdong homepage, analyzed the reasons for the difference of the effect, and gave the general solution.
At that time, there was no demo, only code snippets, which may lead to unclear reading. Therefore, this article specifically analyzes relevant knowledge in detail, gives a general nested sliding solution, and attaches GitHub's demo.

Code related to this article Demo Github address , if it helps, Star wave.

1, Problems and Solutions

Let's look at a picture:

This is the homepage of jd.com. Ignoring the top and the top, we can roughly understand the view structure as follows: the outermost layer is the RecyclerView with multiple layouts, the last item is tabLayout+ViewPager, and every fragment of ViewPager is also RecyclerView. This is a common layout of e-commerce App home page.

Let's look at the slide up effect picture:

It can be seen that when you slide the page up, when the tabLayout slides to the top, the outer RecyclerView stops sliding. At this time, the tabLayout is in the ceiling state, and then the inner RecyclerView in the ViewPager will slide. When sliding down, if tabLayout is in ceiling state, the inner RecyclerView will be slid first, and then the outer RecyclerView.

Then, if we directly implement the above layout structure, will it be JD's effect? The answer is No. the effect is as follows?


It can be seen that the tabLayout is in the ceiling state, and it is unable to continue to slide the inner layer RecyclerView (even if you raise your finger and continue to slide). ( Click to view relevant codes)

So what should I do? according to Sliding conflict We know that the outer RecyclerView intercepts the touch event, and the inner RecyclerView cannot get the event, so it cannot slide. So can we use the outer layer to not intercept events when the tabLayout is on top, so the inner layer RecyclerView can get events and slide them?

This is possible, but after tabLayout slides to the top, you have to lift your finger and slide again, so the inner RecyclerView can continue to slide. Why is this? In the blog mentioned at the beginning:

From the view event distribution mechanism, we know that when the parent View intercepts events, the events of the same event sequence will be directly processed by the parent, and the child view will not accept the events. So follow the normal way of dealing with sliding conflict - when the tab is not at the top, the parent intercepts the event. When the tab is at the top, the parent does not intercept the event. However, because the finger is not raised, the event sequence continues to be given to the parent, not to the internal RecyclerView, and the commodity flow will not slide.

The solution can only be nested sliding layouts. The code is as follows:

<?xml version="1.0" encoding="utf-8"?>
<com.hfy.demo01.module.home.touchevent.view.NestedScrollingParent2LayoutImpl3 xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/nested_scrolling_parent2_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView_parent"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</com.hfy.demo01.module.home.toucheve

We see that we have changed the root layout of the outer RecyclerView to NestedScrollingParent2LayoutImpl3. After running, we find that the above problems have been solved. The sliding effect is the same as that of JD.
What is NestedScrollingParent2LayoutImpl3? NestedScrollingParent2LayoutImpl3 is a LinearLayout inherited from NestedScrollingParent2, which is used to deal with the problems caused by the above nested sliding. ( Click to see the implementation of NestedScrollingParent2LayoutImpl3)

The effect is as follows:

If you don't care about the principle and implementation, this is the end, because NestedScrollingParent2LayoutImpl3 can solve the above problems.

2, Implementation principle of NestedScrollingParent2LayoutImpl3

2.1 let's review the nested sliding mechanism.

If you don't know about nested scrolling and NestedScrollingParent2, it is recommended to read this blog first Advanced chapter of custom View events (I) - nested scrolling mechanism , and read on.

Nestedscrolling mechanism, in short: to generate nested sliding child views, before sliding, first ask whether the parent view corresponding to the nested sliding has priority in handling events and how many events are consumed, and then continue to give the remaining part to the child view after consumption. It can be understood that an event sequence is distributed twice. To generate nested sliding, the child view should implement the interface NestedScrollingChild2, and the parent view should implement the interface NestedScrollingParent2.

The commonly used RecyclerView implements NestedScrollingChild2, while NestedScrollView implements NestedScrollingChild2 and NestedScrollingParent2.

Usually, we have to deal with the situation of RecyclerView as a nested sliding child view manually. NestedScrollView is generally used as the root layout to solve nested sliding.

2.2 take a look at nested Scrollview

In the case of nested RecyclerView, the header and list can slide together. The following picture:

Refer to this article. Real name object to nested RecyclerView in Alibaba Android Development Manual . From this article's analysis conclusion, we know that nested RecyclerView in NestedScrollView can achieve the effect, but RecyclerView will instantly load all item s, and RecyclerView will lose the view recycling feature. Finally, the author suggests using RecyclerView multi layout.
However, in real applications, the data of the header and the list may come from different interfaces. When the data request of the list fails, the default graph will be displayed, but the header will still be displayed. At this time, it is a better choice to separate the header and the list.


Here is the solution:

<?xml version="1.0" encoding="utf-8"?>
<com.hfy.demo01.module.home.touchevent.view.NestedScrollingParent2LayoutImpl2 xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tv_head"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:background="@color/colorAccent"
        android:gravity="center"
        android:padding="15dp"
        android:text="I'm the head. The outermost layer is NestedScrollingParent2LayoutImpl2"
        android:textColor="#fff"
        android:textSize="20dp" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/design_default_color_primary" />

</com.hfy.demo01.module.home.touchevent.view.NestedScrollingParent2LayoutImpl2>

NestedScrollingParent2LayoutImpl2 also implements NestedScrollingParent2. ( Click to view the implementation of NestedScrollingParent2LayoutImpl2)

The effect is as follows: it can be seen that the sliding is smooth, and the critical point does not need to lift the finger to slide again, and viewing the log does not load the item once.

First, let's look at the implementation of NestedScrollingParent2LayoutImpl2, which should be simpler. Then let's look at the implementation principle of NestedScrollingParent2LayoutImpl3. The overall idea is the same.

/**
 * Process header + recyclerView
 * Description:NestedScrolling2 Nested sliding under the mechanism to realize the difference of handling the flying effect under the NestedScrollingParent2 interface
 *
 */
public class NestedScrollingParent2LayoutImpl2 extends NestedScrollingParent2Layout implements NestedScrollingParent2 {


    private View mTopView;
    private View mRecylerVIew;

    private int mTopViewHeight;


    public NestedScrollingParent2LayoutImpl2(Context context) {
        this(context, null);
    }

    public NestedScrollingParent2LayoutImpl2(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public NestedScrollingParent2LayoutImpl2(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setOrientation(VERTICAL);
    }


    @Override
    public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int axes, int type) {
        return (axes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
    }


    /**
     * Before the nested sliding child view does not slide, judge whether the parent view has priority over the child view (that is, the parent view can be consumed first and then the child view)
     *
     * @param target   Specific nested sliding subclass
     * @param dx       The distance that the horizontal nested sliding sub View wants to change
     * @param dy       The vertical nested sliding sub View wants to change the distance dy < 0 slide down dy > 0 slide up
     * @param consumed This parameter needs to be specified when we implement this function. We will tell the child the distance consumed by the current parent View later
     *                 consumed[0] The distance of horizontal consumption and the distance of consumed[1] vertical consumption make the child view adjust accordingly
     * @param type     Slide type, viewcompat.type? Non? Touch flying effect, viewcompat.type? Touch gesture sliding
     */
    @Override
    public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) {
        //No matter the gesture scrolling or flying
        boolean hideTop = dy > 0 && getScrollY() < mTopViewHeight;
        boolean showTop = dy < 0 && getScrollY() >= 0 && !target.canScrollVertically(-1);
        if (hideTop || showTop) {
            scrollBy(0, dy);
            consumed[1] = dy;
        }
    }


    @Override
    public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
        //When the child control finishes processing, it is handed over to the parent control for processing.
        if (dyUnconsumed < 0) {
            //Indicates that it has slid down to the end
            scrollBy(0, dyUnconsumed);
        }
    }

    @Override
    public boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY) {
        return false;
    }


    @Override
    public boolean onNestedFling(@NonNull View target, float velocityX, float velocityY, boolean consumed) {
        return false;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        //Change the height of the mRecylerVIew to the screen height, otherwise the bottom will be blank. (because the scrollTo method is a slider view, it slides up mRecylerVIew)
        ViewGroup.LayoutParams layoutParams = mRecylerVIew.getLayoutParams();
        layoutParams.height = getMeasuredHeight();
        mRecylerVIew.setLayoutParams(layoutParams);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mTopView = findViewById(R.id.tv_head);
        mRecylerVIew = findViewById(R.id.recyclerView);
        if (!(mRecylerVIew instanceof RecyclerView)) {
            throw new RuntimeException("id RecyclerView should be RecyclerView!");
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mTopViewHeight = mTopView.getMeasuredHeight();
    }

    @Override
    public void scrollTo(int x, int y) {
        if (y < 0) {
            y = 0;
        }
        if (y > mTopViewHeight) {
            y = mTopViewHeight;
        }
        super.scrollTo(x, y);
    }

}

The main task is to deal with the critical part in onnested prescroll: when sliding RecyclerView, first slide the root layout to make the head hidden or displayed, and then give it to RecyclerView to slide.

2.3 implementation principle of nestedscrollingparent2layoutimpl3

The code is as follows

/**
 * Handle RecyclerView set viewPager, RecyclerView also exists in fragment s in viewPager, and handle nesting and sliding of outer and inner RecyclerView
 * Similar to Taobao and Jingdong Homepage
 *
 */
public class NestedScrollingParent2LayoutImpl3 extends NestedScrollingParent2Layout {

    private final String TAG = this.getClass().getSimpleName();

    private RecyclerView mParentRecyclerView;


    private RecyclerView mChildRecyclerView;

    private View mLastItemView;


    public NestedScrollingParent2LayoutImpl3(Context context) {
        super(context);
    }

    public NestedScrollingParent2LayoutImpl3(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public NestedScrollingParent2LayoutImpl3(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setOrientation(VERTICAL);
    }


    /**
     * A nested slide is coming. Judge whether the parent view accepts nested slide
     *
     * @param child            The subclass of the parent class corresponding to the nested slide (because the nested slide is not necessarily found at the first level for the parent View, the parent View of the two-level parent View may be selected, and the child's seniority is > = target)
     * @param target           Specific nested sliding subclass
     * @param nestedScrollAxes Nested scrolling axes are supported. Horizontal, vertical, or unspecified
     * @param type             Slide type, viewcompat.type ABCD non ABCD touch flying effect viewcompat.type ABCD touch gesture slide
     */
    @Override
    public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int nestedScrollAxes, int type) {
        //Handle logic by yourself
        //The treatment here is to accept vertical nested sliding
        return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL;
    }

    /**
     * Before the nested sliding child view does not slide, judge whether the parent view has priority over the child view (that is, the parent view can be consumed first and then the child view)
     *
     * @param target   The specific subclass of nested sliding is the view that generates nested sliding
     * @param dx       The distance that the horizontal nested sliding sub View wants to change
     * @param dy       The vertical nested sliding sub View wants to change the distance dy < 0 slide down dy > 0 slide up
     * @param consumed This parameter needs to be specified when we implement this function. We will tell the child the distance consumed by the current parent View later
     *                 consumed[0] The distance of horizontal consumption and the distance of consumed[1] vertical consumption make the child view adjust accordingly
     * @param type     Slide type, viewcompat.type ABCD non ABCD touch flying effect viewcompat.type ABCD touch gesture slide
     */
    @Override
    public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) {
        //Handle logic by yourself

        if (mLastItemView == null) {
            return;
        }

        int lastItemTop = mLastItemView.getTop();

        if (target == mParentRecyclerView) {
            handleParentRecyclerViewScroll(lastItemTop, dy, consumed);
        } else if (target == mChildRecyclerView) {
            handleChildRecyclerViewScroll(lastItemTop, dy, consumed);
        }
    }

    /**
     * Processing when sliding the outer RecyclerView
     *
     * @param lastItemTop tab The distance to the top of the screen is 0, which means to the top
     * @param dy          Target sliding distance, Dy > 0 means upward sliding
     * @param consumed
     */
    private void handleParentRecyclerViewScroll(int lastItemTop, int dy, int[] consumed) {
        //No top
        if (lastItemTop != 0) {
            if (dy > 0) {
                //Upward slide
                if (lastItemTop > dy) {
                    //tab's top > the dy you want to slide, let the external RecyclerView handle it by itself
                } else {
                    //tab's top < = the dy you want to slide, first slide the external RecyclerView, and the sliding distance is lastItemTop, just to the top; the rest will slide the inner layer.
                    consumed[1] = dy;
                    mParentRecyclerView.scrollBy(0, lastItemTop);
                    mChildRecyclerView.scrollBy(0, dy - lastItemTop);
                }
            } else {
                //Slide down and let the external RecyclerView handle it by itself
            }
        } else {
            //Top of tab
            if (dy > 0){
                //Up, the inner layer directly consumes
                mChildRecyclerView.scrollBy(0, dy);
                consumed[1] = dy;
            }else {
                int childScrolledY = mChildRecyclerView.computeVerticalScrollOffset();
                if (childScrolledY > Math.abs(dy)) {
                    //The distance that the inner layer has rolled is greater than the distance that you want to roll, and the inner layer directly consumes it
                    mChildRecyclerView.scrollBy(0, dy);
                    consumed[1] = dy;
                }else {
                    //The distance that the inner layer has rolled is less than the distance that you want to roll. Then the inner layer consumes part of it. When it reaches the top, the rest will slide back to the outer layer
                    mChildRecyclerView.scrollBy(0, -(Math.abs(dy)-childScrolledY));
                    consumed[1] = -(Math.abs(dy)-childScrolledY);
                }
            }
        }

    }

    /**
     * Processing when sliding the inner RecyclerView
     *
     * @param lastItemTop tab The distance to the top of the screen is 0, which means it's at the top
     * @param dy
     * @param consumed
     */
    private void handleChildRecyclerViewScroll(int lastItemTop, int dy, int[] consumed) {
        //No top
        if (lastItemTop != 0) {
            if (dy > 0) {
                //Upward slide
                if (lastItemTop > dy) {
                    //Top > dy to slide, the outer layer is consumed directly
                    mParentRecyclerView.scrollBy(0, dy);
                    consumed[1] = dy;
                } else {
                    //Top of tab < = the dy you want to slide, first slide the outer layer, the consumption distance is lastItemTop, just to the top; the rest slide the inner layer.
                    mParentRecyclerView.scrollBy(0, lastItemTop);
                    consumed[1] = dy - lastItemTop;
                }
            } else {
                //Sliding down, direct consumption of outer layer
                mParentRecyclerView.scrollBy(0, dy);
                consumed[1] = dy;
            }
        }else {
            //Top of tab
            if (dy > 0){
                //Up, inner layer handles by itself
            }else {
                int childScrolledY = mChildRecyclerView.computeVerticalScrollOffset();
                if (childScrolledY > Math.abs(dy)) {
                    //The distance that the inner layer has rolled is greater than the distance that you want to roll. The inner layer handles it by itself
                }else {
                    //The distance that the inner layer has rolled is less than the distance that you want to roll. Then the inner layer consumes part of it. When it reaches the top, the remaining outer layer slides
                    mChildRecyclerView.scrollBy(0, -childScrolledY);
                    mParentRecyclerView.scrollBy(0, -(Math.abs(dy)-childScrolledY));
                    consumed[1] = dy;
                }
            }
        }
    }


    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        //Get outer RecyclerView directly
        mParentRecyclerView = getRecyclerView(this);
        Log.i(TAG, "onFinishInflate: mParentRecyclerView=" + mParentRecyclerView);

        //About the inner RecyclerView: at this time, the RecyclerView of the fragments in the ViewPager cannot be obtained. It needs to be passed in when the fragments are visible after the ViewPager is loaded
    }

    private RecyclerView getRecyclerView(ViewGroup viewGroup) {
        int childCount = viewGroup.getChildCount();
        for (int i = 0; i < childCount; i++) {
            View childAt = getChildAt(i);
            if (childAt instanceof RecyclerView) {
                if (mParentRecyclerView == null) {
                    return (RecyclerView) childAt;
                }
            }
        }
        return null;
    }

    /**
     * Incoming internal RecyclerView
     *
     * @param childRecyclerView
     */
    public void setChildRecyclerView(RecyclerView childRecyclerView) {
        mChildRecyclerView = childRecyclerView;
    }


    /**
     * The last item of the outer RecyclerView, namely: tab + viewPager
     * Used to judge the critical position of sliding
     *
     * @param lastItemView
     */
    public void setLastItem(View lastItemView) {
        mLastItemView = lastItemView;
    }
}

NestedScrollingParent2LayoutImpl3 inherits from NestedScrollingParent2Layout. NestedScrollingParent2Layout inherits from LinearLayout implements and implements NestedScrollingParent2, mainly dealing with general method implementation.

/**
 * Description:  General slide nesting processing layout, which is used to process nested slides containing {@ link Android x.recyclerview. Widget. Recyclerview}
 */
public class NestedScrollingParent2Layout extends LinearLayout implements NestedScrollingParent2 {


    private NestedScrollingParentHelper mNestedScrollingParentHelper = new NestedScrollingParentHelper(this);

    public NestedScrollingParent2Layout(Context context) {
        super(context);
    }

    public NestedScrollingParent2Layout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public NestedScrollingParent2Layout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    /**
     * A nested slide is coming. Judge whether the parent view accepts nested slide
     *
     * @param child            The subclass of the parent class corresponding to the nested slide (because the nested slide is not necessarily found at the first level for the parent View, the parent View of the two-level parent View may be selected, and the child's seniority is > = target)
     * @param target           Specific nested sliding subclass
     * @param nestedScrollAxes Nested scrolling axes are supported. Horizontal, vertical, or unspecified
     * @param type             Slide type, viewcompat.type ABCD non ABCD touch flying effect viewcompat.type ABCD touch gesture slide
     */
    @Override
    public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int nestedScrollAxes, int type) {
        //Handle logic by yourself
        return true;
    }

    /**
     * When the parent view accepts nested sliding, when the onStartNestedScroll method returns true, the method will call
     *
     * @param type Slide type, viewcompat.type ABCD non ABCD touch flying effect viewcompat.type ABCD touch gesture slide
     */
    @Override
    public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int axes, int type) {
        mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes, type);
    }

    /**
     * Before the nested sliding child view does not slide, judge whether the parent view has priority over the child view (that is, the parent view can be consumed first and then the child view)
     *
     * @param target   Specific nested sliding subclass
     * @param dx       The distance that the horizontal nested sliding sub View wants to change
     * @param dy       The vertical nested sliding sub View wants to change the distance dy < 0 slide down dy > 0 slide up
     * @param consumed This parameter needs to be specified when we implement this function. We will tell the child the distance consumed by the current parent View later
     *                 consumed[0] The distance of horizontal consumption and the distance of consumed[1] vertical consumption make the child view adjust accordingly
     * @param type     Slide type, viewcompat.type ABCD non ABCD touch flying effect viewcompat.type ABCD touch gesture slide
     */
    @Override
    public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) {
        //Handle logic by yourself
    }

    /**
     * After the nested sliding child view is sliding, judge whether the parent view continues to process (that is, after the parent consumes a certain distance, the child consumes again, and finally judge whether the parent consumes)
     *
     * @param target       Specific nested sliding subclass
     * @param dxConsumed   Distance of sliding (consumed distance) of sub View nested sliding in horizontal direction
     * @param dyConsumed   Distance of sliding (consumed distance) of sub View nested sliding in vertical direction
     * @param dxUnconsumed The distance that the sub View nested sliding horizontally does not slide (the distance that is not consumed)
     * @param dyUnconsumed Vertical nested sliding sub View unslided distance (unconsumed distance)
     * @param type         Slide type, viewcompat.type ABCD non ABCD touch flying effect viewcompat.type ABCD touch gesture slide
     */
    @Override
    public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
        //Handle logic by yourself
    }

    /**
     * Nested slide end
     *
     * @param type Slide type, viewcompat.type ABCD non ABCD touch flying effect viewcompat.type ABCD touch gesture slide
     */
    @Override
    public void onStopNestedScroll(@NonNull View child, int type) {
        mNestedScrollingParentHelper.onStopNestedScroll(child, type);
    }

    @Override
    public boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY) {
        //Decide whether to deal with it by yourself
        return false;
    }

    @Override
    public boolean onNestedFling(@NonNull View target, float velocityX, float velocityY, boolean consumed) {
        //Handle logic by yourself
        return false;
    }


    @Override
    public int getNestedScrollAxes() {
        return mNestedScrollingParentHelper.getNestedScrollAxes();
    }

}

The implementation principle is mainly in the onnestedperscroll method, that is, before sliding the nested sliding child view, ask whether the corresponding parent view is processed first and how much.

So no matter you swipe the recycling view in the outer city or the recycling view in the inner layer, you will ask NestedScrollingParent2LayoutImpl3, that is, you will go to the onnestedprescoll method. Then, according to the position and direction of tabLayout, decide whether to slide the outer RecyclerView or the inner layer, and how much. It is equivalent to a sequence of personal leave distributed twice, which avoids the problem that the child view cannot handle after the normal event distribution parent view intercepts.

For the specific processing in onnestedpresccroll, please see the code, with detailed comments. It should be understood in combination with the actual sliding situation, so that other situations can be handled in the same way.

Here is a list of three implemented solutions for nested sliding:

  • NestedScrollingParent2LayoutImpl1: process header + tab + viewPager + recyclerView
  • NestedScrollingParent2LayoutImpl2: process header + recyclerView
  • NestedScrollingParent2LayoutImpl3: handles the RecyclerView set viewPager, and RecyclerView is also included in the fragment s in the viewPager. It handles the nesting and sliding of the outer and inner RecyclerView, similar to Taobao and Jingdong homepage.

Demo Github address , if it helps, Star wave.

Welcome to your attention.

Published 56 original articles· Zan Zan 19. 30000 visitors+
Private letter follow

Posted by nloding on Wed, 08 Apr 2020 01:01:01 -0700