diff options
-rw-r--r-- | api/current.txt | 37 | ||||
-rw-r--r-- | core/java/android/view/View.java | 307 | ||||
-rw-r--r-- | core/java/android/view/ViewGroup.java | 76 | ||||
-rw-r--r-- | core/java/android/view/ViewParent.java | 115 | ||||
-rw-r--r-- | core/java/android/view/ViewRootImpl.java | 27 | ||||
-rw-r--r-- | core/java/android/widget/ScrollView.java | 76 | ||||
-rw-r--r-- | core/res/res/values/attrs.xml | 4 | ||||
-rw-r--r-- | core/res/res/values/public.xml | 1 | ||||
-rw-r--r-- | core/res/res/values/symbols.xml | 1 |
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" /> |