blob: a3e89c85fcbc7f7ccf373083a6652b87f84ea3ad [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.widget;
18
Yigit Boyar08b1d992019-02-06 12:34:21 -080019import android.annotation.ColorInt;
Siva Velusamy94a6d152015-05-05 15:07:00 -070020import android.annotation.NonNull;
Mathew Inwood978c6e22018-08-21 15:58:55 +010021import android.annotation.UnsupportedAppUsage;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080022import android.content.Context;
Aurimas Liutikas99441c52016-10-11 16:48:32 -070023import android.content.res.Configuration;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080024import android.content.res.TypedArray;
Adam Powell637d3372010-08-25 14:37:03 -070025import android.graphics.Canvas;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026import android.graphics.Rect;
Aurimas Liutikas99441c52016-10-11 16:48:32 -070027import android.os.Build;
28import android.os.Build.VERSION_CODES;
Svetoslav Ganova1dc7612012-05-10 04:14:53 -070029import android.os.Bundle;
Aurimas Liutikas99441c52016-10-11 16:48:32 -070030import android.os.Parcel;
31import android.os.Parcelable;
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -080032import android.os.StrictMode;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080033import android.util.AttributeSet;
Johan Rosengren0dc291e2011-02-21 09:49:45 +010034import android.util.Log;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080035import android.view.FocusFinder;
Jeff Brown33bbfd22011-02-24 20:55:35 -080036import android.view.InputDevice;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080037import android.view.KeyEvent;
38import android.view.MotionEvent;
39import android.view.VelocityTracker;
40import android.view.View;
41import android.view.ViewConfiguration;
Gilles Debunne2ed2eac2011-02-24 16:29:48 -080042import android.view.ViewDebug;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080043import android.view.ViewGroup;
Siva Velusamy94a6d152015-05-05 15:07:00 -070044import android.view.ViewHierarchyEncoder;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080045import android.view.ViewParent;
Svetoslav Ganova0156172011-06-26 17:55:44 -070046import android.view.accessibility.AccessibilityEvent;
47import android.view.accessibility.AccessibilityNodeInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080048import android.view.animation.AnimationUtils;
Ashley Rose55f9f922019-01-28 19:29:36 -050049import android.view.inspector.InspectableProperty;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080050
Aurimas Liutikas99441c52016-10-11 16:48:32 -070051import com.android.internal.R;
52
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080053import java.util.List;
54
55/**
Joe Fernandez1ef22a52017-04-26 00:15:44 -070056 * A view group that allows the view hierarchy placed within it to be scrolled.
57 * Scroll view may have only one direct child placed within it.
58 * To add multiple views within the scroll view, make
59 * the direct child you add a view group, for example {@link LinearLayout}, and
60 * place additional views within that LinearLayout.
Mindy Pereira4e30d892010-11-24 15:32:39 -080061 *
Joe Fernandez1ef22a52017-04-26 00:15:44 -070062 * <p>Scroll view supports vertical scrolling only. For horizontal scrolling,
63 * use {@link HorizontalScrollView} instead.</p>
64 *
65 * <p>Never add a {@link android.support.v7.widget.RecyclerView} or {@link ListView} to
66 * a scroll view. Doing so results in poor user interface performance and a poor user
67 * experience.</p>
68 *
69 * <p class="note">
70 * For vertical scrolling, consider {@link android.support.v4.widget.NestedScrollView}
71 * instead of scroll view which offers greater user interface flexibility and
72 * support for the material design scrolling patterns.</p>
73 *
74 * <p>To learn more about material design patterns for handling scrolling, see
75 * <a href="https://material.io/guidelines/patterns/scrolling-techniques.html#">
76 * Scrolling techniques</a>.</p>
Romain Guyfdbf4842010-08-16 10:55:49 -070077 *
78 * @attr ref android.R.styleable#ScrollView_fillViewport
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080079 */
80public class ScrollView extends FrameLayout {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080081 static final int ANIMATED_SCROLL_GAP = 250;
82
83 static final float MAX_SCROLL_FACTOR = 0.5f;
84
Johan Rosengren0dc291e2011-02-21 09:49:45 +010085 private static final String TAG = "ScrollView";
86
Mathew Inwood978c6e22018-08-21 15:58:55 +010087 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080088 private long mLastScroll;
89
90 private final Rect mTempRect = new Rect();
Mathew Inwood978c6e22018-08-21 15:58:55 +010091 @UnsupportedAppUsage
Adam Powell637d3372010-08-25 14:37:03 -070092 private OverScroller mScroller;
Yigit Boyar08b1d992019-02-06 12:34:21 -080093 /**
94 * Tracks the state of the top edge glow.
95 *
96 * Even though this field is practically final, we cannot make it final because there are apps
97 * setting it via reflection and they need to keep working until they target Q.
98 */
99 @NonNull
100 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768600)
101 private EdgeEffect mEdgeGlowTop = new EdgeEffect(getContext());
102
103 /**
104 * Tracks the state of the bottom edge glow.
105 *
106 * Even though this field is practically final, we cannot make it final because there are apps
107 * setting it via reflection and they need to keep working until they target Q.
108 */
109 @NonNull
110 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769386)
111 private EdgeEffect mEdgeGlowBottom = new EdgeEffect(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800112
113 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800114 * Position of the last motion event.
115 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100116 @UnsupportedAppUsage
Adam Powelldf3ae4f2012-04-10 18:55:22 -0700117 private int mLastMotionY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800118
119 /**
120 * True when the layout has changed but the traversal has not come through yet.
121 * Ideally the view hierarchy would keep track of this for us.
122 */
123 private boolean mIsLayoutDirty = true;
124
125 /**
126 * The child to give focus to in the event that a child has requested focus while the
127 * layout is dirty. This prevents the scroll from being wrong if the child has not been
128 * laid out before requesting focus.
129 */
Rahul Ravikumar6d21df22019-02-04 16:08:12 -0800130 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769715)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800131 private View mChildToScrollTo = null;
132
133 /**
134 * True if the user is currently dragging this ScrollView around. This is
135 * not the same as 'is being flinged', which can be checked by
136 * mScroller.isFinished() (flinging begins when the user lifts his finger).
137 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100138 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800139 private boolean mIsBeingDragged = false;
140
141 /**
142 * Determines speed during touch scrolling
143 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100144 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800145 private VelocityTracker mVelocityTracker;
146
147 /**
148 * When set to true, the scroll view measure its child to make it fill the currently
149 * visible area.
150 */
Romain Guya174d7a2011-01-07 13:27:39 -0800151 @ViewDebug.ExportedProperty(category = "layout")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800152 private boolean mFillViewport;
153
154 /**
155 * Whether arrow scrolling is animated.
156 */
157 private boolean mSmoothScrollingEnabled = true;
158
159 private int mTouchSlop;
Rahul Ravikumar8065e8e2019-02-08 15:16:08 -0800160 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124051125)
Romain Guy4296fc42009-07-06 11:48:52 -0700161 private int mMinimumVelocity;
162 private int mMaximumVelocity;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800163
Rahul Ravikumar420654a92019-02-08 15:23:33 -0800164 @UnsupportedAppUsage(maxTargetSdk = VERSION_CODES.P, trackingBug = 124050903)
Adam Powell637d3372010-08-25 14:37:03 -0700165 private int mOverscrollDistance;
Rahul Ravikumar420654a92019-02-08 15:23:33 -0800166 @UnsupportedAppUsage(maxTargetSdk = VERSION_CODES.P, trackingBug = 124050903)
Adam Powell637d3372010-08-25 14:37:03 -0700167 private int mOverflingDistance;
168
Aaron Whytef8306522017-03-22 16:30:58 -0700169 private float mVerticalScrollFactor;
Ned Burns20ad0732016-08-18 14:22:57 -0400170
Adam Powellbc4e7532010-02-23 14:49:01 -0800171 /**
172 * ID of the active pointer. This is used to retain consistency during
173 * drags/flings if multiple pointers are used.
174 */
175 private int mActivePointerId = INVALID_POINTER;
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -0800176
177 /**
Adam Powell10ba2772014-04-15 09:46:51 -0700178 * Used during scrolling to retrieve the new offset within the window.
179 */
180 private final int[] mScrollOffset = new int[2];
181 private final int[] mScrollConsumed = new int[2];
Adam Powell744beff2014-09-22 09:47:48 -0700182 private int mNestedYOffset;
Adam Powell10ba2772014-04-15 09:46:51 -0700183
184 /**
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -0800185 * The StrictMode "critical time span" objects to catch animation
186 * stutters. Non-null when a time-sensitive animation is
187 * in-flight. Must call finish() on them when done animating.
188 * These are no-ops on user builds.
189 */
190 private StrictMode.Span mScrollStrictSpan = null; // aka "drag"
Mathew Inwood978c6e22018-08-21 15:58:55 +0100191 @UnsupportedAppUsage
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -0800192 private StrictMode.Span mFlingStrictSpan = null;
193
Adam Powellbc4e7532010-02-23 14:49:01 -0800194 /**
195 * Sentinel value for no current active pointer.
196 * Used by {@link #mActivePointerId}.
197 */
198 private static final int INVALID_POINTER = -1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800199
Fabrice Di Megliod6befbd2013-06-12 16:01:48 -0700200 private SavedState mSavedState;
201
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800202 public ScrollView(Context context) {
203 this(context, null);
204 }
205
206 public ScrollView(Context context, AttributeSet attrs) {
207 this(context, attrs, com.android.internal.R.attr.scrollViewStyle);
208 }
209
Alan Viverette617feb92013-09-09 18:09:13 -0700210 public ScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
211 this(context, attrs, defStyleAttr, 0);
212 }
213
214 public ScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
215 super(context, attrs, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800216 initScrollView();
217
Alan Viverette617feb92013-09-09 18:09:13 -0700218 final TypedArray a = context.obtainStyledAttributes(
219 attrs, com.android.internal.R.styleable.ScrollView, defStyleAttr, defStyleRes);
Aurimas Liutikasab324cf2019-02-07 16:46:38 -0800220 saveAttributeDataForStyleable(context, com.android.internal.R.styleable.ScrollView,
221 attrs, a, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800222
223 setFillViewport(a.getBoolean(R.styleable.ScrollView_fillViewport, false));
224
225 a.recycle();
Adam Powell2fe301d2016-08-15 16:34:37 -0700226
227 if (context.getResources().getConfiguration().uiMode == Configuration.UI_MODE_TYPE_WATCH) {
228 setRevealOnFocusHint(false);
229 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800230 }
231
232 @Override
Patrick Dubroye0a799a2011-05-04 16:19:22 -0700233 public boolean shouldDelayChildPressedState() {
234 return true;
235 }
236
237 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800238 protected float getTopFadingEdgeStrength() {
239 if (getChildCount() == 0) {
240 return 0.0f;
241 }
242
243 final int length = getVerticalFadingEdgeLength();
244 if (mScrollY < length) {
245 return mScrollY / (float) length;
246 }
247
248 return 1.0f;
249 }
250
251 @Override
252 protected float getBottomFadingEdgeStrength() {
253 if (getChildCount() == 0) {
254 return 0.0f;
255 }
256
257 final int length = getVerticalFadingEdgeLength();
258 final int bottomEdge = getHeight() - mPaddingBottom;
259 final int span = getChildAt(0).getBottom() - mScrollY - bottomEdge;
260 if (span < length) {
261 return span / (float) length;
262 }
263
264 return 1.0f;
265 }
266
267 /**
Yigit Boyar08b1d992019-02-06 12:34:21 -0800268 * Sets the edge effect color for both top and bottom edge effects.
269 *
270 * @param color The color for the edge effects.
271 * @see #setTopEdgeEffectColor(int)
272 * @see #setBottomEdgeEffectColor(int)
273 * @see #getTopEdgeEffectColor()
274 * @see #getBottomEdgeEffectColor()
275 */
276 public void setEdgeEffectColor(@ColorInt int color) {
277 setTopEdgeEffectColor(color);
278 setBottomEdgeEffectColor(color);
279 }
280
281 /**
282 * Sets the bottom edge effect color.
283 *
284 * @param color The color for the bottom edge effect.
285 * @see #setTopEdgeEffectColor(int)
286 * @see #setEdgeEffectColor(int)
287 * @see #getTopEdgeEffectColor()
288 * @see #getBottomEdgeEffectColor()
289 */
290 public void setBottomEdgeEffectColor(@ColorInt int color) {
291 mEdgeGlowBottom.setColor(color);
292 }
293
294 /**
295 * Sets the top edge effect color.
296 *
297 * @param color The color for the top edge effect.
298 * @see #setBottomEdgeEffectColor(int)
299 * @see #setEdgeEffectColor(int)
300 * @see #getTopEdgeEffectColor()
301 * @see #getBottomEdgeEffectColor()
302 */
303 public void setTopEdgeEffectColor(@ColorInt int color) {
304 mEdgeGlowTop.setColor(color);
305 }
306
307 /**
308 * Returns the top edge effect color.
309 *
310 * @return The top edge effect color.
311 * @see #setEdgeEffectColor(int)
312 * @see #setTopEdgeEffectColor(int)
313 * @see #setBottomEdgeEffectColor(int)
314 * @see #getBottomEdgeEffectColor()
315 */
316 @ColorInt
317 public int getTopEdgeEffectColor() {
318 return mEdgeGlowTop.getColor();
319 }
320
321 /**
322 * Returns the bottom edge effect color.
323 *
324 * @return The bottom edge effect color.
325 * @see #setEdgeEffectColor(int)
326 * @see #setTopEdgeEffectColor(int)
327 * @see #setBottomEdgeEffectColor(int)
328 * @see #getTopEdgeEffectColor()
329 */
330 @ColorInt
331 public int getBottomEdgeEffectColor() {
332 return mEdgeGlowBottom.getColor();
333 }
334
335 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800336 * @return The maximum amount this scroll view will scroll in response to
337 * an arrow event.
338 */
339 public int getMaxScrollAmount() {
340 return (int) (MAX_SCROLL_FACTOR * (mBottom - mTop));
341 }
342
343
344 private void initScrollView() {
Adam Powell637d3372010-08-25 14:37:03 -0700345 mScroller = new OverScroller(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800346 setFocusable(true);
347 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
348 setWillNotDraw(false);
Romain Guy4296fc42009-07-06 11:48:52 -0700349 final ViewConfiguration configuration = ViewConfiguration.get(mContext);
350 mTouchSlop = configuration.getScaledTouchSlop();
351 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
352 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
Adam Powell637d3372010-08-25 14:37:03 -0700353 mOverscrollDistance = configuration.getScaledOverscrollDistance();
354 mOverflingDistance = configuration.getScaledOverflingDistance();
Aaron Whytef8306522017-03-22 16:30:58 -0700355 mVerticalScrollFactor = configuration.getScaledVerticalScrollFactor();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800356 }
357
358 @Override
359 public void addView(View child) {
360 if (getChildCount() > 0) {
361 throw new IllegalStateException("ScrollView can host only one direct child");
362 }
363
364 super.addView(child);
365 }
366
367 @Override
368 public void addView(View child, int index) {
369 if (getChildCount() > 0) {
370 throw new IllegalStateException("ScrollView can host only one direct child");
371 }
372
373 super.addView(child, index);
374 }
375
376 @Override
377 public void addView(View child, ViewGroup.LayoutParams params) {
378 if (getChildCount() > 0) {
379 throw new IllegalStateException("ScrollView can host only one direct child");
380 }
381
382 super.addView(child, params);
383 }
384
385 @Override
386 public void addView(View child, int index, ViewGroup.LayoutParams params) {
387 if (getChildCount() > 0) {
388 throw new IllegalStateException("ScrollView can host only one direct child");
389 }
390
391 super.addView(child, index, params);
392 }
393
394 /**
395 * @return Returns true this ScrollView can be scrolled
396 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100397 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800398 private boolean canScroll() {
399 View child = getChildAt(0);
400 if (child != null) {
401 int childHeight = child.getHeight();
402 return getHeight() < childHeight + mPaddingTop + mPaddingBottom;
403 }
404 return false;
405 }
406
407 /**
408 * Indicates whether this ScrollView's content is stretched to fill the viewport.
409 *
410 * @return True if the content fills the viewport, false otherwise.
Mindy Pereira4e30d892010-11-24 15:32:39 -0800411 *
Romain Guyfdbf4842010-08-16 10:55:49 -0700412 * @attr ref android.R.styleable#ScrollView_fillViewport
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800413 */
Ashley Rose55f9f922019-01-28 19:29:36 -0500414 @InspectableProperty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800415 public boolean isFillViewport() {
416 return mFillViewport;
417 }
418
419 /**
420 * Indicates this ScrollView whether it should stretch its content height to fill
421 * the viewport or not.
422 *
423 * @param fillViewport True to stretch the content's height to the viewport's
424 * boundaries, false otherwise.
Mindy Pereira4e30d892010-11-24 15:32:39 -0800425 *
Romain Guyfdbf4842010-08-16 10:55:49 -0700426 * @attr ref android.R.styleable#ScrollView_fillViewport
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800427 */
428 public void setFillViewport(boolean fillViewport) {
429 if (fillViewport != mFillViewport) {
430 mFillViewport = fillViewport;
431 requestLayout();
432 }
433 }
434
435 /**
436 * @return Whether arrow scrolling will animate its transition.
437 */
438 public boolean isSmoothScrollingEnabled() {
439 return mSmoothScrollingEnabled;
440 }
441
442 /**
443 * Set whether arrow scrolling will animate its transition.
444 * @param smoothScrollingEnabled whether arrow scrolling will animate its transition
445 */
446 public void setSmoothScrollingEnabled(boolean smoothScrollingEnabled) {
447 mSmoothScrollingEnabled = smoothScrollingEnabled;
448 }
449
450 @Override
451 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
452 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
453
454 if (!mFillViewport) {
455 return;
456 }
457
458 final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
459 if (heightMode == MeasureSpec.UNSPECIFIED) {
460 return;
461 }
462
Romain Guyef0e9ae2009-07-10 14:11:26 -0700463 if (getChildCount() > 0) {
464 final View child = getChildAt(0);
Yigit Boyar115a6f42016-03-22 13:36:47 -0700465 final int widthPadding;
466 final int heightPadding;
467 final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
468 final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
469 if (targetSdkVersion >= VERSION_CODES.M) {
470 widthPadding = mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin;
471 heightPadding = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin;
472 } else {
473 widthPadding = mPaddingLeft + mPaddingRight;
474 heightPadding = mPaddingTop + mPaddingBottom;
475 }
Mindy Pereira4e30d892010-11-24 15:32:39 -0800476
Yigit Boyar115a6f42016-03-22 13:36:47 -0700477 final int desiredHeight = getMeasuredHeight() - heightPadding;
478 if (child.getMeasuredHeight() < desiredHeight) {
Alan Viverette97e1be82015-06-04 10:56:27 -0700479 final int childWidthMeasureSpec = getChildMeasureSpec(
480 widthMeasureSpec, widthPadding, lp.width);
481 final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
Yigit Boyar115a6f42016-03-22 13:36:47 -0700482 desiredHeight, MeasureSpec.EXACTLY);
Romain Guyef0e9ae2009-07-10 14:11:26 -0700483 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
484 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800485 }
486 }
487
488 @Override
489 public boolean dispatchKeyEvent(KeyEvent event) {
490 // Let the focused view and/or our descendants get the key first
Romain Guy8e618e52010-03-08 12:18:20 -0800491 return super.dispatchKeyEvent(event) || executeKeyEvent(event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800492 }
493
494 /**
495 * You can call this function yourself to have the scroll view perform
496 * scrolling from a key event, just as if the event had been dispatched to
497 * it by the view hierarchy.
498 *
499 * @param event The key event to execute.
500 * @return Return true if the event was handled, else false.
501 */
502 public boolean executeKeyEvent(KeyEvent event) {
503 mTempRect.setEmpty();
504
505 if (!canScroll()) {
Romain Guy2d4cff62010-04-09 15:39:00 -0700506 if (isFocused() && event.getKeyCode() != KeyEvent.KEYCODE_BACK) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800507 View currentFocused = findFocus();
508 if (currentFocused == this) currentFocused = null;
509 View nextFocused = FocusFinder.getInstance().findNextFocus(this,
510 currentFocused, View.FOCUS_DOWN);
511 return nextFocused != null
512 && nextFocused != this
513 && nextFocused.requestFocus(View.FOCUS_DOWN);
514 }
515 return false;
516 }
517
518 boolean handled = false;
519 if (event.getAction() == KeyEvent.ACTION_DOWN) {
520 switch (event.getKeyCode()) {
521 case KeyEvent.KEYCODE_DPAD_UP:
522 if (!event.isAltPressed()) {
523 handled = arrowScroll(View.FOCUS_UP);
524 } else {
525 handled = fullScroll(View.FOCUS_UP);
526 }
527 break;
528 case KeyEvent.KEYCODE_DPAD_DOWN:
529 if (!event.isAltPressed()) {
530 handled = arrowScroll(View.FOCUS_DOWN);
531 } else {
532 handled = fullScroll(View.FOCUS_DOWN);
533 }
534 break;
535 case KeyEvent.KEYCODE_SPACE:
536 pageScroll(event.isShiftPressed() ? View.FOCUS_UP : View.FOCUS_DOWN);
537 break;
538 }
539 }
540
541 return handled;
542 }
543
Joe Onorato9f0e8ee2010-02-23 19:19:22 -0800544 private boolean inChild(int x, int y) {
545 if (getChildCount() > 0) {
Adam Powell352b9782010-03-24 14:23:43 -0700546 final int scrollY = mScrollY;
Joe Onorato9f0e8ee2010-02-23 19:19:22 -0800547 final View child = getChildAt(0);
Adam Powell352b9782010-03-24 14:23:43 -0700548 return !(y < child.getTop() - scrollY
549 || y >= child.getBottom() - scrollY
Joe Onorato9f0e8ee2010-02-23 19:19:22 -0800550 || x < child.getLeft()
551 || x >= child.getRight());
552 }
553 return false;
554 }
555
Michael Jurka13451a42011-08-22 15:54:21 -0700556 private void initOrResetVelocityTracker() {
557 if (mVelocityTracker == null) {
558 mVelocityTracker = VelocityTracker.obtain();
559 } else {
560 mVelocityTracker.clear();
561 }
562 }
563
564 private void initVelocityTrackerIfNotExists() {
565 if (mVelocityTracker == null) {
566 mVelocityTracker = VelocityTracker.obtain();
567 }
568 }
569
570 private void recycleVelocityTracker() {
571 if (mVelocityTracker != null) {
572 mVelocityTracker.recycle();
573 mVelocityTracker = null;
574 }
575 }
576
577 @Override
578 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
579 if (disallowIntercept) {
580 recycleVelocityTracker();
581 }
582 super.requestDisallowInterceptTouchEvent(disallowIntercept);
583 }
584
585
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800586 @Override
587 public boolean onInterceptTouchEvent(MotionEvent ev) {
588 /*
589 * This method JUST determines whether we want to intercept the motion.
590 * If we return true, onMotionEvent will be called and we do the actual
591 * scrolling there.
592 */
593
594 /*
595 * Shortcut the most recurring case: the user is in the dragging
596 * state and he is moving his finger. We want to intercept this
597 * motion.
598 */
599 final int action = ev.getAction();
600 if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
601 return true;
602 }
603
Keisuke Kuroyanagid85bc502016-01-21 14:50:38 +0900604 if (super.onInterceptTouchEvent(ev)) {
605 return true;
606 }
607
Adam Powell0278c2f2012-07-31 16:39:32 -0700608 /*
609 * Don't try to intercept touch if we can't scroll anyway.
610 */
611 if (getScrollY() == 0 && !canScrollVertically(1)) {
612 return false;
613 }
614
Adam Powellbc4e7532010-02-23 14:49:01 -0800615 switch (action & MotionEvent.ACTION_MASK) {
616 case MotionEvent.ACTION_MOVE: {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800617 /*
618 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
619 * whether the user has moved far enough from his original down touch.
620 */
621
622 /*
623 * Locally do absolute value. mLastMotionY is set to the y value
624 * of the down event.
625 */
Adam Powell9d0335b2010-03-24 13:42:51 -0700626 final int activePointerId = mActivePointerId;
627 if (activePointerId == INVALID_POINTER) {
628 // If we don't have a valid id, the touch down wasn't on content.
629 break;
630 }
631
632 final int pointerIndex = ev.findPointerIndex(activePointerId);
Johan Rosengren0dc291e2011-02-21 09:49:45 +0100633 if (pointerIndex == -1) {
634 Log.e(TAG, "Invalid pointerId=" + activePointerId
635 + " in onInterceptTouchEvent");
636 break;
637 }
638
Adam Powelldf3ae4f2012-04-10 18:55:22 -0700639 final int y = (int) ev.getY(pointerIndex);
640 final int yDiff = Math.abs(y - mLastMotionY);
Adam Powell10ba2772014-04-15 09:46:51 -0700641 if (yDiff > mTouchSlop && (getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800642 mIsBeingDragged = true;
Romain Guyf7b4acc2009-12-01 16:24:45 -0800643 mLastMotionY = y;
Michael Jurka13451a42011-08-22 15:54:21 -0700644 initVelocityTrackerIfNotExists();
645 mVelocityTracker.addMovement(ev);
Adam Powell744beff2014-09-22 09:47:48 -0700646 mNestedYOffset = 0;
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -0800647 if (mScrollStrictSpan == null) {
648 mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll");
649 }
Adam Powellb3e02c42012-05-02 22:05:46 -0700650 final ViewParent parent = getParent();
651 if (parent != null) {
652 parent.requestDisallowInterceptTouchEvent(true);
653 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800654 }
655 break;
Adam Powellbc4e7532010-02-23 14:49:01 -0800656 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800657
Adam Powellbc4e7532010-02-23 14:49:01 -0800658 case MotionEvent.ACTION_DOWN: {
Adam Powelldf3ae4f2012-04-10 18:55:22 -0700659 final int y = (int) ev.getY();
Adam Powell4cd47702010-02-25 11:21:14 -0800660 if (!inChild((int) ev.getX(), (int) y)) {
Joe Onorato9f0e8ee2010-02-23 19:19:22 -0800661 mIsBeingDragged = false;
Michael Jurka13451a42011-08-22 15:54:21 -0700662 recycleVelocityTracker();
Joe Onorato9f0e8ee2010-02-23 19:19:22 -0800663 break;
664 }
665
Adam Powellbc4e7532010-02-23 14:49:01 -0800666 /*
667 * Remember location of down touch.
668 * ACTION_DOWN always refers to pointer index 0.
669 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800670 mLastMotionY = y;
Adam Powellbc4e7532010-02-23 14:49:01 -0800671 mActivePointerId = ev.getPointerId(0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800672
Michael Jurka13451a42011-08-22 15:54:21 -0700673 initOrResetVelocityTracker();
674 mVelocityTracker.addMovement(ev);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800675 /*
Chris Banesf2a7e972015-12-07 11:53:26 +0000676 * If being flinged and user touches the screen, initiate drag;
677 * otherwise don't. mScroller.isFinished should be false when
678 * being flinged. We need to call computeScrollOffset() first so that
679 * isFinished() is correct.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800680 */
Chris Banesf2a7e972015-12-07 11:53:26 +0000681 mScroller.computeScrollOffset();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800682 mIsBeingDragged = !mScroller.isFinished();
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -0800683 if (mIsBeingDragged && mScrollStrictSpan == null) {
684 mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll");
685 }
Adam Powelle9a16a52014-04-25 14:21:27 -0700686 startNestedScroll(SCROLL_AXIS_VERTICAL);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800687 break;
Adam Powellbc4e7532010-02-23 14:49:01 -0800688 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800689
690 case MotionEvent.ACTION_CANCEL:
691 case MotionEvent.ACTION_UP:
692 /* Release the drag */
693 mIsBeingDragged = false;
Adam Powellbc4e7532010-02-23 14:49:01 -0800694 mActivePointerId = INVALID_POINTER;
Michael Jurka13451a42011-08-22 15:54:21 -0700695 recycleVelocityTracker();
Adam Powell637d3372010-08-25 14:37:03 -0700696 if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) {
Adam Powelldf3ae4f2012-04-10 18:55:22 -0700697 postInvalidateOnAnimation();
Adam Powell637d3372010-08-25 14:37:03 -0700698 }
Adam Powelle9a16a52014-04-25 14:21:27 -0700699 stopNestedScroll();
Adam Powellbc4e7532010-02-23 14:49:01 -0800700 break;
701 case MotionEvent.ACTION_POINTER_UP:
702 onSecondaryPointerUp(ev);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800703 break;
704 }
705
706 /*
707 * The only time we want to intercept motion events is if we are in the
708 * drag mode.
709 */
710 return mIsBeingDragged;
711 }
712
Yigit Boyar08b1d992019-02-06 12:34:21 -0800713 private boolean shouldDisplayEdgeEffects() {
714 return getOverScrollMode() != OVER_SCROLL_NEVER;
715 }
716
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800717 @Override
718 public boolean onTouchEvent(MotionEvent ev) {
Michael Jurka13451a42011-08-22 15:54:21 -0700719 initVelocityTrackerIfNotExists();
Adam Powell96d62af2014-05-02 10:04:38 -0700720
721 MotionEvent vtev = MotionEvent.obtain(ev);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800722
Adam Powell744beff2014-09-22 09:47:48 -0700723 final int actionMasked = ev.getActionMasked();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800724
Adam Powell744beff2014-09-22 09:47:48 -0700725 if (actionMasked == MotionEvent.ACTION_DOWN) {
726 mNestedYOffset = 0;
727 }
728 vtev.offsetLocation(0, mNestedYOffset);
729
730 switch (actionMasked) {
Adam Powellbc4e7532010-02-23 14:49:01 -0800731 case MotionEvent.ACTION_DOWN: {
Adam Powellb3e02c42012-05-02 22:05:46 -0700732 if (getChildCount() == 0) {
Jeff Brownfb757382011-01-18 18:42:33 -0800733 return false;
734 }
Adam Powellb3e02c42012-05-02 22:05:46 -0700735 if ((mIsBeingDragged = !mScroller.isFinished())) {
736 final ViewParent parent = getParent();
737 if (parent != null) {
738 parent.requestDisallowInterceptTouchEvent(true);
739 }
740 }
Mindy Pereira4e30d892010-11-24 15:32:39 -0800741
Adam Powell352b9782010-03-24 14:23:43 -0700742 /*
743 * If being flinged and user touches, stop the fling. isFinished
744 * will be false if being flinged.
745 */
746 if (!mScroller.isFinished()) {
747 mScroller.abortAnimation();
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -0800748 if (mFlingStrictSpan != null) {
749 mFlingStrictSpan.finish();
750 mFlingStrictSpan = null;
751 }
Adam Powell352b9782010-03-24 14:23:43 -0700752 }
753
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800754 // Remember where the motion event started
Adam Powelldf3ae4f2012-04-10 18:55:22 -0700755 mLastMotionY = (int) ev.getY();
Adam Powellbc4e7532010-02-23 14:49:01 -0800756 mActivePointerId = ev.getPointerId(0);
Adam Powell10ba2772014-04-15 09:46:51 -0700757 startNestedScroll(SCROLL_AXIS_VERTICAL);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800758 break;
Adam Powellbc4e7532010-02-23 14:49:01 -0800759 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800760 case MotionEvent.ACTION_MOVE:
Adam Powellb3e02c42012-05-02 22:05:46 -0700761 final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
Johan Rosengren0dc291e2011-02-21 09:49:45 +0100762 if (activePointerIndex == -1) {
763 Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
764 break;
765 }
766
Adam Powellb3e02c42012-05-02 22:05:46 -0700767 final int y = (int) ev.getY(activePointerIndex);
768 int deltaY = mLastMotionY - y;
Adam Powell10ba2772014-04-15 09:46:51 -0700769 if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset)) {
Yorke Leee9a0d6a2014-05-02 18:33:35 -0700770 deltaY -= mScrollConsumed[1];
Adam Powell96d62af2014-05-02 10:04:38 -0700771 vtev.offsetLocation(0, mScrollOffset[1]);
Adam Powell744beff2014-09-22 09:47:48 -0700772 mNestedYOffset += mScrollOffset[1];
Adam Powell10ba2772014-04-15 09:46:51 -0700773 }
Adam Powellb3e02c42012-05-02 22:05:46 -0700774 if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) {
775 final ViewParent parent = getParent();
776 if (parent != null) {
777 parent.requestDisallowInterceptTouchEvent(true);
778 }
779 mIsBeingDragged = true;
780 if (deltaY > 0) {
781 deltaY -= mTouchSlop;
782 } else {
783 deltaY += mTouchSlop;
784 }
785 }
Joe Onorato9f0e8ee2010-02-23 19:19:22 -0800786 if (mIsBeingDragged) {
787 // Scroll to follow the motion event
Yorke Leee9a0d6a2014-05-02 18:33:35 -0700788 mLastMotionY = y - mScrollOffset[1];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800789
Adam Powell637d3372010-08-25 14:37:03 -0700790 final int oldY = mScrollY;
791 final int range = getScrollRange();
Fabrice Di Meglioe9dbef82011-09-12 16:36:46 -0700792 final int overscrollMode = getOverScrollMode();
Adam Powell10ba2772014-04-15 09:46:51 -0700793 boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
Fabrice Di Meglioe9dbef82011-09-12 16:36:46 -0700794 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
795
Alan Viverettecb25bd82013-06-03 17:10:44 -0700796 // Calling overScrollBy will call onOverScrolled, which
797 // calls onScrollChanged if applicable.
Adam Powell10ba2772014-04-15 09:46:51 -0700798 if (overScrollBy(0, deltaY, 0, mScrollY, 0, range, 0, mOverscrollDistance, true)
799 && !hasNestedScrollingParent()) {
Adam Powell637d3372010-08-25 14:37:03 -0700800 // Break our velocity if we hit a scroll barrier.
801 mVelocityTracker.clear();
802 }
Adam Powell637d3372010-08-25 14:37:03 -0700803
Adam Powell10ba2772014-04-15 09:46:51 -0700804 final int scrolledDeltaY = mScrollY - oldY;
805 final int unconsumedY = deltaY - scrolledDeltaY;
806 if (dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, mScrollOffset)) {
807 mLastMotionY -= mScrollOffset[1];
Adam Powell96d62af2014-05-02 10:04:38 -0700808 vtev.offsetLocation(0, mScrollOffset[1]);
Adam Powell744beff2014-09-22 09:47:48 -0700809 mNestedYOffset += mScrollOffset[1];
Adam Powell10ba2772014-04-15 09:46:51 -0700810 } else if (canOverscroll) {
Adam Powell637d3372010-08-25 14:37:03 -0700811 final int pulledToY = oldY + deltaY;
812 if (pulledToY < 0) {
Adam Powellc501db9f2014-05-08 12:50:10 -0700813 mEdgeGlowTop.onPull((float) deltaY / getHeight(),
814 ev.getX(activePointerIndex) / getWidth());
Adam Powell637d3372010-08-25 14:37:03 -0700815 if (!mEdgeGlowBottom.isFinished()) {
816 mEdgeGlowBottom.onRelease();
817 }
818 } else if (pulledToY > range) {
Adam Powellc501db9f2014-05-08 12:50:10 -0700819 mEdgeGlowBottom.onPull((float) deltaY / getHeight(),
820 1.f - ev.getX(activePointerIndex) / getWidth());
Adam Powell637d3372010-08-25 14:37:03 -0700821 if (!mEdgeGlowTop.isFinished()) {
822 mEdgeGlowTop.onRelease();
823 }
824 }
Yigit Boyar08b1d992019-02-06 12:34:21 -0800825 if (shouldDisplayEdgeEffects()
Adam Powell637d3372010-08-25 14:37:03 -0700826 && (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished())) {
Adam Powelldf3ae4f2012-04-10 18:55:22 -0700827 postInvalidateOnAnimation();
Adam Powell637d3372010-08-25 14:37:03 -0700828 }
829 }
Joe Onorato9f0e8ee2010-02-23 19:19:22 -0800830 }
Adam Powellbc4e7532010-02-23 14:49:01 -0800831 break;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800832 case MotionEvent.ACTION_UP:
Joe Onorato9f0e8ee2010-02-23 19:19:22 -0800833 if (mIsBeingDragged) {
834 final VelocityTracker velocityTracker = mVelocityTracker;
835 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
Adam Powellbc4e7532010-02-23 14:49:01 -0800836 int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800837
Adam Powell10ba2772014-04-15 09:46:51 -0700838 if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
839 flingWithNestedDispatch(-initialVelocity);
840 } else if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0,
841 getScrollRange())) {
842 postInvalidateOnAnimation();
Adam Powell17dfce12010-01-25 18:38:22 -0800843 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800844
Adam Powellbc4e7532010-02-23 14:49:01 -0800845 mActivePointerId = INVALID_POINTER;
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -0800846 endDrag();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800847 }
Adam Powellbc4e7532010-02-23 14:49:01 -0800848 break;
Adam Powell352b9782010-03-24 14:23:43 -0700849 case MotionEvent.ACTION_CANCEL:
850 if (mIsBeingDragged && getChildCount() > 0) {
Adam Powell637d3372010-08-25 14:37:03 -0700851 if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) {
Adam Powelldf3ae4f2012-04-10 18:55:22 -0700852 postInvalidateOnAnimation();
Adam Powell637d3372010-08-25 14:37:03 -0700853 }
Adam Powell352b9782010-03-24 14:23:43 -0700854 mActivePointerId = INVALID_POINTER;
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -0800855 endDrag();
Adam Powell352b9782010-03-24 14:23:43 -0700856 }
857 break;
Adam Powell9bc30d32011-02-28 10:27:49 -0800858 case MotionEvent.ACTION_POINTER_DOWN: {
859 final int index = ev.getActionIndex();
Adam Powelldf3ae4f2012-04-10 18:55:22 -0700860 mLastMotionY = (int) ev.getY(index);
Adam Powell9bc30d32011-02-28 10:27:49 -0800861 mActivePointerId = ev.getPointerId(index);
862 break;
863 }
Adam Powellbc4e7532010-02-23 14:49:01 -0800864 case MotionEvent.ACTION_POINTER_UP:
865 onSecondaryPointerUp(ev);
Adam Powelldf3ae4f2012-04-10 18:55:22 -0700866 mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
Adam Powellbc4e7532010-02-23 14:49:01 -0800867 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800868 }
Adam Powell96d62af2014-05-02 10:04:38 -0700869
870 if (mVelocityTracker != null) {
871 mVelocityTracker.addMovement(vtev);
872 }
873 vtev.recycle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800874 return true;
875 }
Mindy Pereira4e30d892010-11-24 15:32:39 -0800876
Adam Powellbc4e7532010-02-23 14:49:01 -0800877 private void onSecondaryPointerUp(MotionEvent ev) {
878 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
879 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
880 final int pointerId = ev.getPointerId(pointerIndex);
881 if (pointerId == mActivePointerId) {
882 // This was our active pointer going up. Choose a new
883 // active pointer and adjust accordingly.
884 // TODO: Make this decision more intelligent.
885 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
Adam Powelldf3ae4f2012-04-10 18:55:22 -0700886 mLastMotionY = (int) ev.getY(newPointerIndex);
Adam Powellbc4e7532010-02-23 14:49:01 -0800887 mActivePointerId = ev.getPointerId(newPointerIndex);
888 if (mVelocityTracker != null) {
889 mVelocityTracker.clear();
890 }
891 }
892 }
Mindy Pereira4e30d892010-11-24 15:32:39 -0800893
Adam Powell637d3372010-08-25 14:37:03 -0700894 @Override
Jeff Brown33bbfd22011-02-24 20:55:35 -0800895 public boolean onGenericMotionEvent(MotionEvent event) {
Ned Burns20ad0732016-08-18 14:22:57 -0400896 switch (event.getAction()) {
897 case MotionEvent.ACTION_SCROLL:
898 final float axisValue;
899 if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
900 axisValue = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
901 } else if (event.isFromSource(InputDevice.SOURCE_ROTARY_ENCODER)) {
902 axisValue = event.getAxisValue(MotionEvent.AXIS_SCROLL);
903 } else {
904 axisValue = 0;
905 }
906
Aaron Whytef8306522017-03-22 16:30:58 -0700907 final int delta = Math.round(axisValue * mVerticalScrollFactor);
Ned Burns20ad0732016-08-18 14:22:57 -0400908 if (delta != 0) {
909 final int range = getScrollRange();
910 int oldScrollY = mScrollY;
911 int newScrollY = oldScrollY - delta;
912 if (newScrollY < 0) {
913 newScrollY = 0;
914 } else if (newScrollY > range) {
915 newScrollY = range;
916 }
917 if (newScrollY != oldScrollY) {
918 super.scrollTo(mScrollX, newScrollY);
919 return true;
Jeff Brown33bbfd22011-02-24 20:55:35 -0800920 }
921 }
Ned Burns20ad0732016-08-18 14:22:57 -0400922 break;
Jeff Brown33bbfd22011-02-24 20:55:35 -0800923 }
Ned Burns20ad0732016-08-18 14:22:57 -0400924
Jeff Brown33bbfd22011-02-24 20:55:35 -0800925 return super.onGenericMotionEvent(event);
926 }
927
928 @Override
Adam Powell637d3372010-08-25 14:37:03 -0700929 protected void onOverScrolled(int scrollX, int scrollY,
930 boolean clampedX, boolean clampedY) {
931 // Treat animating scrolls differently; see #computeScroll() for why.
932 if (!mScroller.isFinished()) {
Alan Viverettecb25bd82013-06-03 17:10:44 -0700933 final int oldX = mScrollX;
934 final int oldY = mScrollY;
Adam Powell637d3372010-08-25 14:37:03 -0700935 mScrollX = scrollX;
936 mScrollY = scrollY;
Romain Guy0fd89bf2011-01-26 15:41:30 -0800937 invalidateParentIfNeeded();
Alan Viverettecb25bd82013-06-03 17:10:44 -0700938 onScrollChanged(mScrollX, mScrollY, oldX, oldY);
Adam Powell637d3372010-08-25 14:37:03 -0700939 if (clampedY) {
940 mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange());
941 }
942 } else {
943 super.scrollTo(scrollX, scrollY);
944 }
Romain Guye979e622012-03-20 13:50:27 -0700945
Romain Guye72cf732012-03-20 14:23:09 -0700946 awakenScrollBars();
Adam Powell637d3372010-08-25 14:37:03 -0700947 }
948
Alan Viverettea54956a2015-01-07 16:05:02 -0800949 /** @hide */
Svetoslav Ganova0156172011-06-26 17:55:44 -0700950 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -0800951 public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
952 if (super.performAccessibilityActionInternal(action, arguments)) {
Svetoslav Ganov48d15862012-05-15 10:10:00 -0700953 return true;
954 }
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -0700955 if (!isEnabled()) {
956 return false;
957 }
Svetoslav Ganova1dc7612012-05-10 04:14:53 -0700958 switch (action) {
Maxim Bogatovac6ffce2015-04-27 13:45:52 -0700959 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
960 case R.id.accessibilityActionScrollDown: {
Svetoslav Ganova1dc7612012-05-10 04:14:53 -0700961 final int viewportHeight = getHeight() - mPaddingBottom - mPaddingTop;
962 final int targetScrollY = Math.min(mScrollY + viewportHeight, getScrollRange());
963 if (targetScrollY != mScrollY) {
964 smoothScrollTo(0, targetScrollY);
965 return true;
966 }
967 } return false;
Maxim Bogatovac6ffce2015-04-27 13:45:52 -0700968 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
969 case R.id.accessibilityActionScrollUp: {
Svetoslav Ganova1dc7612012-05-10 04:14:53 -0700970 final int viewportHeight = getHeight() - mPaddingBottom - mPaddingTop;
971 final int targetScrollY = Math.max(mScrollY - viewportHeight, 0);
972 if (targetScrollY != mScrollY) {
973 smoothScrollTo(0, targetScrollY);
974 return true;
975 }
976 } return false;
977 }
Svetoslav Ganov48d15862012-05-15 10:10:00 -0700978 return false;
Svetoslav Ganova1dc7612012-05-10 04:14:53 -0700979 }
980
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -0800981 @Override
982 public CharSequence getAccessibilityClassName() {
983 return ScrollView.class.getName();
984 }
985
Alan Viverettea54956a2015-01-07 16:05:02 -0800986 /** @hide */
Svetoslav Ganova1dc7612012-05-10 04:14:53 -0700987 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -0800988 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
989 super.onInitializeAccessibilityNodeInfoInternal(info);
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -0700990 if (isEnabled()) {
991 final int scrollRange = getScrollRange();
992 if (scrollRange > 0) {
993 info.setScrollable(true);
994 if (mScrollY > 0) {
Maxim Bogatovac6ffce2015-04-27 13:45:52 -0700995 info.addAction(
996 AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD);
997 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP);
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -0700998 }
999 if (mScrollY < scrollRange) {
Maxim Bogatovac6ffce2015-04-27 13:45:52 -07001000 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
1001 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_DOWN);
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -07001002 }
Svetoslav Ganova1dc7612012-05-10 04:14:53 -07001003 }
1004 }
Svetoslav Ganova0156172011-06-26 17:55:44 -07001005 }
1006
Alan Viverettea54956a2015-01-07 16:05:02 -08001007 /** @hide */
Svetoslav Ganova0156172011-06-26 17:55:44 -07001008 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -08001009 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
1010 super.onInitializeAccessibilityEventInternal(event);
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -07001011 final boolean scrollable = getScrollRange() > 0;
1012 event.setScrollable(scrollable);
1013 event.setScrollX(mScrollX);
1014 event.setScrollY(mScrollY);
1015 event.setMaxScrollX(mScrollX);
1016 event.setMaxScrollY(getScrollRange());
Svetoslav Ganova0156172011-06-26 17:55:44 -07001017 }
1018
Adam Powell637d3372010-08-25 14:37:03 -07001019 private int getScrollRange() {
1020 int scrollRange = 0;
1021 if (getChildCount() > 0) {
1022 View child = getChildAt(0);
1023 scrollRange = Math.max(0,
1024 child.getHeight() - (getHeight() - mPaddingBottom - mPaddingTop));
1025 }
1026 return scrollRange;
1027 }
1028
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001029 /**
1030 * <p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001031 * Finds the next focusable component that fits in the specified bounds.
1032 * </p>
1033 *
1034 * @param topFocus look for a candidate is the one at the top of the bounds
1035 * if topFocus is true, or at the bottom of the bounds if topFocus is
1036 * false
1037 * @param top the top offset of the bounds in which a focusable must be
1038 * found
1039 * @param bottom the bottom offset of the bounds in which a focusable must
1040 * be found
1041 * @return the next focusable component in the bounds or null if none can
1042 * be found
1043 */
1044 private View findFocusableViewInBounds(boolean topFocus, int top, int bottom) {
1045
1046 List<View> focusables = getFocusables(View.FOCUS_FORWARD);
1047 View focusCandidate = null;
1048
1049 /*
1050 * A fully contained focusable is one where its top is below the bound's
1051 * top, and its bottom is above the bound's bottom. A partially
1052 * contained focusable is one where some part of it is within the
1053 * bounds, but it also has some part that is not within bounds. A fully contained
1054 * focusable is preferred to a partially contained focusable.
1055 */
1056 boolean foundFullyContainedFocusable = false;
1057
1058 int count = focusables.size();
1059 for (int i = 0; i < count; i++) {
1060 View view = focusables.get(i);
1061 int viewTop = view.getTop();
1062 int viewBottom = view.getBottom();
1063
1064 if (top < viewBottom && viewTop < bottom) {
1065 /*
1066 * the focusable is in the target area, it is a candidate for
1067 * focusing
1068 */
1069
1070 final boolean viewIsFullyContained = (top < viewTop) &&
1071 (viewBottom < bottom);
1072
1073 if (focusCandidate == null) {
1074 /* No candidate, take this one */
1075 focusCandidate = view;
1076 foundFullyContainedFocusable = viewIsFullyContained;
1077 } else {
1078 final boolean viewIsCloserToBoundary =
1079 (topFocus && viewTop < focusCandidate.getTop()) ||
1080 (!topFocus && viewBottom > focusCandidate
1081 .getBottom());
1082
1083 if (foundFullyContainedFocusable) {
1084 if (viewIsFullyContained && viewIsCloserToBoundary) {
1085 /*
1086 * We're dealing with only fully contained views, so
1087 * it has to be closer to the boundary to beat our
1088 * candidate
1089 */
1090 focusCandidate = view;
1091 }
1092 } else {
1093 if (viewIsFullyContained) {
1094 /* Any fully contained view beats a partially contained view */
1095 focusCandidate = view;
1096 foundFullyContainedFocusable = true;
1097 } else if (viewIsCloserToBoundary) {
1098 /*
1099 * Partially contained view beats another partially
1100 * contained view if it's closer
1101 */
1102 focusCandidate = view;
1103 }
1104 }
1105 }
1106 }
1107 }
1108
1109 return focusCandidate;
1110 }
1111
1112 /**
1113 * <p>Handles scrolling in response to a "page up/down" shortcut press. This
1114 * method will scroll the view by one page up or down and give the focus
1115 * to the topmost/bottommost component in the new visible area. If no
1116 * component is a good candidate for focus, this scrollview reclaims the
1117 * focus.</p>
1118 *
1119 * @param direction the scroll direction: {@link android.view.View#FOCUS_UP}
1120 * to go one page up or
1121 * {@link android.view.View#FOCUS_DOWN} to go one page down
1122 * @return true if the key event is consumed by this method, false otherwise
1123 */
1124 public boolean pageScroll(int direction) {
1125 boolean down = direction == View.FOCUS_DOWN;
1126 int height = getHeight();
1127
1128 if (down) {
1129 mTempRect.top = getScrollY() + height;
1130 int count = getChildCount();
1131 if (count > 0) {
1132 View view = getChildAt(count - 1);
1133 if (mTempRect.top + height > view.getBottom()) {
1134 mTempRect.top = view.getBottom() - height;
1135 }
1136 }
1137 } else {
1138 mTempRect.top = getScrollY() - height;
1139 if (mTempRect.top < 0) {
1140 mTempRect.top = 0;
1141 }
1142 }
1143 mTempRect.bottom = mTempRect.top + height;
1144
1145 return scrollAndFocus(direction, mTempRect.top, mTempRect.bottom);
1146 }
1147
1148 /**
1149 * <p>Handles scrolling in response to a "home/end" shortcut press. This
1150 * method will scroll the view to the top or bottom and give the focus
1151 * to the topmost/bottommost component in the new visible area. If no
1152 * component is a good candidate for focus, this scrollview reclaims the
1153 * focus.</p>
1154 *
1155 * @param direction the scroll direction: {@link android.view.View#FOCUS_UP}
1156 * to go the top of the view or
1157 * {@link android.view.View#FOCUS_DOWN} to go the bottom
1158 * @return true if the key event is consumed by this method, false otherwise
1159 */
1160 public boolean fullScroll(int direction) {
1161 boolean down = direction == View.FOCUS_DOWN;
1162 int height = getHeight();
1163
1164 mTempRect.top = 0;
1165 mTempRect.bottom = height;
1166
1167 if (down) {
1168 int count = getChildCount();
1169 if (count > 0) {
1170 View view = getChildAt(count - 1);
Mattias Petersson5435a062011-04-07 15:46:56 +02001171 mTempRect.bottom = view.getBottom() + mPaddingBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001172 mTempRect.top = mTempRect.bottom - height;
1173 }
1174 }
1175
1176 return scrollAndFocus(direction, mTempRect.top, mTempRect.bottom);
1177 }
1178
1179 /**
1180 * <p>Scrolls the view to make the area defined by <code>top</code> and
1181 * <code>bottom</code> visible. This method attempts to give the focus
1182 * to a component visible in this area. If no component can be focused in
Gilles Debunne2ed2eac2011-02-24 16:29:48 -08001183 * the new visible area, the focus is reclaimed by this ScrollView.</p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001184 *
1185 * @param direction the scroll direction: {@link android.view.View#FOCUS_UP}
Gilles Debunne2ed2eac2011-02-24 16:29:48 -08001186 * to go upward, {@link android.view.View#FOCUS_DOWN} to downward
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001187 * @param top the top offset of the new area to be made visible
1188 * @param bottom the bottom offset of the new area to be made visible
1189 * @return true if the key event is consumed by this method, false otherwise
1190 */
1191 private boolean scrollAndFocus(int direction, int top, int bottom) {
1192 boolean handled = true;
1193
1194 int height = getHeight();
1195 int containerTop = getScrollY();
1196 int containerBottom = containerTop + height;
1197 boolean up = direction == View.FOCUS_UP;
1198
1199 View newFocused = findFocusableViewInBounds(up, top, bottom);
1200 if (newFocused == null) {
1201 newFocused = this;
1202 }
1203
1204 if (top >= containerTop && bottom <= containerBottom) {
1205 handled = false;
1206 } else {
1207 int delta = up ? (top - containerTop) : (bottom - containerBottom);
1208 doScrollY(delta);
1209 }
1210
Gilles Debunne2ed2eac2011-02-24 16:29:48 -08001211 if (newFocused != findFocus()) newFocused.requestFocus(direction);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001212
1213 return handled;
1214 }
1215
1216 /**
1217 * Handle scrolling in response to an up or down arrow click.
1218 *
1219 * @param direction The direction corresponding to the arrow key that was
1220 * pressed
1221 * @return True if we consumed the event, false otherwise
1222 */
1223 public boolean arrowScroll(int direction) {
1224
1225 View currentFocused = findFocus();
1226 if (currentFocused == this) currentFocused = null;
1227
1228 View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, direction);
1229
1230 final int maxJump = getMaxScrollAmount();
1231
Jack Veenstra7d4200d2009-09-21 19:45:14 -07001232 if (nextFocused != null && isWithinDeltaOfScreen(nextFocused, maxJump, getHeight())) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001233 nextFocused.getDrawingRect(mTempRect);
1234 offsetDescendantRectToMyCoords(nextFocused, mTempRect);
1235 int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
1236 doScrollY(scrollDelta);
1237 nextFocused.requestFocus(direction);
1238 } else {
1239 // no new focus
1240 int scrollDelta = maxJump;
1241
1242 if (direction == View.FOCUS_UP && getScrollY() < scrollDelta) {
1243 scrollDelta = getScrollY();
1244 } else if (direction == View.FOCUS_DOWN) {
Romain Guyef0e9ae2009-07-10 14:11:26 -07001245 if (getChildCount() > 0) {
1246 int daBottom = getChildAt(0).getBottom();
Mattias Petersson5435a062011-04-07 15:46:56 +02001247 int screenBottom = getScrollY() + getHeight() - mPaddingBottom;
Romain Guyef0e9ae2009-07-10 14:11:26 -07001248 if (daBottom - screenBottom < maxJump) {
1249 scrollDelta = daBottom - screenBottom;
1250 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001251 }
1252 }
1253 if (scrollDelta == 0) {
1254 return false;
1255 }
1256 doScrollY(direction == View.FOCUS_DOWN ? scrollDelta : -scrollDelta);
1257 }
1258
1259 if (currentFocused != null && currentFocused.isFocused()
1260 && isOffScreen(currentFocused)) {
1261 // previously focused item still has focus and is off screen, give
1262 // it up (take it back to ourselves)
1263 // (also, need to temporarily force FOCUS_BEFORE_DESCENDANTS so we are
1264 // sure to
1265 // get it)
1266 final int descendantFocusability = getDescendantFocusability(); // save
1267 setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
1268 requestFocus();
1269 setDescendantFocusability(descendantFocusability); // restore
1270 }
1271 return true;
1272 }
1273
1274 /**
1275 * @return whether the descendant of this scroll view is scrolled off
1276 * screen.
1277 */
1278 private boolean isOffScreen(View descendant) {
Jack Veenstra7d4200d2009-09-21 19:45:14 -07001279 return !isWithinDeltaOfScreen(descendant, 0, getHeight());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001280 }
1281
1282 /**
1283 * @return whether the descendant of this scroll view is within delta
1284 * pixels of being on the screen.
1285 */
Jack Veenstra7d4200d2009-09-21 19:45:14 -07001286 private boolean isWithinDeltaOfScreen(View descendant, int delta, int height) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001287 descendant.getDrawingRect(mTempRect);
1288 offsetDescendantRectToMyCoords(descendant, mTempRect);
1289
1290 return (mTempRect.bottom + delta) >= getScrollY()
Jack Veenstra7d4200d2009-09-21 19:45:14 -07001291 && (mTempRect.top - delta) <= (getScrollY() + height);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001292 }
1293
1294 /**
1295 * Smooth scroll by a Y delta
1296 *
1297 * @param delta the number of pixels to scroll by on the Y axis
1298 */
1299 private void doScrollY(int delta) {
1300 if (delta != 0) {
1301 if (mSmoothScrollingEnabled) {
1302 smoothScrollBy(0, delta);
1303 } else {
1304 scrollBy(0, delta);
1305 }
1306 }
1307 }
1308
1309 /**
1310 * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
1311 *
1312 * @param dx the number of pixels to scroll by on the X axis
1313 * @param dy the number of pixels to scroll by on the Y axis
1314 */
1315 public final void smoothScrollBy(int dx, int dy) {
Adam Powell3fc37372010-01-28 13:52:24 -08001316 if (getChildCount() == 0) {
1317 // Nothing to do.
1318 return;
1319 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001320 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
1321 if (duration > ANIMATED_SCROLL_GAP) {
Adam Powellf5446052010-01-28 17:24:56 -08001322 final int height = getHeight() - mPaddingBottom - mPaddingTop;
1323 final int bottom = getChildAt(0).getHeight();
1324 final int maxY = Math.max(0, bottom - height);
1325 final int scrollY = mScrollY;
1326 dy = Math.max(0, Math.min(scrollY + dy, maxY)) - scrollY;
1327
1328 mScroller.startScroll(mScrollX, scrollY, 0, dy);
Adam Powelldf3ae4f2012-04-10 18:55:22 -07001329 postInvalidateOnAnimation();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001330 } else {
1331 if (!mScroller.isFinished()) {
1332 mScroller.abortAnimation();
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -08001333 if (mFlingStrictSpan != null) {
1334 mFlingStrictSpan.finish();
1335 mFlingStrictSpan = null;
1336 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001337 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001338 scrollBy(dx, dy);
1339 }
1340 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
1341 }
1342
1343 /**
1344 * Like {@link #scrollTo}, but scroll smoothly instead of immediately.
1345 *
1346 * @param x the position where to scroll on the X axis
1347 * @param y the position where to scroll on the Y axis
1348 */
1349 public final void smoothScrollTo(int x, int y) {
1350 smoothScrollBy(x - mScrollX, y - mScrollY);
1351 }
1352
1353 /**
1354 * <p>The scroll range of a scroll view is the overall height of all of its
1355 * children.</p>
1356 */
1357 @Override
1358 protected int computeVerticalScrollRange() {
Adam Powella2f91012010-02-16 12:26:33 -08001359 final int count = getChildCount();
1360 final int contentHeight = getHeight() - mPaddingBottom - mPaddingTop;
Adam Powell0b8bb422010-02-08 14:30:45 -08001361 if (count == 0) {
Adam Powella2f91012010-02-16 12:26:33 -08001362 return contentHeight;
Adam Powell0b8bb422010-02-08 14:30:45 -08001363 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08001364
Adam Powell637d3372010-08-25 14:37:03 -07001365 int scrollRange = getChildAt(0).getBottom();
1366 final int scrollY = mScrollY;
1367 final int overscrollBottom = Math.max(0, scrollRange - contentHeight);
1368 if (scrollY < 0) {
1369 scrollRange -= scrollY;
1370 } else if (scrollY > overscrollBottom) {
1371 scrollRange += scrollY - overscrollBottom;
1372 }
1373
1374 return scrollRange;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001375 }
1376
Adam Powell0b8bb422010-02-08 14:30:45 -08001377 @Override
1378 protected int computeVerticalScrollOffset() {
1379 return Math.max(0, super.computeVerticalScrollOffset());
1380 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001381
1382 @Override
Filip Gruszczynskib6824bf2015-04-13 09:16:25 -07001383 protected void measureChild(View child, int parentWidthMeasureSpec,
1384 int parentHeightMeasureSpec) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001385 ViewGroup.LayoutParams lp = child.getLayoutParams();
1386
1387 int childWidthMeasureSpec;
1388 int childHeightMeasureSpec;
1389
1390 childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft
1391 + mPaddingRight, lp.width);
Yigit Boyar115a6f42016-03-22 13:36:47 -07001392 final int verticalPadding = mPaddingTop + mPaddingBottom;
Adam Powelld5dbf4b2015-06-11 13:19:24 -07001393 childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
Yigit Boyar115a6f42016-03-22 13:36:47 -07001394 Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - verticalPadding),
1395 MeasureSpec.UNSPECIFIED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001396
1397 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
1398 }
1399
1400 @Override
1401 protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
1402 int parentHeightMeasureSpec, int heightUsed) {
1403 final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
1404
1405 final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
1406 mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
1407 + widthUsed, lp.width);
Yigit Boyar115a6f42016-03-22 13:36:47 -07001408 final int usedTotal = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin +
1409 heightUsed;
Adam Powelld5dbf4b2015-06-11 13:19:24 -07001410 final int childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
Yigit Boyar115a6f42016-03-22 13:36:47 -07001411 Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - usedTotal),
1412 MeasureSpec.UNSPECIFIED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001413
1414 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
1415 }
1416
1417 @Override
1418 public void computeScroll() {
1419 if (mScroller.computeScrollOffset()) {
1420 // This is called at drawing time by ViewGroup. We don't want to
1421 // re-show the scrollbars at this point, which scrollTo will do,
1422 // so we replicate most of scrollTo here.
1423 //
1424 // It's a little odd to call onScrollChanged from inside the drawing.
1425 //
1426 // It is, except when you remember that computeScroll() is used to
1427 // animate scrolling. So unless we want to defer the onScrollChanged()
1428 // until the end of the animated scrolling, we don't really have a
1429 // choice here.
1430 //
1431 // I agree. The alternative, which I think would be worse, is to post
1432 // something and tell the subclasses later. This is bad because there
1433 // will be a window where mScrollX/Y is different from what the app
1434 // thinks it is.
1435 //
1436 int oldX = mScrollX;
1437 int oldY = mScrollY;
1438 int x = mScroller.getCurrX();
1439 int y = mScroller.getCurrY();
Adam Powell17dfce12010-01-25 18:38:22 -08001440
Adam Powell637d3372010-08-25 14:37:03 -07001441 if (oldX != x || oldY != y) {
Fabrice Di Meglioe9dbef82011-09-12 16:36:46 -07001442 final int range = getScrollRange();
1443 final int overscrollMode = getOverScrollMode();
1444 final boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
1445 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
1446
1447 overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range,
Adam Powell637d3372010-08-25 14:37:03 -07001448 0, mOverflingDistance, false);
1449 onScrollChanged(mScrollX, mScrollY, oldX, oldY);
1450
Fabrice Di Meglioe9dbef82011-09-12 16:36:46 -07001451 if (canOverscroll) {
Adam Powell637d3372010-08-25 14:37:03 -07001452 if (y < 0 && oldY >= 0) {
1453 mEdgeGlowTop.onAbsorb((int) mScroller.getCurrVelocity());
1454 } else if (y > range && oldY <= range) {
1455 mEdgeGlowBottom.onAbsorb((int) mScroller.getCurrVelocity());
1456 }
Adam Powell9d32d242010-03-29 16:02:07 -07001457 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001458 }
Fabrice Di Meglioe9dbef82011-09-12 16:36:46 -07001459
Romain Guye979e622012-03-20 13:50:27 -07001460 if (!awakenScrollBars()) {
1461 // Keep on drawing until the animation has finished.
Adam Powelldf3ae4f2012-04-10 18:55:22 -07001462 postInvalidateOnAnimation();
Romain Guye979e622012-03-20 13:50:27 -07001463 }
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -08001464 } else {
1465 if (mFlingStrictSpan != null) {
1466 mFlingStrictSpan.finish();
1467 mFlingStrictSpan = null;
1468 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001469 }
1470 }
1471
1472 /**
1473 * Scrolls the view to the given child.
1474 *
1475 * @param child the View to scroll to
1476 */
Aurimas Liutikascc879ee2019-03-01 16:18:21 -08001477 public void scrollToDescendant(@NonNull View child) {
Rahul Ravikumar6d21df22019-02-04 16:08:12 -08001478 if (!mIsLayoutDirty) {
1479 child.getDrawingRect(mTempRect);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001480
Rahul Ravikumar6d21df22019-02-04 16:08:12 -08001481 /* Offset from child's local coordinates to ScrollView coordinates */
1482 offsetDescendantRectToMyCoords(child, mTempRect);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001483
Rahul Ravikumar6d21df22019-02-04 16:08:12 -08001484 int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001485
Rahul Ravikumar6d21df22019-02-04 16:08:12 -08001486 if (scrollDelta != 0) {
1487 scrollBy(0, scrollDelta);
1488 }
1489 } else {
1490 mChildToScrollTo = child;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001491 }
1492 }
1493
1494 /**
1495 * If rect is off screen, scroll just enough to get it (or at least the
1496 * first screen size chunk of it) on screen.
1497 *
1498 * @param rect The rectangle.
1499 * @param immediate True to scroll immediately without animation
1500 * @return true if scrolling was performed
1501 */
1502 private boolean scrollToChildRect(Rect rect, boolean immediate) {
1503 final int delta = computeScrollDeltaToGetChildRectOnScreen(rect);
1504 final boolean scroll = delta != 0;
1505 if (scroll) {
1506 if (immediate) {
1507 scrollBy(0, delta);
1508 } else {
1509 smoothScrollBy(0, delta);
1510 }
1511 }
1512 return scroll;
1513 }
1514
1515 /**
1516 * Compute the amount to scroll in the Y direction in order to get
1517 * a rectangle completely on the screen (or, if taller than the screen,
1518 * at least the first screen size chunk of it).
1519 *
1520 * @param rect The rect.
1521 * @return The scroll delta.
1522 */
1523 protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) {
Romain Guyef0e9ae2009-07-10 14:11:26 -07001524 if (getChildCount() == 0) return 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001525
1526 int height = getHeight();
1527 int screenTop = getScrollY();
1528 int screenBottom = screenTop + height;
1529
1530 int fadingEdge = getVerticalFadingEdgeLength();
1531
1532 // leave room for top fading edge as long as rect isn't at very top
1533 if (rect.top > 0) {
1534 screenTop += fadingEdge;
1535 }
1536
1537 // leave room for bottom fading edge as long as rect isn't at very bottom
1538 if (rect.bottom < getChildAt(0).getHeight()) {
1539 screenBottom -= fadingEdge;
1540 }
1541
1542 int scrollYDelta = 0;
1543
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001544 if (rect.bottom > screenBottom && rect.top > screenTop) {
1545 // need to move down to get it in view: move down just enough so
1546 // that the entire rectangle is in view (or at least the first
1547 // screen size chunk).
1548
1549 if (rect.height() > height) {
1550 // just enough to get screen size chunk on
1551 scrollYDelta += (rect.top - screenTop);
1552 } else {
1553 // get entire rect at bottom of screen
1554 scrollYDelta += (rect.bottom - screenBottom);
1555 }
1556
1557 // make sure we aren't scrolling beyond the end of our content
Romain Guyef0e9ae2009-07-10 14:11:26 -07001558 int bottom = getChildAt(0).getBottom();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001559 int distanceToBottom = bottom - screenBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001560 scrollYDelta = Math.min(scrollYDelta, distanceToBottom);
1561
1562 } else if (rect.top < screenTop && rect.bottom < screenBottom) {
1563 // need to move up to get it in view: move up just enough so that
1564 // entire rectangle is in view (or at least the first screen
1565 // size chunk of it).
1566
1567 if (rect.height() > height) {
1568 // screen size chunk
1569 scrollYDelta -= (screenBottom - rect.bottom);
1570 } else {
1571 // entire rect at top
1572 scrollYDelta -= (screenTop - rect.top);
1573 }
1574
1575 // make sure we aren't scrolling any further than the top our content
1576 scrollYDelta = Math.max(scrollYDelta, -getScrollY());
1577 }
1578 return scrollYDelta;
1579 }
1580
1581 @Override
1582 public void requestChildFocus(View child, View focused) {
Adam Powell9b24e5c2017-04-04 15:33:23 -07001583 if (focused != null && focused.getRevealOnFocusHint()) {
Adam Powell2fe301d2016-08-15 16:34:37 -07001584 if (!mIsLayoutDirty) {
Rahul Ravikumar6d21df22019-02-04 16:08:12 -08001585 scrollToDescendant(focused);
Adam Powell2fe301d2016-08-15 16:34:37 -07001586 } else {
1587 // The child may not be laid out yet, we can't compute the scroll yet
1588 mChildToScrollTo = focused;
1589 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001590 }
1591 super.requestChildFocus(child, focused);
1592 }
1593
1594
1595 /**
1596 * When looking for focus in children of a scroll view, need to be a little
1597 * more careful not to give focus to something that is scrolled off screen.
1598 *
1599 * This is more expensive than the default {@link android.view.ViewGroup}
1600 * implementation, otherwise this behavior might have been made the default.
1601 */
1602 @Override
1603 protected boolean onRequestFocusInDescendants(int direction,
1604 Rect previouslyFocusedRect) {
1605
1606 // convert from forward / backward notation to up / down / left / right
1607 // (ugh).
1608 if (direction == View.FOCUS_FORWARD) {
1609 direction = View.FOCUS_DOWN;
1610 } else if (direction == View.FOCUS_BACKWARD) {
1611 direction = View.FOCUS_UP;
1612 }
1613
1614 final View nextFocus = previouslyFocusedRect == null ?
1615 FocusFinder.getInstance().findNextFocus(this, null, direction) :
1616 FocusFinder.getInstance().findNextFocusFromRect(this,
1617 previouslyFocusedRect, direction);
1618
1619 if (nextFocus == null) {
1620 return false;
1621 }
1622
1623 if (isOffScreen(nextFocus)) {
1624 return false;
1625 }
1626
1627 return nextFocus.requestFocus(direction, previouslyFocusedRect);
Mindy Pereira4e30d892010-11-24 15:32:39 -08001628 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001629
1630 @Override
1631 public boolean requestChildRectangleOnScreen(View child, Rect rectangle,
1632 boolean immediate) {
1633 // offset into coordinate space of this scroll view
1634 rectangle.offset(child.getLeft() - child.getScrollX(),
1635 child.getTop() - child.getScrollY());
1636
1637 return scrollToChildRect(rectangle, immediate);
1638 }
1639
1640 @Override
1641 public void requestLayout() {
1642 mIsLayoutDirty = true;
1643 super.requestLayout();
1644 }
1645
1646 @Override
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -08001647 protected void onDetachedFromWindow() {
1648 super.onDetachedFromWindow();
1649
1650 if (mScrollStrictSpan != null) {
1651 mScrollStrictSpan.finish();
1652 mScrollStrictSpan = null;
1653 }
1654 if (mFlingStrictSpan != null) {
1655 mFlingStrictSpan.finish();
1656 mFlingStrictSpan = null;
1657 }
1658 }
1659
1660 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001661 protected void onLayout(boolean changed, int l, int t, int r, int b) {
1662 super.onLayout(changed, l, t, r, b);
1663 mIsLayoutDirty = false;
Mindy Pereira4e30d892010-11-24 15:32:39 -08001664 // Give a child focus if it needs it
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001665 if (mChildToScrollTo != null && isViewDescendantOf(mChildToScrollTo, this)) {
Rahul Ravikumar6d21df22019-02-04 16:08:12 -08001666 scrollToDescendant(mChildToScrollTo);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001667 }
1668 mChildToScrollTo = null;
1669
Chet Haase7a46dde2013-07-17 10:22:53 -07001670 if (!isLaidOut()) {
Fabrice Di Megliod6befbd2013-06-12 16:01:48 -07001671 if (mSavedState != null) {
1672 mScrollY = mSavedState.scrollPosition;
1673 mSavedState = null;
1674 } // mScrollY default value is "0"
Fabrice Di Megliod6d54392013-06-18 14:09:07 -07001675
1676 final int childHeight = (getChildCount() > 0) ? getChildAt(0).getMeasuredHeight() : 0;
1677 final int scrollRange = Math.max(0,
1678 childHeight - (b - t - mPaddingBottom - mPaddingTop));
1679
Fabrice Di Megliod6befbd2013-06-12 16:01:48 -07001680 // Don't forget to clamp
1681 if (mScrollY > scrollRange) {
1682 mScrollY = scrollRange;
1683 } else if (mScrollY < 0) {
1684 mScrollY = 0;
1685 }
1686 }
1687
Ken Wakasaf76a50c2012-03-09 19:56:35 +09001688 // Calling this with the present values causes it to re-claim them
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001689 scrollTo(mScrollX, mScrollY);
1690 }
1691
1692 @Override
1693 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
1694 super.onSizeChanged(w, h, oldw, oldh);
1695
1696 View currentFocused = findFocus();
1697 if (null == currentFocused || this == currentFocused)
1698 return;
1699
Jack Veenstra7d4200d2009-09-21 19:45:14 -07001700 // If the currently-focused view was visible on the screen when the
1701 // screen was at the old height, then scroll the screen to make that
1702 // view visible with the new screen height.
1703 if (isWithinDeltaOfScreen(currentFocused, 0, oldh)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001704 currentFocused.getDrawingRect(mTempRect);
1705 offsetDescendantRectToMyCoords(currentFocused, mTempRect);
1706 int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
1707 doScrollY(scrollDelta);
1708 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08001709 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001710
1711 /**
Ken Wakasaf76a50c2012-03-09 19:56:35 +09001712 * Return true if child is a descendant of parent, (or equal to the parent).
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001713 */
Romain Guye979e622012-03-20 13:50:27 -07001714 private static boolean isViewDescendantOf(View child, View parent) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001715 if (child == parent) {
1716 return true;
1717 }
1718
1719 final ViewParent theParent = child.getParent();
1720 return (theParent instanceof ViewGroup) && isViewDescendantOf((View) theParent, parent);
Mindy Pereira4e30d892010-11-24 15:32:39 -08001721 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001722
1723 /**
1724 * Fling the scroll view
1725 *
1726 * @param velocityY The initial velocity in the Y direction. Positive
Gilles Debunne52964242010-02-24 11:05:19 -08001727 * numbers mean that the finger/cursor is moving down the screen,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001728 * which means we want to scroll towards the top.
1729 */
1730 public void fling(int velocityY) {
Romain Guyef0e9ae2009-07-10 14:11:26 -07001731 if (getChildCount() > 0) {
1732 int height = getHeight() - mPaddingBottom - mPaddingTop;
1733 int bottom = getChildAt(0).getHeight();
Mindy Pereira4e30d892010-11-24 15:32:39 -08001734
1735 mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0,
Adam Powell637d3372010-08-25 14:37:03 -07001736 Math.max(0, bottom - height), 0, height/2);
Mindy Pereira4e30d892010-11-24 15:32:39 -08001737
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -08001738 if (mFlingStrictSpan == null) {
1739 mFlingStrictSpan = StrictMode.enterCriticalSpan("ScrollView-fling");
1740 }
1741
Adam Powelldf3ae4f2012-04-10 18:55:22 -07001742 postInvalidateOnAnimation();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001743 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001744 }
1745
Adam Powell10ba2772014-04-15 09:46:51 -07001746 private void flingWithNestedDispatch(int velocityY) {
Adam Powellb36e4f92014-05-01 10:23:33 -07001747 final boolean canFling = (mScrollY > 0 || velocityY > 0) &&
1748 (mScrollY < getScrollRange() || velocityY < 0);
Adam Powell9413b242014-08-06 17:34:24 -07001749 if (!dispatchNestedPreFling(0, velocityY)) {
1750 dispatchNestedFling(0, velocityY, canFling);
1751 if (canFling) {
1752 fling(velocityY);
1753 }
Adam Powell10ba2772014-04-15 09:46:51 -07001754 }
1755 }
1756
Mathew Inwood978c6e22018-08-21 15:58:55 +01001757 @UnsupportedAppUsage
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -08001758 private void endDrag() {
1759 mIsBeingDragged = false;
1760
Michael Jurka13451a42011-08-22 15:54:21 -07001761 recycleVelocityTracker();
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -08001762
Yigit Boyar08b1d992019-02-06 12:34:21 -08001763 if (shouldDisplayEdgeEffects()) {
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -08001764 mEdgeGlowTop.onRelease();
1765 mEdgeGlowBottom.onRelease();
1766 }
1767
1768 if (mScrollStrictSpan != null) {
1769 mScrollStrictSpan.finish();
1770 mScrollStrictSpan = null;
1771 }
1772 }
1773
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001774 /**
1775 * {@inheritDoc}
1776 *
1777 * <p>This version also clamps the scrolling to the bounds of our child.
1778 */
Gilles Debunne52964242010-02-24 11:05:19 -08001779 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001780 public void scrollTo(int x, int y) {
1781 // we rely on the fact the View.scrollBy calls scrollTo.
1782 if (getChildCount() > 0) {
1783 View child = getChildAt(0);
1784 x = clamp(x, getWidth() - mPaddingRight - mPaddingLeft, child.getWidth());
1785 y = clamp(y, getHeight() - mPaddingBottom - mPaddingTop, child.getHeight());
1786 if (x != mScrollX || y != mScrollY) {
1787 super.scrollTo(x, y);
1788 }
1789 }
1790 }
1791
Adam Powell637d3372010-08-25 14:37:03 -07001792 @Override
Adam Powell10ba2772014-04-15 09:46:51 -07001793 public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
1794 return (nestedScrollAxes & SCROLL_AXIS_VERTICAL) != 0;
1795 }
1796
Adam Powellb36e4f92014-05-01 10:23:33 -07001797 @Override
1798 public void onNestedScrollAccepted(View child, View target, int axes) {
1799 super.onNestedScrollAccepted(child, target, axes);
1800 startNestedScroll(SCROLL_AXIS_VERTICAL);
1801 }
1802
Adam Powell10ba2772014-04-15 09:46:51 -07001803 /**
1804 * @inheritDoc
1805 */
1806 @Override
1807 public void onStopNestedScroll(View target) {
1808 super.onStopNestedScroll(target);
1809 }
1810
1811 @Override
1812 public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
1813 int dxUnconsumed, int dyUnconsumed) {
Adam Powellb36e4f92014-05-01 10:23:33 -07001814 final int oldScrollY = mScrollY;
Adam Powell10ba2772014-04-15 09:46:51 -07001815 scrollBy(0, dyUnconsumed);
Adam Powellb36e4f92014-05-01 10:23:33 -07001816 final int myConsumed = mScrollY - oldScrollY;
1817 final int myUnconsumed = dyUnconsumed - myConsumed;
1818 dispatchNestedScroll(0, myConsumed, 0, myUnconsumed, null);
Adam Powell10ba2772014-04-15 09:46:51 -07001819 }
1820
1821 /**
1822 * @inheritDoc
1823 */
1824 @Override
Adam Powellb36e4f92014-05-01 10:23:33 -07001825 public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
1826 if (!consumed) {
1827 flingWithNestedDispatch((int) velocityY);
1828 return true;
1829 }
1830 return false;
Adam Powell10ba2772014-04-15 09:46:51 -07001831 }
1832
1833 @Override
Adam Powell637d3372010-08-25 14:37:03 -07001834 public void draw(Canvas canvas) {
1835 super.draw(canvas);
Yigit Boyar08b1d992019-02-06 12:34:21 -08001836 if (shouldDisplayEdgeEffects()) {
Adam Powell637d3372010-08-25 14:37:03 -07001837 final int scrollY = mScrollY;
Doris Liuc81c0822015-05-28 10:18:46 -07001838 final boolean clipToPadding = getClipToPadding();
Adam Powell637d3372010-08-25 14:37:03 -07001839 if (!mEdgeGlowTop.isFinished()) {
1840 final int restoreCount = canvas.save();
Doris Liuc81c0822015-05-28 10:18:46 -07001841 final int width;
1842 final int height;
1843 final float translateX;
1844 final float translateY;
1845 if (clipToPadding) {
1846 width = getWidth() - mPaddingLeft - mPaddingRight;
1847 height = getHeight() - mPaddingTop - mPaddingBottom;
1848 translateX = mPaddingLeft;
1849 translateY = mPaddingTop;
1850 } else {
1851 width = getWidth();
1852 height = getHeight();
1853 translateX = 0;
1854 translateY = 0;
1855 }
1856 canvas.translate(translateX, Math.min(0, scrollY) + translateY);
1857 mEdgeGlowTop.setSize(width, height);
Adam Powell637d3372010-08-25 14:37:03 -07001858 if (mEdgeGlowTop.draw(canvas)) {
Adam Powelldf3ae4f2012-04-10 18:55:22 -07001859 postInvalidateOnAnimation();
Adam Powell637d3372010-08-25 14:37:03 -07001860 }
1861 canvas.restoreToCount(restoreCount);
1862 }
1863 if (!mEdgeGlowBottom.isFinished()) {
1864 final int restoreCount = canvas.save();
Doris Liuc81c0822015-05-28 10:18:46 -07001865 final int width;
1866 final int height;
1867 final float translateX;
1868 final float translateY;
1869 if (clipToPadding) {
1870 width = getWidth() - mPaddingLeft - mPaddingRight;
1871 height = getHeight() - mPaddingTop - mPaddingBottom;
1872 translateX = mPaddingLeft;
1873 translateY = mPaddingTop;
1874 } else {
1875 width = getWidth();
1876 height = getHeight();
1877 translateX = 0;
1878 translateY = 0;
1879 }
1880 canvas.translate(-width + translateX,
1881 Math.max(getScrollRange(), scrollY) + height + translateY);
Adam Powell637d3372010-08-25 14:37:03 -07001882 canvas.rotate(180, width, 0);
Mindy Pereirab1297f72010-12-07 15:06:47 -08001883 mEdgeGlowBottom.setSize(width, height);
Adam Powell637d3372010-08-25 14:37:03 -07001884 if (mEdgeGlowBottom.draw(canvas)) {
Adam Powelldf3ae4f2012-04-10 18:55:22 -07001885 postInvalidateOnAnimation();
Adam Powell637d3372010-08-25 14:37:03 -07001886 }
1887 canvas.restoreToCount(restoreCount);
1888 }
1889 }
1890 }
1891
Romain Guye979e622012-03-20 13:50:27 -07001892 private static int clamp(int n, int my, int child) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001893 if (my >= child || n < 0) {
1894 /* my >= child is this case:
1895 * |--------------- me ---------------|
1896 * |------ child ------|
1897 * or
1898 * |--------------- me ---------------|
1899 * |------ child ------|
1900 * or
1901 * |--------------- me ---------------|
1902 * |------ child ------|
1903 *
1904 * n < 0 is this case:
1905 * |------ me ------|
1906 * |-------- child --------|
1907 * |-- mScrollX --|
1908 */
1909 return 0;
1910 }
1911 if ((my+n) > child) {
1912 /* this case:
1913 * |------ me ------|
1914 * |------ child ------|
1915 * |-- mScrollX --|
1916 */
1917 return child-my;
1918 }
1919 return n;
1920 }
Fabrice Di Megliod6befbd2013-06-12 16:01:48 -07001921
1922 @Override
1923 protected void onRestoreInstanceState(Parcelable state) {
Adam Powell90f339a2013-06-13 17:44:04 -07001924 if (mContext.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR2) {
1925 // Some old apps reused IDs in ways they shouldn't have.
1926 // Don't break them, but they don't get scroll state restoration.
1927 super.onRestoreInstanceState(state);
1928 return;
1929 }
Fabrice Di Megliod6befbd2013-06-12 16:01:48 -07001930 SavedState ss = (SavedState) state;
1931 super.onRestoreInstanceState(ss.getSuperState());
1932 mSavedState = ss;
1933 requestLayout();
1934 }
1935
1936 @Override
1937 protected Parcelable onSaveInstanceState() {
Adam Powell90f339a2013-06-13 17:44:04 -07001938 if (mContext.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR2) {
1939 // Some old apps reused IDs in ways they shouldn't have.
1940 // Don't break them, but they don't get scroll state restoration.
1941 return super.onSaveInstanceState();
1942 }
Fabrice Di Megliod6befbd2013-06-12 16:01:48 -07001943 Parcelable superState = super.onSaveInstanceState();
1944 SavedState ss = new SavedState(superState);
1945 ss.scrollPosition = mScrollY;
1946 return ss;
1947 }
1948
Siva Velusamy94a6d152015-05-05 15:07:00 -07001949 /** @hide */
1950 @Override
1951 protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
1952 super.encodeProperties(encoder);
1953 encoder.addProperty("fillViewport", mFillViewport);
1954 }
1955
Fabrice Di Megliod6befbd2013-06-12 16:01:48 -07001956 static class SavedState extends BaseSavedState {
1957 public int scrollPosition;
1958
1959 SavedState(Parcelable superState) {
1960 super(superState);
1961 }
1962
1963 public SavedState(Parcel source) {
1964 super(source);
1965 scrollPosition = source.readInt();
1966 }
1967
1968 @Override
1969 public void writeToParcel(Parcel dest, int flags) {
1970 super.writeToParcel(dest, flags);
1971 dest.writeInt(scrollPosition);
1972 }
1973
1974 @Override
1975 public String toString() {
Aaron Whytef8306522017-03-22 16:30:58 -07001976 return "ScrollView.SavedState{"
Fabrice Di Megliod6befbd2013-06-12 16:01:48 -07001977 + Integer.toHexString(System.identityHashCode(this))
1978 + " scrollPosition=" + scrollPosition + "}";
1979 }
1980
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -07001981 public static final @android.annotation.NonNull Parcelable.Creator<SavedState> CREATOR
Fabrice Di Megliod6befbd2013-06-12 16:01:48 -07001982 = new Parcelable.Creator<SavedState>() {
1983 public SavedState createFromParcel(Parcel in) {
1984 return new SavedState(in);
1985 }
1986
1987 public SavedState[] newArray(int size) {
1988 return new SavedState[size];
1989 }
1990 };
1991 }
1992
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001993}