Sometimes when you’re developing a project, you’ll come across a complex page that requires multiple lists or sliding layouts, or even webViews, to form a complete page. To achieve such a complex page, in the past, we may nest multiple recyclerViews, WebViews and ScrollViews in a large ScrollView by nesting layout. But not only does this nesting approach seriously affect layout performance, but it can also be a headache to handle the collision of sliding events, which can seriously affect the user experience. ConsecutiveScrollerLayout is designed to solve this difficult problem slide layout, It can nest multiple sliding layouts (RecyclerView, WebView, ScrollView, etc.) and ordinary controls (TextView, ImageView, LinearLayou, custom View, etc.) at the same time, it treats all the sub-views as a whole, Unified process layout by ConsecutiveScrollerLayout sliding, allows multiple sliding layout as a whole continuous sliding, its effect is like a ScrollView. And it supports the nesting of all views, with good versatility. The user doesn’t have to worry about how it slides, doesn’t have to worry about sliding conflicts, and doesn’t have to worry about it affecting the performance of the child View.

The following is an introduction to the use of ConsecutiveScrollerLayout and a written notice.

Project address: ConsecutiveScroller

ConsecutiveScroller design train of thought and the analysis of source: Android sustainable slide layout: ConsecutiveScrollerLayout

rendering

Introduction of depend on

Add the following code in Project build.gradle

allprojects {
      repositories {
         ...
         maven { url 'https://jitpack.io'}}}Copy the code

Add the following code to build.gradle in Module

implementation 'com. Making. Donkingliang: ConsecutiveScroller: 2.6.2'
Copy the code

Note: If you plan to use this library, be sure to read the documentation below carefully. It can make you understand ConsecutiveScrollerLayout can realize the function, and avoid unnecessary mistakes.

The basic use

ConsecutiveScrollerLayout use is very simple, need the layout of the sliding as ConsecutiveScrollerLayout immediate child of the View.

<?xml version="1.0" encoding="utf-8"? >
<com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/scrollerLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scrollbars="vertical">

    <WebView
        android:id="@+id/webView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:background="@android:color/holo_red_dark"
        android:gravity="center"
        android:orientation="vertical">

    </LinearLayout>

    <androidx.core.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    </androidx.core.widget.NestedScrollView>

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

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:scaleType="fitXY"
        android:src="@drawable/temp" />

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    </ScrollView>

  <! - can be nested ConsecutiveScrollerLayout -- -- >
  <com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/design_default_color_primary">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text=""
            android:textColor="@android:color/black"
            android:textSize="18sp" />

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

    </com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout>
</com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout>
Copy the code

On the margin

ConsecutiveScrollerLayout support left and right margin, but does not support the fluctuation margin, spacing between the View can be set through the Space.

<?xml version="1.0" encoding="utf-8"? >
<com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/scrollerLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scrollbars="vertical">

    <! -- Use Space to set upper and lower margins -->
    <Space
        android:layout_width="0dp"
        android:layout_height="20dp" />

    <! - ConsecutiveScrollerLayout support left and right margin, but does not support fluctuation margin -- -- >
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"
        android:background="@android:color/holo_red_dark"
        android:gravity="center"
        android:orientation="vertical">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="LinearLayout"
            android:textColor="@android:color/black"
            android:textSize="18sp" />

    </LinearLayout>

    <! -- Use Space to set upper and lower margins -->
    <Space
        android:layout_width="0dp"
        android:layout_height="20dp" />

</com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout>

Copy the code

Layout alignment

ConsecutiveScrollerLayout layout is similar to the vertical LinearLayout, but it’s not gravity and child view layout_gravity attribute. ConsecutiveScrollerLayout child view for it to provide the layout_align properties, father is used to set the view and the layout of the alignment. Layout_align has three values: LEFT, RIGHT, and CENTER.

