Nested series navigation

  • 1. Basic analysis of NestedScrolling nested sliding mechanism
  • 2. Brief analysis of NestedScrolling practice of nested sliding mechanism – imitation writing ele. me business details page
  • 3. Analyze the NestedScrolling nested CoordinatorLayout sliding mechanism. The behaviors
  • 4. Brief analysis of NestedScrolling practice – custom Behavior implementation of Xiaomi music singer details

This article has been originally published on the public account Hongyang. Without permission, shall not be reproduced in any form!

An overview of the

Common effects mentioned in basic Analysis of NestedScrolling nested sliding mechanism mentioned that Behavior also uses NestedScrolling mechanism to achieve various magical sliding effects. It was introduced with CoordinatorLayout in Revision 24.1.0 android.support.v4 compatible package, and combined with CoordinatorLayout to realize the linkage of various controls. Proxies can be intercepted for CoordinatorLayout measurements, layouts, WindowInsets, touch events, nested slides.

Introduction of behaviors

Behavior is an interactive plug-in for a CoordinatorLayout’s direct child View. A Behavior implements one or more user interactions, which may include dragging, sliding, swiping, or other gestures.

    /** * generic 
      
        is the Behavior associated View */
      
    public static abstract class Behavior<V extends View> {

        /** * default constructor, used to create annotations or create */ in code
        public Behavior(a) {}

        /** * the constructor for XML parsing the layout_Behavior attribute, which is required if Behavior support is to be used in XML */
        public Behavior(Context context, AttributeSet attrs) {}

        /** * called after LayoutParams is instantiated, or when layoutParams.setBehavior (behavior) is called. */
        public void onAttachedToLayoutParams(@NonNull CoordinatorLayout.LayoutParams params) {}

        /** * Called when LayoutParams removes Behavior, such as when LayoutParams.setBehavior(null) is called. * View is removed from View This method is not called when removing from a Tree. */
        public void onDetachedFromLayoutParams(a) {}

        /** * Intercepts the Touch event before the CoordinatorLayout is distributed to the child View */
        public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) {
            return false;
        }

        /** * The Touch event is consumed before the CoordinatorLayout is distributed to the child View */
        public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) {
            return false;
        }

        /** * Disconnects the View below the Behavior */
        public boolean blocksInteractionBelow(CoordinatorLayout parent, V child) {
            return getScrimOpacity(parent, child) > 0.f;
        }

        /** * When blocksInteractionBelow returns true, the CoordinatorLayout will draw a masked getScrimColor() color on the top layer of the View to show areas that cannot interact */
        @ColorInt
        public int getScrimColor(CoordinatorLayout parent, V child) {
            return Color.BLACK;
        }

        /** * getScrimColor() draws color transparency */
        @FloatRange(from = 0, to = 1)
        public float getScrimOpacity(CoordinatorLayout parent, V child){
            return 0.f;
        }

        /** * The associated View and the interested View depend on */
        public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) {
            return false;
        }

        /** * callback */ depending on the position and size of the View
        public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) {
            return false;
        }

        /** * callback */ when the View is removed from the layout
        public void onDependentViewRemoved(CoordinatorLayout parent, V child, View dependency) {}

        /** * The CoordinatorLayout agent measures the child View. Note that the child View is associated with the current Behavior, and return true to measure the child View using the Behavior's *onMeasureChild() parameter. * returns false and uses the default CoordinatorLayout method for measuring subviews. * /
        public boolean onMeasureChild(CoordinatorLayout parent, V child,
                int parentWidthMeasureSpec, int widthUsed,
                int parentHeightMeasureSpec, int heightUsed) {
            return false;
        }

        /** * The agent CoordinatorLayout the layout of the child View * returns true indicating that the Behavior's onLayoutChild() is used to layout the child View * If false is returned, the CoordinatorLayout's default measurement method for the subview is used. * /
        public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) {
            return false;
        }
        
        /** * The agent consumes the WindowInsets for CoordinatorLayout */
        @NonNull
        public WindowInsetsCompat onApplyWindowInsets(CoordinatorLayout coordinatorLayout, V child, WindowInsetsCompat insets) {
            return insets;
        }

        // Here are the methods for NestedScrolling
        @Deprecated
        public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
                @NonNull V child, @NonNull View directTargetChild, @NonNull View target,
                @ScrollAxis int axes) {
            return false;
        }

        public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
                @NonNull V child, @NonNull View directTargetChild, @NonNull View target,
                @ScrollAxis int axes, @NestedScrollType int type) {
            if (type == ViewCompat.TYPE_TOUCH) {
                return onStartNestedScroll(coordinatorLayout, child, directTargetChild,
                        target, axes);
            }
            return false;
        }

        @Deprecated
        public void onNestedScrollAccepted(@NonNull CoordinatorLayout coordinatorLayout,
                @NonNull V child, @NonNull View directTargetChild, @NonNull View target,
                @ScrollAxis int axes) {}public void onNestedScrollAccepted(@NonNull CoordinatorLayout coordinatorLayout,
                @NonNull V child, @NonNull View directTargetChild, @NonNull View target,
                @ScrollAxis int axes, @NestedScrollType int type) {
            if(type == ViewCompat.TYPE_TOUCH) { onNestedScrollAccepted(coordinatorLayout, child, directTargetChild, target, axes); }}@Deprecated
        public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View target) {}public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
                @NonNull V child, @NonNull View target, @NestedScrollType int type) {
            if(type == ViewCompat.TYPE_TOUCH) { onStopNestedScroll(coordinatorLayout, child, target); }}@Deprecated
        public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child,
                @NonNull View target, int dxConsumed, int dyConsumed,
                int dxUnconsumed, int dyUnconsumed) {}public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child,
                @NonNull View target, int dxConsumed, int dyConsumed,
                int dxUnconsumed, int dyUnconsumed, @NestedScrollType int type) {
            if(type == ViewCompat.TYPE_TOUCH) { onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed); }}@Deprecated
        public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout,
                @NonNull V child, @NonNull View target, int dx, int dy, @NonNull int[] consumed) {}public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout,
                @NonNull V child, @NonNull View target, int dx, int dy, @NonNull int[] consumed,
                @NestedScrollType int type) {
            if(type == ViewCompat.TYPE_TOUCH) { onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed); }}public boolean onNestedFling(@NonNull CoordinatorLayout coordinatorLayout,
                @NonNull V child, @NonNull View target, float velocityX, float velocityY,
                boolean consumed) {
            return false;
        }

        public boolean onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout,
                @NonNull V child, @NonNull View target, float velocityX, float velocityY) {
                    return false;
        }

        // The ellipsis is very useful. }Copy the code

