summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/current.txt37
-rw-r--r--core/java/android/view/View.java307
-rw-r--r--core/java/android/view/ViewGroup.java76
-rw-r--r--core/java/android/view/ViewParent.java115
-rw-r--r--core/java/android/view/ViewRootImpl.java27
-rw-r--r--core/java/android/widget/ScrollView.java76
-rw-r--r--core/res/res/values/attrs.xml4
-rw-r--r--core/res/res/values/public.xml1
-rw-r--r--core/res/res/values/symbols.xml1
9 files changed, 616 insertions, 28 deletions
diff --git a/api/current.txt b/api/current.txt
index ef6aed7..6f511b5 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -825,6 +825,7 @@ package android {
field public static final int name = 16842755; // 0x1010003
field public static final int navigationMode = 16843471; // 0x10102cf
field public static final int negativeButtonText = 16843254; // 0x10101f6
+ field public static final int nestedScrollingEnabled = 16843843; // 0x1010443
field public static final int nextFocusDown = 16842980; // 0x10100e4
field public static final int nextFocusForward = 16843580; // 0x101033c
field public static final int nextFocusLeft = 16842977; // 0x10100e1
@@ -1285,8 +1286,8 @@ package android {
field public static final int windowActionBar = 16843469; // 0x10102cd
field public static final int windowActionBarOverlay = 16843492; // 0x10102e4
field public static final int windowActionModeOverlay = 16843485; // 0x10102dd
- field public static final int windowAllowEnterTransitionOverlap = 16843848; // 0x1010448
- field public static final int windowAllowExitTransitionOverlap = 16843847; // 0x1010447
+ field public static final int windowAllowEnterTransitionOverlap = 16843849; // 0x1010449
+ field public static final int windowAllowExitTransitionOverlap = 16843848; // 0x1010448
field public static final int windowAnimationStyle = 16842926; // 0x10100ae
field public static final int windowBackground = 16842836; // 0x1010054
field public static final int windowCloseOnTouchOutside = 16843611; // 0x101035b
@@ -1296,9 +1297,9 @@ package android {
field public static final int windowDisablePreview = 16843298; // 0x1010222
field public static final int windowEnableSplitTouch = 16843543; // 0x1010317
field public static final int windowEnterAnimation = 16842932; // 0x10100b4
- field public static final int windowEnterTransition = 16843843; // 0x1010443
+ field public static final int windowEnterTransition = 16843844; // 0x1010444
field public static final int windowExitAnimation = 16842933; // 0x10100b5
- field public static final int windowExitTransition = 16843844; // 0x1010444
+ field public static final int windowExitTransition = 16843845; // 0x1010445
field public static final int windowFrame = 16842837; // 0x1010055
field public static final int windowFullscreen = 16843277; // 0x101020d
field public static final int windowHideAnimation = 16842935; // 0x10100b7
@@ -1309,8 +1310,8 @@ package android {
field public static final int windowNoDisplay = 16843294; // 0x101021e
field public static final int windowNoTitle = 16842838; // 0x1010056
field public static final int windowOverscan = 16843727; // 0x10103cf
- field public static final int windowSharedElementEnterTransition = 16843845; // 0x1010445
- field public static final int windowSharedElementExitTransition = 16843846; // 0x1010446
+ field public static final int windowSharedElementEnterTransition = 16843846; // 0x1010446
+ field public static final int windowSharedElementExitTransition = 16843847; // 0x1010447
field public static final int windowShowAnimation = 16842934; // 0x10100b6
field public static final int windowShowWallpaper = 16843410; // 0x1010292
field public static final int windowSoftInputMode = 16843307; // 0x101022b
@@ -29983,6 +29984,9 @@ package android.view {
method public boolean dispatchKeyEvent(android.view.KeyEvent);
method public boolean dispatchKeyEventPreIme(android.view.KeyEvent);
method public boolean dispatchKeyShortcutEvent(android.view.KeyEvent);
+ method public boolean dispatchNestedFling(float, float);
+ method public boolean dispatchNestedPreScroll(int, int, int[], int[]);
+ method public boolean dispatchNestedScroll(int, int, int, int, int[]);
method public boolean dispatchPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
method protected void dispatchRestoreInstanceState(android.util.SparseArray<android.os.Parcelable>);
method protected void dispatchSaveInstanceState(android.util.SparseArray<android.os.Parcelable>);
@@ -30132,6 +30136,7 @@ package android.view {
method public float getY();
method public boolean hasFocus();
method public boolean hasFocusable();
+ method public boolean hasNestedScrollingParent();
method public boolean hasOnClickListeners();
method public boolean hasOverlappingRendering();
method public boolean hasTransientState();
@@ -30167,6 +30172,7 @@ package android.view {
method public boolean isLayoutDirectionResolved();
method public boolean isLayoutRequested();
method public boolean isLongClickable();
+ method public boolean isNestedScrollingEnabled();
method public boolean isOpaque();
method protected boolean isPaddingOffsetRequired();
method public boolean isPaddingRelative();
@@ -30318,6 +30324,7 @@ package android.view {
method protected final void setMeasuredDimension(int, int);
method public void setMinimumHeight(int);
method public void setMinimumWidth(int);
+ method public void setNestedScrollingEnabled(boolean);
method public void setNextFocusDownId(int);
method public void setNextFocusForwardId(int);
method public void setNextFocusLeftId(int);
@@ -30382,6 +30389,8 @@ package android.view {
method public android.view.ActionMode startActionMode(android.view.ActionMode.Callback);
method public void startAnimation(android.view.animation.Animation);
method public final boolean startDrag(android.content.ClipData, android.view.View.DragShadowBuilder, java.lang.Object, int);
+ method public boolean startNestedScroll(int);
+ method public void stopNestedScroll();
method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable);
method public void unscheduleDrawable(android.graphics.drawable.Drawable);
method protected boolean verifyDrawable(android.graphics.drawable.Drawable);
@@ -30470,6 +30479,9 @@ package android.view {
field public static final int SCROLLBAR_POSITION_DEFAULT = 0; // 0x0
field public static final int SCROLLBAR_POSITION_LEFT = 1; // 0x1
field public static final int SCROLLBAR_POSITION_RIGHT = 2; // 0x2
+ field public static final int SCROLL_AXIS_HORIZONTAL = 1; // 0x1
+ field public static final int SCROLL_AXIS_NONE = 0; // 0x0
+ field public static final int SCROLL_AXIS_VERTICAL = 2; // 0x2
field protected static final int[] SELECTED_STATE_SET;
field protected static final int[] SELECTED_WINDOW_FOCUSED_STATE_SET;
field public static final int SOUND_EFFECTS_ENABLED = 134217728; // 0x8000000
@@ -30739,6 +30751,7 @@ package android.view {
method public android.view.animation.Animation.AnimationListener getLayoutAnimationListener();
method public int getLayoutMode();
method public android.animation.LayoutTransition getLayoutTransition();
+ method public int getNestedScrollAxes();
method public int getPersistentDrawingCache();
method public int indexOfChild(android.view.View);
method public final void invalidateChild(android.view.View, android.graphics.Rect);
@@ -30759,8 +30772,14 @@ package android.view {
method public boolean onInterceptHoverEvent(android.view.MotionEvent);
method public boolean onInterceptTouchEvent(android.view.MotionEvent);
method protected abstract void onLayout(boolean, int, int, int, int);
+ method public boolean onNestedFling(android.view.View, float, float);
+ method public void onNestedPreScroll(android.view.View, int, int, int[]);
+ method public void onNestedScroll(android.view.View, int, int, int, int);
+ method public void onNestedScrollAccepted(android.view.View, android.view.View, int);
method protected boolean onRequestFocusInDescendants(int, android.graphics.Rect);
method public boolean onRequestSendAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
+ method public boolean onStartNestedScroll(android.view.View, android.view.View, int);
+ method public void onStopNestedScroll(android.view.View);
method public void recomputeViewAttributes(android.view.View);
method public void removeAllViews();
method public void removeAllViewsInLayout();
@@ -30891,6 +30910,12 @@ package android.view {
method public abstract boolean isTextAlignmentResolved();
method public abstract boolean isTextDirectionResolved();
method public abstract void notifySubtreeAccessibilityStateChanged(android.view.View, android.view.View, int);
+ method public abstract boolean onNestedFling(android.view.View, float, float);
+ method public abstract void onNestedPreScroll(android.view.View, int, int, int[]);
+ method public abstract void onNestedScroll(android.view.View, int, int, int, int);
+ method public abstract void onNestedScrollAccepted(android.view.View, android.view.View, int);
+ method public abstract boolean onStartNestedScroll(android.view.View, android.view.View, int);
+ method public abstract void onStopNestedScroll(android.view.View);
method public abstract void recomputeViewAttributes(android.view.View);
method public abstract void requestChildFocus(android.view.View, android.view.View);
method public abstract boolean requestChildRectangleOnScreen(android.view.View, android.graphics.Rect, boolean);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index cb05b05..84d1328 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -27,7 +27,6 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
-import android.graphics.Camera;
import android.graphics.Canvas;
import android.graphics.Insets;
import android.graphics.Interpolator;
@@ -2395,6 +2394,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
static final int PFLAG3_FITTING_SYSTEM_WINDOWS = 0x100;
+ /**
+ * Flag indicating that nested scrolling is enabled for this view.
+ * The view will optionally cooperate with views up its parent chain to allow for
+ * integrated nested scrolling along the same axis.
+ */
+ static final int PFLAG3_NESTED_SCROLLING_ENABLED = 0x200;
+
/* End of masks for mPrivateFlags3 */
static final int DRAG_MASK = PFLAG2_DRAG_CAN_ACCEPT | PFLAG2_DRAG_HOVERED;
@@ -2847,6 +2853,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
public static final int SCREEN_STATE_ON = 0x1;
/**
+ * Indicates no axis of view scrolling.
+ */
+ public static final int SCROLL_AXIS_NONE = 0;
+
+ /**
+ * Indicates scrolling along the horizontal axis.
+ */
+ public static final int SCROLL_AXIS_HORIZONTAL = 1 << 0;
+
+ /**
+ * Indicates scrolling along the vertical axis.
+ */
+ public static final int SCROLL_AXIS_VERTICAL = 1 << 1;
+
+ /**
* Controls the over-scroll mode for this view.
* See {@link #overScrollBy(int, int, int, int, int, int, int, int, boolean)},
* {@link #OVER_SCROLL_ALWAYS}, {@link #OVER_SCROLL_IF_CONTENT_SCROLLS},
@@ -3473,6 +3494,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
ViewOverlay mOverlay;
/**
+ * The currently active parent view for receiving delegated nested scrolling events.
+ * This is set by {@link #startNestedScroll(int)} during a touch interaction and cleared
+ * by {@link #stopNestedScroll()} at the same point where we clear
+ * requestDisallowInterceptTouchEvent.
+ */
+ private ViewParent mNestedScrollingParent;
+
+ /**
* Consistency verifier for debugging purposes.
* @hide
*/
@@ -3482,6 +3511,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);
+ private int[] mTempNestedScrollConsumed;
+
/**
* Simple constructor to use when creating a view from code.
*
@@ -3963,6 +3994,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
case R.styleable.View_sharedElementName:
setSharedElementName(a.getString(attr));
break;
+ case R.styleable.View_nestedScrollingEnabled:
+ setNestedScrollingEnabled(a.getBoolean(attr, false));
+ break;
}
}
@@ -7976,27 +8010,46 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @return True if the event was handled by the view, false otherwise.
*/
public boolean dispatchTouchEvent(MotionEvent event) {
+ boolean result = false;
+
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
+ final int actionMasked = event.getActionMasked();
+ if (actionMasked == MotionEvent.ACTION_DOWN) {
+ // Defensive cleanup for new gesture
+ stopNestedScroll();
+ }
+
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
- if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
+ if (li != null && li.mOnTouchListener != null
+ && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
- return true;
+ result = true;
}
- if (onTouchEvent(event)) {
- return true;
+ if (!result && onTouchEvent(event)) {
+ result = true;
}
}
- if (mInputEventConsistencyVerifier != null) {
+ if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
- return false;
+
+ // Clean up after nested scrolls if this is the end of a gesture;
+ // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
+ // of the gesture.
+ if (actionMasked == MotionEvent.ACTION_UP ||
+ actionMasked == MotionEvent.ACTION_CANCEL ||
+ (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
+ stopNestedScroll();
+ }
+
+ return result;
}
/**
@@ -12735,6 +12788,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
removeLongPressCallback();
removePerformClickCallback();
removeSendViewScrolledAccessibilityEventCallback();
+ stopNestedScroll();
destroyDrawingCache();
destroyLayer(false);
@@ -17901,6 +17955,245 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * Enable or disable nested scrolling for this view.
+ *
+ * <p>If this property is set to true the view will be permitted to initiate nested
+ * scrolling operations with a compatible parent view in the current hierarchy. If this
+ * view does not implement nested scrolling this will have no effect.</p>
+ *
+ * @param enabled true to enable nested scrolling, false to disable
+ *
+ * @see #isNestedScrollingEnabled()
+ */
+ public void setNestedScrollingEnabled(boolean enabled) {
+ if (enabled) {
+ mPrivateFlags3 |= PFLAG3_NESTED_SCROLLING_ENABLED;
+ } else {
+ mPrivateFlags3 &= ~PFLAG3_NESTED_SCROLLING_ENABLED;
+ }
+ }
+
+ /**
+ * Returns true if nested scrolling is enabled for this view.
+ *
+ * <p>If nested scrolling is enabled and this View class implementation supports it,
+ * this view will act as a nested scrolling child view when applicable, forwarding data
+ * about the scroll operation in progress to a compatible and cooperating nested scrolling
+ * parent.</p>
+ *
+ * @return true if nested scrolling is enabled
+ *
+ * @see #setNestedScrollingEnabled(boolean)
+ */
+ public boolean isNestedScrollingEnabled() {
+ return (mPrivateFlags3 & PFLAG3_NESTED_SCROLLING_ENABLED) ==
+ PFLAG3_NESTED_SCROLLING_ENABLED;
+ }
+
+ /**
+ * Begin a nestable scroll operation along the given axes.
+ *
+ * <p>A view starting a nested scroll promises to abide by the following contract:</p>
+ *
+ * <p>The view will call startNestedScroll upon initiating a scroll operation. In the case
+ * of a touch scroll this corresponds to the initial {@link MotionEvent#ACTION_DOWN}.
+ * In the case of touch scrolling the nested scroll will be terminated automatically in
+ * the same manner as {@link ViewParent#requestDisallowInterceptTouchEvent(boolean)}.
+ * In the event of programmatic scrolling the caller must explicitly call
+ * {@link #stopNestedScroll()} to indicate the end of the nested scroll.</p>
+ *
+ * <p>If <code>startNestedScroll</code> returns true, a cooperative parent was found.
+ * If it returns false the caller may ignore the rest of this contract until the next scroll.
+ * </p>
+ *
+ * <p>At each incremental step of the scroll the caller should invoke
+ * {@link #dispatchNestedPreScroll(int, int, int[], int[]) dispatchNestedPreScroll}
+ * once it has calculated the requested scrolling delta. If it returns true the nested scrolling
+ * parent at least partially consumed the scroll and the caller should adjust the amount it
+ * scrolls by.</p>
+ *
+ * <p>After applying the remainder of the scroll delta the caller should invoke
+ * {@link #dispatchNestedScroll(int, int, int, int, int[]) dispatchNestedScroll}, passing
+ * both the delta consumed and the delta unconsumed. A nested scrolling parent may treat
+ * these values differently. See {@link ViewParent#onNestedScroll(View, int, int, int, int)}.
+ * </p>
+ *
+ * @param axes Flags consisting of a combination of {@link #SCROLL_AXIS_HORIZONTAL} and/or
+ * {@link #SCROLL_AXIS_VERTICAL}.
+ * @return true if a cooperative parent was found and nested scrolling has been enabled for
+ * the current gesture.
+ *
+ * @see #stopNestedScroll()
+ * @see #dispatchNestedPreScroll(int, int, int[], int[])
+ * @see #dispatchNestedScroll(int, int, int, int, int[])
+ */
+ public boolean startNestedScroll(int axes) {
+ if (isNestedScrollingEnabled()) {
+ ViewParent p = getParent();
+ View child = this;
+ while (p != null) {
+ try {
+ if (p.onStartNestedScroll(child, this, axes)) {
+ mNestedScrollingParent = p;
+ p.onNestedScrollAccepted(child, this, axes);
+ return true;
+ }
+ } catch (AbstractMethodError e) {
+ Log.e(VIEW_LOG_TAG, "ViewParent " + p + " does not implement interface " +
+ "method onStartNestedScroll", e);
+ // Allow the search upward to continue
+ }
+ if (p instanceof View) {
+ child = (View) p;
+ }
+ p = p.getParent();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Stop a nested scroll in progress.
+ *
+ * <p>Calling this method when a nested scroll is not currently in progress is harmless.</p>
+ *
+ * @see #startNestedScroll(int)
+ */
+ public void stopNestedScroll() {
+ if (mNestedScrollingParent != null) {
+ mNestedScrollingParent.onStopNestedScroll(this);
+ mNestedScrollingParent = null;
+ }
+ }
+
+ /**
+ * Returns true if this view has a nested scrolling parent.
+ *
+ * <p>The presence of a nested scrolling parent indicates that this view has initiated
+ * a nested scroll and it was accepted by an ancestor view further up the view hierarchy.</p>
+ *
+ * @return whether this view has a nested scrolling parent
+ */
+ public boolean hasNestedScrollingParent() {
+ return mNestedScrollingParent != null;
+ }
+
+ /**
+ * Dispatch one step of a nested scroll in progress.
+ *
+ * <p>Implementations of views that support nested scrolling should call this to report
+ * info about a scroll in progress to the current nested scrolling parent. If a nested scroll
+ * is not currently in progress or nested scrolling is not
+ * {@link #isNestedScrollingEnabled() enabled} for this view this method does nothing.</p>
+ *
+ * <p>Compatible View implementations should also call
+ * {@link #dispatchNestedPreScroll(int, int, int[], int[]) dispatchNestedPreScroll} before
+ * consuming a component of the scroll event themselves.</p>
+ *
+ * @param dxConsumed Horizontal distance in pixels consumed by this view during this scroll step
+ * @param dyConsumed Vertical distance in pixels consumed by this view during this scroll step
+ * @param dxUnconsumed Horizontal scroll distance in pixels not consumed by this view
+ * @param dyUnconsumed Horizontal scroll distance in pixels not consumed by this view
+ * @param offsetInWindow Optional. If not null, on return this will contain the offset
+ * in local view coordinates of this view from before this operation
+ * to after it completes. View implementations may use this to adjust
+ * expected input coordinate tracking.
+ * @return true if the event was dispatched, false if it could not be dispatched.
+ * @see #dispatchNestedPreScroll(int, int, int[], int[])
+ */
+ public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
+ int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {
+ if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
+ int startX = 0;
+ int startY = 0;
+ if (offsetInWindow != null) {
+ getLocationInWindow(offsetInWindow);
+ startX = offsetInWindow[0];
+ startY = offsetInWindow[1];
+ }
+
+ mNestedScrollingParent.onNestedScroll(this, dxConsumed, dyConsumed,
+ dxUnconsumed, dyUnconsumed);
+
+ if (offsetInWindow != null) {
+ getLocationInWindow(offsetInWindow);
+ offsetInWindow[0] -= startX;
+ offsetInWindow[1] -= startY;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Dispatch one step of a nested scroll in progress before this view consumes any portion of it.
+ *
+ * <p>Nested pre-scroll events are to nested scroll events what touch intercept is to touch.
+ * <code>dispatchNestedPreScroll</code> offers an opportunity for the parent view in a nested
+ * scrolling operation to consume some or all of the scroll operation before the child view
+ * consumes it.</p>
+ *
+ * @param dx Horizontal scroll distance in pixels
+ * @param dy Vertical scroll distance in pixels
+ * @param consumed Output. If not null, consumed[0] will contain the consumed component of dx
+ * and consumed[1] the consumed dy.
+ * @param offsetInWindow Optional. If not null, on return this will contain the offset
+ * in local view coordinates of this view from before this operation
+ * to after it completes. View implementations may use this to adjust
+ * expected input coordinate tracking.
+ * @return true if the parent consumed some or all of the scroll delta
+ * @see #dispatchNestedScroll(int, int, int, int, int[])
+ */
+ public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
+ if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
+ int startX = 0;
+ int startY = 0;
+ if (offsetInWindow != null) {
+ getLocationInWindow(offsetInWindow);
+ startX = offsetInWindow[0];
+ startY = offsetInWindow[1];
+ }
+
+ if (consumed == null) {
+ if (mTempNestedScrollConsumed == null) {
+ mTempNestedScrollConsumed = new int[2];
+ }
+ consumed = mTempNestedScrollConsumed;
+ }
+ consumed[0] = 0;
+ consumed[1] = 0;
+ mNestedScrollingParent.onNestedPreScroll(this, dx, dy, consumed);
+
+ if (offsetInWindow != null) {
+ getLocationInWindow(offsetInWindow);
+ offsetInWindow[0] -= startX;
+ offsetInWindow[1] -= startY;
+ }
+ return consumed[0] != 0 || consumed[1] != 0;
+ }
+ return false;
+ }
+
+ /**
+ * Dispatch a fling to a nested scrolling parent.
+ *
+ * <p>If a nested scrolling child view would normally fling but it is at the edge of its
+ * own content it should use this method to delegate the fling to its nested scrolling parent.
+ * The view implementation can use a {@link VelocityTracker} to obtain the velocity values
+ * to pass.</p>
+ *
+ * @param velocityX Horizontal fling velocity in pixels per second
+ * @param velocityY Vertical fling velocity in pixels per second
+ * @return true if the nested scrolling parent consumed the fling
+ */
+ public boolean dispatchNestedFling(float velocityX, float velocityY) {
+ if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
+ return mNestedScrollingParent.onNestedFling(this, velocityX, velocityY);
+ }
+ return false;
+ }
+
+ /**
* Gets a scale factor that determines the distance the view should scroll
* vertically in response to {@link MotionEvent#ACTION_SCROLL}.
* @return The vertical scroll scale factor.
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 9d4ffb1..ad76145 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -460,6 +460,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
@ViewDebug.ExportedProperty(category = "layout")
private int mChildCountWithTransientState = 0;
+ /**
+ * Currently registered axes for nested scrolling. Flag set consisting of
+ * {@link #SCROLL_AXIS_HORIZONTAL} {@link #SCROLL_AXIS_VERTICAL} or {@link #SCROLL_AXIS_NONE}
+ * for null.
+ */
+ private int mNestedScrollAxes;
+
public ViewGroup(Context context) {
this(context, null);
}
@@ -2011,6 +2018,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
clearTouchTargets();
resetCancelNextUpFlag(this);
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
+ mNestedScrollAxes = SCROLL_AXIS_NONE;
}
/**
@@ -5856,6 +5864,74 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
return true;
}
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
+ return false;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public void onNestedScrollAccepted(View child, View target, int axes) {
+ mNestedScrollAxes = axes;
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * <p>The default implementation of onStopNestedScroll calls
+ * {@link #stopNestedScroll()} to halt any recursive nested scrolling in progress.</p>
+ */
+ @Override
+ public void onStopNestedScroll(View child) {
+ // Stop any recursive nested scrolling.
+ stopNestedScroll();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
+ int dxUnconsumed, int dyUnconsumed) {
+ // Do nothing
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
+ // Do nothing
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public boolean onNestedFling(View target, float velocityX, float velocityY) {
+ return false;
+ }
+
+ /**
+ * Return the current axes of nested scrolling for this ViewGroup.
+ *
+ * <p>A ViewGroup returning something other than {@link #SCROLL_AXIS_NONE} is currently
+ * acting as a nested scrolling parent for one or more descendant views in the hierarchy.</p>
+ *
+ * @return Flags indicating the current axes of nested scrolling
+ * @see #SCROLL_AXIS_HORIZONTAL
+ * @see #SCROLL_AXIS_VERTICAL
+ * @see #SCROLL_AXIS_NONE
+ */
+ public int getNestedScrollAxes() {
+ return mNestedScrollAxes;
+ }
+
/** @hide */
protected void onSetLayoutParams(View child, LayoutParams layoutParams) {
}
diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java
index 0137693..3cd6449 100644
--- a/core/java/android/view/ViewParent.java
+++ b/core/java/android/view/ViewParent.java
@@ -407,4 +407,119 @@ public interface ViewParent {
* {@link View#TEXT_ALIGNMENT_VIEW_END}
*/
public int getTextAlignment();
+
+ /**
+ * React to a descendant view initiating a nestable scroll operation, claiming the
+ * nested scroll operation if appropriate.
+ *
+ * <p>This method will be called in response to a descendant view invoking
+ * {@link View#startNestedScroll(int)}. Each parent up the view hierarchy will be
+ * given an opportunity to respond and claim the nested scrolling operation by returning
+ * <code>true</code>.</p>
+ *
+ * <p>This method may be overridden by ViewParent implementations to indicate when the view
+ * is willing to support a nested scrolling operation that is about to begin. If it returns
+ * true, this ViewParent will become the target view's nested scrolling parent for the duration
+ * of the scroll operation in progress. When the nested scroll is finished this ViewParent
+ * will receive a call to {@link #onStopNestedScroll(View)}.
+ * </p>
+ *
+ * @param child Direct child of this ViewParent containing target
+ * @param target View that initiated the nested scroll
+ * @param nestedScrollAxes Flags consisting of {@link View#SCROLL_AXIS_HORIZONTAL},
+ * {@link View#SCROLL_AXIS_VERTICAL} or both
+ * @return true if this ViewParent accepts the nested scroll operation
+ */
+ public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes);
+
+ /**
+ * React to the successful claiming of a nested scroll operation.
+ *
+ * <p>This method will be called after
+ * {@link #onStartNestedScroll(View, View, int) onStartNestedScroll} returns true. It offers
+ * an opportunity for the view and its superclasses to perform initial configuration
+ * for the nested scroll. Implementations of this method should always call their superclass's
+ * implementation of this method if one is present.</p>
+ *
+ * @param child Direct child of this ViewParent containing target
+ * @param target View that initiated the nested scroll
+ * @param nestedScrollAxes Flags consisting of {@link View#SCROLL_AXIS_HORIZONTAL},
+ * {@link View#SCROLL_AXIS_VERTICAL} or both
+ * @see #onStartNestedScroll(View, View, int)
+ * @see #onStopNestedScroll(View)
+ */
+ public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes);
+
+ /**
+ * React to a nested scroll operation ending.
+ *
+ * <p>Perform cleanup after a nested scrolling operation.
+ * This method will be called when a nested scroll stops, for example when a nested touch
+ * scroll ends with a {@link MotionEvent#ACTION_UP} or {@link MotionEvent#ACTION_CANCEL} event.
+ * Implementations of this method should always call their superclass's implementation of this
+ * method if one is present.</p>
+ *
+ * @param target View that initiated the nested scroll
+ */
+ public void onStopNestedScroll(View target);
+
+ /**
+ * React to a nested scroll in progress.
+ *
+ * <p>This method will be called when the ViewParent's current nested scrolling child view
+ * dispatches a nested scroll event. To receive calls to this method the ViewParent must have
+ * previously returned <code>true</code> for a call to
+ * {@link #onStartNestedScroll(View, View, int)}.</p>
+ *
+ * <p>Both the consumed and unconsumed portions of the scroll distance are reported to the
+ * ViewParent. An implementation may choose to use the consumed portion to match or chase scroll
+ * position of multiple child elements, for example. The unconsumed portion may be used to
+ * allow continuous dragging of multiple scrolling or draggable elements, such as scrolling
+ * a list within a vertical drawer where the drawer begins dragging once the edge of inner
+ * scrolling content is reached.</p>
+ *
+ * @param target The descendent view controlling the nested scroll
+ * @param dxConsumed Horizontal scroll distance in pixels already consumed by target
+ * @param dyConsumed Vertical scroll distance in pixels already consumed by target
+ * @param dxUnconsumed Horizontal scroll distance in pixels not consumed by target
+ * @param dyUnconsumed Vertical scroll distance in pixels not consumed by target
+ */
+ public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
+ int dxUnconsumed, int dyUnconsumed);
+
+ /**
+ * React to a nested scroll in progress before the target view consumes a portion of the scroll.
+ *
+ * <p>When working with nested scrolling often the parent view may want an opportunity
+ * to consume the scroll before the nested scrolling child does. An example of this is a
+ * drawer that contains a scrollable list. The user will want to be able to scroll the list
+ * fully into view before the list itself begins scrolling.</p>
+ *
+ * <p><code>onNestedPreScroll</code> is called when a nested scrolling child invokes
+ * {@link View#dispatchNestedPreScroll(int, int, int[], int[])}. The implementation should
+ * report how any pixels of the scroll reported by dx, dy were consumed in the
+ * <code>consumed</code> array. Index 0 corresponds to dx and index 1 corresponds to dy.
+ * This parameter will never be null. Initial values for consumed[0] and consumed[1]
+ * will always be 0.</p>
+ *
+ * @param target View that initiated the nested scroll
+ * @param dx Horizontal scroll distance in pixels
+ * @param dy Vertical scroll distance in pixels
+ * @param consumed Output. The horizontal and vertical scroll distance consumed by this parent
+ */
+ public void onNestedPreScroll(View target, int dx, int dy, int[] consumed);
+
+ /**
+ * Request a fling from a nested scroll.
+ *
+ * <p>If a nested scrolling child view would normally fling but it is at the edge of
+ * its own content, it can delegate the fling to its nested scrolling parent instead.
+ * This method allows the parent to optionally consume the fling.</p>
+ *
+ * @param target View that initiated the nested scroll
+ * @param velocityX Horizontal velocity in pixels per second.
+ * @param velocityY Vertical velocity in pixels per second
+ * @return true if this parent consumed the fling
+ */
+ public boolean onNestedFling(View target, float velocityX, float velocityY);
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 23be203..246905d 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -6090,6 +6090,33 @@ public final class ViewRootImpl implements ViewParent,
// Do nothing.
}
+ @Override
+ public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
+ return false;
+ }
+
+ @Override
+ public void onStopNestedScroll(View target) {
+ }
+
+ @Override
+ public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {
+ }
+
+ @Override
+ public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
+ int dxUnconsumed, int dyUnconsumed) {
+ }
+
+ @Override
+ public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
+ }
+
+ @Override
+ public boolean onNestedFling(View target, float velocityX, float velocityY) {
+ return false;
+ }
+
void changeCanvasOpacity(boolean opaque) {
// TODO(romainguy): recreate Canvas (software or hardware) to reflect the opacity change.
Log.d(TAG, "changeCanvasOpacity: opaque=" + opaque);
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 082d728..34a6a40 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -138,6 +138,12 @@ public class ScrollView extends FrameLayout {
private int mActivePointerId = INVALID_POINTER;
/**
+ * Used during scrolling to retrieve the new offset within the window.
+ */
+ private final int[] mScrollOffset = new int[2];
+ private final int[] mScrollConsumed = new int[2];
+
+ /**
* The StrictMode "critical time span" objects to catch animation
* stutters. Non-null when a time-sensitive animation is
* in-flight. Must call finish() on them when done animating.
@@ -505,7 +511,7 @@ public class ScrollView extends FrameLayout {
final int y = (int) ev.getY(pointerIndex);
final int yDiff = Math.abs(y - mLastMotionY);
- if (yDiff > mTouchSlop) {
+ if (yDiff > mTouchSlop && (getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) {
mIsBeingDragged = true;
mLastMotionY = y;
initVelocityTrackerIfNotExists();
@@ -606,6 +612,7 @@ public class ScrollView extends FrameLayout {
// Remember where the motion event started
mLastMotionY = (int) ev.getY();
mActivePointerId = ev.getPointerId(0);
+ startNestedScroll(SCROLL_AXIS_VERTICAL);
break;
}
case MotionEvent.ACTION_MOVE:
@@ -617,6 +624,9 @@ public class ScrollView extends FrameLayout {
final int y = (int) ev.getY(activePointerIndex);
int deltaY = mLastMotionY - y;
+ if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset)) {
+ deltaY -= mScrollConsumed[1] + mScrollOffset[1];
+ }
if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) {
final ViewParent parent = getParent();
if (parent != null) {
@@ -633,22 +643,25 @@ public class ScrollView extends FrameLayout {
// Scroll to follow the motion event
mLastMotionY = y;
- final int oldX = mScrollX;
final int oldY = mScrollY;
final int range = getScrollRange();
final int overscrollMode = getOverScrollMode();
- final boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
+ boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
(overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
// Calling overScrollBy will call onOverScrolled, which
// calls onScrollChanged if applicable.
- if (overScrollBy(0, deltaY, 0, mScrollY,
- 0, range, 0, mOverscrollDistance, true)) {
+ if (overScrollBy(0, deltaY, 0, mScrollY, 0, range, 0, mOverscrollDistance, true)
+ && !hasNestedScrollingParent()) {
// Break our velocity if we hit a scroll barrier.
mVelocityTracker.clear();
}
- if (canOverscroll) {
+ final int scrolledDeltaY = mScrollY - oldY;
+ final int unconsumedY = deltaY - scrolledDeltaY;
+ if (dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, mScrollOffset)) {
+ mLastMotionY -= mScrollOffset[1];
+ } else if (canOverscroll) {
final int pulledToY = oldY + deltaY;
if (pulledToY < 0) {
mEdgeGlowTop.onPull((float) deltaY / getHeight());
@@ -674,15 +687,11 @@ public class ScrollView extends FrameLayout {
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
- if (getChildCount() > 0) {
- if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
- fling(-initialVelocity);
- } else {
- if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0,
- getScrollRange())) {
- postInvalidateOnAnimation();
- }
- }
+ if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
+ flingWithNestedDispatch(-initialVelocity);
+ } else if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0,
+ getScrollRange())) {
+ postInvalidateOnAnimation();
}
mActivePointerId = INVALID_POINTER;
@@ -1553,6 +1562,15 @@ public class ScrollView extends FrameLayout {
}
}
+ private void flingWithNestedDispatch(int velocityY) {
+ if (mScrollY == 0 && velocityY < 0 ||
+ mScrollY == getScrollRange() && velocityY > 0) {
+ dispatchNestedFling(0, velocityY);
+ } else {
+ fling(velocityY);
+ }
+ }
+
private void endDrag() {
mIsBeingDragged = false;
@@ -1603,6 +1621,34 @@ public class ScrollView extends FrameLayout {
}
@Override
+ public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
+ return (nestedScrollAxes & SCROLL_AXIS_VERTICAL) != 0;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public void onStopNestedScroll(View target) {
+ super.onStopNestedScroll(target);
+ }
+
+ @Override
+ public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
+ int dxUnconsumed, int dyUnconsumed) {
+ scrollBy(0, dyUnconsumed);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public boolean onNestedFling(View target, float velocityX, float velocityY) {
+ flingWithNestedDispatch((int) velocityY);
+ return true;
+ }
+
+ @Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (mEdgeGlowTop != null) {
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index b7bffde..508a557 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2357,6 +2357,10 @@
Activity to UI element in the called Activity. Names should be unique in the
View hierarchy. -->
<attr name="sharedElementName" format="string" />
+
+ <!-- Specifies that this view should permit nested scrolling within a compatible
+ ancestor view. -->
+ <attr name="nestedScrollingEnabled" format="boolean" />
</declare-styleable>
<!-- Attributes that can be assigned to a tag for a particular View. -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 9712c03..7a6cc5b 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2153,6 +2153,7 @@
<public type="attr" name="colorPrimary" />
<public type="attr" name="colorPrimaryDark" />
<public type="attr" name="colorAccent" />
+ <public type="attr" name="nestedScrollingEnabled" />
<public-padding type="dimen" name="l_resource_pad" end="0x01050010" />
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index bc92107..bb0d184 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1801,6 +1801,7 @@
<java-symbol type="attr" name="amPmSelectedBackgroundColor" />
<java-symbol type="attr" name="numbersSelectorColor" />
<java-symbol type="attr" name="timePickerHeaderTimeLabelTextAppearance" />
+ <java-symbol type="attr" name="nestedScrollingEnabled" />
<java-symbol type="style" name="TextAppearance.Holo.TimePicker.TimeLabel" />
<java-symbol type="layout" name="time_picker_holo" />
<java-symbol type="layout" name="time_header_label" />