I don’t know if you read the source code, it is mainly the use of nested scrolling, and the code is relatively difficult to understand some.

Recently I saw the author of this article, the author abandoned nested scrolling, through the way of custom ViewGroup implementation, relatively speaking, the code is easy to understand too much, I also have a practical experience, the experience effect is ok.

Carefully look at the source code, the author’s idea is still worth learning, at the beginning I thought is the default to all the internal space expanded lose the scrolling effect, to the outer layer, after actually looking at the code found that did not abandon RecyclerView reuse, he scrollTo its outer layer, will consider to the inner control scrolling, Interested can learn a wave.

An overview of the

ConsecutiveScrollerLayout is my a in making open source Android custom slide layout, it can make multiple slide layout and normal controls on the interface as a whole continuous sliding.

Imagine that we have such a requirement, on an interface, there is a rotation chart, a classified layout like a nine-grid, several lists of different styles, and in the middle are mixed with various advertising pictures and the layout of displaying various activities, such a design is very common on the home page of large apps.

Another example is a WebView with a native review list, recommendation list and advertising space, such as an article details page for consulting or a product details page for e-commerce. This kind of complex layout is often difficult to implement, and it requires high sliding smoothness of the page and display efficiency of the layout.

Before I met this kind of complex layout, can I use in making open source project GroupedRecyclerViewAdapter implementation.

https://github.com/donkingliang/GroupedRecyclerViewAdapter

GroupedRecyclerViewAdapter was designed, which is in order to make secondary RecyclerView easily lists, lists and displayed on a RecyclerView different list.

GroupedRecyclerViewAdapter support set different item types of the head, tail, and children, all it can on a RecyclerView display a variety of different layout and list, also accord with the demand of complex layout.

But because GroupedRecyclerViewAdapter is not designed for the complex layout, use it to achieve this layout, need to users on the subclass of GroupedRecyclerViewAdapter manage data and various types of layout of the page display logic, heavy look trouble again.

If it is not integrated in a RecyclerView, but the nesting implementation of the layout, not only seriously affect the performance of the layout, but also to solve the sliding conflict is a headache. Although Google introduced NestedScrolling in Android 5.0 to better handle scrolling conflicts, it’s still not easy to handle scrolling on your own.

No matter how complex a page is, it is made up of small controls. If we can have a layout container that handles the sliding of all the child views in the layout, so that both the normal controls and the sliding layout can be slid in the container as a whole, just like sliding a normal ScrollView. So we don’t have to worry about layout sliding conflicts and sliding performance anymore.


No matter how complex the layout, we only need to think about the small parts of the layout and use what controls, any complex layout will not be complicated.


ConsecutiveScrollerLayout designed based on this demand.



Design ideas


ConsecutiveScrollerLayout in conception, I was considering using NestedScrolling mechanism to realize, but later I gave up the plan, there are two main reasons:


1. NestedScrolling coordinates parent and child scroll conflicts, distributes scroll events, and allows them to scroll independently.


This is not what I want to put ConsecutiveScrollerLayout slide of all child View as a whole idea, I hope the content of the traverse to the child View as part of ConsecutiveScrollerLayout content, Both in ConsecutiveScrollerLayout itself and its View, unified handling by ConsecutiveScrollerLayout sliding event.


2. NestedScrolling requires the parent to implement NestedScrollingParent, and all sliding child views to implement NestedScrollingChild.


And I hope ConsecutiveScrollerLayout on using as much as possible without limitation, any View in it can be a very good job, and the child View do not need to care about how it is sliding.


After rejecting the NestedScrolling mechanism, I tried to find a breakthrough point by sliding the content of the View. I’ve noticed that almost all Android views slide their contents through the scrollBy() -> scrollTo() method, and most sliding layouts call this method either directly or indirectly.


So these two methods are the entry point for handling layout sliding, and you can rewrite them to redefine the layout sliding logic.


Specific ideas by intercepting can slide as part of the view of sliding, prevented it from sliding, and the event was a ConsecutiveScrollerLayout processing, ConsecutiveScrollerLayout rewrite scrollBy (), scrollTo () method, the scrollTo () method of distribution of sliding offset by calculation, decisions are made by itself or specific consumption View of sliding distance, Call its super.scrollto () and its child’s scrollBy() to slide the contents of its and child views.