Set the View behaviors

XML layout file Settings

<! -- Layout file -->
<android.support.design.widget.CoordinatorLayout>
    <android.support.v4.widget.NestedScrollView 
        app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
</android.support.design.widget.CoordinatorLayout>

<! -- values.xml -->
<string name="appbar_scrolling_view_behavior" translatable="false">
android.support.design.widget.AppBarLayout$ScrollingViewBehavior
</string>
Copy the code

The CoordinatorLayout file adds a property called App: layout_Behavio to the direct child View of the CoordinatorLayout. The property is the Behavior class full package name. You can either put the value in the Values file or write it directly into the layout file. The constructor of the Behavior two parameters is created on the parseBehavior() of the CoordinatorLayout.

Code dynamic setting

    AppBarLayout.ScrollingViewBehavior behavior = new AppBarLayout.ScrollingViewBehavior();
    CoordinatorLayout.LayoutParams params =(CoordinatorLayout.LayoutParams) view.getLayoutParams();
    params.setBehavior(behavior);
Copy the code

Annotation way

@CoordinatorLayout.DefaultBehavior(AppBarLayout.Behavior.class)
public class AppBarLayout extends LinearLayout {}
Copy the code

Note that if you use annotations and an XML layout file to set the Behavior of the same view, the annotated Behavior takes effect. If you use this method in custom Behavior, you need a constructor with no arguments. The CoordinatorLayout calls the reflection Behavior parameterless constructor when the getResolvedLayoutParams() is parsed, and this annotation is Deprecated in support27.1.0.

Interface implementation return

View implementation CoordinatorLayout. AttachedBehavior interface and autotype getBehavior () returns the behaviors. The CoordinatorLayout calls getBehavior() when it parses getResolvedLayoutParams() to get the Behavior, Then call CoordinatorLayout. LayoutParams. SetBehavior incoming ().

public class MyLayout extends LinearLayout implements CoordinatorLayout.AttachedBehavior{
    @NonNull
    @Override
    Behavior getBehavior(a){
        return new AppBarLayout.ScrollingViewBehavior()
    };
}
Copy the code

Proxy in Behavior

The proxy for CoordinatorLayout measures the child View

