preface

In the previous article, we analyzed Google’s design of NestedScrolling mechanism to understand the usage of different interfaces. Now let’s use a practical example to consolidate the knowledge we learned before.

Results show

Let’s look at the actual effect we need to copy. As shown below:

The demo shown above has a concrete implementation in the project NestedScrollingDemo.

In the Demo above, the entire interface is divided into title bar, display picture, TabLayout, ViewPage. The ViewPager contains multiple fragments. Each fragment has a RecyclerView. The implementation effect of the whole Demo is as follows:

  • When producingupwardIf the picture is not covered by the parent control, the parent control blocks the event and slides. When the picture is completely blocked, the child control (RecyclerView) then processing.
  • When producingdownIf the picture is not fully displayed, the parent control first intercepts the event and slides. When the image is fully displayed, RecyclerView is used to process the child control.
  • The back key in the title bar slides from white to black as the parent control slides.
  • Transparency in the title bar changes from 0 to 1 as the parent controls slide.

Now let’s achieve the effect together!!

Interface Usage Analysis

To implement nested sliding, the first thing that comes to mind is to implement the NestedScrollingChild and NestedScrollingParent interfaces, But our Demo requires the parent control to handle part of the fling, so we use the NestedScrollingChild2 interface with the NestedScrollingParent2 interface. And because of RecyclerView, NestedScrollView and other scrolling View, in Google have implemented NestedScrollingChild2 interface, so we do not need to deal with the child control of gesture sliding and fling distribution, we only care about the parent control processing on the line.

And because the overall layout is vertical, we inherit LinearLayout and implement NestedScrollingParent2 interface. In order to compatible with low version at the same time, we also want to use NestedScrollingParentHelper the helper classes. The concrete class implementation class StickyNavLayout code is shown below;

public class StickyNavLayout extends LinearLayout implements NestedScrollingParent2 {

    private NestedScrollingParentHelper mNestedScrollingParentHelper = new NestedScrollingParentHelper(this);

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

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

    public StickyNavLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setOrientation(VERTICAL);// Set the layout direction to vertical.
    }

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

    @Override
    public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int axes, int type) {
        mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes, type);
    }

    @Override
    public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) {}


    @Override
    public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {}

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

    @Override
    public void onStopNestedScroll(@NonNull View target, int type) {
        mNestedScrollingParentHelper.onStopNestedScroll(target, type);
    }

    @Override
    public int getNestedScrollAxes(a) {
        returnmNestedScrollingParentHelper.getNestedScrollAxes(); }}Copy the code

In the above code, we need to note the following:

  • The default layout of the StickyNavLayout implementation class is vertical.
  • In order for the parent control to handle events in the vertical direction, we need theonStartNestedScrollMethods to judgeaxes & ViewCompat.SCROLL_AXIS_VERTICAL.
  • In order for the child control to also handle the fling, we need to have theonNestedPreFlingMethod returnsfalse. Because in nested slides, if this method returns true, the child controls have no chance to process the fling.
  • To be compatible with lower versions and get the correct nested sliding state, We need to call NestedScrollingParentHelper in onNestedScrollAccepted, onStopNestedScroll, onStopNestedScroll, corresponding method.

Layout Settings

With the basic framework of the parent control in place, we are now ready to deal with the layout of the entire interface. Looking at the Demo, we found that when the title bar is transparent, the image is fully displayed, which means that the title bar layout is on top of the image. The general layout is as follows:

Looking at the Demo implementation, we can see the following points:

  • When producingupwardIf the picture is not covered by the parent control, the parent control blocks the event and slides. When the picture is completely obscured, the child control takes over.
  • When producingdownIf the picture is not fully displayed, the parent control first intercepts the event and slides. When the picture is fully displayed, the child control takes over.

So how to achieve the effect of display picture occlusion? In fact, we simply need to add a transparent View in our parent control at the same height as the display image. So when the parent control is scrolling, it can create a masking effect. The specific design is shown in the figure below:

Then corresponding to the Android layout file, the layout of the entire interface would look like the following:

<?xml version="1.0" encoding="utf-8"? >
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <! -- Show pictures -->
    <ImageView
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:scaleType="fitXY"
        android:src="@drawable/ic_launcher_background"/>

    <! -- Title bar -->
    <include layout="@layout/layout_common_toolbar"/>

    <! Nested sliding parent control -->
    <com.jennifer.andy.nestedscrollingdemo.view.StickyNavLayout
        android:id="@+id/sick_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <! - transparent withmicrosoft -- -- >
        <View
            android:id="@+id/sl_top_view"
            android:layout_width="match_parent"
            android:layout_height="200dp"/>
        <! --TabLayout-->
        <android.support.design.widget.TabLayout
            android:id="@+id/sl_tab"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="#fff"
            app:tabIndicatorColor="@color/colorPrimaryDark"/>
        <! --ViewPager-->
        <android.support.v4.view.ViewPager
            android:id="@+id/sl_viewpager"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#fff"/>
    </com.jennifer.andy.nestedscrollingdemo.view.StickyNavLayout>

</RelativeLayout>
Copy the code

Parent control sliding range

Now that we’ve laid out the overall interface, we need to handle scrolling for the parent control. Looking at the Demo, we can see that the parent control scrolls to the height of the display image minus the height of the title bar. To calculate the scrolling range of the parent control, we need to get the height of the TopView inside the parent control (the transparent View with the same height as the display image). To get the child control of the parent control, we use the onFinishInflate method. The specific code is as follows:

    private View mTopView;// Transparent View at the same height as the display image

   @Override
    protected void onFinishInflate(a) {
        super.onFinishInflate();
        mTopView = findViewById(R.id.sl_top_view);
    }
