blob: 202616969eb305f86808ec0c3994f2c6393ce76c [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
Adam Powell90f339a2013-06-13 17:44:04 -070019import android.os.Build;
Fabrice Di Megliod6befbd2013-06-12 16:01:48 -070020import android.os.Parcel;
21import android.os.Parcelable;
Adam Powell17dfce12010-01-25 18:38:22 -080022import com.android.internal.R;
23
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080024import android.content.Context;
25import android.content.res.TypedArray;
Adam Powell637d3372010-08-25 14:37:03 -070026import android.graphics.Canvas;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027import android.graphics.Rect;
Svetoslav Ganova1dc7612012-05-10 04:14:53 -070028import android.os.Bundle;
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -080029import android.os.StrictMode;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030import android.util.AttributeSet;
Johan Rosengren0dc291e2011-02-21 09:49:45 +010031import android.util.Log;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080032import android.view.FocusFinder;
Jeff Brown33bbfd22011-02-24 20:55:35 -080033import android.view.InputDevice;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080034import android.view.KeyEvent;
35import android.view.MotionEvent;
36import android.view.VelocityTracker;
37import android.view.View;
38import android.view.ViewConfiguration;
Gilles Debunne2ed2eac2011-02-24 16:29:48 -080039import android.view.ViewDebug;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040import android.view.ViewGroup;
41import android.view.ViewParent;
Svetoslav Ganova0156172011-06-26 17:55:44 -070042import android.view.accessibility.AccessibilityEvent;
43import android.view.accessibility.AccessibilityNodeInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080044import android.view.animation.AnimationUtils;
45
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080046import java.util.List;
47
48/**
49 * Layout container for a view hierarchy that can be scrolled by the user,
50 * allowing it to be larger than the physical display. A ScrollView
51 * is a {@link FrameLayout}, meaning you should place one child in it
52 * containing the entire contents to scroll; this child may itself be a layout
53 * manager with a complex hierarchy of objects. A child that is often used
54 * is a {@link LinearLayout} in a vertical orientation, presenting a vertical
55 * array of top-level items that the user can scroll through.
Scott Main15279cf2012-07-02 21:49:47 -070056 * <p>You should never use a ScrollView with a {@link ListView}, because
57 * ListView takes care of its own vertical scrolling. Most importantly, doing this
58 * defeats all of the important optimizations in ListView for dealing with
59 * large lists, since it effectively forces the ListView to display its entire
60 * list of items to fill up the infinite container supplied by ScrollView.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080061 * <p>The {@link TextView} class also
62 * takes care of its own scrolling, so does not require a ScrollView, but
63 * using the two together is possible to achieve the effect of a text view
64 * within a larger container.
Mindy Pereira4e30d892010-11-24 15:32:39 -080065 *
Scott Main15279cf2012-07-02 21:49:47 -070066 * <p>ScrollView only supports vertical scrolling. For horizontal scrolling,
67 * use {@link HorizontalScrollView}.
Romain Guyfdbf4842010-08-16 10:55:49 -070068 *
69 * @attr ref android.R.styleable#ScrollView_fillViewport
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080070 */
71public class ScrollView extends FrameLayout {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080072 static final int ANIMATED_SCROLL_GAP = 250;
73
74 static final float MAX_SCROLL_FACTOR = 0.5f;
75
Johan Rosengren0dc291e2011-02-21 09:49:45 +010076 private static final String TAG = "ScrollView";
77
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080078 private long mLastScroll;
79
80 private final Rect mTempRect = new Rect();
Adam Powell637d3372010-08-25 14:37:03 -070081 private OverScroller mScroller;
Adam Powell89935e42011-08-31 14:26:12 -070082 private EdgeEffect mEdgeGlowTop;
83 private EdgeEffect mEdgeGlowBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080084
85 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080086 * Position of the last motion event.
87 */
Adam Powelldf3ae4f2012-04-10 18:55:22 -070088 private int mLastMotionY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080089
90 /**
91 * True when the layout has changed but the traversal has not come through yet.
92 * Ideally the view hierarchy would keep track of this for us.
93 */
94 private boolean mIsLayoutDirty = true;
95
96 /**
97 * The child to give focus to in the event that a child has requested focus while the
98 * layout is dirty. This prevents the scroll from being wrong if the child has not been
99 * laid out before requesting focus.
100 */
101 private View mChildToScrollTo = null;
102
103 /**
104 * True if the user is currently dragging this ScrollView around. This is
105 * not the same as 'is being flinged', which can be checked by
106 * mScroller.isFinished() (flinging begins when the user lifts his finger).
107 */
108 private boolean mIsBeingDragged = false;
109
110 /**
111 * Determines speed during touch scrolling
112 */
113 private VelocityTracker mVelocityTracker;
114
115 /**
116 * When set to true, the scroll view measure its child to make it fill the currently
117 * visible area.
118 */
Romain Guya174d7a2011-01-07 13:27:39 -0800119 @ViewDebug.ExportedProperty(category = "layout")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800120 private boolean mFillViewport;
121
122 /**
123 * Whether arrow scrolling is animated.
124 */
125 private boolean mSmoothScrollingEnabled = true;
126
127 private int mTouchSlop;
Romain Guy4296fc42009-07-06 11:48:52 -0700128 private int mMinimumVelocity;
129 private int mMaximumVelocity;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800130
Adam Powell637d3372010-08-25 14:37:03 -0700131 private int mOverscrollDistance;
132 private int mOverflingDistance;
133
Adam Powellbc4e7532010-02-23 14:49:01 -0800134 /**
135 * ID of the active pointer. This is used to retain consistency during
136 * drags/flings if multiple pointers are used.
137 */
138 private int mActivePointerId = INVALID_POINTER;
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -0800139
140 /**
Adam Powell10ba2772014-04-15 09:46:51 -0700141 * Used during scrolling to retrieve the new offset within the window.
142 */
143 private final int[] mScrollOffset = new int[2];
144 private final int[] mScrollConsumed = new int[2];
Adam Powell744beff2014-09-22 09:47:48 -0700145 private int mNestedYOffset;
Adam Powell10ba2772014-04-15 09:46:51 -0700146
147 /**
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -0800148 * The StrictMode "critical time span" objects to catch animation
149 * stutters. Non-null when a time-sensitive animation is
150 * in-flight. Must call finish() on them when done animating.
151 * These are no-ops on user builds.
152 */
153 private StrictMode.Span mScrollStrictSpan = null; // aka "drag"
154 private StrictMode.Span mFlingStrictSpan = null;
155
Adam Powellbc4e7532010-02-23 14:49:01 -0800156 /**
157 * Sentinel value for no current active pointer.
158 * Used by {@link #mActivePointerId}.
159 */
160 private static final int INVALID_POINTER = -1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800161
Fabrice Di Megliod6befbd2013-06-12 16:01:48 -0700162 private SavedState mSavedState;
163
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800164 public ScrollView(Context context) {
165 this(context, null);
166 }
167
168 public ScrollView(Context context, AttributeSet attrs) {
169 this(context, attrs, com.android.internal.R.attr.scrollViewStyle);
170 }
171
Alan Viverette617feb92013-09-09 18:09:13 -0700172 public ScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
173 this(context, attrs, defStyleAttr, 0);
174 }
175
176 public ScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
177 super(context, attrs, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800178 initScrollView();
179
Alan Viverette617feb92013-09-09 18:09:13 -0700180 final TypedArray a = context.obtainStyledAttributes(
181 attrs, com.android.internal.R.styleable.ScrollView, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800182
183 setFillViewport(a.getBoolean(R.styleable.ScrollView_fillViewport, false));
184
185 a.recycle();
186 }
187
188 @Override
Patrick Dubroye0a799a2011-05-04 16:19:22 -0700189 public boolean shouldDelayChildPressedState() {
190 return true;
191 }
192
193 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800194 protected float getTopFadingEdgeStrength() {
195 if (getChildCount() == 0) {
196 return 0.0f;
197 }
198
199 final int length = getVerticalFadingEdgeLength();
200 if (mScrollY < length) {
201 return mScrollY / (float) length;
202 }
203
204 return 1.0f;
205 }
206
207 @Override
208 protected float getBottomFadingEdgeStrength() {
209 if (getChildCount() == 0) {
210 return 0.0f;
211 }
212
213 final int length = getVerticalFadingEdgeLength();
214 final int bottomEdge = getHeight() - mPaddingBottom;
215 final int span = getChildAt(0).getBottom() - mScrollY - bottomEdge;
216 if (span < length) {
217 return span / (float) length;
218 }
219
220 return 1.0f;
221 }
222
223 /**
224 * @return The maximum amount this scroll view will scroll in response to
225 * an arrow event.
226 */
227 public int getMaxScrollAmount() {
228 return (int) (MAX_SCROLL_FACTOR * (mBottom - mTop));
229 }
230
231
232 private void initScrollView() {
Adam Powell637d3372010-08-25 14:37:03 -0700233 mScroller = new OverScroller(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800234 setFocusable(true);
235 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
236 setWillNotDraw(false);
Romain Guy4296fc42009-07-06 11:48:52 -0700237 final ViewConfiguration configuration = ViewConfiguration.get(mContext);
238 mTouchSlop = configuration.getScaledTouchSlop();
239 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
240 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
Adam Powell637d3372010-08-25 14:37:03 -0700241 mOverscrollDistance = configuration.getScaledOverscrollDistance();
242 mOverflingDistance = configuration.getScaledOverflingDistance();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800243 }
244
245 @Override
246 public void addView(View child) {
247 if (getChildCount() > 0) {
248 throw new IllegalStateException("ScrollView can host only one direct child");
249 }
250
251 super.addView(child);
252 }
253
254 @Override
255 public void addView(View child, int index) {
256 if (getChildCount() > 0) {
257 throw new IllegalStateException("ScrollView can host only one direct child");
258 }
259
260 super.addView(child, index);
261 }
262
263 @Override
264 public void addView(View child, ViewGroup.LayoutParams params) {
265 if (getChildCount() > 0) {
266 throw new IllegalStateException("ScrollView can host only one direct child");
267 }
268
269 super.addView(child, params);
270 }
271
272 @Override
273 public void addView(View child, int index, ViewGroup.LayoutParams params) {
274 if (getChildCount() > 0) {
275 throw new IllegalStateException("ScrollView can host only one direct child");
276 }
277
278 super.addView(child, index, params);
279 }
280
281 /**
282 * @return Returns true this ScrollView can be scrolled
283 */
284 private boolean canScroll() {
285 View child = getChildAt(0);
286 if (child != null) {
287 int childHeight = child.getHeight();
288 return getHeight() < childHeight + mPaddingTop + mPaddingBottom;
289 }
290 return false;
291 }
292
293 /**
294 * Indicates whether this ScrollView's content is stretched to fill the viewport.
295 *
296 * @return True if the content fills the viewport, false otherwise.
Mindy Pereira4e30d892010-11-24 15:32:39 -0800297 *
Romain Guyfdbf4842010-08-16 10:55:49 -0700298 * @attr ref android.R.styleable#ScrollView_fillViewport
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800299 */
300 public boolean isFillViewport() {
301 return mFillViewport;
302 }
303
304 /**
305 * Indicates this ScrollView whether it should stretch its content height to fill
306 * the viewport or not.
307 *
308 * @param fillViewport True to stretch the content's height to the viewport's
309 * boundaries, false otherwise.
Mindy Pereira4e30d892010-11-24 15:32:39 -0800310 *
Romain Guyfdbf4842010-08-16 10:55:49 -0700311 * @attr ref android.R.styleable#ScrollView_fillViewport
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800312 */
313 public void setFillViewport(boolean fillViewport) {
314 if (fillViewport != mFillViewport) {
315 mFillViewport = fillViewport;
316 requestLayout();
317 }
318 }
319
320 /**
321 * @return Whether arrow scrolling will animate its transition.
322 */
323 public boolean isSmoothScrollingEnabled() {
324 return mSmoothScrollingEnabled;
325 }
326
327 /**
328 * Set whether arrow scrolling will animate its transition.
329 * @param smoothScrollingEnabled whether arrow scrolling will animate its transition
330 */
331 public void setSmoothScrollingEnabled(boolean smoothScrollingEnabled) {
332 mSmoothScrollingEnabled = smoothScrollingEnabled;
333 }
334
335 @Override
336 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
337 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
338
339 if (!mFillViewport) {
340 return;
341 }
342
343 final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
344 if (heightMode == MeasureSpec.UNSPECIFIED) {
345 return;
346 }
347
Romain Guyef0e9ae2009-07-10 14:11:26 -0700348 if (getChildCount() > 0) {
349 final View child = getChildAt(0);
350 int height = getMeasuredHeight();
351 if (child.getMeasuredHeight() < height) {
352 final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
Mindy Pereira4e30d892010-11-24 15:32:39 -0800353
Romain Guy9c957372011-01-04 17:39:43 -0800354 int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
355 mPaddingLeft + mPaddingRight, lp.width);
Romain Guyef0e9ae2009-07-10 14:11:26 -0700356 height -= mPaddingTop;
357 height -= mPaddingBottom;
358 int childHeightMeasureSpec =
359 MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
Mindy Pereira4e30d892010-11-24 15:32:39 -0800360
Romain Guyef0e9ae2009-07-10 14:11:26 -0700361 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
362 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800363 }
364 }
365
366 @Override
367 public boolean dispatchKeyEvent(KeyEvent event) {
368 // Let the focused view and/or our descendants get the key first
Romain Guy8e618e52010-03-08 12:18:20 -0800369 return super.dispatchKeyEvent(event) || executeKeyEvent(event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800370 }
371
372 /**
373 * You can call this function yourself to have the scroll view perform
374 * scrolling from a key event, just as if the event had been dispatched to
375 * it by the view hierarchy.
376 *
377 * @param event The key event to execute.
378 * @return Return true if the event was handled, else false.
379 */
380 public boolean executeKeyEvent(KeyEvent event) {
381 mTempRect.setEmpty();
382
383 if (!canScroll()) {
Romain Guy2d4cff62010-04-09 15:39:00 -0700384 if (isFocused() && event.getKeyCode() != KeyEvent.KEYCODE_BACK) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800385 View currentFocused = findFocus();
386 if (currentFocused == this) currentFocused = null;
387 View nextFocused = FocusFinder.getInstance().findNextFocus(this,
388 currentFocused, View.FOCUS_DOWN);
389 return nextFocused != null
390 && nextFocused != this
391 && nextFocused.requestFocus(View.FOCUS_DOWN);
392 }
393 return false;
394 }
395
396 boolean handled = false;
397 if (event.getAction() == KeyEvent.ACTION_DOWN) {
398 switch (event.getKeyCode()) {
399 case KeyEvent.KEYCODE_DPAD_UP:
400 if (!event.isAltPressed()) {
401 handled = arrowScroll(View.FOCUS_UP);
402 } else {
403 handled = fullScroll(View.FOCUS_UP);
404 }
405 break;
406 case KeyEvent.KEYCODE_DPAD_DOWN:
407 if (!event.isAltPressed()) {
408 handled = arrowScroll(View.FOCUS_DOWN);
409 } else {
410 handled = fullScroll(View.FOCUS_DOWN);
411 }
412 break;
413 case KeyEvent.KEYCODE_SPACE:
414 pageScroll(event.isShiftPressed() ? View.FOCUS_UP : View.FOCUS_DOWN);
415 break;
416 }
417 }
418
419 return handled;
420 }
421
Joe Onorato9f0e8ee2010-02-23 19:19:22 -0800422 private boolean inChild(int x, int y) {
423 if (getChildCount() > 0) {
Adam Powell352b9782010-03-24 14:23:43 -0700424 final int scrollY = mScrollY;
Joe Onorato9f0e8ee2010-02-23 19:19:22 -0800425 final View child = getChildAt(0);
Adam Powell352b9782010-03-24 14:23:43 -0700426 return !(y < child.getTop() - scrollY
427 || y >= child.getBottom() - scrollY
Joe Onorato9f0e8ee2010-02-23 19:19:22 -0800428 || x < child.getLeft()
429 || x >= child.getRight());
430 }
431 return false;
432 }
433
Michael Jurka13451a42011-08-22 15:54:21 -0700434 private void initOrResetVelocityTracker() {
435 if (mVelocityTracker == null) {
436 mVelocityTracker = VelocityTracker.obtain();
437 } else {
438 mVelocityTracker.clear();
439 }
440 }
441
442 private void initVelocityTrackerIfNotExists() {
443 if (mVelocityTracker == null) {
444 mVelocityTracker = VelocityTracker.obtain();
445 }
446 }
447
448 private void recycleVelocityTracker() {
449 if (mVelocityTracker != null) {
450 mVelocityTracker.recycle();
451 mVelocityTracker = null;
452 }
453 }
454
455 @Override
456 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
457 if (disallowIntercept) {
458 recycleVelocityTracker();
459 }
460 super.requestDisallowInterceptTouchEvent(disallowIntercept);
461 }
462
463
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800464 @Override
465 public boolean onInterceptTouchEvent(MotionEvent ev) {
466 /*
467 * This method JUST determines whether we want to intercept the motion.
468 * If we return true, onMotionEvent will be called and we do the actual
469 * scrolling there.
470 */
471
472 /*
473 * Shortcut the most recurring case: the user is in the dragging
474 * state and he is moving his finger. We want to intercept this
475 * motion.
476 */
477 final int action = ev.getAction();
478 if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
479 return true;
480 }
481
Adam Powell0278c2f2012-07-31 16:39:32 -0700482 /*
483 * Don't try to intercept touch if we can't scroll anyway.
484 */
485 if (getScrollY() == 0 && !canScrollVertically(1)) {
486 return false;
487 }
488
Adam Powellbc4e7532010-02-23 14:49:01 -0800489 switch (action & MotionEvent.ACTION_MASK) {
490 case MotionEvent.ACTION_MOVE: {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800491 /*
492 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
493 * whether the user has moved far enough from his original down touch.
494 */
495
496 /*
497 * Locally do absolute value. mLastMotionY is set to the y value
498 * of the down event.
499 */
Adam Powell9d0335b2010-03-24 13:42:51 -0700500 final int activePointerId = mActivePointerId;
501 if (activePointerId == INVALID_POINTER) {
502 // If we don't have a valid id, the touch down wasn't on content.
503 break;
504 }
505
506 final int pointerIndex = ev.findPointerIndex(activePointerId);
Johan Rosengren0dc291e2011-02-21 09:49:45 +0100507 if (pointerIndex == -1) {
508 Log.e(TAG, "Invalid pointerId=" + activePointerId
509 + " in onInterceptTouchEvent");
510 break;
511 }
512
Adam Powelldf3ae4f2012-04-10 18:55:22 -0700513 final int y = (int) ev.getY(pointerIndex);
514 final int yDiff = Math.abs(y - mLastMotionY);
Adam Powell10ba2772014-04-15 09:46:51 -0700515 if (yDiff > mTouchSlop && (getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800516 mIsBeingDragged = true;
Romain Guyf7b4acc2009-12-01 16:24:45 -0800517 mLastMotionY = y;
Michael Jurka13451a42011-08-22 15:54:21 -0700518 initVelocityTrackerIfNotExists();
519 mVelocityTracker.addMovement(ev);
Adam Powell744beff2014-09-22 09:47:48 -0700520 mNestedYOffset = 0;
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -0800521 if (mScrollStrictSpan == null) {
522 mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll");
523 }
Adam Powellb3e02c42012-05-02 22:05:46 -0700524 final ViewParent parent = getParent();
525 if (parent != null) {
526 parent.requestDisallowInterceptTouchEvent(true);
527 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800528 }
529 break;
Adam Powellbc4e7532010-02-23 14:49:01 -0800530 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800531
Adam Powellbc4e7532010-02-23 14:49:01 -0800532 case MotionEvent.ACTION_DOWN: {
Adam Powelldf3ae4f2012-04-10 18:55:22 -0700533 final int y = (int) ev.getY();
Adam Powell4cd47702010-02-25 11:21:14 -0800534 if (!inChild((int) ev.getX(), (int) y)) {
Joe Onorato9f0e8ee2010-02-23 19:19:22 -0800535 mIsBeingDragged = false;
Michael Jurka13451a42011-08-22 15:54:21 -0700536 recycleVelocityTracker();
Joe Onorato9f0e8ee2010-02-23 19:19:22 -0800537 break;
538 }
539
Adam Powellbc4e7532010-02-23 14:49:01 -0800540 /*
541 * Remember location of down touch.
542 * ACTION_DOWN always refers to pointer index 0.
543 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800544 mLastMotionY = y;
Adam Powellbc4e7532010-02-23 14:49:01 -0800545 mActivePointerId = ev.getPointerId(0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800546
Michael Jurka13451a42011-08-22 15:54:21 -0700547 initOrResetVelocityTracker();
548 mVelocityTracker.addMovement(ev);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800549 /*
550 * If being flinged and user touches the screen, initiate drag;
551 * otherwise don't. mScroller.isFinished should be false when
552 * being flinged.
553 */
554 mIsBeingDragged = !mScroller.isFinished();
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -0800555 if (mIsBeingDragged && mScrollStrictSpan == null) {
556 mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll");
557 }
Adam Powelle9a16a52014-04-25 14:21:27 -0700558 startNestedScroll(SCROLL_AXIS_VERTICAL);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800559 break;
Adam Powellbc4e7532010-02-23 14:49:01 -0800560 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800561
562 case MotionEvent.ACTION_CANCEL:
563 case MotionEvent.ACTION_UP:
564 /* Release the drag */
565 mIsBeingDragged = false;
Adam Powellbc4e7532010-02-23 14:49:01 -0800566 mActivePointerId = INVALID_POINTER;
Michael Jurka13451a42011-08-22 15:54:21 -0700567 recycleVelocityTracker();
Adam Powell637d3372010-08-25 14:37:03 -0700568 if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) {
Adam Powelldf3ae4f2012-04-10 18:55:22 -0700569 postInvalidateOnAnimation();
Adam Powell637d3372010-08-25 14:37:03 -0700570 }
Adam Powelle9a16a52014-04-25 14:21:27 -0700571 stopNestedScroll();
Adam Powellbc4e7532010-02-23 14:49:01 -0800572 break;
573 case MotionEvent.ACTION_POINTER_UP:
574 onSecondaryPointerUp(ev);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800575 break;
576 }
577
578 /*
579 * The only time we want to intercept motion events is if we are in the
580 * drag mode.
581 */
582 return mIsBeingDragged;
583 }
584
585 @Override
586 public boolean onTouchEvent(MotionEvent ev) {
Michael Jurka13451a42011-08-22 15:54:21 -0700587 initVelocityTrackerIfNotExists();
Adam Powell96d62af2014-05-02 10:04:38 -0700588
589 MotionEvent vtev = MotionEvent.obtain(ev);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800590
Adam Powell744beff2014-09-22 09:47:48 -0700591 final int actionMasked = ev.getActionMasked();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800592
Adam Powell744beff2014-09-22 09:47:48 -0700593 if (actionMasked == MotionEvent.ACTION_DOWN) {
594 mNestedYOffset = 0;
595 }
596 vtev.offsetLocation(0, mNestedYOffset);
597
598 switch (actionMasked) {
Adam Powellbc4e7532010-02-23 14:49:01 -0800599 case MotionEvent.ACTION_DOWN: {
Adam Powellb3e02c42012-05-02 22:05:46 -0700600 if (getChildCount() == 0) {
Jeff Brownfb757382011-01-18 18:42:33 -0800601 return false;
602 }
Adam Powellb3e02c42012-05-02 22:05:46 -0700603 if ((mIsBeingDragged = !mScroller.isFinished())) {
604 final ViewParent parent = getParent();
605 if (parent != null) {
606 parent.requestDisallowInterceptTouchEvent(true);
607 }
608 }
Mindy Pereira4e30d892010-11-24 15:32:39 -0800609
Adam Powell352b9782010-03-24 14:23:43 -0700610 /*
611 * If being flinged and user touches, stop the fling. isFinished
612 * will be false if being flinged.
613 */
614 if (!mScroller.isFinished()) {
615 mScroller.abortAnimation();
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -0800616 if (mFlingStrictSpan != null) {
617 mFlingStrictSpan.finish();
618 mFlingStrictSpan = null;
619 }
Adam Powell352b9782010-03-24 14:23:43 -0700620 }
621
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800622 // Remember where the motion event started
Adam Powelldf3ae4f2012-04-10 18:55:22 -0700623 mLastMotionY = (int) ev.getY();
Adam Powellbc4e7532010-02-23 14:49:01 -0800624 mActivePointerId = ev.getPointerId(0);
Adam Powell10ba2772014-04-15 09:46:51 -0700625 startNestedScroll(SCROLL_AXIS_VERTICAL);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800626 break;
Adam Powellbc4e7532010-02-23 14:49:01 -0800627 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800628 case MotionEvent.ACTION_MOVE:
Adam Powellb3e02c42012-05-02 22:05:46 -0700629 final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
Johan Rosengren0dc291e2011-02-21 09:49:45 +0100630 if (activePointerIndex == -1) {
631 Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
632 break;
633 }
634
Adam Powellb3e02c42012-05-02 22:05:46 -0700635 final int y = (int) ev.getY(activePointerIndex);
636 int deltaY = mLastMotionY - y;
Adam Powell10ba2772014-04-15 09:46:51 -0700637 if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset)) {
Yorke Leee9a0d6a2014-05-02 18:33:35 -0700638 deltaY -= mScrollConsumed[1];
Adam Powell96d62af2014-05-02 10:04:38 -0700639 vtev.offsetLocation(0, mScrollOffset[1]);
Adam Powell744beff2014-09-22 09:47:48 -0700640 mNestedYOffset += mScrollOffset[1];
Adam Powell10ba2772014-04-15 09:46:51 -0700641 }
Adam Powellb3e02c42012-05-02 22:05:46 -0700642 if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) {
643 final ViewParent parent = getParent();
644 if (parent != null) {
645 parent.requestDisallowInterceptTouchEvent(true);
646 }
647 mIsBeingDragged = true;
648 if (deltaY > 0) {
649 deltaY -= mTouchSlop;
650 } else {
651 deltaY += mTouchSlop;
652 }
653 }
Joe Onorato9f0e8ee2010-02-23 19:19:22 -0800654 if (mIsBeingDragged) {
655 // Scroll to follow the motion event
Yorke Leee9a0d6a2014-05-02 18:33:35 -0700656 mLastMotionY = y - mScrollOffset[1];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800657
Adam Powell637d3372010-08-25 14:37:03 -0700658 final int oldY = mScrollY;
659 final int range = getScrollRange();
Fabrice Di Meglioe9dbef82011-09-12 16:36:46 -0700660 final int overscrollMode = getOverScrollMode();
Adam Powell10ba2772014-04-15 09:46:51 -0700661 boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
Fabrice Di Meglioe9dbef82011-09-12 16:36:46 -0700662 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
663
Alan Viverettecb25bd82013-06-03 17:10:44 -0700664 // Calling overScrollBy will call onOverScrolled, which
665 // calls onScrollChanged if applicable.
Adam Powell10ba2772014-04-15 09:46:51 -0700666 if (overScrollBy(0, deltaY, 0, mScrollY, 0, range, 0, mOverscrollDistance, true)
667 && !hasNestedScrollingParent()) {
Adam Powell637d3372010-08-25 14:37:03 -0700668 // Break our velocity if we hit a scroll barrier.
669 mVelocityTracker.clear();
670 }
Adam Powell637d3372010-08-25 14:37:03 -0700671
Adam Powell10ba2772014-04-15 09:46:51 -0700672 final int scrolledDeltaY = mScrollY - oldY;
673 final int unconsumedY = deltaY - scrolledDeltaY;
674 if (dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, mScrollOffset)) {
675 mLastMotionY -= mScrollOffset[1];
Adam Powell96d62af2014-05-02 10:04:38 -0700676 vtev.offsetLocation(0, mScrollOffset[1]);
Adam Powell744beff2014-09-22 09:47:48 -0700677 mNestedYOffset += mScrollOffset[1];
Adam Powell10ba2772014-04-15 09:46:51 -0700678 } else if (canOverscroll) {
Adam Powell637d3372010-08-25 14:37:03 -0700679 final int pulledToY = oldY + deltaY;
680 if (pulledToY < 0) {
Adam Powellc501db9f2014-05-08 12:50:10 -0700681 mEdgeGlowTop.onPull((float) deltaY / getHeight(),
682 ev.getX(activePointerIndex) / getWidth());
Adam Powell637d3372010-08-25 14:37:03 -0700683 if (!mEdgeGlowBottom.isFinished()) {
684 mEdgeGlowBottom.onRelease();
685 }
686 } else if (pulledToY > range) {
Adam Powellc501db9f2014-05-08 12:50:10 -0700687 mEdgeGlowBottom.onPull((float) deltaY / getHeight(),
688 1.f - ev.getX(activePointerIndex) / getWidth());
Adam Powell637d3372010-08-25 14:37:03 -0700689 if (!mEdgeGlowTop.isFinished()) {
690 mEdgeGlowTop.onRelease();
691 }
692 }
693 if (mEdgeGlowTop != null
694 && (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished())) {
Adam Powelldf3ae4f2012-04-10 18:55:22 -0700695 postInvalidateOnAnimation();
Adam Powell637d3372010-08-25 14:37:03 -0700696 }
697 }
Joe Onorato9f0e8ee2010-02-23 19:19:22 -0800698 }
Adam Powellbc4e7532010-02-23 14:49:01 -0800699 break;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800700 case MotionEvent.ACTION_UP:
Joe Onorato9f0e8ee2010-02-23 19:19:22 -0800701 if (mIsBeingDragged) {
702 final VelocityTracker velocityTracker = mVelocityTracker;
703 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
Adam Powellbc4e7532010-02-23 14:49:01 -0800704 int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800705
Adam Powell10ba2772014-04-15 09:46:51 -0700706 if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
707 flingWithNestedDispatch(-initialVelocity);
708 } else if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0,
709 getScrollRange())) {
710 postInvalidateOnAnimation();
Adam Powell17dfce12010-01-25 18:38:22 -0800711 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800712
Adam Powellbc4e7532010-02-23 14:49:01 -0800713 mActivePointerId = INVALID_POINTER;
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -0800714 endDrag();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800715 }
Adam Powellbc4e7532010-02-23 14:49:01 -0800716 break;
Adam Powell352b9782010-03-24 14:23:43 -0700717 case MotionEvent.ACTION_CANCEL:
718 if (mIsBeingDragged && getChildCount() > 0) {
Adam Powell637d3372010-08-25 14:37:03 -0700719 if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) {
Adam Powelldf3ae4f2012-04-10 18:55:22 -0700720 postInvalidateOnAnimation();
Adam Powell637d3372010-08-25 14:37:03 -0700721 }
Adam Powell352b9782010-03-24 14:23:43 -0700722 mActivePointerId = INVALID_POINTER;
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -0800723 endDrag();
Adam Powell352b9782010-03-24 14:23:43 -0700724 }
725 break;
Adam Powell9bc30d32011-02-28 10:27:49 -0800726 case MotionEvent.ACTION_POINTER_DOWN: {
727 final int index = ev.getActionIndex();
Adam Powelldf3ae4f2012-04-10 18:55:22 -0700728 mLastMotionY = (int) ev.getY(index);
Adam Powell9bc30d32011-02-28 10:27:49 -0800729 mActivePointerId = ev.getPointerId(index);
730 break;
731 }
Adam Powellbc4e7532010-02-23 14:49:01 -0800732 case MotionEvent.ACTION_POINTER_UP:
733 onSecondaryPointerUp(ev);
Adam Powelldf3ae4f2012-04-10 18:55:22 -0700734 mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
Adam Powellbc4e7532010-02-23 14:49:01 -0800735 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800736 }
Adam Powell96d62af2014-05-02 10:04:38 -0700737
738 if (mVelocityTracker != null) {
739 mVelocityTracker.addMovement(vtev);
740 }
741 vtev.recycle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800742 return true;
743 }
Mindy Pereira4e30d892010-11-24 15:32:39 -0800744
Adam Powellbc4e7532010-02-23 14:49:01 -0800745 private void onSecondaryPointerUp(MotionEvent ev) {
746 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
747 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
748 final int pointerId = ev.getPointerId(pointerIndex);
749 if (pointerId == mActivePointerId) {
750 // This was our active pointer going up. Choose a new
751 // active pointer and adjust accordingly.
752 // TODO: Make this decision more intelligent.
753 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
Adam Powelldf3ae4f2012-04-10 18:55:22 -0700754 mLastMotionY = (int) ev.getY(newPointerIndex);
Adam Powellbc4e7532010-02-23 14:49:01 -0800755 mActivePointerId = ev.getPointerId(newPointerIndex);
756 if (mVelocityTracker != null) {
757 mVelocityTracker.clear();
758 }
759 }
760 }
Mindy Pereira4e30d892010-11-24 15:32:39 -0800761
Adam Powell637d3372010-08-25 14:37:03 -0700762 @Override
Jeff Brown33bbfd22011-02-24 20:55:35 -0800763 public boolean onGenericMotionEvent(MotionEvent event) {
764 if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
765 switch (event.getAction()) {
766 case MotionEvent.ACTION_SCROLL: {
767 if (!mIsBeingDragged) {
768 final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
769 if (vscroll != 0) {
770 final int delta = (int) (vscroll * getVerticalScrollFactor());
771 final int range = getScrollRange();
772 int oldScrollY = mScrollY;
773 int newScrollY = oldScrollY - delta;
774 if (newScrollY < 0) {
775 newScrollY = 0;
776 } else if (newScrollY > range) {
777 newScrollY = range;
778 }
779 if (newScrollY != oldScrollY) {
780 super.scrollTo(mScrollX, newScrollY);
781 return true;
782 }
783 }
784 }
785 }
786 }
787 }
788 return super.onGenericMotionEvent(event);
789 }
790
791 @Override
Adam Powell637d3372010-08-25 14:37:03 -0700792 protected void onOverScrolled(int scrollX, int scrollY,
793 boolean clampedX, boolean clampedY) {
794 // Treat animating scrolls differently; see #computeScroll() for why.
795 if (!mScroller.isFinished()) {
Alan Viverettecb25bd82013-06-03 17:10:44 -0700796 final int oldX = mScrollX;
797 final int oldY = mScrollY;
Adam Powell637d3372010-08-25 14:37:03 -0700798 mScrollX = scrollX;
799 mScrollY = scrollY;
Romain Guy0fd89bf2011-01-26 15:41:30 -0800800 invalidateParentIfNeeded();
Alan Viverettecb25bd82013-06-03 17:10:44 -0700801 onScrollChanged(mScrollX, mScrollY, oldX, oldY);
Adam Powell637d3372010-08-25 14:37:03 -0700802 if (clampedY) {
803 mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange());
804 }
805 } else {
806 super.scrollTo(scrollX, scrollY);
807 }
Romain Guye979e622012-03-20 13:50:27 -0700808
Romain Guye72cf732012-03-20 14:23:09 -0700809 awakenScrollBars();
Adam Powell637d3372010-08-25 14:37:03 -0700810 }
811
Alan Viverettea54956a2015-01-07 16:05:02 -0800812 /** @hide */
Svetoslav Ganova0156172011-06-26 17:55:44 -0700813 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -0800814 public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
815 if (super.performAccessibilityActionInternal(action, arguments)) {
Svetoslav Ganov48d15862012-05-15 10:10:00 -0700816 return true;
817 }
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -0700818 if (!isEnabled()) {
819 return false;
820 }
Svetoslav Ganova1dc7612012-05-10 04:14:53 -0700821 switch (action) {
822 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
823 final int viewportHeight = getHeight() - mPaddingBottom - mPaddingTop;
824 final int targetScrollY = Math.min(mScrollY + viewportHeight, getScrollRange());
825 if (targetScrollY != mScrollY) {
826 smoothScrollTo(0, targetScrollY);
827 return true;
828 }
829 } return false;
830 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
831 final int viewportHeight = getHeight() - mPaddingBottom - mPaddingTop;
832 final int targetScrollY = Math.max(mScrollY - viewportHeight, 0);
833 if (targetScrollY != mScrollY) {
834 smoothScrollTo(0, targetScrollY);
835 return true;
836 }
837 } return false;
838 }
Svetoslav Ganov48d15862012-05-15 10:10:00 -0700839 return false;
Svetoslav Ganova1dc7612012-05-10 04:14:53 -0700840 }
841
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -0800842 @Override
843 public CharSequence getAccessibilityClassName() {
844 return ScrollView.class.getName();
845 }
846
Alan Viverettea54956a2015-01-07 16:05:02 -0800847 /** @hide */
Svetoslav Ganova1dc7612012-05-10 04:14:53 -0700848 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -0800849 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
850 super.onInitializeAccessibilityNodeInfoInternal(info);
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -0700851 if (isEnabled()) {
852 final int scrollRange = getScrollRange();
853 if (scrollRange > 0) {
854 info.setScrollable(true);
855 if (mScrollY > 0) {
856 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
857 }
858 if (mScrollY < scrollRange) {
859 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
860 }
Svetoslav Ganova1dc7612012-05-10 04:14:53 -0700861 }
862 }
Svetoslav Ganova0156172011-06-26 17:55:44 -0700863 }
864
Alan Viverettea54956a2015-01-07 16:05:02 -0800865 /** @hide */
Svetoslav Ganova0156172011-06-26 17:55:44 -0700866 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -0800867 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
868 super.onInitializeAccessibilityEventInternal(event);
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -0700869 final boolean scrollable = getScrollRange() > 0;
870 event.setScrollable(scrollable);
871 event.setScrollX(mScrollX);
872 event.setScrollY(mScrollY);
873 event.setMaxScrollX(mScrollX);
874 event.setMaxScrollY(getScrollRange());
Svetoslav Ganova0156172011-06-26 17:55:44 -0700875 }
876
Adam Powell637d3372010-08-25 14:37:03 -0700877 private int getScrollRange() {
878 int scrollRange = 0;
879 if (getChildCount() > 0) {
880 View child = getChildAt(0);
881 scrollRange = Math.max(0,
882 child.getHeight() - (getHeight() - mPaddingBottom - mPaddingTop));
883 }
884 return scrollRange;
885 }
886
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800887 /**
888 * <p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800889 * Finds the next focusable component that fits in the specified bounds.
890 * </p>
891 *
892 * @param topFocus look for a candidate is the one at the top of the bounds
893 * if topFocus is true, or at the bottom of the bounds if topFocus is
894 * false
895 * @param top the top offset of the bounds in which a focusable must be
896 * found
897 * @param bottom the bottom offset of the bounds in which a focusable must
898 * be found
899 * @return the next focusable component in the bounds or null if none can
900 * be found
901 */
902 private View findFocusableViewInBounds(boolean topFocus, int top, int bottom) {
903
904 List<View> focusables = getFocusables(View.FOCUS_FORWARD);
905 View focusCandidate = null;
906
907 /*
908 * A fully contained focusable is one where its top is below the bound's
909 * top, and its bottom is above the bound's bottom. A partially
910 * contained focusable is one where some part of it is within the
911 * bounds, but it also has some part that is not within bounds. A fully contained
912 * focusable is preferred to a partially contained focusable.
913 */
914 boolean foundFullyContainedFocusable = false;
915
916 int count = focusables.size();
917 for (int i = 0; i < count; i++) {
918 View view = focusables.get(i);
919 int viewTop = view.getTop();
920 int viewBottom = view.getBottom();
921
922 if (top < viewBottom && viewTop < bottom) {
923 /*
924 * the focusable is in the target area, it is a candidate for
925 * focusing
926 */
927
928 final boolean viewIsFullyContained = (top < viewTop) &&
929 (viewBottom < bottom);
930
931 if (focusCandidate == null) {
932 /* No candidate, take this one */
933 focusCandidate = view;
934 foundFullyContainedFocusable = viewIsFullyContained;
935 } else {
936 final boolean viewIsCloserToBoundary =
937 (topFocus && viewTop < focusCandidate.getTop()) ||
938 (!topFocus && viewBottom > focusCandidate
939 .getBottom());
940
941 if (foundFullyContainedFocusable) {
942 if (viewIsFullyContained && viewIsCloserToBoundary) {
943 /*
944 * We're dealing with only fully contained views, so
945 * it has to be closer to the boundary to beat our
946 * candidate
947 */
948 focusCandidate = view;
949 }
950 } else {
951 if (viewIsFullyContained) {
952 /* Any fully contained view beats a partially contained view */
953 focusCandidate = view;
954 foundFullyContainedFocusable = true;
955 } else if (viewIsCloserToBoundary) {
956 /*
957 * Partially contained view beats another partially
958 * contained view if it's closer
959 */
960 focusCandidate = view;
961 }
962 }
963 }
964 }
965 }
966
967 return focusCandidate;
968 }
969
970 /**
971 * <p>Handles scrolling in response to a "page up/down" shortcut press. This
972 * method will scroll the view by one page up or down and give the focus
973 * to the topmost/bottommost component in the new visible area. If no
974 * component is a good candidate for focus, this scrollview reclaims the
975 * focus.</p>
976 *
977 * @param direction the scroll direction: {@link android.view.View#FOCUS_UP}
978 * to go one page up or
979 * {@link android.view.View#FOCUS_DOWN} to go one page down
980 * @return true if the key event is consumed by this method, false otherwise
981 */
982 public boolean pageScroll(int direction) {
983 boolean down = direction == View.FOCUS_DOWN;
984 int height = getHeight();
985
986 if (down) {
987 mTempRect.top = getScrollY() + height;
988 int count = getChildCount();
989 if (count > 0) {
990 View view = getChildAt(count - 1);
991 if (mTempRect.top + height > view.getBottom()) {
992 mTempRect.top = view.getBottom() - height;
993 }
994 }
995 } else {
996 mTempRect.top = getScrollY() - height;
997 if (mTempRect.top < 0) {
998 mTempRect.top = 0;
999 }
1000 }
1001 mTempRect.bottom = mTempRect.top + height;
1002
1003 return scrollAndFocus(direction, mTempRect.top, mTempRect.bottom);
1004 }
1005
1006 /**
1007 * <p>Handles scrolling in response to a "home/end" shortcut press. This
1008 * method will scroll the view to the top or bottom and give the focus
1009 * to the topmost/bottommost component in the new visible area. If no
1010 * component is a good candidate for focus, this scrollview reclaims the
1011 * focus.</p>
1012 *
1013 * @param direction the scroll direction: {@link android.view.View#FOCUS_UP}
1014 * to go the top of the view or
1015 * {@link android.view.View#FOCUS_DOWN} to go the bottom
1016 * @return true if the key event is consumed by this method, false otherwise
1017 */
1018 public boolean fullScroll(int direction) {
1019 boolean down = direction == View.FOCUS_DOWN;
1020 int height = getHeight();
1021
1022 mTempRect.top = 0;
1023 mTempRect.bottom = height;
1024
1025 if (down) {
1026 int count = getChildCount();
1027 if (count > 0) {
1028 View view = getChildAt(count - 1);
Mattias Petersson5435a062011-04-07 15:46:56 +02001029 mTempRect.bottom = view.getBottom() + mPaddingBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001030 mTempRect.top = mTempRect.bottom - height;
1031 }
1032 }
1033
1034 return scrollAndFocus(direction, mTempRect.top, mTempRect.bottom);
1035 }
1036
1037 /**
1038 * <p>Scrolls the view to make the area defined by <code>top</code> and
1039 * <code>bottom</code> visible. This method attempts to give the focus
1040 * to a component visible in this area. If no component can be focused in
Gilles Debunne2ed2eac2011-02-24 16:29:48 -08001041 * the new visible area, the focus is reclaimed by this ScrollView.</p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001042 *
1043 * @param direction the scroll direction: {@link android.view.View#FOCUS_UP}
Gilles Debunne2ed2eac2011-02-24 16:29:48 -08001044 * to go upward, {@link android.view.View#FOCUS_DOWN} to downward
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001045 * @param top the top offset of the new area to be made visible
1046 * @param bottom the bottom offset of the new area to be made visible
1047 * @return true if the key event is consumed by this method, false otherwise
1048 */
1049 private boolean scrollAndFocus(int direction, int top, int bottom) {
1050 boolean handled = true;
1051
1052 int height = getHeight();
1053 int containerTop = getScrollY();
1054 int containerBottom = containerTop + height;
1055 boolean up = direction == View.FOCUS_UP;
1056
1057 View newFocused = findFocusableViewInBounds(up, top, bottom);
1058 if (newFocused == null) {
1059 newFocused = this;
1060 }
1061
1062 if (top >= containerTop && bottom <= containerBottom) {
1063 handled = false;
1064 } else {
1065 int delta = up ? (top - containerTop) : (bottom - containerBottom);
1066 doScrollY(delta);
1067 }
1068
Gilles Debunne2ed2eac2011-02-24 16:29:48 -08001069 if (newFocused != findFocus()) newFocused.requestFocus(direction);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001070
1071 return handled;
1072 }
1073
1074 /**
1075 * Handle scrolling in response to an up or down arrow click.
1076 *
1077 * @param direction The direction corresponding to the arrow key that was
1078 * pressed
1079 * @return True if we consumed the event, false otherwise
1080 */
1081 public boolean arrowScroll(int direction) {
1082
1083 View currentFocused = findFocus();
1084 if (currentFocused == this) currentFocused = null;
1085
1086 View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, direction);
1087
1088 final int maxJump = getMaxScrollAmount();
1089
Jack Veenstra7d4200d2009-09-21 19:45:14 -07001090 if (nextFocused != null && isWithinDeltaOfScreen(nextFocused, maxJump, getHeight())) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001091 nextFocused.getDrawingRect(mTempRect);
1092 offsetDescendantRectToMyCoords(nextFocused, mTempRect);
1093 int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
1094 doScrollY(scrollDelta);
1095 nextFocused.requestFocus(direction);
1096 } else {
1097 // no new focus
1098 int scrollDelta = maxJump;
1099
1100 if (direction == View.FOCUS_UP && getScrollY() < scrollDelta) {
1101 scrollDelta = getScrollY();
1102 } else if (direction == View.FOCUS_DOWN) {
Romain Guyef0e9ae2009-07-10 14:11:26 -07001103 if (getChildCount() > 0) {
1104 int daBottom = getChildAt(0).getBottom();
Mattias Petersson5435a062011-04-07 15:46:56 +02001105 int screenBottom = getScrollY() + getHeight() - mPaddingBottom;
Romain Guyef0e9ae2009-07-10 14:11:26 -07001106 if (daBottom - screenBottom < maxJump) {
1107 scrollDelta = daBottom - screenBottom;
1108 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001109 }
1110 }
1111 if (scrollDelta == 0) {
1112 return false;
1113 }
1114 doScrollY(direction == View.FOCUS_DOWN ? scrollDelta : -scrollDelta);
1115 }
1116
1117 if (currentFocused != null && currentFocused.isFocused()
1118 && isOffScreen(currentFocused)) {
1119 // previously focused item still has focus and is off screen, give
1120 // it up (take it back to ourselves)
1121 // (also, need to temporarily force FOCUS_BEFORE_DESCENDANTS so we are
1122 // sure to
1123 // get it)
1124 final int descendantFocusability = getDescendantFocusability(); // save
1125 setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
1126 requestFocus();
1127 setDescendantFocusability(descendantFocusability); // restore
1128 }
1129 return true;
1130 }
1131
1132 /**
1133 * @return whether the descendant of this scroll view is scrolled off
1134 * screen.
1135 */
1136 private boolean isOffScreen(View descendant) {
Jack Veenstra7d4200d2009-09-21 19:45:14 -07001137 return !isWithinDeltaOfScreen(descendant, 0, getHeight());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001138 }
1139
1140 /**
1141 * @return whether the descendant of this scroll view is within delta
1142 * pixels of being on the screen.
1143 */
Jack Veenstra7d4200d2009-09-21 19:45:14 -07001144 private boolean isWithinDeltaOfScreen(View descendant, int delta, int height) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001145 descendant.getDrawingRect(mTempRect);
1146 offsetDescendantRectToMyCoords(descendant, mTempRect);
1147
1148 return (mTempRect.bottom + delta) >= getScrollY()
Jack Veenstra7d4200d2009-09-21 19:45:14 -07001149 && (mTempRect.top - delta) <= (getScrollY() + height);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001150 }
1151
1152 /**
1153 * Smooth scroll by a Y delta
1154 *
1155 * @param delta the number of pixels to scroll by on the Y axis
1156 */
1157 private void doScrollY(int delta) {
1158 if (delta != 0) {
1159 if (mSmoothScrollingEnabled) {
1160 smoothScrollBy(0, delta);
1161 } else {
1162 scrollBy(0, delta);
1163 }
1164 }
1165 }
1166
1167 /**
1168 * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
1169 *
1170 * @param dx the number of pixels to scroll by on the X axis
1171 * @param dy the number of pixels to scroll by on the Y axis
1172 */
1173 public final void smoothScrollBy(int dx, int dy) {
Adam Powell3fc37372010-01-28 13:52:24 -08001174 if (getChildCount() == 0) {
1175 // Nothing to do.
1176 return;
1177 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001178 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
1179 if (duration > ANIMATED_SCROLL_GAP) {
Adam Powellf5446052010-01-28 17:24:56 -08001180 final int height = getHeight() - mPaddingBottom - mPaddingTop;
1181 final int bottom = getChildAt(0).getHeight();
1182 final int maxY = Math.max(0, bottom - height);
1183 final int scrollY = mScrollY;
1184 dy = Math.max(0, Math.min(scrollY + dy, maxY)) - scrollY;
1185
1186 mScroller.startScroll(mScrollX, scrollY, 0, dy);
Adam Powelldf3ae4f2012-04-10 18:55:22 -07001187 postInvalidateOnAnimation();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001188 } else {
1189 if (!mScroller.isFinished()) {
1190 mScroller.abortAnimation();
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -08001191 if (mFlingStrictSpan != null) {
1192 mFlingStrictSpan.finish();
1193 mFlingStrictSpan = null;
1194 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001195 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001196 scrollBy(dx, dy);
1197 }
1198 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
1199 }
1200
1201 /**
1202 * Like {@link #scrollTo}, but scroll smoothly instead of immediately.
1203 *
1204 * @param x the position where to scroll on the X axis
1205 * @param y the position where to scroll on the Y axis
1206 */
1207 public final void smoothScrollTo(int x, int y) {
1208 smoothScrollBy(x - mScrollX, y - mScrollY);
1209 }
1210
1211 /**
1212 * <p>The scroll range of a scroll view is the overall height of all of its
1213 * children.</p>
1214 */
1215 @Override
1216 protected int computeVerticalScrollRange() {
Adam Powella2f91012010-02-16 12:26:33 -08001217 final int count = getChildCount();
1218 final int contentHeight = getHeight() - mPaddingBottom - mPaddingTop;
Adam Powell0b8bb422010-02-08 14:30:45 -08001219 if (count == 0) {
Adam Powella2f91012010-02-16 12:26:33 -08001220 return contentHeight;
Adam Powell0b8bb422010-02-08 14:30:45 -08001221 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08001222
Adam Powell637d3372010-08-25 14:37:03 -07001223 int scrollRange = getChildAt(0).getBottom();
1224 final int scrollY = mScrollY;
1225 final int overscrollBottom = Math.max(0, scrollRange - contentHeight);
1226 if (scrollY < 0) {
1227 scrollRange -= scrollY;
1228 } else if (scrollY > overscrollBottom) {
1229 scrollRange += scrollY - overscrollBottom;
1230 }
1231
1232 return scrollRange;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001233 }
1234
Adam Powell0b8bb422010-02-08 14:30:45 -08001235 @Override
1236 protected int computeVerticalScrollOffset() {
1237 return Math.max(0, super.computeVerticalScrollOffset());
1238 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001239
1240 @Override
Filip Gruszczynskib6824bf2015-04-13 09:16:25 -07001241 protected void measureChild(View child, int parentWidthMeasureSpec,
1242 int parentHeightMeasureSpec) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001243 ViewGroup.LayoutParams lp = child.getLayoutParams();
1244
1245 int childWidthMeasureSpec;
1246 int childHeightMeasureSpec;
1247
1248 childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft
1249 + mPaddingRight, lp.width);
1250
Filip Gruszczynskib6824bf2015-04-13 09:16:25 -07001251 childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
1252 MeasureSpec.getSize(parentHeightMeasureSpec), MeasureSpec.UNSPECIFIED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001253
1254 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
1255 }
1256
1257 @Override
1258 protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
1259 int parentHeightMeasureSpec, int heightUsed) {
1260 final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
1261
1262 final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
1263 mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
1264 + widthUsed, lp.width);
1265 final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
Filip Gruszczynskib6824bf2015-04-13 09:16:25 -07001266 MeasureSpec.getSize(parentHeightMeasureSpec), MeasureSpec.UNSPECIFIED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001267
1268 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
1269 }
1270
1271 @Override
1272 public void computeScroll() {
1273 if (mScroller.computeScrollOffset()) {
1274 // This is called at drawing time by ViewGroup. We don't want to
1275 // re-show the scrollbars at this point, which scrollTo will do,
1276 // so we replicate most of scrollTo here.
1277 //
1278 // It's a little odd to call onScrollChanged from inside the drawing.
1279 //
1280 // It is, except when you remember that computeScroll() is used to
1281 // animate scrolling. So unless we want to defer the onScrollChanged()
1282 // until the end of the animated scrolling, we don't really have a
1283 // choice here.
1284 //
1285 // I agree. The alternative, which I think would be worse, is to post
1286 // something and tell the subclasses later. This is bad because there
1287 // will be a window where mScrollX/Y is different from what the app
1288 // thinks it is.
1289 //
1290 int oldX = mScrollX;
1291 int oldY = mScrollY;
1292 int x = mScroller.getCurrX();
1293 int y = mScroller.getCurrY();
Adam Powell17dfce12010-01-25 18:38:22 -08001294
Adam Powell637d3372010-08-25 14:37:03 -07001295 if (oldX != x || oldY != y) {
Fabrice Di Meglioe9dbef82011-09-12 16:36:46 -07001296 final int range = getScrollRange();
1297 final int overscrollMode = getOverScrollMode();
1298 final boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
1299 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
1300
1301 overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range,
Adam Powell637d3372010-08-25 14:37:03 -07001302 0, mOverflingDistance, false);
1303 onScrollChanged(mScrollX, mScrollY, oldX, oldY);
1304
Fabrice Di Meglioe9dbef82011-09-12 16:36:46 -07001305 if (canOverscroll) {
Adam Powell637d3372010-08-25 14:37:03 -07001306 if (y < 0 && oldY >= 0) {
1307 mEdgeGlowTop.onAbsorb((int) mScroller.getCurrVelocity());
1308 } else if (y > range && oldY <= range) {
1309 mEdgeGlowBottom.onAbsorb((int) mScroller.getCurrVelocity());
1310 }
Adam Powell9d32d242010-03-29 16:02:07 -07001311 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001312 }
Fabrice Di Meglioe9dbef82011-09-12 16:36:46 -07001313
Romain Guye979e622012-03-20 13:50:27 -07001314 if (!awakenScrollBars()) {
1315 // Keep on drawing until the animation has finished.
Adam Powelldf3ae4f2012-04-10 18:55:22 -07001316 postInvalidateOnAnimation();
Romain Guye979e622012-03-20 13:50:27 -07001317 }
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -08001318 } else {
1319 if (mFlingStrictSpan != null) {
1320 mFlingStrictSpan.finish();
1321 mFlingStrictSpan = null;
1322 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001323 }
1324 }
1325
1326 /**
1327 * Scrolls the view to the given child.
1328 *
1329 * @param child the View to scroll to
1330 */
1331 private void scrollToChild(View child) {
1332 child.getDrawingRect(mTempRect);
1333
1334 /* Offset from child's local coordinates to ScrollView coordinates */
1335 offsetDescendantRectToMyCoords(child, mTempRect);
1336
1337 int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
1338
1339 if (scrollDelta != 0) {
1340 scrollBy(0, scrollDelta);
1341 }
1342 }
1343
1344 /**
1345 * If rect is off screen, scroll just enough to get it (or at least the
1346 * first screen size chunk of it) on screen.
1347 *
1348 * @param rect The rectangle.
1349 * @param immediate True to scroll immediately without animation
1350 * @return true if scrolling was performed
1351 */
1352 private boolean scrollToChildRect(Rect rect, boolean immediate) {
1353 final int delta = computeScrollDeltaToGetChildRectOnScreen(rect);
1354 final boolean scroll = delta != 0;
1355 if (scroll) {
1356 if (immediate) {
1357 scrollBy(0, delta);
1358 } else {
1359 smoothScrollBy(0, delta);
1360 }
1361 }
1362 return scroll;
1363 }
1364
1365 /**
1366 * Compute the amount to scroll in the Y direction in order to get
1367 * a rectangle completely on the screen (or, if taller than the screen,
1368 * at least the first screen size chunk of it).
1369 *
1370 * @param rect The rect.
1371 * @return The scroll delta.
1372 */
1373 protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) {
Romain Guyef0e9ae2009-07-10 14:11:26 -07001374 if (getChildCount() == 0) return 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001375
1376 int height = getHeight();
1377 int screenTop = getScrollY();
1378 int screenBottom = screenTop + height;
1379
1380 int fadingEdge = getVerticalFadingEdgeLength();
1381
1382 // leave room for top fading edge as long as rect isn't at very top
1383 if (rect.top > 0) {
1384 screenTop += fadingEdge;
1385 }
1386
1387 // leave room for bottom fading edge as long as rect isn't at very bottom
1388 if (rect.bottom < getChildAt(0).getHeight()) {
1389 screenBottom -= fadingEdge;
1390 }
1391
1392 int scrollYDelta = 0;
1393
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001394 if (rect.bottom > screenBottom && rect.top > screenTop) {
1395 // need to move down to get it in view: move down just enough so
1396 // that the entire rectangle is in view (or at least the first
1397 // screen size chunk).
1398
1399 if (rect.height() > height) {
1400 // just enough to get screen size chunk on
1401 scrollYDelta += (rect.top - screenTop);
1402 } else {
1403 // get entire rect at bottom of screen
1404 scrollYDelta += (rect.bottom - screenBottom);
1405 }
1406
1407 // make sure we aren't scrolling beyond the end of our content
Romain Guyef0e9ae2009-07-10 14:11:26 -07001408 int bottom = getChildAt(0).getBottom();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001409 int distanceToBottom = bottom - screenBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001410 scrollYDelta = Math.min(scrollYDelta, distanceToBottom);
1411
1412 } else if (rect.top < screenTop && rect.bottom < screenBottom) {
1413 // need to move up to get it in view: move up just enough so that
1414 // entire rectangle is in view (or at least the first screen
1415 // size chunk of it).
1416
1417 if (rect.height() > height) {
1418 // screen size chunk
1419 scrollYDelta -= (screenBottom - rect.bottom);
1420 } else {
1421 // entire rect at top
1422 scrollYDelta -= (screenTop - rect.top);
1423 }
1424
1425 // make sure we aren't scrolling any further than the top our content
1426 scrollYDelta = Math.max(scrollYDelta, -getScrollY());
1427 }
1428 return scrollYDelta;
1429 }
1430
1431 @Override
1432 public void requestChildFocus(View child, View focused) {
Gilles Debunne2ed2eac2011-02-24 16:29:48 -08001433 if (!mIsLayoutDirty) {
1434 scrollToChild(focused);
1435 } else {
1436 // The child may not be laid out yet, we can't compute the scroll yet
1437 mChildToScrollTo = focused;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001438 }
1439 super.requestChildFocus(child, focused);
1440 }
1441
1442
1443 /**
1444 * When looking for focus in children of a scroll view, need to be a little
1445 * more careful not to give focus to something that is scrolled off screen.
1446 *
1447 * This is more expensive than the default {@link android.view.ViewGroup}
1448 * implementation, otherwise this behavior might have been made the default.
1449 */
1450 @Override
1451 protected boolean onRequestFocusInDescendants(int direction,
1452 Rect previouslyFocusedRect) {
1453
1454 // convert from forward / backward notation to up / down / left / right
1455 // (ugh).
1456 if (direction == View.FOCUS_FORWARD) {
1457 direction = View.FOCUS_DOWN;
1458 } else if (direction == View.FOCUS_BACKWARD) {
1459 direction = View.FOCUS_UP;
1460 }
1461
1462 final View nextFocus = previouslyFocusedRect == null ?
1463 FocusFinder.getInstance().findNextFocus(this, null, direction) :
1464 FocusFinder.getInstance().findNextFocusFromRect(this,
1465 previouslyFocusedRect, direction);
1466
1467 if (nextFocus == null) {
1468 return false;
1469 }
1470
1471 if (isOffScreen(nextFocus)) {
1472 return false;
1473 }
1474
1475 return nextFocus.requestFocus(direction, previouslyFocusedRect);
Mindy Pereira4e30d892010-11-24 15:32:39 -08001476 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001477
1478 @Override
1479 public boolean requestChildRectangleOnScreen(View child, Rect rectangle,
1480 boolean immediate) {
1481 // offset into coordinate space of this scroll view
1482 rectangle.offset(child.getLeft() - child.getScrollX(),
1483 child.getTop() - child.getScrollY());
1484
1485 return scrollToChildRect(rectangle, immediate);
1486 }
1487
1488 @Override
1489 public void requestLayout() {
1490 mIsLayoutDirty = true;
1491 super.requestLayout();
1492 }
1493
1494 @Override
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -08001495 protected void onDetachedFromWindow() {
1496 super.onDetachedFromWindow();
1497
1498 if (mScrollStrictSpan != null) {
1499 mScrollStrictSpan.finish();
1500 mScrollStrictSpan = null;
1501 }
1502 if (mFlingStrictSpan != null) {
1503 mFlingStrictSpan.finish();
1504 mFlingStrictSpan = null;
1505 }
1506 }
1507
1508 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001509 protected void onLayout(boolean changed, int l, int t, int r, int b) {
1510 super.onLayout(changed, l, t, r, b);
1511 mIsLayoutDirty = false;
Mindy Pereira4e30d892010-11-24 15:32:39 -08001512 // Give a child focus if it needs it
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001513 if (mChildToScrollTo != null && isViewDescendantOf(mChildToScrollTo, this)) {
Romain Guy9c957372011-01-04 17:39:43 -08001514 scrollToChild(mChildToScrollTo);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001515 }
1516 mChildToScrollTo = null;
1517
Chet Haase7a46dde2013-07-17 10:22:53 -07001518 if (!isLaidOut()) {
Fabrice Di Megliod6befbd2013-06-12 16:01:48 -07001519 if (mSavedState != null) {
1520 mScrollY = mSavedState.scrollPosition;
1521 mSavedState = null;
1522 } // mScrollY default value is "0"
Fabrice Di Megliod6d54392013-06-18 14:09:07 -07001523
1524 final int childHeight = (getChildCount() > 0) ? getChildAt(0).getMeasuredHeight() : 0;
1525 final int scrollRange = Math.max(0,
1526 childHeight - (b - t - mPaddingBottom - mPaddingTop));
1527
Fabrice Di Megliod6befbd2013-06-12 16:01:48 -07001528 // Don't forget to clamp
1529 if (mScrollY > scrollRange) {
1530 mScrollY = scrollRange;
1531 } else if (mScrollY < 0) {
1532 mScrollY = 0;
1533 }
1534 }
1535
Ken Wakasaf76a50c2012-03-09 19:56:35 +09001536 // Calling this with the present values causes it to re-claim them
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001537 scrollTo(mScrollX, mScrollY);
1538 }
1539
1540 @Override
1541 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
1542 super.onSizeChanged(w, h, oldw, oldh);
1543
1544 View currentFocused = findFocus();
1545 if (null == currentFocused || this == currentFocused)
1546 return;
1547
Jack Veenstra7d4200d2009-09-21 19:45:14 -07001548 // If the currently-focused view was visible on the screen when the
1549 // screen was at the old height, then scroll the screen to make that
1550 // view visible with the new screen height.
1551 if (isWithinDeltaOfScreen(currentFocused, 0, oldh)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001552 currentFocused.getDrawingRect(mTempRect);
1553 offsetDescendantRectToMyCoords(currentFocused, mTempRect);
1554 int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
1555 doScrollY(scrollDelta);
1556 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08001557 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001558
1559 /**
Ken Wakasaf76a50c2012-03-09 19:56:35 +09001560 * Return true if child is a descendant of parent, (or equal to the parent).
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001561 */
Romain Guye979e622012-03-20 13:50:27 -07001562 private static boolean isViewDescendantOf(View child, View parent) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001563 if (child == parent) {
1564 return true;
1565 }
1566
1567 final ViewParent theParent = child.getParent();
1568 return (theParent instanceof ViewGroup) && isViewDescendantOf((View) theParent, parent);
Mindy Pereira4e30d892010-11-24 15:32:39 -08001569 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001570
1571 /**
1572 * Fling the scroll view
1573 *
1574 * @param velocityY The initial velocity in the Y direction. Positive
Gilles Debunne52964242010-02-24 11:05:19 -08001575 * numbers mean that the finger/cursor is moving down the screen,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001576 * which means we want to scroll towards the top.
1577 */
1578 public void fling(int velocityY) {
Romain Guyef0e9ae2009-07-10 14:11:26 -07001579 if (getChildCount() > 0) {
1580 int height = getHeight() - mPaddingBottom - mPaddingTop;
1581 int bottom = getChildAt(0).getHeight();
Mindy Pereira4e30d892010-11-24 15:32:39 -08001582
1583 mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0,
Adam Powell637d3372010-08-25 14:37:03 -07001584 Math.max(0, bottom - height), 0, height/2);
Mindy Pereira4e30d892010-11-24 15:32:39 -08001585
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -08001586 if (mFlingStrictSpan == null) {
1587 mFlingStrictSpan = StrictMode.enterCriticalSpan("ScrollView-fling");
1588 }
1589
Adam Powelldf3ae4f2012-04-10 18:55:22 -07001590 postInvalidateOnAnimation();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001591 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001592 }
1593
Adam Powell10ba2772014-04-15 09:46:51 -07001594 private void flingWithNestedDispatch(int velocityY) {
Adam Powellb36e4f92014-05-01 10:23:33 -07001595 final boolean canFling = (mScrollY > 0 || velocityY > 0) &&
1596 (mScrollY < getScrollRange() || velocityY < 0);
Adam Powell9413b242014-08-06 17:34:24 -07001597 if (!dispatchNestedPreFling(0, velocityY)) {
1598 dispatchNestedFling(0, velocityY, canFling);
1599 if (canFling) {
1600 fling(velocityY);
1601 }
Adam Powell10ba2772014-04-15 09:46:51 -07001602 }
1603 }
1604
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -08001605 private void endDrag() {
1606 mIsBeingDragged = false;
1607
Michael Jurka13451a42011-08-22 15:54:21 -07001608 recycleVelocityTracker();
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -08001609
1610 if (mEdgeGlowTop != null) {
1611 mEdgeGlowTop.onRelease();
1612 mEdgeGlowBottom.onRelease();
1613 }
1614
1615 if (mScrollStrictSpan != null) {
1616 mScrollStrictSpan.finish();
1617 mScrollStrictSpan = null;
1618 }
1619 }
1620
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001621 /**
1622 * {@inheritDoc}
1623 *
1624 * <p>This version also clamps the scrolling to the bounds of our child.
1625 */
Gilles Debunne52964242010-02-24 11:05:19 -08001626 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001627 public void scrollTo(int x, int y) {
1628 // we rely on the fact the View.scrollBy calls scrollTo.
1629 if (getChildCount() > 0) {
1630 View child = getChildAt(0);
1631 x = clamp(x, getWidth() - mPaddingRight - mPaddingLeft, child.getWidth());
1632 y = clamp(y, getHeight() - mPaddingBottom - mPaddingTop, child.getHeight());
1633 if (x != mScrollX || y != mScrollY) {
1634 super.scrollTo(x, y);
1635 }
1636 }
1637 }
1638
Adam Powell637d3372010-08-25 14:37:03 -07001639 @Override
1640 public void setOverScrollMode(int mode) {
1641 if (mode != OVER_SCROLL_NEVER) {
1642 if (mEdgeGlowTop == null) {
Mindy Pereira4e30d892010-11-24 15:32:39 -08001643 Context context = getContext();
Adam Powell89935e42011-08-31 14:26:12 -07001644 mEdgeGlowTop = new EdgeEffect(context);
1645 mEdgeGlowBottom = new EdgeEffect(context);
Adam Powell637d3372010-08-25 14:37:03 -07001646 }
1647 } else {
1648 mEdgeGlowTop = null;
1649 mEdgeGlowBottom = null;
1650 }
1651 super.setOverScrollMode(mode);
1652 }
1653
1654 @Override
Adam Powell10ba2772014-04-15 09:46:51 -07001655 public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
1656 return (nestedScrollAxes & SCROLL_AXIS_VERTICAL) != 0;
1657 }
1658
Adam Powellb36e4f92014-05-01 10:23:33 -07001659 @Override
1660 public void onNestedScrollAccepted(View child, View target, int axes) {
1661 super.onNestedScrollAccepted(child, target, axes);
1662 startNestedScroll(SCROLL_AXIS_VERTICAL);
1663 }
1664
Adam Powell10ba2772014-04-15 09:46:51 -07001665 /**
1666 * @inheritDoc
1667 */
1668 @Override
1669 public void onStopNestedScroll(View target) {
1670 super.onStopNestedScroll(target);
1671 }
1672
1673 @Override
1674 public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
1675 int dxUnconsumed, int dyUnconsumed) {
Adam Powellb36e4f92014-05-01 10:23:33 -07001676 final int oldScrollY = mScrollY;
Adam Powell10ba2772014-04-15 09:46:51 -07001677 scrollBy(0, dyUnconsumed);
Adam Powellb36e4f92014-05-01 10:23:33 -07001678 final int myConsumed = mScrollY - oldScrollY;
1679 final int myUnconsumed = dyUnconsumed - myConsumed;
1680 dispatchNestedScroll(0, myConsumed, 0, myUnconsumed, null);
Adam Powell10ba2772014-04-15 09:46:51 -07001681 }
1682
1683 /**
1684 * @inheritDoc
1685 */
1686 @Override
Adam Powellb36e4f92014-05-01 10:23:33 -07001687 public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
1688 if (!consumed) {
1689 flingWithNestedDispatch((int) velocityY);
1690 return true;
1691 }
1692 return false;
Adam Powell10ba2772014-04-15 09:46:51 -07001693 }
1694
1695 @Override
Adam Powell637d3372010-08-25 14:37:03 -07001696 public void draw(Canvas canvas) {
1697 super.draw(canvas);
1698 if (mEdgeGlowTop != null) {
1699 final int scrollY = mScrollY;
1700 if (!mEdgeGlowTop.isFinished()) {
1701 final int restoreCount = canvas.save();
Adam Powell7d863782011-02-15 15:05:03 -08001702 final int width = getWidth() - mPaddingLeft - mPaddingRight;
Adam Powell637d3372010-08-25 14:37:03 -07001703
Adam Powell7d863782011-02-15 15:05:03 -08001704 canvas.translate(mPaddingLeft, Math.min(0, scrollY));
Mindy Pereirab1297f72010-12-07 15:06:47 -08001705 mEdgeGlowTop.setSize(width, getHeight());
Adam Powell637d3372010-08-25 14:37:03 -07001706 if (mEdgeGlowTop.draw(canvas)) {
Adam Powelldf3ae4f2012-04-10 18:55:22 -07001707 postInvalidateOnAnimation();
Adam Powell637d3372010-08-25 14:37:03 -07001708 }
1709 canvas.restoreToCount(restoreCount);
1710 }
1711 if (!mEdgeGlowBottom.isFinished()) {
1712 final int restoreCount = canvas.save();
Adam Powell7d863782011-02-15 15:05:03 -08001713 final int width = getWidth() - mPaddingLeft - mPaddingRight;
Adam Powell637d3372010-08-25 14:37:03 -07001714 final int height = getHeight();
1715
Adam Powell7d863782011-02-15 15:05:03 -08001716 canvas.translate(-width + mPaddingLeft,
1717 Math.max(getScrollRange(), scrollY) + height);
Adam Powell637d3372010-08-25 14:37:03 -07001718 canvas.rotate(180, width, 0);
Mindy Pereirab1297f72010-12-07 15:06:47 -08001719 mEdgeGlowBottom.setSize(width, height);
Adam Powell637d3372010-08-25 14:37:03 -07001720 if (mEdgeGlowBottom.draw(canvas)) {
Adam Powelldf3ae4f2012-04-10 18:55:22 -07001721 postInvalidateOnAnimation();
Adam Powell637d3372010-08-25 14:37:03 -07001722 }
1723 canvas.restoreToCount(restoreCount);
1724 }
1725 }
1726 }
1727
Romain Guye979e622012-03-20 13:50:27 -07001728 private static int clamp(int n, int my, int child) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001729 if (my >= child || n < 0) {
1730 /* my >= child is this case:
1731 * |--------------- me ---------------|
1732 * |------ child ------|
1733 * or
1734 * |--------------- me ---------------|
1735 * |------ child ------|
1736 * or
1737 * |--------------- me ---------------|
1738 * |------ child ------|
1739 *
1740 * n < 0 is this case:
1741 * |------ me ------|
1742 * |-------- child --------|
1743 * |-- mScrollX --|
1744 */
1745 return 0;
1746 }
1747 if ((my+n) > child) {
1748 /* this case:
1749 * |------ me ------|
1750 * |------ child ------|
1751 * |-- mScrollX --|
1752 */
1753 return child-my;
1754 }
1755 return n;
1756 }
Fabrice Di Megliod6befbd2013-06-12 16:01:48 -07001757
1758 @Override
1759 protected void onRestoreInstanceState(Parcelable state) {
Adam Powell90f339a2013-06-13 17:44:04 -07001760 if (mContext.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR2) {
1761 // Some old apps reused IDs in ways they shouldn't have.
1762 // Don't break them, but they don't get scroll state restoration.
1763 super.onRestoreInstanceState(state);
1764 return;
1765 }
Fabrice Di Megliod6befbd2013-06-12 16:01:48 -07001766 SavedState ss = (SavedState) state;
1767 super.onRestoreInstanceState(ss.getSuperState());
1768 mSavedState = ss;
1769 requestLayout();
1770 }
1771
1772 @Override
1773 protected Parcelable onSaveInstanceState() {
Adam Powell90f339a2013-06-13 17:44:04 -07001774 if (mContext.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR2) {
1775 // Some old apps reused IDs in ways they shouldn't have.
1776 // Don't break them, but they don't get scroll state restoration.
1777 return super.onSaveInstanceState();
1778 }
Fabrice Di Megliod6befbd2013-06-12 16:01:48 -07001779 Parcelable superState = super.onSaveInstanceState();
1780 SavedState ss = new SavedState(superState);
1781 ss.scrollPosition = mScrollY;
1782 return ss;
1783 }
1784
1785 static class SavedState extends BaseSavedState {
1786 public int scrollPosition;
1787
1788 SavedState(Parcelable superState) {
1789 super(superState);
1790 }
1791
1792 public SavedState(Parcel source) {
1793 super(source);
1794 scrollPosition = source.readInt();
1795 }
1796
1797 @Override
1798 public void writeToParcel(Parcel dest, int flags) {
1799 super.writeToParcel(dest, flags);
1800 dest.writeInt(scrollPosition);
1801 }
1802
1803 @Override
1804 public String toString() {
1805 return "HorizontalScrollView.SavedState{"
1806 + Integer.toHexString(System.identityHashCode(this))
1807 + " scrollPosition=" + scrollPosition + "}";
1808 }
1809
1810 public static final Parcelable.Creator<SavedState> CREATOR
1811 = new Parcelable.Creator<SavedState>() {
1812 public SavedState createFromParcel(Parcel in) {
1813 return new SavedState(in);
1814 }
1815
1816 public SavedState[] newArray(int size) {
1817 return new SavedState[size];
1818 }
1819 };
1820 }
1821
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001822}