The onMeasureChild() of the Behavior can proxy the measurement of the CoordinatorLayout subview. Note that the subview is associated with the current Behavior and returns a Boolean value. Return true if the Behavior’s onMeasureChild() method is used to measure the child in the parameter. Return false if the CoordinatorLayout’s default method is used to measure the child.

    //CoordinatorLayout.Behavior
    public boolean onMeasureChild(CoordinatorLayout parent, V child,
                int parentWidthMeasureSpec, int widthUsed,
                int parentHeightMeasureSpec, int heightUsed) {
            return false;
    }
Copy the code

In the onMeasure() of the CoordinatorLayout, you can see the measurement of the proxy sub-view in the Behavior:

    //CoordinatorLayout
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {...final int childCount = mDependencySortedChildren.size();
        for (int i = 0; i < childCount; i++) {
            finalView child = mDependencySortedChildren.get(i); .finalLayoutParams lp = (LayoutParams) child.getLayoutParams(); .//Behavior Specifies whether the idle detection can act as measure
            final Behavior b = lp.getBehavior();
            if (b == null| |! b.onMeasureChild(this, child, childWidthMeasureSpec, keylineWidthUsed,
                    childHeightMeasureSpec, 0)) {
                onMeasureChild(child, childWidthMeasureSpec, keylineWidthUsed,
                        childHeightMeasureSpec, 0); }... }}Copy the code

The agent CoordinatorLayout is the layout of a child View

Similar to the above, Behavior’s onLayoutChild() can proxy the CoordinatorLayout child’s layout. It returns a Boolean value, which returns true to indicate that the Behavior’s onLayoutChild() is used to coordinate the child View. If false is returned, the CoordinatorLayout’s default measurement method for the subview is used.

    //CoordinatorLayout.Behavior
    public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) {
        return false;
    }
Copy the code

In the onLayout() of CoordinatorLayout you can see the layout of the proxy sub-view in the Behavior:

    //CoordinatorLayout
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {...final int childCount = mDependencySortedChildren.size();
        for (int i = 0; i < childCount; i++) {
            finalView child = mDependencySortedChildren.get(i); .final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            final Behavior behavior = lp.getBehavior();
            //Behavior Detects whether the layout can be used as a proxy
            if (behavior == null| |! behavior.onLayoutChild(this, child, layoutDirection)) { onLayoutChild(child, layoutDirection); }}}Copy the code

Proxies for CoordinatorLayout WindowInsets

Behavior’s onApplyWindowInsets() can be proxy-consumed for CoordinatorLayout WindowInsets.

    //CoordinatorLayout.Behavior
    public WindowInsetsCompat onApplyWindowInsets(CoordinatorLayout coordinatorLayout, V child, WindowInsetsCompat insets) {
        return insets;
    }
Copy the code

In the onLayout() for CoordinatorLayout you can see the WindowInsets in the Behavior for consumption: setFitsSystemWindows()->setupForInsets()->setWindowInsets()->dispatchApplyWindowInsetsToBehaviors()

    //CoordinatorLayout
    private WindowInsetsCompat dispatchApplyWindowInsetsToBehaviors(WindowInsetsCompat insets) {...for (int i = 0, z = getChildCount(); i < z; i++) {
            final View child = getChildAt(i);
            if (ViewCompat.getFitsSystemWindows(child)) {
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                final Behavior b = lp.getBehavior();

                if(b ! =null) {
                    // If the view has a behavior, let it try first
                    insets = b.onApplyWindowInsets(this, child, insets);
                    if (insets.isConsumed()) {
                        // If it consumed the insets, break
                        break; }}}}return insets;
    }
Copy the code

The Touch event for the agent CoordinatorLayout

The onInterceptTouchEvent() and onTouchEvent() of the Behavior can be intercepted and consumed before the CoordinatorLayout is distributed to the child View. If the Behavior intercepts the Touch event from CoordinatorLayout, the child views of CoordinatorLayout cannot receive the Touch event. Behavior blocksInteractionBelow() indicates whether to block the interaction of the views below the Behavior. This method can affect the interception of Touch events. If blocksInteractionBelow() is true, GetScrimOpacity () returns a value greater than zero, and the CoordinatorLayout will draw a shielded getScrimColor() color on the top of the View to show areas where there is no interaction:

    //CoordinatorLayout.Behavior
    public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) {
        return false;
    }
    
    public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) {
        return false;
    }

    public boolean blocksInteractionBelow(CoordinatorLayout parent, V child) {
            return getScrimOpacity(parent, child) > 0.f;
    }

    public float getScrimOpacity(CoordinatorLayout parent, V child) {
        return 0.f;
    }

    public int getScrimColor(CoordinatorLayout parent, V child) {
        return Color.BLACK;
    }