Copy the code

Once we get the child control, we can get, in onSizeChanged, the distance that the parent control can slide (mCanScrollDistance = the height of the display image – the height of the title bar). The specific code is as follows:

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        mCanScrollDistance = mTopView.getMeasuredHeight() - getResources().getDimension(R.dimen.normal_title_height);
    }
Copy the code

Since the title bar height is usually 48dp, I didn’t get the title bar height separately. Instead, I declared normal_title_height = 48dp in values/ DIMens.

Parent control nested sliding implementation

Having handled the sliding range of the parent control, it is now time to handle the most critical nested sliding. When RecyclerView in ViewPager receives sliding, it will distribute sliding to the parent control first. Our parent control (StickyNavaLayout) needs to determine whether to consume, and the conditions for determining whether to consume are as follows:

  • When scrolling down, if RecyclerView can’t continue to slide down and the parent control (StickyNavaLayout) has slid the distance, the parent control (StickyNavaLayout) needs to consume.
  • When you swipe up, if the parent control has slid some distance, then the parent control needs to consume, needs to consume.

The specific code is as follows:

    @Override
    public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) {
        // If the child view wants to slide up, hand it to the parent view first
        boolean hideTop = dy > 0 && getScrollY() < mCanScrollDistance;
        // If the child view wants to slide down, the child view must not slide down before it can be handed to the parent view
        boolean showTop = dy < 0 && getScrollY() >= 0 && !target.canScrollVertically(-1);
        if (hideTop || showTop) {
            scrollBy(0, dy);
            consumed[1] = dy;// Consumed [0] Indicates the horizontal consumed distance. Consumed [1] indicates the vertical consumed distance}}Copy the code

In the above code, we called the View’s canScrollVertically(int direction) method to check whether the View is able to slide downwards. If direcation is negative, we check whether the View is able to slide downwards. Return false otherwise. If direcation is positive, check whether the View can slide up. If yes, return true; otherwise return false.

Note that in the onNestedPreScroll method, we do not distinguish between gesture sliding and fling, i.e. TYPE_TOUCH(0) and TYPE_NON_TOUCH(1). Because it doesn’t matter if you swipe or fling. Parent controls are handled in Demo effects. So we’re not judging.

Once we’ve dealt with the onNestedPreScroll method, we also need to deal with the onNestedScroll method. Because according to the nesting sliding mechanism, when the parent control is preprocessed, the child control will consume the remaining distance, if the child control consumes, there is still the remaining distance. This is then passed to the parent control. So it’s going to do the onNestedScroll method. In this method, we only need to process the remaining downward fling of the child controls individually. The specific code is as follows:

  @Override
    public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
        if (dyUnconsumed < 0 && type == ViewCompat.TYPE_NON_TOUCH) {//表示已经向下滑动到头,且为fling
            scrollBy(0, dyUnconsumed); }}Copy the code

When a child control has an fling, it passes to the parent control if the child control is not exhausted. So dyConsumed must be useful because we only care about the fling down. So the code says this.

Now that we’re done with nested slides, we also need to check the scrolling range of the parent control (StickyNavaLayout), so we’ll override the scrollTo method directly. Just make a judgment call.

    @Override
    public void scrollTo(int x, int y) {
        if (y < 0) {
            y = 0;
        }
        if (y > mCanScrollDistance) {
            y = (int) mCanScrollDistance;
        }
        if(getScrollY() ! = y)super.scrollTo(x, y);
    }
Copy the code

The parent control (StickyNavaLayout) has a scroll range of 0-mcanScrollDistance. Where mCanScrollDistance = the height of the display image – the height of the title bar.

ViewPager height correction

By now you might think that’s the end of the basic nesting slide. But there’s a problem if you write like this: when our parent control (StickyNavaLayout) scrolls under the title bar, we see that our ViewPager doesn’t fill the remaining distance of the screen, it has a blank distance. As follows:

Because our parent control (StickyNavaLayout) inherits the LinearLayout and the ViewPager height is match_parent, according to the View measurement rules, the ViewPager’s actual height is the remaining height of the screen. So when the parent control (StickyNavaLayout) scrolls under the title bar, there’s a blank space, so in order for the ViewPager to fill the screen, we need to reset the ViewPager height. So we need to override the onMeasure method that’s the parent control, StickyNavaLayout. The specific code is as follows:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // Measure first
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //ViewPager Height = Total height -TabLayout height
        ViewGroup.LayoutParams lp = mViewPager.getLayoutParams();
        lp.height = getMeasuredHeight() - mNavView.getMeasuredHeight();
        mViewPager.setLayoutParams(lp);
        // Because the ViewPager has changed the height, it needs to be remeasured
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
Copy the code

Gradient effect implementation

Now we have left over the last two results, the back key gradients and the change of the title bar of the transparency, implementations are actually very simple, because our parent control (StickyNavaLayout) has a maximum sliding range, then we can get the current parent control the proportion of the sliding distance and maximum range, after obtaining the proportion, We can set the transparency of the title bar. You can also get gradient colors through ArgbEvaluator provided by Google. The concrete realization way, the reader friend can ponder to solve by oneself. Because of space constraints, I won’t explain the specific implementation here. Friend in need, you can see the implementation of the project NestedScrollingDemo NestedScrolling2DemoActivity.

The last

The whole Demo is over, if you have any questions, please feel free to ask ~