<?xml version="1.0" encoding="utf-8"? >
<com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout xmlns:android="http://schemas.android.com/apk/res/android"                                                                    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/scrollerLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scrollbars="vertical">
  
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/white"
        android:padding="10dp"
        android:text="吸顶View - 1"
        android:textColor="@android:color/black"
        android:textSize="18sp"
        app:layout_isSticky="true"
        app:layout_align="LEFT"/>// Alignment</com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout>
Copy the code

Nested fragments

If you want to put a nested fragments in ConsecutiveScrollerLayout. Usually we need a layout container to hold our Fragment, or write the Fragment directly into the activity’s layout. If the Fragment is a vertical sliding, then load container needs to be ConsecutiveScrollerLayout fragments, and fragments of root layout is also need to be vertical sliding. We recommend ConsecutiveScrollerLayout or other vertical sliding control (such as: RecyclerView, NestedScrollView) as the root layout of fragments. If your Fragment doesn’t slide vertically, you can ignore this restriction.

<?xml version="1.0" encoding="utf-8"? >
<com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/scrollerLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scrollbars="vertical">

    <! -- Fragment container -->
    <com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout
        android:id="@+id/fragment_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
  
<! MyFragment's root layout is a vertical sliding control -->
   <fragment
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:name="com.donkingliang.consecutivescrollerdemo.MyFragment"/>

</com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout>
Copy the code

Layout to suck the top

In the past, we might have written two identical layouts, one hidden at the top and one nested under a ScrollView, listening to the ScrollView slide to show and hide the top layout. This approach is cumbersome and inelegant to implement. ConsecutiveScrollerLayout internal implementation the adsorption at the top of the View function, as long as set a property, you can achieve top absorption function. And support to set up a number of sub-view top, behind the View to top when the front of the top View out of the screen.

<?xml version="1.0" encoding="utf-8"? >
<com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/scrollerLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scrollbars="vertical">

  <! App :layout_isSticky="true" -->
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/white"
        android:padding="10dp"
        android:text="吸顶View - 1"
        android:textColor="@android:color/black"
        android:textSize="18sp"
        app:layout_isSticky="true" />

    <WebView
        android:id="@+id/webView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/white"
        android:orientation="vertical"
        app:layout_isSticky="true">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="Top view-2 I'm a LinearLayout"
            android:textColor="@android:color/black"
            android:textSize="18sp" />

    </LinearLayout>

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

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/white"
        android:padding="10dp"
        android:text="吸顶View - 3"
        android:textColor="@android:color/black"
        android:textSize="18sp"
        app:layout_isSticky="true" />

    <androidx.core.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    </androidx.core.widget.NestedScrollView>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/white"
        android:padding="10dp"
        android:text="吸顶View - 4"
        android:textColor="@android:color/black"
        android:textSize="18sp"
        app:layout_isSticky="true" />

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

</com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout>
Copy the code

Resident roof absorption

If you don’t want the top view to be pushed out of the screen by the top view, and multiple top views are arranged on the top, you can set the permanent top mode: app:isPermanent=”true”.

<?xml version="1.0" encoding="utf-8"? >
<com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/scrollerLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:isPermanent="true"// Keep the ceilingandroid:scrollbars="vertical">

</com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout>
Copy the code

Other methods for top suction function

// Set the distance from the top to the top, and hover the top when it is a certain distance from the top
scrollerLayout.setStickyOffset(50);

// Monitor top change (normal mode)
scrollerLayout.setOnStickyChangeListener(OnStickyChangeListener);
// Monitor top change (permanent mode)
scrollerLayout.setOnPermanentStickyChangeListener(OnPermanentStickyChangeListener);
// Get the current top view(normal mode)
scrollerLayout.getCurrentStickyView(); 
// Get the current top view(permanent mode)
scrollerLayout.getCurrentStickyViews();
Copy the code

Local sliding

