Why does this article exist

I wrote an article about two problems encountered in the process of using CoordinatorLayout and analyzed the working mechanism of CoordinatorLayout. This article mainly talked about the problems encountered when the effect of TAB suction was realized through CoordinatorLayout. The effect is similar to the home page of JINGdong and Taobao, but the implementation method is different, but the use of CoordinatorLayout to achieve is difficult to deal with a lot of details, the following will be introduced in detail.

First of all, we can simply look at the effect of JD home page GIF, to see what kind of effect we want to achieve:

The TAB filtering area on the homepage of JINGdong divides the feed into two parts, on which are various items. The lower part of the TAB can be swiped left and right, and the drop-down can be weighted more. Theoretically, it can be pulled down indefinitely as long as there is data on the network.

In fact, CoordinatorLayout to achieve TAB top, if you can handle some details well, in fact, you can roughly achieve the effect similar to the front page of JINGdong. For details, you can refer to the previous article at the beginning of the article. The article talked about the problems of animation jitter and page rebound when using CoordinatorLayout to achieve similar effects and the corresponding solutions.

So why don’t you use CoordinatorLayout instead of nesting RecyclerView?

First of all, let’s look at the general layout of CoordinatorLayout:

One problem is that the slide effect from AppBarLayout cannot be transferred to the following ViewPager. I tried various methods but failed to solve this problem.

As you can see in the GIF, the inertia disappears when the AppBarLayout slides up, and the area under the TAB cannot continue scrolling.

I found an article on the Internet to solve the problem of the disappearance of inertia as follows: Alipay home page interaction trilogy 3 to realize alipay home page interaction, the realization method is basically to realize the mechanism of CoordinatorLayout again, because it is realized by myself, some of the mechanisms are relatively convenient to change. Its logic to deal with inertia is roughly to take out the unconsumed Y-axis offset of AppBarLayout and then transfer it to RecyclerView to slide, the code is as follows:

mHeaderView.setOnHeaderFlingUnConsumedListener(new APHeaderView.OnHeaderFlingUnConsumedListener() {
    @Override
    public int onFlingUnConsumed(APHeaderView header, int targetOffset, int unconsumed) {
        APHeaderView.Behavior behavior = mHeaderView.getBehavior();
        int dy = -unconsumed;
        if(behavior ! =null) {
            mRecyclerView.scrollBy(0, dy);
        }
        returndy; }});Copy the code

Due to the lack of space, I will not introduce it in detail here. If you are interested, you can click on the link above to study it. The GitHub address is also posted in the article.

Let’s go back to our original question, why do we want to replace CoordinatorLayout? Another problem is that CoordinatorLayout is a relatively simple implementation, but it leads to a very deep nesting level of the page. We can see from the layout posted above, the view nesting level is very deep. And if we want to achieve the similar effect of jingdong or Taobao home page, the area above TabLayout, which is marked by the arrow below, must use RecyclerView to achieve, because the content and number of TAB upper part is uncertain, it is more convenient to use RecyclerView. But this makes the page more hierarchical and slower to load.

How to implement

To get rid of CoordinatorLayout, how do you do that?

We used the Layout Inspector in Android Studio to check the Layout of jd’s home page, and found that it was realized by nesting two RecyclerView layers. The displayed Layout is roughly as follows:

So here’s how to achieve this effect. At first I thought I was going to use nested scrolling, but it turns out I can do it without nested scrolling.

Now we can roughly construct a layout like this:

We take ViewPager and TabLayout as an item of external RecyclerView. ViewPager may have multiple internal recyclerViews. We can basically solve this problem if we can get the external RecyclerView and internal RecyclerView sliding events to distribute correctly.

If we just construct this layout, we find that the internal RecyclerView will not be displayed, because the sliding is completely taken over by the external RecyclerView.

So the point is, how do you handle this situation?

In fact, RecyclerView LayoutManager has these two methods used to judge whether RecyclerView can roll in the horizontal direction and the vertical direction.

public boolean canScrollHorizontally() {
    return false;
}

public boolean canScrollVertically() {
    return false;
}
Copy the code

And then there are all kinds of different implementation LinearLayoutManager LayoutManager, StaggeredGridLayoutManager, etc

This layer Manager canScrollHorizontally and canScrollHorizontally in RecyclerView onInterceptTouchEvent is used as a judgment. Determine whether the current RecyclerView needs to process sliding events.

There is also a point to note: we deal with the sliding conflict of internal and external RecyclerView, mainly want to solve the following two scenarios: A, finger sliding, when the external RecyclerView sliding bottom, the internal RecyclerView can continue to respond to the user’s sliding, because the internal RecyclerView can theoretically be infinite rolling; Two, finger slide, when the internal RecyclerView slide to the top, the external RecyclerView can continue to respond to the user’s sliding events.

In fact, the above has said how to deal with the most important point of nested RecyclerView, the other part is equivalent to some details of the processing.

Rewrite LayoutManager canScrollVertically in external RecyclerView(ParentRecyclerView)

fun initLayoutManager() { val linearLayoutManager = object :LinearLayoutManager(context) { override fun canScrollVertically():Boolean { / / find the current childRecyclerView val childRecyclerView = findNestedScrollingChildRecyclerView () ParentRecyclerView can be rotated vertically only when the current childRecyclerView slides to the topreturn childRecyclerView == null || childRecyclerView.isScrollTop()
        }

    }
    linearLayoutManager.orientation = LinearLayoutManager.VERTICAL
    layoutManager = linearLayoutManager
}
Copy the code

IsScrollTop () is defined in internal RecyclerView(hereinafter referred to as ChildRecyclerView), which is used to judge whether ChildRecyclerView scrolls to the top.

Fun isScrollTop () : a Boolean {/ / RecyclerView canScrollVertically (1) the value of the said whether can scroll down,falseScroll to the topreturn! canScrollVertically(-1) }Copy the code

In addition, in ParentRecyclerView’s onTouchEvent method:

override fun onTouchEvent(e: MotionEvent): Boolean {
        if(lastY == 0f) {
            lastY = e.y
        }
        if(isScrollEnd()) {// If the parent RecyclerView has slid to the bottom, Need to let the child RecyclerView sliding distance remaining val childRecyclerView = findNestedScrollingChildRecyclerView childRecyclerView ()? .run { val deltaY = (lastY - e.y).toInt()if(deltaY ! = 0) { scrollBy(0,deltaY) } } } lastY = e.yreturn try {
            super.onTouchEvent(e)
        } catch (e: Exception) {
            e.printStackTrace()
            false}}Copy the code

The main code about sliding events is the above, you can see the project code NestedRecyclerView.

In RecyclerView onScrollStateChanged callback listen for the total offset of the Y axis totalDy And then when RecyclerView does not scroll, it is handed over to internal or external RecyclerView to fling. There is no need to elaborate here. The specific code can be seen in the project.

Finally, paste a running GIF of the project to show:

The last

Writing is not easy, welcome to give your thumbs up, if you have any questions, welcome to discuss, your thumbs up is the biggest motivation for my writing, thank you!