Copy the code

The onInterceptTouchEvent() and onTouchEvent() of CoordinatorLayout are provided by the Behavior proxy.

    //CoordinatorLayout
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {...final booleanintercepted = performIntercept(ev, TYPE_ON_INTERCEPT); .return intercepted;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        boolean handled = false;
        boolean cancelSuper = false;
        MotionEvent cancelEvent = null; .if(mBehaviorTouchView ! =null || (cancelSuper = performIntercept(ev, TYPE_ON_TOUCH))) {
            // Safe since performIntercept guarantees that
            // mBehaviorTouchView ! = null if it returns true
            final LayoutParams lp = (LayoutParams) mBehaviorTouchView.getLayoutParams();
            final Behavior b = lp.getBehavior();
            //Behavior is not empty, and events are assigned to Behavior
            if(b ! =null) {
                handled = b.onTouchEvent(this, mBehaviorTouchView, ev); }}// Keep the super implementation correct(go to the default CoordinatorLayout method)
        if (mBehaviorTouchView == null) {
            handled |= super.onTouchEvent(ev);
        } else if (cancelSuper) {
            if (cancelEvent == null) {
                final long now = SystemClock.uptimeMillis();
                cancelEvent = MotionEvent.obtain(now, now,
                        MotionEvent.ACTION_CANCEL, 0.0 f.0.0 f.0);
            }
            super.onTouchEvent(cancelEvent); }...return handled;
    }

    private boolean performIntercept(MotionEvent ev, final int type) {
        boolean intercepted = false;
        // Records whether Behavior blocksInteractionBelow() returns true, according to this notation
        // Assign a CANCEL MotionEvent to the remaining traversal Behavior
        boolean newBlock = false;
        MotionEvent cancelEvent = null;

        final int action = ev.getActionMasked();
        // The View is sorted from highest to lowest, and stored in temporary containers
        final List<View> topmostChildList = mTempList1;
        getTopSortedChildren(topmostChildList);

        // Set the Behavior of the outermost View to the Touch event agent.
        // Let topmost child views inspect first
        final int childCount = topmostChildList.size();
        for (int i = 0; i < childCount; i++) {
            final View child = topmostChildList.get(i);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            final Behavior b = lp.getBehavior();
            // If the Touch event is already blocked by the Behavior traversed earlier, or if newBlock is true, the Behavior traversed earlier has blocked the interaction, and the action is not DOWN
            // Then the rest of the iterated Behavior issues a MotionEvent of the CANCEL
            if((intercepted || newBlock) && action ! = MotionEvent.ACTION_DOWN) {// Cancel all behaviors beneath the one that intercepted.
                // If the event is "down" then we don't have anything to cancel yet.
                if(b ! =null) {
                    if (cancelEvent == null) {
                        final long now = SystemClock.uptimeMillis();
                        cancelEvent = MotionEvent.obtain(now, now,
                                MotionEvent.ACTION_CANCEL, 0.0 f.0.0 f.0);
                    }
                    switch (type) {
                        case TYPE_ON_INTERCEPT:
                            b.onInterceptTouchEvent(this, child, cancelEvent);
                            break;
                        case TYPE_ON_TOUCH:
                            b.onTouchEvent(this, child, cancelEvent);
                            break; }}continue;
            }

            // No Touch event is intercepted, Behavior is not empty, and the event is distributed to Behavior
            if(! intercepted && b ! =null) {
                switch (type) {
                    case TYPE_ON_INTERCEPT:
                        intercepted = b.onInterceptTouchEvent(this, child, ev);
                        break;
                    case TYPE_ON_TOUCH:
                        intercepted = b.onTouchEvent(this, child, ev);
                        break;
                }
                // If the Behavior intercepts the Touch event, mark its associated View
                if(intercepted) { mBehaviorTouchView = child; }}// Don't keep going if we're not allowing interaction below this.
            // Setting newBlock will make sure we cancel the rest of the behaviors.
            final boolean wasBlocking = lp.didBlockInteraction();
            final boolean isBlocking = lp.isBlockingInteractionBelow(this, child); newBlock = isBlocking && ! wasBlocking;if(isBlocking && ! newBlock) {// onInterceptTouchEvent() is blocked from performIntercept()Behavior.
                // Go to onTouchEvent() and enter performIntercept()
                // Stop here since we don't have anything more to cancel - we already did
                // when the behavior first started blocking things below this point.
                break;
            }
        }
        topmostChildList.clear();
        return intercepted;
    }

    //CoordinatorLayout.LayoutParams
    /** * Indicates whether the Behavior has been blocked from the View below the Behavior */
    boolean didBlockInteraction(a) {
        if (mBehavior == null) {
            mDidBlockInteraction = false;
        }
        return mDidBlockInteraction;
    }

    /** * Behavior has blocked the interaction of the View below the Behavior, returns true, or blocksInteractionBelow calls the Behavior and records that it has blocked */
    boolean isBlockingInteractionBelow(CoordinatorLayout parent, View child) {
        if (mDidBlockInteraction) {
            return true;
        }
        returnmDidBlockInteraction |= mBehavior ! =null
                ? mBehavior.blocksInteractionBelow(parent, child)
                : false;
    }    
