As of version 5.3, as the circle detail page became more and more rich, the previous page structure was no longer sufficient for our needs, and a new layout scheme was needed to host the various circle elements and accommodate our custom interactions.

Before the revision

Before the revision, the structure is relatively simple. The head shows the basic information of the circle, such as picture, title and introduction, and the bottom shows the list of messages in the circle. The foldable head area slides up so that users can browse the list of messages more attentively.

  • CoordinatorLayout
    • AppBarLayout
    • ViewPager
      • Fragment
        • RecyclerView
      • Fragment
        • RecyclerView

CoordinatorLayout as a container is responsible for the two-part layout and linkage, AppBarLayout is responsible for the head information, the bottom through ViewPager and Fragment to achieve multi-tab pages, Fragment internal message list through RecyclerView.

After revision

After the revision, some new elements were added to the header, such as plug-ins and creators. The original element display area was expanded, resulting in an increase in the height of the header. To solve the problem of making the message list area almost invisible when the user first enters the circle page, we need the page to quickly switch between the head and the list, and also to slide when the head is more than one screen. A brief summary of our requirements:

  1. When the header information is less, that is, the performance is consistent with the original implementation when the header does not reach a screen, the header can be folded by sliding along the list.
  2. When there is a lot of header information, that is, when there is more than one screen, in addition to sliding and folding the header along with the list, it can also quickly switch between the header and the list.
The solution

The original CoordinatorLayout can support the first requirement. The problem is how to realize the fast switching in the second requirement. Finally, our product colleague gave the following solution:

From this screenshot it looks like the original folding of the head, but it’s not. To fold the head, you need to slide the head out of the interface first, but the head is not actually sliding, the list is on top of the head, and when you want to see the head, slide the list down.

One might say, well, what’s the difference between this and the old one? It’s all sliding. There are three main benefits to this implementation:

  1. The list can only be expanded and hidden, and there is no half display. When you drag the list to the middle of the screen and release it, it will automatically slide to expand or hide, reducing the difficulty of switching between the head and the list.
  2. After the list has been sliding for several screens, you can still drag the Scroll bar to slide the list out of the display head without sliding the list to the top and then pulling out the head.
  3. When the header is long, the header content slide can be dragged anywhere to slide the ScrollBar out of the list, without needing to slide the header to the bottom.

The Scrollbar here is a helper component, which I’ll cover later, but I won’t expand it here.

The original CoordinatorLayout could not meet these requirements, so we needed to implement a custom component, which we named SlideLayout because the main function of this component is to slide the bottom of the page in and out.

Detailed idea
Nested rolling

Both the original logic and the new logic belong to the linkage interaction of a pair of nested components, and it is not difficult to see the need to use the nested rolling mechanism to achieve. First, let’s take a quick look at nested scrolling:

In the diagram, Parent represents the component that implements the NestedScrollingParent interface, and Child represents the component that implements the NestedScrollingChild interface. Parent receives the scrolling events distributed by the Child, and they are not directly related.

SlideLayout structure

As shown in the image above, when you pull down the refresh animation in SlideLayout, you can see three components: Refresh, Header, and Slider, which have the following meanings:

  1. Refresh: Refresh is responsible for displaying the loading animation when the user pulls down to refresh the page.
  2. Header: Responsible for the page header and needs to include a component that implements NestedScrollingChild to distribute scroll events to SlideLayout.
  3. Slider: Is responsible for the list area and also needs to contain a component that implements NestedScrollingChild for the same reason as header.

NestedScrollingChild components have NestedScrollView, RecyclerView, etc., circle details page for this page, NestedScrollView realized the page head, RecyclerView realized the message list.

Operating state

When dealing with scrolling events in SlideLayout, we define three states with an enumerated type:

enum class SlideGesture { SCROLL, SLIDE, REFRESH }
Copy the code
  • SCROLL

    The slider and header are attached, that is, the bottom edge of the header and the top edge of the slider do not overlap. This state needs to be consistent with the old version, that is, the head scroll as the list. The diagram below:

  • SLIDE

    The slider is placed on top of the header. The slider may be displayed or hidden. Its main job is to slide the slider out or hide it. The diagram below:

  • REFRESH

    Show the refresh animation, that is, the refresh animation part height is greater than 0. The following figure only shows the scrolling state, but it is also possible to enter the Refresh state from the Slide state. Similar to this, a Refresh animation display area will appear above the head, which is not listed here.

The conversion relationship of the three states is shown as follows:

Once the state definition is confirmed, the rest of the work is basically divided into two parts: state recognition and scroll processing.

State recognition

According to the state definition mentioned above, the state judgment logic can be obtained as follows:

We set the priority of the Refresh state to the highest and check whether the Refresh region is in Refresh state by checking whether the height of the Refresh region is greater than 0. Since Slide is defined as a slider with a header that overlaps, and sliders are represented by sliderTop in SlideLayout, So we can check if it is Slide state by sliderTop < headerHeight. If neither of the last two conditions is satisfied, it’s going to be an Scroll state.

The rolling process

For different states, different processing rules are defined for rolling events to achieve the desired interaction. The specific processing logic is shown in the following table:

Horizontal: state

Vertical: scroll
Refresh Scroll Slide
NestedPreScroll scroll up Consume the scroll event and collapse the refresh animation area Consume the scroll event, folding the header Consume the scroll event and expand slider
NestedPreScroll scroll down Don’t consume Don’t consume Don’t consume
NestedScroll scroll up Don’t consume Don’t consume Don’t consume
NestedScroll down Consume the scroll event and expand the refresh animation area Consume the scroll event, expanding the header Consume the scroll event and collapse slider

Horizontal representation of three states, vertical representation of two types of rolling events, combined with six different cases. The logical processing given here is relatively simple, the actual implementation will encounter a lot of special processing, here is not a list, interested students can view the project source, the project address will be given at the end.

Using the instance

Let’s use a GIF to see what the result looks like. The first is when the head does not exceed the screen:

Then look at the head over the screen:

The page layout structure is as follows:

<SlideLayout>
    <! -- header -->
    <FrameLayout>
        <androidx.core.widget.NestedScrollView>
            <! -- header content here -->
        </androidx.core.widget.NestedScrollView>
    </FrameLayout>
    <! -- slider -->
    <FrameLayout>
        <LinearLayout>
            <SlideBarLayout>
                <! -- slide bar content here -->
            </SlideBarLayout>
            <androidx.recyclerview.widget.RecyclerView />
        </LinearLayout>
    </FrameLayout>
    <! -- refresh -->
    <RefreshViewLayout/>
</SlideLayout>
Copy the code
  • SlideBarLayout: The scroll bar in the GIF image above, refer to the slider component implemented by AppBarLayout, we have source code in the open source project, interested students can go to check.
  • RefreshViewLayout: a container for the refresh animation component, which can be implemented by implementing the RefreshView interface to create a custom refresh animation and setting the refreshInterface to RefreshViewLayout.
  • For more information on how to use SlideLayout, visit the SlideLayout project home page.
conclusion

This article introduces why SlideLayout is needed, and briefly explains the design idea and implementation mechanism, hoping to inspire and help readers. As a concrete implementation of nested scrolling mechanism, I deeply feel the power of this interface function in the development process. Although the definition is simple, it can almost achieve a variety of page linkage effects. Due to my limited level, if there is any problem in the article or code, it is inevitable, welcome to comment or issue.

SlideLayout project address: Iftech-Android-Slide-Layout, if you are interested in the specific implementation can go to check, welcome star and follow.

Reference article:

  • Level 3 NestedScroll NestedScroll practice
  • NestedScrollingParent2
  • NestedScrollingChild2