Say so many, let us through code, analyze ConsecutiveScrollerLayout is how to implement. The code given below is some of the main snippet of the source code, deleted some of the processing details unrelated to the design ideas and the main process, for better understanding of its design and implementation principles.


rendering


Before beginning, let everybody see ConsecutiveScrollerLayout implementation effect.




OnMeasure, onLayout


ConsecutiveScrollerLayout inherited from ViewGroup, a custom layout always be rewritten onMeasure, onLayout to measure and locate the View.


@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int count = getChildCount();
    for (int i = 0; i < count; i++) {
        View child = getChildAt(i);
        measureChild(child, widthMeasureSpec, heightMeasureSpec);
    }
}

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    mScrollRange = 0;
    int childTop = t + getPaddingTop();
    int left = l + getPaddingLeft();

    List<View> children = getNonGoneChildren();
    int count = children.size();
    for(int i = 0; i < count; i++) { View child = children.get(i); int bottom = childTop + child.getMeasuredHeight(); child.layout(left, childTop, left + child.getMeasuredWidth(), bottom); childTop = bottom; MScrollRange += child.getheight (); mScrollRange += child.getheight (); } mScrollRange -= getMeasuredHeight() -getPaddingTop () -getPaddingBottom (); } /** * private List<View>getNonGoneChildren() {
    List<View> children = new ArrayList<>();
    int count = getChildCount();
    for (int i = 0; i < count; i++) {
        View child = getChildAt(i);
        if (child.getVisibility() != GONE) {
            children.add(child);
        }
    }
    return children;
}Copy the code


The logic of onMeasured is very simple, traversing the measurement sub VEW.


OnLayout is an arrangement of child views from top to bottom, just like a vertical LinearLayout. The getNonGoneChildren() method filters out hidden child views that do not participate in the layout.


The mScrollRange variable above is the slider range of the layout itself, which is equal to the height of all child Views minus the height of the layout’s own content display. Later, it will be used to calculate slide offsets and margin limits for the layout.



Intercept slide event


Said earlier ConsecutiveScrollerLayout will intercept it slide of the view of sliding, sliding to handle all by itself. Here is an implementation of its interception of events.


@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    if(ev.getAction() == motionEvent.action_move) {// The event needs to be interceptedif (isIntercept(ev)) {
            return true; }}return super.onInterceptTouchEvent(ev);
}Copy the code


If it is sliding event (ACTION_MOVE), determine whether need to intercept the events, intercept directly returns true, let events by ConsecutiveScrollerLayout onTouchEvent treatments. The key to determining whether an intercept is needed is the isIntercept(EV) method.


Private Boolean isIntercept(MotionEvent ev) {// Get the child view target = of the current touch according to the touch point getTouchTarget((int) ev.getRawX(), (int) ev.getRawY());if(target ! Viewgroup.layoutparams lp = target.getLayOutParams ();if (lp instanceof LayoutParams) {
              if(! ((LayoutParams) lp).isConsecutive) {return false; }} // Check whether the child view can slide verticallyif (ScrollUtils.canScrollVertically(target)) {
              return true; }}return false;
  }

public class ScrollUtils {

static boolean canScrollVertically(View view) {
      return canScrollVertically(view, 1) || canScrollVertically(view, -1);
  }

static boolean canScrollVertically(View view, int direction) {
      returnview.canScrollVertically(direction); }}Copy the code


Determine whether need to intercept the events, mainly by judging whether can touch the child view of vertical sliding, vertical sliding, if can intercept events, event processing by ConsecutiveScrollerLayout themselves. If not, don’t intercept, generally can’t sliding view will not consume events, so events will eventually by ConsecutiveScrollerLayout consumption. The reason for not intercepting directly is to give the child view as much opportunity as possible to handle the event and distribute it to the view below.


Here’s a isConsecutive LayoutParams attributes, it is ConsecutiveScrollerLayout LayoutParams custom attributes, Used to represent a child view whether to allow ConsecutiveScrollerLayout intercept it slide events, the default is true. If it is set to false, the parent layout will not intercept the child view’s events, but will defer them entirely to the child view. This gives the child view the opportunity to handle the sliding event and the initiative to distribute the event.


This is useful for special needs that require sliding within a local area. I have a detailed explanation of isavg in the demo and usage guide provided on GitHub, so I won’t go into too much detail here.


Sliding handle

Once the event is intercepted, the sliding event is handled in the onTouchEvent method.


@Override
public boolean onTouchEvent(MotionEvent ev) {
    switch (ev.getAction()) {
        caseACTION_DOWN: // Record the touch point mTouchY = (int) ev.gety (); / / track sliding speed initOrResetVelocityTracker (); mVelocityTracker.addMovement(ev);break;
        case MotionEvent.ACTION_MOVE:
            if (mTouchY == 0) {
                mTouchY = (int) ev.getY();
                return true; } int y = (int) ev.getY(); int dy = y - mTouchY; mTouchY = y; // Slide layout scrollBy(0, -dy); / / track sliding speed initVelocityTrackerIfNotExists (); mVelocityTracker.addMovement(ev);break;
        case MotionEvent.ACTION_CANCEL:
        case MotionEvent.ACTION_UP:
            mTouchY = 0;

            if(mVelocityTracker ! Sliding mVelocityTracker.com = null) {/ / processing inertia puteCurrentVelocity (1000, mMaximumVelocity); int yVelocity = (int) mVelocityTracker.getYVelocity(); recycleVelocityTracker(); fling(-yVelocity); }break;
    }
    return true; } // Private void fling(int velocityY) {if (Math.abs(velocityY) > mMinimumVelocity) {
        mScroller.fling(0, mOwnScrollY,
                1, velocityY,
                0, 0,
                Integer.MIN_VALUE, Integer.MAX_VALUE);
        invalidate();
    }
}

@Override
public void computeScroll() {
    if(mScroller.computeScrollOffset()) { int curY = mScroller.getCurrY(); // dispatchScroll(curY); invalidate(); }}Copy the code


The logic of the onTouchEvent method is very simple, which is to slide the layout content through the View’s scrollBy method according to the sliding distance of the finger, and track the sliding speed of the finger through the VelocityTracker. Inertial sliding is realized by Scroller with computeScroll() method.


Distribution of slip distance

In the treatment of the inertia is sliding, we call the dispatchScroll () method, this method is the core of the whole ConsecutiveScrollerLayout, it determines who should to consume the sliding, sliding the layout should be.


In fact ConsecutiveScrollerLayout scrollBy () and scrollTo () method is invoked to handle eventually slip distribution.

There’s a mOwnScrollY attribute, is used to record ConsecutiveScrollerLayout integral sliding distance, equivalent to the View of mScrollY properties.


The dispatchScroll() method divides sliding into two parts: up and down. Let’s look at the handling of the slide up part first.

private void scrollUp(int offset) { int scrollOffset = 0; Int remainder = offset; // Unconsumed sliding distancedo{ scrollOffset = 0; // Whether to slide to the bottomif(! IsScrollBottom () {firstVisibleView = findFirstVisibleView();if(firstVisibleView ! = null) { awakenScrollBars(); / / get the View at the bottom of the slide to itself the offset int bottomOffset = ScrollUtils. GetScrollBottomOffset (firstVisibleView);if(bottomOffset > 0) {// If bottomOffset is greater than 0, it means that the view has not slid to the bottom of itself, so the view consumes the sliding distance. int childOldScrollY = ScrollUtils.computeVerticalScrollOffset(firstVisibleView); // Calculate the distance to slide scrollOffset = math. min(REMAINDER, bottomOffset); // scrollChild(firstVisibleView, scrollOffset); / / calculate the real sliding distance scrollOffset = ScrollUtils.com puteVerticalScrollOffset (firstVisibleView) - childOldScrollY; }elseInt selfOldScrollY = getScrollY(); int selfOldScrollY = getScrollY(); int selfOldScrollY = getScrollY(); / / computing needs of sliding distance scrollOffset = math.h min (remainder, firstVisibleView getBottom (), getPaddingTop () - getScrollY ()); // Slide parent layout scrollSelf(getScrollY() + scrollOffset); ScrollOffset = getScrollY() -selfoldscrolly; } // Calculate the sliding distance of consumption, if not finished consumption, continue the cycle of consumption. mOwnScrollY += scrollOffset; remainder = remainder - scrollOffset; }}}while (scrollOffset > 0 && remainder > 0);
}

public boolean isScrollBottom() {
    List<View> children = getNonGoneChildren();
    if (children.size() > 0) {
        View child = children.get(children.size() - 1);
        returngetScrollY() >= mScrollRange && ! child.canScrollVertically(1); }return true;
}

public View findFirstVisibleView() {
    int offset = getScrollY() + getPaddingTop();
    List<View> children = getNonGoneChildren();
    int count = children.size();
    for (int i = 0; i < count; i++) {
        View child = children.get(i);
        if (child.getTop() <= offset && child.getBottom() > offset) {
            returnchild; }}returnnull; } private void scrollSelf(int y) { int scrollY = y; // boundary detectionif (scrollY < 0) {
        scrollY = 0;
    } else if (scrollY > mScrollRange) {
        scrollY = mScrollRange;
    }
    super.scrollTo(0, scrollY);
}

private void scrollChild(View child, int y) {
    child.scrollBy(0, y);
}Copy the code


The logic for swiping up is to find the first child view that is currently displayed, determine whether its contents have been swiped to the bottom, and if not, let it consume the swiping distance.


If the slide to the bottom of it already, it is consumption by ConsecutiveScrollerLayout sliding distance, until the child view slide out of the screen. So the next time display for the first view is the next view, repeat the above operation, until the ConsecutiveScrollerLayout and all sub view slide to the bottom, so the whole slide to the bottom.


We use a while loop here, because a sliding distance can be consumed by multiple objects, such as a sliding distance of 50px, but the first child view that is currently displayed needs to slide 10px to its bottom, so the child view will consume 10px. The remaining 40px is the next distribution, finding the objects that need to consume it, and so on.

The process of sliding down is the same as that of sliding up, but the object judged and the direction of sliding are different.

private void scrollDown(int offset) { int scrollOffset = 0; Int remainder = offset; // Unconsumed sliding distancedo{ scrollOffset = 0; // Whether to slide to the topif(! IsScrollTop ()) {// findLastVisibleView = findLastVisibleView();if(lastVisibleView ! = null) { awakenScrollBars(); / / get the View on top of the slide to itself the offset int childScrollOffset = ScrollUtils. GetScrollTopOffset (lastVisibleView);if(childScrollOffset < 0) {// If childScrollOffset is greater than 0, the view has not yet slid to the top of itself, so the view consumes the sliding distance. int childOldScrollY = ScrollUtils.computeVerticalScrollOffset(lastVisibleView); // Calculate the distance to slide scrollOffset = math. Max (REMAINDER, childScrollOffset); // scrollChild(lastVisibleView, scrollOffset); / / calculate the real sliding distance scrollOffset = ScrollUtils.com puteVerticalScrollOffset (lastVisibleView) - childOldScrollY; }elseInt scrollY = getScrollY(); int scrollY = getScrollY(); int scrollY = getScrollY(); ScrollOffset = math.max (REMAINDER, lastVisibleView.getTop() + getPaddingBottom() - scrolly-getheight ()); // Slide parent layout scrollSelf(scrollY + scrollOffset); ScrollOffset = getScrollY() -scrolly; } // Calculate the sliding distance of consumption, if not finished consumption, continue the cycle of consumption. mOwnScrollY += scrollOffset; remainder = remainder - scrollOffset; }}}while (scrollOffset < 0 && remainder < 0);
}

public boolean isScrollTop() {
    List<View> children = getNonGoneChildren();
    if (children.size() > 0) {
        View child = children.get(0);
        returngetScrollY() <= 0 && ! child.canScrollVertically(-1); }return true;
}

public View findLastVisibleView() {
    int offset = getHeight() - getPaddingBottom() + getScrollY();
    List<View> children = getNonGoneChildren();
    int count = children.size();
    for (int i = 0; i < count; i++) {
        View child = children.get(i);
        if (child.getTop() < offset && child.getBottom() >= offset) {
            returnchild; }}return null;
}Copy the code

This article is not easy, if you like this article, or helpful to you hope you more, like, forward, follow oh. The article will be updated continuously. Absolutely dry!!