Improve header dragging

- Moved re-usable code from ABL to the new
  base class.
- Added fling support to header dragging.
- Fixed NavigationView not scrolling
- Hid a few new methods which shouldn't
  be public.

Change-Id: Ia7335c385951a66d27316dbf675bb8860f549cc4
diff --git a/design/api/current.txt b/design/api/current.txt
index a5feb3d..5523b5e 100644
--- a/design/api/current.txt
+++ b/design/api/current.txt
@@ -15,7 +15,6 @@
   public static class AppBarLayout.Behavior extends android.support.design.widget.HeaderBehavior {
     ctor public AppBarLayout.Behavior();
     ctor public AppBarLayout.Behavior(android.content.Context, android.util.AttributeSet);
-    method public boolean onInterceptTouchEvent(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, android.view.MotionEvent);
     method public boolean onLayoutChild(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, int);
     method public boolean onNestedFling(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, android.view.View, float, float, boolean);
     method public void onNestedPreScroll(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, android.view.View, int, int, int[]);
@@ -24,7 +23,12 @@
     method public android.os.Parcelable onSaveInstanceState(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout);
     method public boolean onStartNestedScroll(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, android.view.View, android.view.View, int);
     method public void onStopNestedScroll(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, android.view.View);
-    method public boolean onTouchEvent(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, android.view.MotionEvent);
+    method public void setDragCallback(android.support.design.widget.AppBarLayout.Behavior.DragCallback);
+  }
+
+  public static abstract class AppBarLayout.Behavior.DragCallback {
+    ctor public AppBarLayout.Behavior.DragCallback();
+    method public abstract boolean canDrag(android.support.design.widget.AppBarLayout);
   }
 
   protected static class AppBarLayout.Behavior.SavedState extends android.view.View.BaseSavedState {
@@ -59,7 +63,6 @@
   public static class AppBarLayout.ScrollingViewBehavior extends android.support.design.widget.HeaderScrollingViewBehavior {
     ctor public AppBarLayout.ScrollingViewBehavior();
     ctor public AppBarLayout.ScrollingViewBehavior(android.content.Context, android.util.AttributeSet);
-    method protected android.view.View findFirstDependency(java.util.List<android.view.View>);
     method public int getOverlayTop();
     method public boolean layoutDependsOn(android.support.design.widget.CoordinatorLayout, android.view.View, android.view.View);
     method public boolean onDependentViewChanged(android.support.design.widget.CoordinatorLayout, android.view.View, android.view.View);
@@ -206,39 +209,14 @@
    abstract class HeaderBehavior extends android.support.design.widget.ViewOffsetBehavior {
     ctor public HeaderBehavior();
     ctor public HeaderBehavior(android.content.Context, android.util.AttributeSet);
-    method protected boolean fling(android.support.design.widget.CoordinatorLayout, android.view.View, int, int, float);
-    method protected int getTopBottomOffsetForScrollingSibling();
-    method protected int scroll(android.support.design.widget.CoordinatorLayout, android.view.View, int, int, int);
-    method protected int setHeaderTopBottomOffset(android.support.design.widget.CoordinatorLayout, android.view.View, int);
-    method protected int setHeaderTopBottomOffset(android.support.design.widget.CoordinatorLayout, android.view.View, int, int, int);
   }
 
    abstract class HeaderScrollingViewBehavior extends android.support.design.widget.ViewOffsetBehavior {
     ctor public HeaderScrollingViewBehavior();
     ctor public HeaderScrollingViewBehavior(android.content.Context, android.util.AttributeSet);
-    method protected abstract android.view.View findFirstDependency(java.util.List<android.view.View>);
-    method protected int getScrollRange(android.view.View);
     method public boolean onMeasureChild(android.support.design.widget.CoordinatorLayout, android.view.View, int, int, int, int);
   }
 
-  public final class NavigationHeaderBehavior extends android.support.design.widget.HeaderBehavior {
-    ctor public NavigationHeaderBehavior();
-    ctor public NavigationHeaderBehavior(android.content.Context, android.util.AttributeSet);
-    method public boolean layoutDependsOn(android.support.design.widget.CoordinatorLayout, android.view.View, android.view.View);
-    method public boolean onNestedFling(android.support.design.widget.CoordinatorLayout, android.view.View, android.view.View, float, float, boolean);
-    method public void onNestedPreScroll(android.support.design.widget.CoordinatorLayout, android.view.View, android.view.View, int, int, int[]);
-    method public void onNestedScroll(android.support.design.widget.CoordinatorLayout, android.view.View, android.view.View, int, int, int, int);
-    method public boolean onStartNestedScroll(android.support.design.widget.CoordinatorLayout, android.view.View, android.view.View, android.view.View, int);
-  }
-
-  public static class NavigationHeaderBehavior.MenuViewBehavior extends android.support.design.widget.HeaderScrollingViewBehavior {
-    ctor public NavigationHeaderBehavior.MenuViewBehavior();
-    ctor public NavigationHeaderBehavior.MenuViewBehavior(android.content.Context, android.util.AttributeSet);
-    method protected android.view.View findFirstDependency(java.util.List<android.view.View>);
-    method public boolean layoutDependsOn(android.support.design.widget.CoordinatorLayout, android.view.View, android.view.View);
-    method public boolean onDependentViewChanged(android.support.design.widget.CoordinatorLayout, android.view.View, android.view.View);
-  }
-
   public class NavigationView extends android.widget.FrameLayout {
     ctor public NavigationView(android.content.Context);
     ctor public NavigationView(android.content.Context, android.util.AttributeSet);
@@ -404,17 +382,17 @@
     ctor public TextInputLayout(android.content.Context);
     ctor public TextInputLayout(android.content.Context, android.util.AttributeSet);
     ctor public TextInputLayout(android.content.Context, android.util.AttributeSet, int);
+    method public int getCounterMaxLength();
     method public android.widget.EditText getEditText();
     method public java.lang.CharSequence getError();
     method public java.lang.CharSequence getHint();
     method public android.graphics.Typeface getTypeface();
     method public boolean isErrorEnabled();
     method public boolean isHintAnimationEnabled();
-    method public void setError(java.lang.CharSequence);
-    method public void setErrorEnabled(boolean);
     method public void setCounterEnabled(boolean);
     method public void setCounterMaxLength(int);
-    method public int getCounterMaxLength();
+    method public void setError(java.lang.CharSequence);
+    method public void setErrorEnabled(boolean);
     method public void setHint(java.lang.CharSequence);
     method public void setHintAnimationEnabled(boolean);
     method public void setHintTextAppearance(int);
diff --git a/design/src/android/support/design/widget/AppBarLayout.java b/design/src/android/support/design/widget/AppBarLayout.java
index 23cd9702..944b2b0 100644
--- a/design/src/android/support/design/widget/AppBarLayout.java
+++ b/design/src/android/support/design/widget/AppBarLayout.java
@@ -21,14 +21,13 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.design.R;
-import android.support.v4.view.MotionEventCompat;
 import android.support.v4.view.ViewCompat;
 import android.support.v4.view.WindowInsetsCompat;
 import android.util.AttributeSet;
-import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.animation.Interpolator;
 import android.widget.LinearLayout;
@@ -645,9 +644,24 @@
      * scroll handling with offsetting.
      */
     public static class Behavior extends HeaderBehavior<AppBarLayout> {
-        private static final int INVALID_POINTER = -1;
         private static final int INVALID_POSITION = -1;
 
+        /**
+         * Callback to allow control over any {@link AppBarLayout} dragging.
+         */
+        public static abstract class DragCallback {
+            /**
+             * Allows control over whether the given {@link AppBarLayout} can be dragged or not.
+             *
+             * <p>Dragging is defined as a direct touch on the AppBarLayout with movement. This
+             * call does not affect any nested scrolling.</p>
+             *
+             * @return true if we are in a position to scroll the AppBarLayout via a drag, false
+             *         if not.
+             */
+            public abstract boolean canDrag(@NonNull AppBarLayout appBarLayout);
+        }
+
         private int mOffsetDelta;
 
         private boolean mSkipNestedPreScroll;
@@ -659,12 +673,8 @@
         private boolean mOffsetToChildIndexOnLayoutIsMinHeight;
         private float mOffsetToChildIndexOnLayoutPerc;
 
-        private boolean mIsBeingDragged;
-        private int mActivePointerId = INVALID_POINTER;
-        private int mLastMotionY;
-        private int mTouchSlop = -1;
-
         private WeakReference<View> mLastNestedScrollingChildRef;
+        private DragCallback mOnDragCallback;
 
         public Behavior() {}
 
@@ -743,115 +753,6 @@
         }
 
         @Override
-        public boolean onInterceptTouchEvent(CoordinatorLayout parent, AppBarLayout child,
-                MotionEvent ev) {
-            if (mTouchSlop < 0) {
-                mTouchSlop = ViewConfiguration.get(parent.getContext()).getScaledTouchSlop();
-            }
-
-            final int action = ev.getAction();
-
-            // Shortcut since we're being dragged
-            if (action == MotionEvent.ACTION_MOVE && mIsBeingDragged) {
-                return true;
-            }
-
-            switch (MotionEventCompat.getActionMasked(ev)) {
-                case MotionEvent.ACTION_MOVE: {
-                    final int activePointerId = mActivePointerId;
-                    if (activePointerId == INVALID_POINTER) {
-                        // If we don't have a valid id, the touch down wasn't on content.
-                        break;
-                    }
-                    final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId);
-                    if (pointerIndex == -1) {
-                        break;
-                    }
-
-                    final int y = (int) MotionEventCompat.getY(ev, pointerIndex);
-                    final int yDiff = Math.abs(y - mLastMotionY);
-                    if (yDiff > mTouchSlop) {
-                        mIsBeingDragged = true;
-                        mLastMotionY = y;
-                    }
-                    break;
-                }
-
-                case MotionEvent.ACTION_DOWN: {
-                    mIsBeingDragged = false;
-                    final int x = (int) ev.getX();
-                    final int y = (int) ev.getY();
-                    if (parent.isPointInChildBounds(child, x, y) && canDragAppBarLayout()) {
-                        mLastMotionY = y;
-                        mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
-                    }
-                    break;
-                }
-
-                case MotionEvent.ACTION_CANCEL:
-                case MotionEvent.ACTION_UP:
-                    mIsBeingDragged = false;
-                    mActivePointerId = INVALID_POINTER;
-                    break;
-            }
-
-            return mIsBeingDragged;
-        }
-
-        @Override
-        public boolean onTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent ev) {
-            if (mTouchSlop < 0) {
-                mTouchSlop = ViewConfiguration.get(parent.getContext()).getScaledTouchSlop();
-            }
-
-            int x = (int) ev.getX();
-            int y = (int) ev.getY();
-
-            switch (MotionEventCompat.getActionMasked(ev)) {
-                case MotionEvent.ACTION_DOWN:
-                    if (parent.isPointInChildBounds(child, x, y) && canDragAppBarLayout()) {
-                        mLastMotionY = y;
-                        mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
-                    } else {
-                        return false;
-                    }
-                    break;
-                case MotionEvent.ACTION_MOVE:
-                    final int activePointerIndex = MotionEventCompat.findPointerIndex(ev,
-                            mActivePointerId);
-                    if (activePointerIndex == -1) {
-                        return false;
-                    }
-
-                    y = (int) MotionEventCompat.getY(ev, activePointerIndex);
-
-                    int dy = mLastMotionY - y;
-                    if (!mIsBeingDragged && Math.abs(dy) > mTouchSlop) {
-                        mIsBeingDragged = true;
-                        if (dy > 0) {
-                            dy -= mTouchSlop;
-                        } else {
-                            dy += mTouchSlop;
-                        }
-                    }
-
-                    if (mIsBeingDragged) {
-                        mLastMotionY = y;
-                        // We're being dragged so scroll the ABL
-                        scroll(parent, child, dy, -child.getDownNestedScrollRange(), 0);
-                    }
-                    break;
-                case MotionEvent.ACTION_UP:
-                case MotionEvent.ACTION_CANCEL:
-                    mIsBeingDragged = false;
-                    mActivePointerId = INVALID_POINTER;
-                    break;
-            }
-
-            return true;
-        }
-
-        @Override
         public boolean onNestedFling(final CoordinatorLayout coordinatorLayout,
                 final AppBarLayout child, View target, float velocityX, float velocityY,
                 boolean consumed) {
@@ -890,6 +791,15 @@
             return flung;
         }
 
+        /**
+         * Set a callback to control any {@link AppBarLayout} dragging.
+         *
+         * @param callback the callback to use, or {@code null} to use the default behavior.
+         */
+        public void setDragCallback(@Nullable DragCallback callback) {
+            mOnDragCallback = callback;
+        }
+
         private void animateOffsetTo(final CoordinatorLayout coordinatorLayout,
                 final AppBarLayout child, int offset) {
             if (mAnimator == null) {
@@ -910,7 +820,6 @@
             mAnimator.start();
         }
 
-
         private View getChildOnOffset(AppBarLayout abl, final int offset) {
             for (int i = 0, count = abl.getChildCount(); i < count; i++) {
                 View child = abl.getChildAt(i);
@@ -979,21 +888,42 @@
             return handled;
         }
 
-        private boolean canDragAppBarLayout() {
-            if (mLastNestedScrollingChildRef != null) {
-                final View view = mLastNestedScrollingChildRef.get();
-                return view != null && view.isShown() && !ViewCompat.canScrollVertically(view, -1);
+        @Override
+        boolean canDragView(AppBarLayout view) {
+            if (mOnDragCallback != null) {
+                // If there is a drag callback set, it's in control
+                return mOnDragCallback.canDrag(view);
             }
-            return false;
+
+            // Else we'll use the default behaviour of seeing if it can scroll down
+            if (mLastNestedScrollingChildRef != null) {
+                // If we have a reference to a scrolling view, check it
+                final View scrollingView = mLastNestedScrollingChildRef.get();
+                return scrollingView != null && scrollingView.isShown()
+                        && !ViewCompat.canScrollVertically(scrollingView, -1);
+            } else {
+                // Otherwise we assume that the scrolling view hasn't been scrolled and can drag.
+                return true;
+            }
         }
 
         @Override
-        protected int setHeaderTopBottomOffset(CoordinatorLayout coordinatorLayout,
-                View header, int newOffset, int minOffset, int maxOffset) {
+        int getMaxDragOffset(AppBarLayout view) {
+            return -view.getDownNestedScrollRange();
+        }
+
+        @Override
+        int getScrollRangeForDragFling(AppBarLayout view) {
+            return view.getTotalScrollRange();
+        }
+
+        @Override
+        int setHeaderTopBottomOffset(CoordinatorLayout coordinatorLayout,
+                AppBarLayout header, int newOffset, int minOffset, int maxOffset) {
             final int curOffset = getTopBottomOffsetForScrollingSibling();
             int consumed = 0;
 
-            if ((header instanceof AppBarLayout) && minOffset != 0 && curOffset >= minOffset
+            if (minOffset != 0 && curOffset >= minOffset
                     && curOffset <= maxOffset) {
                 // If we have some scrolling range, and we're currently within the min and max
                 // offsets, calculate a new offset
@@ -1088,7 +1018,7 @@
         }
 
         @Override
-        protected int getTopBottomOffsetForScrollingSibling() {
+        int getTopBottomOffsetForScrollingSibling() {
             return getTopAndBottomOffset() + mOffsetDelta;
         }
 
@@ -1271,18 +1201,18 @@
         }
 
         @Override
-        protected View findFirstDependency(List<View> views) {
+        View findFirstDependency(List<View> views) {
             for (int i = 0, z = views.size(); i < z; i++) {
                 View view = views.get(i);
                 if (view instanceof AppBarLayout) {
-                    return (AppBarLayout) view;
+                    return view;
                 }
             }
             return null;
         }
 
         @Override
-        protected int getScrollRange(View v) {
+        int getScrollRange(View v) {
             if (v instanceof AppBarLayout) {
                 return ((AppBarLayout) v).getTotalScrollRange();
             } else {
diff --git a/design/src/android/support/design/widget/HeaderBehavior.java b/design/src/android/support/design/widget/HeaderBehavior.java
index ae61b62..6e7a1c0 100644
--- a/design/src/android/support/design/widget/HeaderBehavior.java
+++ b/design/src/android/support/design/widget/HeaderBehavior.java
@@ -18,10 +18,15 @@
 
 import android.content.Context;
 import android.support.design.widget.CoordinatorLayout.Behavior;
+import android.support.v4.view.MotionEventCompat;
+import android.support.v4.view.VelocityTrackerCompat;
 import android.support.v4.view.ViewCompat;
 import android.support.v4.widget.ScrollerCompat;
 import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
 import android.view.View;
+import android.view.ViewConfiguration;
 
 /**
  * The {@link Behavior} for a view that sits vertically above scrolling a view.
@@ -29,25 +34,170 @@
  */
 abstract class HeaderBehavior<V extends View> extends ViewOffsetBehavior<V> {
 
+    private static final int INVALID_POINTER = -1;
+
     private Runnable mFlingRunnable;
     private ScrollerCompat mScroller;
 
-    public HeaderBehavior() {
-        super();
-    }
+    private boolean mIsBeingDragged;
+    private int mActivePointerId = INVALID_POINTER;
+    private int mLastMotionY;
+    private int mTouchSlop = -1;
+    private VelocityTracker mVelocityTracker;
+
+    public HeaderBehavior() {}
 
     public HeaderBehavior(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
-    protected int setHeaderTopBottomOffset(CoordinatorLayout coordinatorLayout,
-            View header, int newOffset) {
-        return setHeaderTopBottomOffset(coordinatorLayout, header, newOffset,
+    @Override
+    public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) {
+        if (mTouchSlop < 0) {
+            mTouchSlop = ViewConfiguration.get(parent.getContext()).getScaledTouchSlop();
+        }
+
+        final int action = ev.getAction();
+
+        // Shortcut since we're being dragged
+        if (action == MotionEvent.ACTION_MOVE && mIsBeingDragged) {
+            return true;
+        }
+
+        switch (MotionEventCompat.getActionMasked(ev)) {
+            case MotionEvent.ACTION_DOWN: {
+                mIsBeingDragged = false;
+                final int x = (int) ev.getX();
+                final int y = (int) ev.getY();
+                if (canDragView(child) && parent.isPointInChildBounds(child, x, y)) {
+                    mLastMotionY = y;
+                    mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
+                    ensureVelocityTracker();
+                }
+                break;
+            }
+
+            case MotionEvent.ACTION_MOVE: {
+                final int activePointerId = mActivePointerId;
+                if (activePointerId == INVALID_POINTER) {
+                    // If we don't have a valid id, the touch down wasn't on content.
+                    break;
+                }
+                final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId);
+                if (pointerIndex == -1) {
+                    break;
+                }
+
+                final int y = (int) MotionEventCompat.getY(ev, pointerIndex);
+                final int yDiff = Math.abs(y - mLastMotionY);
+                if (yDiff > mTouchSlop) {
+                    mIsBeingDragged = true;
+                    mLastMotionY = y;
+                }
+                break;
+            }
+
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP: {
+                mIsBeingDragged = false;
+                mActivePointerId = INVALID_POINTER;
+                if (mVelocityTracker != null) {
+                    mVelocityTracker.recycle();
+                    mVelocityTracker = null;
+                }
+                break;
+            }
+        }
+
+        if (mVelocityTracker != null) {
+            mVelocityTracker.addMovement(ev);
+        }
+
+        return mIsBeingDragged;
+    }
+
+    @Override
+    public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) {
+        if (mTouchSlop < 0) {
+            mTouchSlop = ViewConfiguration.get(parent.getContext()).getScaledTouchSlop();
+        }
+
+        switch (MotionEventCompat.getActionMasked(ev)) {
+            case MotionEvent.ACTION_DOWN: {
+                final int x = (int) ev.getX();
+                final int y = (int) ev.getY();
+
+                if (parent.isPointInChildBounds(child, x, y) && canDragView(child)) {
+                    mLastMotionY = y;
+                    mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
+                    ensureVelocityTracker();
+                } else {
+                    return false;
+                }
+                break;
+            }
+
+            case MotionEvent.ACTION_MOVE: {
+                final int activePointerIndex = MotionEventCompat.findPointerIndex(ev,
+                        mActivePointerId);
+                if (activePointerIndex == -1) {
+                    return false;
+                }
+
+                final int y = (int) MotionEventCompat.getY(ev, activePointerIndex);
+                int dy = mLastMotionY - y;
+
+                if (!mIsBeingDragged && Math.abs(dy) > mTouchSlop) {
+                    mIsBeingDragged = true;
+                    if (dy > 0) {
+                        dy -= mTouchSlop;
+                    } else {
+                        dy += mTouchSlop;
+                    }
+                }
+
+                if (mIsBeingDragged) {
+                    mLastMotionY = y;
+                    // We're being dragged so scroll the ABL
+                    scroll(parent, child, dy, getMaxDragOffset(child), 0);
+                }
+                break;
+            }
+
+            case MotionEvent.ACTION_UP:
+                if (mVelocityTracker != null) {
+                    mVelocityTracker.addMovement(ev);
+                    mVelocityTracker.computeCurrentVelocity(1000);
+                    float yvel = VelocityTrackerCompat.getYVelocity(mVelocityTracker,
+                            mActivePointerId);
+                    fling(parent, child, -getScrollRangeForDragFling(child), 0, yvel);
+                }
+                // $FALLTHROUGH
+            case MotionEvent.ACTION_CANCEL: {
+                mIsBeingDragged = false;
+                mActivePointerId = INVALID_POINTER;
+                if (mVelocityTracker != null) {
+                    mVelocityTracker.recycle();
+                    mVelocityTracker = null;
+                }
+                break;
+            }
+        }
+
+        if (mVelocityTracker != null) {
+            mVelocityTracker.addMovement(ev);
+        }
+
+        return true;
+    }
+
+    int setHeaderTopBottomOffset(CoordinatorLayout parent, V header, int newOffset) {
+        return setHeaderTopBottomOffset(parent, header, newOffset,
                 Integer.MIN_VALUE, Integer.MAX_VALUE);
     }
 
-    protected int setHeaderTopBottomOffset(CoordinatorLayout coordinatorLayout,
-            View header, int newOffset, int minOffset, int maxOffset) {
+    int setHeaderTopBottomOffset(CoordinatorLayout parent, V header, int newOffset,
+            int minOffset, int maxOffset) {
         final int curOffset = getTopAndBottomOffset();
         int consumed = 0;
 
@@ -66,20 +216,21 @@
         return consumed;
     }
 
-    protected int getTopBottomOffsetForScrollingSibling() {
+    int getTopBottomOffsetForScrollingSibling() {
         return getTopAndBottomOffset();
     }
 
-    protected int scroll(CoordinatorLayout coordinatorLayout, View header,
+    final int scroll(CoordinatorLayout coordinatorLayout, V header,
             int dy, int minOffset, int maxOffset) {
         return setHeaderTopBottomOffset(coordinatorLayout, header,
                 getTopBottomOffsetForScrollingSibling() - dy, minOffset, maxOffset);
     }
 
-    protected boolean fling(CoordinatorLayout coordinatorLayout, View layout, int minOffset,
+    final boolean fling(CoordinatorLayout coordinatorLayout, V layout, int minOffset,
             int maxOffset, float velocityY) {
         if (mFlingRunnable != null) {
             layout.removeCallbacks(mFlingRunnable);
+            mFlingRunnable = null;
         }
 
         if (mScroller == null) {
@@ -96,17 +247,39 @@
             mFlingRunnable = new FlingRunnable(coordinatorLayout, layout);
             ViewCompat.postOnAnimation(layout, mFlingRunnable);
             return true;
-        } else {
-            mFlingRunnable = null;
-            return false;
+        }
+        return false;
+    }
+
+    /**
+     * Return true if the view can be dragged.
+     */
+    boolean canDragView(V view) {
+        return false;
+    }
+
+    /**
+     * Returns the maximum px offset when {@code view} is being dragged.
+     */
+    int getMaxDragOffset(V view) {
+        return -view.getHeight();
+    }
+
+    int getScrollRangeForDragFling(V view) {
+        return view.getHeight();
+    }
+
+    private void ensureVelocityTracker() {
+        if (mVelocityTracker == null) {
+            mVelocityTracker = VelocityTracker.obtain();
         }
     }
 
     private class FlingRunnable implements Runnable {
         private final CoordinatorLayout mParent;
-        private final View mLayout;
+        private final V mLayout;
 
-        FlingRunnable(CoordinatorLayout parent, View layout) {
+        FlingRunnable(CoordinatorLayout parent, V layout) {
             mParent = parent;
             mLayout = layout;
         }
diff --git a/design/src/android/support/design/widget/HeaderScrollingViewBehavior.java b/design/src/android/support/design/widget/HeaderScrollingViewBehavior.java
index 3b43a91..ac24ca2 100644
--- a/design/src/android/support/design/widget/HeaderScrollingViewBehavior.java
+++ b/design/src/android/support/design/widget/HeaderScrollingViewBehavior.java
@@ -31,16 +31,12 @@
  */
 abstract class HeaderScrollingViewBehavior extends ViewOffsetBehavior<View> {
 
-    public HeaderScrollingViewBehavior() {
-        super();
-    }
+    public HeaderScrollingViewBehavior() {}
 
     public HeaderScrollingViewBehavior(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
-    protected abstract View findFirstDependency(List<View> views);
-
     @Override
     public boolean onMeasureChild(CoordinatorLayout parent, View child,
             int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec,
@@ -88,7 +84,9 @@
         return false;
     }
 
-    protected int getScrollRange(View v) {
+    abstract View findFirstDependency(List<View> views);
+
+    int getScrollRange(View v) {
         return v.getMeasuredHeight();
     }
 }
\ No newline at end of file
diff --git a/design/src/android/support/design/widget/NavigationHeaderBehavior.java b/design/src/android/support/design/widget/NavigationHeaderBehavior.java
index 4b38146..4ffe84d 100644
--- a/design/src/android/support/design/widget/NavigationHeaderBehavior.java
+++ b/design/src/android/support/design/widget/NavigationHeaderBehavior.java
@@ -30,11 +30,9 @@
  * The {@link Behavior} for the {@link NavigationView} header. This handles scrolling the
  * header view with the list that displays the menu in {@link NavigationView}.
  */
-public final class NavigationHeaderBehavior extends HeaderBehavior<View> {
+class NavigationHeaderBehavior extends HeaderBehavior<View> {
 
-    public NavigationHeaderBehavior() {
-        super();
-    }
+    public NavigationHeaderBehavior() {}
 
     public NavigationHeaderBehavior(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -45,16 +43,13 @@
         return dependency instanceof RecyclerView;
     }
 
+    @Override
     public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View header,
             View directTargetChild, View target, int nestedScrollAxes) {
         // Return true if we're nested scrolling vertically, and the scrolling view is big enough
         // to scroll.
-        boolean canTargetScroll = ViewCompat.canScrollVertically(target, -1)
-                || ViewCompat.canScrollVertically(target,1);
-        final boolean started = (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0
-                && coordinatorLayout.getHeight() - target.getHeight() <= header.getHeight()
-                && canTargetScroll;
-        return started;
+        return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0
+                && coordinatorLayout.getHeight() - target.getHeight() <= header.getHeight();
     }
 
     @Override
@@ -89,10 +84,9 @@
     /**
      * Defines the behavior for the scrolling view used to back {@link NavigationView}.
      */
-    public static class MenuViewBehavior extends HeaderScrollingViewBehavior {
+    static final class MenuViewBehavior extends HeaderScrollingViewBehavior {
 
-        public MenuViewBehavior() {
-        }
+        public MenuViewBehavior() {}
 
         public MenuViewBehavior(Context context, AttributeSet attrs) {
             super(context, attrs);
@@ -112,14 +106,14 @@
             if (behavior instanceof NavigationHeaderBehavior) {
                 // Offset the menu so that it is below the header.
                 final int headerOffset = ((NavigationHeaderBehavior) behavior)
-                        .getTopAndBottomOffset();
+                        .getTopBottomOffsetForScrollingSibling();
                 setTopAndBottomOffset(dependency.getHeight() + headerOffset);
             }
             return false;
         }
 
         @Override
-        protected View findFirstDependency(List<View> views) {
+        View findFirstDependency(List<View> views) {
             for (int i = 0, z = views.size(); i < z; i++) {
                 View view = views.get(i);
                 if (view.getId() == R.id.navigation_header_container) {