ConsecutiveScrollerLayout all child View will be regarded as a whole, unified handling page slide events by it, so it will default to intercept slide child View slide events, distributed processing by yourself. And will track the user’s finger sliding events, calculate adjustment ConsecutiveScrollerLayout sliding shift. If you want a child View to handle its own slide events, you can tell the parent View not to block its slide events by setting the layout_issize property. This allows the parent View to slide within its own height. Rather than as part of the ConsecutiveScrollerLayout to deal with.

<?xml version="1.0" encoding="utf-8"? >
<com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/scrollerLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scrollbars="vertical">
  
<! App: layout_isavg ="false" so that the parent layout does not block sliding events -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_isConsecutive="false">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="The red area below is a RecyclerView, which slides independently within its own boundaries."
            android:textColor="@android:color/black"
            android:textSize="18sp"
            app:layout_isSticky="true" />

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerView1"
            android:layout_width="match_parent"
            android:layout_height="300dp"
            android:layout_marginLeft="50dp"
            android:layout_marginRight="50dp"
            android:layout_marginBottom="30dp"
            android:background="@android:color/holo_red_dark"/>

    </LinearLayout>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:text="Down here is a NestedScrollView, and it slides in its own scope."
        android:textColor="@android:color/black"
        android:textSize="18sp" />

    <androidx.core.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="250dp"
        app:layout_isConsecutive="false">

    </androidx.core.widget.NestedScrollView>

</com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout>
Copy the code

In this example NestedScrollView hope in their height sliding their own content, rather than follow ConsecutiveScrollerLayout sliding, as long as layout_isConsecutive = “false” to set it up. The LinearLayout is not a sliding layout, but it has a sliding RecyclerView nested underneath it, so it also needs to set layout_issize =”false”.

ConsecutiveScrollerLayout support NestedScrolling mechanism, if you view has realized the partial sliding NestedScrollingChild interface (such as: RecyclerView, NestedScrollView, etc.), it will slide after the completion of the sliding event to the parent layout. If you don’t want your child view or its child view to slide nested with the parent layout, you can set app layout_isNestedScroll=”false” for the child view. It can view and ConsecutiveScrollerLayout nested sliding is prohibited

Slide a child view of a child view