Copy the code

CoordinatorLayout’s onInterceptTouchEvent() performs the main intercept logic in performIntercept() :

  • 1. First, sort the sub-views in descending order according to their hierarchy and then traverse their behaviors in sequence;
  • 2. During traversal, it is judged that the Touch event has been blocked or blocked by the previous traversal Behavior and is not a DOWN event. If these conditions are met, a MotionEvent of CANCEL is distributed to the remaining traversal behaviors.
  • If the Behavior blocks the Touch event, the variable mBehaviorTouchView records its associated View. 3.
  • 4. Then call CoordinatorLayout. LayoutParams two judgement block interactive method with variable newBlock record block interaction behaviors.

CoordinatorLayout’s onTouchEvent() logic is as follows:

  • 1. Check whether the onInterceptTouchEvent() records mBehaviorTouchView. If so, call onTouchEvent() of the Behavior. If not, call performIntercept() and return cancelSuper;
  • 2. If cancelSuper is true, the Behavior called onTouchEvent() to consume the Touch event, record mBehaviorTouchView, and then pass the LayoutParam of mBehaviorTouchView Call Behavior onTouchEvent(), which returns true, to ensure that the mBehaviorTouchView is not empty. Behavior’s onTouchEvent() is executed twice.
  • If no Behavior intercepts, the parent class onTouchEvent() will be called. If no Behavior intercepts, the parent class cancelSuper will be checked to see if it is true. True prevents onTouchEvent from passing a Cancel event to the parent class that has already passed an event to the parent class.

Here’s a summary: If you want to override the Behavior’s onInterceptTouchEvent() and onTouchEvent(), you should be careful about their logic The rationality of onInterceptTouchEvent() and onTouchEvent() in CoordinatorLayout, because the processing of the Behavior agent touch event is a little complicated and tedious, and there will be a large number of abnormal cancel events.

Nested slides for the agent CoordinatorLayout

CoordinatorLayout implements the NestedScrollingParent2 interface and overrides it to be compatible with NestedScrollingParent, but it doesn’t handle nested sliding itself, it gives it all to the Behavior agent, Behavior agent nested sliding through NestedScrollingParent2, NestedScrollingParent corresponding to two more parameters: one is CoordinatorLayout, the other is the Behavior associated View. Because there are many methods involved, it is not suitable to expand here. For nested sliding, you can refer to the Basic Analysis of NestedScrolling nested sliding mechanism I wrote before.

    //CoordinatorLayout.Behavior
    public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout,
            @NonNull V child, @NonNull View target, int dx, int dy, @NonNull int[] consumed,
            @NestedScrollType int type) {
        if(type == ViewCompat.TYPE_TOUCH) { onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed); }}@Deprecated
    public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout,
            @NonNull V child, @NonNull View target, int dx, int dy, @NonNull int[] consumed) {
        // Do nothing
    }
Copy the code

Then look at the nested sliding Behavior agent for CoordinatorLayout. There are only two methods analyzed here. The other methods are very similar:

    //CoordinatorLayout
    @Override
    public boolean onStartNestedScroll(View child, View target, int axes, int type) {
        boolean handled = false;

        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            finalView view = getChildAt(i); .final LayoutParams lp = (LayoutParams) view.getLayoutParams();
            final Behavior viewBehavior = lp.getBehavior();
            if(viewBehavior ! =null) {
                / / onStartNestedScroll behaviors agent
                final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child,
                        target, axes, type);
                handled |= accepted;
                // Whether the LayoutParams record in the Behavior associated View accepts nested sliding
                lp.setNestedScrollAccepted(type, accepted);
            } else {
                lp.setNestedScrollAccepted(type, false); }}return handled;
    }

    //CoordinatorLayout.LayoutParams
    void setNestedScrollAccepted(int type, boolean accept) {
        switch (type) {
            case ViewCompat.TYPE_TOUCH:
                mDidAcceptNestedScrollTouch = accept;
                break;
            case ViewCompat.TYPE_NON_TOUCH:
                mDidAcceptNestedScrollNonTouch = accept;
                break; }}Copy the code