ConsecutiveScrollerLayout by default will only deal with its immediate child view slide, but sometimes need to slide layout may not be ConsecutiveScrollerLayout child view directly, but the son view nested view at a lower level. Such as ConsecutiveScrollerLayout nested FrameLayout FrameLayout nested ScrollView, we hope ConsecutiveScrollerLayout can normal processing ScrollView sliding. To support this requirement, ConsecutiveScroller provided an interface: IConsecutiveScroller. Child view IConsecutiveScroller interface is implemented, and by the method of implementing an interface tell ConsecutiveScrollerLayout need slide view at a lower level, sliding ConsecutiveScrollerLayout can correctly handle it. IConsecutiveScroller needs to implement two methods:

    /** * returns the current subview that needs to be slid. Only one view can slide at a time. * /
    View getCurrentScrollerView(a);

    /** * returns all child views that can slide. Since ConsecutiveScrollerLayout allows its child view contains more than one slide of view, it returns a list view. * /
    List<View> getScrolledViews(a);
Copy the code

In the previous example, we could implement this:

public class MyFrameLayout extends FrameLayout implements IConsecutiveScroller {

    @Override
    public View getCurrentScrollerView(a) {
        // Returns the ScrollView to slide
        return getChildAt(0);
    }

    @Override
    public List<View> getScrolledViews(a) {
        // Returns the ScrollView to slide
        List<View> views = new ArrayList<>();
        views.add(getChildAt(0));
        returnviews; }}Copy the code
<?xml version="1.0" encoding="utf-8"? >
<com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/scrollerLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scrollbars="vertical">

    <com.donkingliang.consecutivescrollerdemo.widget.MyFrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <ScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent">

            </LinearLayout>
        </ScrollView>
    </com.donkingliang.consecutivescrollerdemo.widget.MyFrameLayout>
</com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout>
Copy the code

So ConsecutiveScrollerLayout can correctly handle ScrollView sliding. This is a simple example, in a real world requirement, we generally don’t need to do this.

Note:

GetCurrentScrollerView () and getScrolledViews() must correctly return the views to be slid, which may be nested in multiple layers, not necessarily direct child views. So users should implement the two methods according to their own actual scenarios.

2. The sliding control should be the same height as the nested child view, that is, the sliding control should be match_parent, and its child view and ppAdding should not be set margin or ppAdding. Width has no such limitation.

ViewPager support

If your ViewPager hosts sub-layouts (or fragments) that don’t slide vertically, use plain ViewPager. If you can slide vertically, then your ViewPager needs to implement the IConsecutiveScroller interface and return the view object that you want to slide. The framework provided a custom control that implemented the IConsecutiveScroller interface: ConsecutiveViewPager. Using this control, then your ConsecutiveViewPager child view(or the root layout of the Fragment) is a vertically sliding view, as in: RecyclerView, NestedScrollView, ConsecutiveScrollerLayout can. So your ViewPager can correctly with ConsecutiveScrollerLayout sliding.

<?xml version="1.0" encoding="utf-8"? >
<com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/scrollerLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scrollbars="vertical">

    <com.google.android.material.tabs.TabLayout
        android:id="@+id/tabLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/white"
        app:tabGravity="fill"
        app:tabIndicatorColor="@color/colorPrimary"
        app:tabIndicatorHeight="3dp"
        app:tabMode="scrollable"
        app:tabSelectedTextColor="@color/colorPrimary" />

    <com.donkingliang.consecutivescroller.ConsecutiveViewPager
        android:id="@+id/viewPager"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</com.donkingliang.consecutivescroller.ConsecutiveScrollerLayout>
Copy the code

The top of a layout is overlaid on top of the following layout. Sometimes we want the top of a TabLayout to float at the top, but we don’t want it to overwrite the content blocking the ViewPager. ConsecutiveViewPager provides setAdjustHeight to adjust its layout height so that it is not covered by a TabLayout. Note: only ConsecutiveScrollerLayout is ConsecutiveScrollerLayout lowest department can do that.

// Ensure that the height of the tabLayout can be obtained
tabLayout.post(new Runnable() {
    @Override
    public void run(a) { viewPager.setAdjustHeight(tabLayout.getHeight()); }});Copy the code

Other Matters needing attention

1. Sliding layout of WebView during loading may lead to disconnection between WebView and other views in display. The following method can avoid this problem to some extent.

webView.setWebChromeClient(new WebChromeClient() {
            @Override
            public void onProgressChanged(WebView view, int newProgress) {
                super.onProgressChanged(view, newProgress); scrollerLayout.checkLayoutChange(); }});Copy the code

2, SmartRefreshLayout and SwipeRefreshLayout refresh controls can be nested ConsecutiveScrollerLayout drop-down refresh functions, But ConsecutiveScrollerLayout nested inside them to refresh the view, because the son ConsecutiveScrollerLayout sliding content such as part of the view. Unless you set SmartRefreshLayout or SwipeRefreshLayout to app: layout_isrow =”false”.

3, inherit the AbsListView layout (ListView, GridView, etc.), sliding may be out of sync with the user’s finger sliding, RecyclerView is recommended instead.

4. ConsecutiveScroller’s minSdkVersion is 19, and if your project supports less than 19, you can set:

<uses-sdk tools:overrideLibrary="com.donkingliang.consecutivescroller"/>
Copy the code

But don’t in the minSdkVersion is less than 19 projects using AbsListView subclass, because ConsecutiveScrollerLayout use is only for more than 19 AbsListView API.

5, use ConsecutiveScrollerLayout setOnVerticalScrollChangeListener slide () method to monitor the layout of the event. View the setOnScrollChangeListener provided () method has been invalid.

6, through getOwnScrollY () method to obtain ConsecutiveScrollerLayout vertical sliding distance, the View of getScrollY () method to get not ConsecutiveScrollerLayout integral sliding distance.