The CoordinatorLayout’s onStartNestedScroll() traverses the child View, gets the Behavior of the child View, calls onStartNestedScroll(), and records whether nested sliding is accepted in LayoutParams.

    //CoordinatorLayout
    @Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed, int  type) {...final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            finalView view = getChildAt(i); .final LayoutParams lp = (LayoutParams) view.getLayoutParams();
            // Check whether Behavior accepts nested sliding
            if(! lp.isNestedScrollAccepted(type)) {continue;
            }

            final Behavior viewBehavior = lp.getBehavior();
            if(viewBehavior ! =null) {.../ / / / behaviors onNestedPreScroll agent
                viewBehavior.onNestedPreScroll(this, view, target, dx, dy, mTempIntPair, type); . }}... }Copy the code

The onNestedPreScroll() of CoordinatorLayout traverses the sub-view and obtains the LayoutParams of the sub-view to judge whether the Behavior accepts nested sliding. If accepted, get the Behavior of the child View and call onNestedPreScroll().

summary

Behavior is very powerful, but generally speaking, the measurement and layout of the sub-view can be handled in the custom View, while the distribution of CoordinatorLayout WindowInsets and Touch events to the sub-view have a fixed sequence. If you should pay attention to the rationality of the logic in CoordinatorLayout when you are dealing with the Behavior, you don’t need to use it for the purpose of using the Behavior, nesting slides are very useful in achieving the effect of magic slides, and also decoupled the logic of custom NestedScrollParent.

Behavior’s View dependencies

Establish dependencies between views

<android.support.design.widget.CoordinatorLayout>
    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar"/>
    <android.support.design.widget.FloatingActionButton
        app:layout_anchor="@id/app_bar" 
        app:layout_anchorGravity="bottom|end"
    />
</android.support.design.widget.CoordinatorLayout>
Copy the code

Another option is to add a layout_anchor to the layout file to establish a dependency, but this dependency can only listen for the onDependentViewChanged() callback when the position or size of the dependent View changes.

    //CoordinatorLayout.Behavior
    /** * the return value indicates whether the child depends on dependency */
    public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) {
        return false;
    }

    /** * The return value indicates whether the Behavior changes the size or position of the child */
    public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) {
        return false;
    }

    public void onDependentViewRemoved(CoordinatorLayout parent, V child, View dependency) {}Copy the code

Sort View dependencies

CoordinatorLayout’s dependency on View is topological ordering through the DirectedAcyclicGraph of the Support package.

In graph theory, a directed graph is a DAG (directed acyclic Graph) if it starts from any vertex and cannot return to that point by several edges

PrepareChildren () ina CoordinatorLayout onMeasure() is to sort the View dependencies:

    private final List<View> mDependencySortedChildren = new ArrayList<>();
    private final DirectedAcyclicGraph<View> mChildDag = new DirectedAcyclicGraph<>();

    private void prepareChildren(a) {
        mDependencySortedChildren.clear();
        mChildDag.clear();

        for (int i = 0, count = getChildCount(); i < count; i++) {
            final View view = getChildAt(i);
            // Find the View's Anchor point
            final LayoutParams lp = getResolvedLayoutParams(view);
            lp.findAnchorView(this, view);
            // Add view as a node to a directed acyclic graph
            mChildDag.addNode(view);

            // Now iterate again over the other children, adding any dependencies to the graph
            for (int j = 0; j < count; j++) {
                if (j == i) {
                    continue;
                }
                final View other = getChildAt(j);
                if (lp.dependsOn(this, view, other)) {// Check whether view has a dependency with other
                    if(! mChildDag.contains(other)) {// If other is not in the graph, add it to ensure that view is dependent on other.
                        // Make sure that the other node is added
                        mChildDag.addNode(other);
                    }
                    // create a dependency between View and Other on the edge of the graph.
                    // Now add the dependency to the graphmChildDag.addEdge(other, view); }}}// Store a depth-first list of graph nodes in a list container
        // Finally add the sorted graph list to our list
        mDependencySortedChildren.addAll(mChildDag.getSortedList());
        // Invert the list so that views without dependencies come first.
        // We also need to reverse the result since we want the start of the list to contain
        // Views which have no dependencies, then dependent views after that
        Collections.reverse(mDependencySortedChildren);
    }
Copy the code
  • 1. CoordinatorLayout traversal traversal view, called CoordinatorLayout. LayoutParams. FindAnchorView () find the view for an Anchor point, and the current view as a node is added to the directed acyclic graph.
  • 2. In the loop in open loop through the other View, through CoordinatorLayout. LayoutParams. DependsOn judgement and outer loop () View of the existence of a dependency relationship, if any as well based on figure added.
  • 3. After the two loop execution, there will be a directed acyclic graph (dag) the depth of the node to the list of priorities in the mDependencySortedChildren, then reverse mDependencySortedChildren let no dependencies of the view in front of the list.

Behavior relies on the View callback to trigger the procedure

The Behavior onDependentViewChanged() and onDependentViewRemoved() are triggered on the CoordinatorLayout onChildViewsChanged(). The method type parameter has three values: EVENT_PRE_DRAW(depends on the event type before the view draws), EVENT_NESTED_SCROLL(depends on the view to nest the sliding event type), and EVENT_VIEW_REMOVED(depends on the view to remove the event type from the layout).

    final void onChildViewsChanged(@DispatchChangeEvent final int type) {...final intchildCount = mDependencySortedChildren.size(); .for (int i = 0; i < childCount; i++) {
            final View child = mDependencySortedChildren.get(i);
            finalLayoutParams lp = (LayoutParams) child.getLayoutParams(); .for (int j = 0; j < i; j++) {
                final View checkChild = mDependencySortedChildren.get(j);
                if (lp.mAnchorDirectChild == checkChild) {
                    // Check whether the anchor position of the view has changed to adjust the position of the dependent viewoffsetChildToAnchor(child, layoutDirection); }}...for (int j = i + 1; j < childCount; j++) {
                final View checkChild = mDependencySortedChildren.get(j);
                final LayoutParams checkLp = (LayoutParams) checkChild.getLayoutParams();
                final Behavior b = checkLp.getBehavior();
                // Check whether checkChild depends on child
                if(b ! =null && b.layoutDependsOn(this, checkChild, child)) {
                    ...
                    final boolean handled;
                    switch (type) {
                        case EVENT_VIEW_REMOVED:
                            // remove events from the layout to the Behavior.
                            b.onDependentViewRemoved(this, checkChild, child);
                            handled = true;
                            break;
                        default:
                            // Assign a sliding event to the Behavior that depends on the view drawing.
                            handled = b.onDependentViewChanged(this, checkChild, child);
                            break; }}}}... }void offsetChildToAnchor(View child, int layoutDirection) {...// Notice that the view and anchor anchor positions have been adjusted, notify Behavior of this change
        // If we have needed to move, make sure to notify the child's Behavior
        final Behavior b = lp.getBehavior();
        if(b ! =null) {
            b.onDependentViewChanged(this, child, lp.mAnchorView); }... }Copy the code

For CoordinatorLayout onNestedFling(), onNestedPreScroll(), onNestedPreScroll(), if NestedScrollingChild handles nested slides, it goes through onChildViews Changed(EVENT_NESTED_SCROLL) distributes the nested sliding event to the Behavior depending on the view. The following uses the onNestedScroll code as an example.

    //CoordiantorLayout.java
    @Override
    public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
            int dxUnconsumed, int dyUnconsumed, int type) {...if(accepted) { onChildViewsChanged(EVENT_NESTED_SCROLL); }}Copy the code

In the constructor of CoordinatorLayout through setOnHierarchyChangeListener () registered OnHierarchyChangeListener listening to add or remove the hierarchy of the View changes, While CoordinatorLayout. OnHierarchyChangeListener in the View has been removed in the callback call onChildViewsChanged (EVENT_VIEW_REMOVED) will depend on the View from the layout to remove event type distribution to the Behav The ior.

    public CoordinatorLayout(Context context, AttributeSet attrs, int defStyleAttr) {...super.setOnHierarchyChangeListener(new HierarchyChangeListener();
    }    

    private class HierarchyChangeListener implements OnHierarchyChangeListener {...@Override
        public void onChildViewRemoved(View parent, View child) {
            // Remove event types that depend on the View from the layout and distribute them to the BehavioronChildViewsChanged(EVENT_VIEW_REMOVED); . }}Copy the code

In CoordinatorLayout onAttachedToWindow () to ViewTreeObserver registered a CoordinatorLayout OnPreDrawListener, It calls onChildViewsChanged() in the callback after each refresh to determine the size position of each View and before drawing to distribute the event type dependent on the View before drawing to the corresponding Behavior.

    // Whether the mOnPreDrawListener flag needs to be registered
    private boolean mNeedsPreDrawListener;
    // Whether the onAttachedToWindow() flag has been executed
    private boolean mIsAttachedToWindow;
    private OnPreDrawListener mOnPreDrawListener;

    @Override
    public void onAttachedToWindow(a) {...if (mNeedsPreDrawListener) {
            if (mOnPreDrawListener == null) {
                mOnPreDrawListener = new OnPreDrawListener();
            }
            finalViewTreeObserver vto = getViewTreeObserver(); vto.addOnPreDrawListener(mOnPreDrawListener); }... mIsAttachedToWindow =true;
    }

    class OnPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
        @Override
        public boolean onPreDraw(a) {
            // The distribution depends on the event type before the view is drawn
            onChildViewsChanged(EVENT_PRE_DRAW);
            return true;
    }
Copy the code

Although onAttachedToWindow() will be called before onDraw(), it may also be called before onMeasure(). If there are no dependencies between views, mOnPreDrawListener is removed from ViewTree to prevent memory leaks. So the ensurePreDrawListener() of onMeasure() checks for dependencies between views and registers or unregisters mOnPreDrawListener.

    void ensurePreDrawListener(a) {
        boolean hasDependencies = false;
        final int childCount = getChildCount();
        // Walk through the child views to see if they have dependencies
        for (int i = 0; i < childCount; i++) {
            final View child = getChildAt(i);
            if (hasDependencies(child)) {
                hasDependencies = true;
                break; }}if(hasDependencies ! = mNeedsPreDrawListener) {if (hasDependencies) {
                // Dependencies exist, register mOnPreDrawListener
                addPreDrawListener();
            } else {
                //// no dependencies, unlog mOnPreDrawListenerremovePreDrawListener(); }}}void addPreDrawListener(a) {
        // If onAttachedToWindow() is executed
        if (mIsAttachedToWindow) {
            // Add the listener
            if (mOnPreDrawListener == null) {
                mOnPreDrawListener = new OnPreDrawListener();
            }
            final ViewTreeObserver vto = getViewTreeObserver();
            vto.addOnPreDrawListener(mOnPreDrawListener);
        }
        // Because onMeasure() and onAttachedToWindow() are called in an uncertain order,
        // So this identifies the mNeedsPreDrawListener variable to handle registering mOnPreDrawListener.
        // Record that we need the listener regardless of whether or not we're attached.
        // We'll add the real listener when we become attached.
        mNeedsPreDrawListener = true;
    }

    void removePreDrawListener(a) {
        if (mIsAttachedToWindow) {
            if(mOnPreDrawListener ! =null) {
                final ViewTreeObserver vto = getViewTreeObserver();
                vto.removeOnPreDrawListener(mOnPreDrawListener);
            }
        }
        mNeedsPreDrawListener = false; }}Copy the code

Custom behaviors

  • 1. Before you customize the Behavior, you can check whether the system’s own Behavior meets the requirements. For example, the Behavior inside FloatActionButton can ensure that the Snackbar is not covered by the FAB when it pops up.

  • 2. Whether it is necessary to use CoordinatorLayout+Behavior for the measurement, layout, distribution of WindowInsets and Touch events of the child View, and whether this part of logic can be handled inside the custom View.

  • 3. Combining the View dependencies of Behavior with NestedScrolling to achieve sliding is more convenient.

    In the above picture, I wrote “Brief Analysis of NestedScrolling nested sliding mechanism practice – copying me business details page” effect. The Content section handles nested sliding logic, while the Header section, Collapse Content section, TopBar section, and Shop Bar section have dependencies on the Content section via behavior.layoutdependson (). To monitor the Content part of the sliding callback behaviors. OnDependentViewChanged () for each part of the animation, alpha, such as the Transition effect, relative to the custom before the View, the implementation logic more decoupling is clear.

conclusion

The combination of CoordinatorLayout and Behavior is very powerful, but this paper tends to conceptual content, which is inevitably boring. In the next paper, the practice of custom Behavior is practiced. Due to my limited level, I only provide you with reference, hoping to throw a brick into the discussion, if you have any questions to discuss, you can leave a message in the comment area or contact me.

reference

Intercepting everything with CoordinatorLayout Behaviors