blob: b95c27d35208f46920cb0c156f07f7dcac46df41 [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
1241 protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
1242 ViewGroup.LayoutParams lp = child.getLayoutParams();
1243
1244 int childWidthMeasureSpec;
1245 int childHeightMeasureSpec;
1246
1247 childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft
1248 + mPaddingRight, lp.width);
1249
1250 childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
1251
1252 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
1253 }
1254
1255 @Override
1256 protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
1257 int parentHeightMeasureSpec, int heightUsed) {
1258 final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
1259
1260 final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
1261 mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
1262 + widthUsed, lp.width);
1263 final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
1264 lp.topMargin + lp.bottomMargin, MeasureSpec.UNSPECIFIED);
1265
1266 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
1267 }
1268
1269 @Override
1270 public void computeScroll() {
1271 if (mScroller.computeScrollOffset()) {
1272 // This is called at drawing time by ViewGroup. We don't want to
1273 // re-show the scrollbars at this point, which scrollTo will do,
1274 // so we replicate most of scrollTo here.
1275 //
1276 // It's a little odd to call onScrollChanged from inside the drawing.
1277 //
1278 // It is, except when you remember that computeScroll() is used to
1279 // animate scrolling. So unless we want to defer the onScrollChanged()
1280 // until the end of the animated scrolling, we don't really have a
1281 // choice here.
1282 //
1283 // I agree. The alternative, which I think would be worse, is to post
1284 // something and tell the subclasses later. This is bad because there
1285 // will be a window where mScrollX/Y is different from what the app
1286 // thinks it is.
1287 //
1288 int oldX = mScrollX;
1289 int oldY = mScrollY;
1290 int x = mScroller.getCurrX();
1291 int y = mScroller.getCurrY();
Adam Powell17dfce12010-01-25 18:38:22 -08001292
Adam Powell637d3372010-08-25 14:37:03 -07001293 if (oldX != x || oldY != y) {
Fabrice Di Meglioe9dbef82011-09-12 16:36:46 -07001294 final int range = getScrollRange();
1295 final int overscrollMode = getOverScrollMode();
1296 final boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
1297 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
1298
1299 overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range,
Adam Powell637d3372010-08-25 14:37:03 -07001300 0, mOverflingDistance, false);
1301 onScrollChanged(mScrollX, mScrollY, oldX, oldY);
1302
Fabrice Di Meglioe9dbef82011-09-12 16:36:46 -07001303 if (canOverscroll) {
Adam Powell637d3372010-08-25 14:37:03 -07001304 if (y < 0 && oldY >= 0) {
1305 mEdgeGlowTop.onAbsorb((int) mScroller.getCurrVelocity());
1306 } else if (y > range && oldY <= range) {
1307 mEdgeGlowBottom.onAbsorb((int) mScroller.getCurrVelocity());
1308 }
Adam Powell9d32d242010-03-29 16:02:07 -07001309 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001310 }
Fabrice Di Meglioe9dbef82011-09-12 16:36:46 -07001311
Romain Guye979e622012-03-20 13:50:27 -07001312 if (!awakenScrollBars()) {
1313 // Keep on drawing until the animation has finished.
Adam Powelldf3ae4f2012-04-10 18:55:22 -07001314 postInvalidateOnAnimation();
Romain Guye979e622012-03-20 13:50:27 -07001315 }
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -08001316 } else {
1317 if (mFlingStrictSpan != null) {
1318 mFlingStrictSpan.finish();
1319 mFlingStrictSpan = null;
1320 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001321 }
1322 }
1323
1324 /**
1325 * Scrolls the view to the given child.
1326 *
1327 * @param child the View to scroll to
1328 */
1329 private void scrollToChild(View child) {
1330 child.getDrawingRect(mTempRect);
1331
1332 /* Offset from child's local coordinates to ScrollView coordinates */
1333 offsetDescendantRectToMyCoords(child, mTempRect);
1334
1335 int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
1336
1337 if (scrollDelta != 0) {
1338 scrollBy(0, scrollDelta);
1339 }
1340 }
1341
1342 /**
1343 * If rect is off screen, scroll just enough to get it (or at least the
1344 * first screen size chunk of it) on screen.
1345 *
1346 * @param rect The rectangle.
1347 * @param immediate True to scroll immediately without animation
1348 * @return true if scrolling was performed
1349 */
1350 private boolean scrollToChildRect(Rect rect, boolean immediate) {
1351 final int delta = computeScrollDeltaToGetChildRectOnScreen(rect);
1352 final boolean scroll = delta != 0;
1353 if (scroll) {
1354 if (immediate) {
1355 scrollBy(0, delta);
1356 } else {
1357 smoothScrollBy(0, delta);
1358 }
1359 }
1360 return scroll;
1361 }
1362
1363 /**
1364 * Compute the amount to scroll in the Y direction in order to get
1365 * a rectangle completely on the screen (or, if taller than the screen,
1366 * at least the first screen size chunk of it).
1367 *
1368 * @param rect The rect.
1369 * @return The scroll delta.
1370 */
1371 protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) {
Romain Guyef0e9ae2009-07-10 14:11:26 -07001372 if (getChildCount() == 0) return 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001373
1374 int height = getHeight();
1375 int screenTop = getScrollY();
1376 int screenBottom = screenTop + height;
1377
1378 int fadingEdge = getVerticalFadingEdgeLength();
1379
1380 // leave room for top fading edge as long as rect isn't at very top
1381 if (rect.top > 0) {
1382 screenTop += fadingEdge;
1383 }
1384
1385 // leave room for bottom fading edge as long as rect isn't at very bottom
1386 if (rect.bottom < getChildAt(0).getHeight()) {
1387 screenBottom -= fadingEdge;
1388 }
1389
1390 int scrollYDelta = 0;
1391
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001392 if (rect.bottom > screenBottom && rect.top > screenTop) {
1393 // need to move down to get it in view: move down just enough so
1394 // that the entire rectangle is in view (or at least the first
1395 // screen size chunk).
1396
1397 if (rect.height() > height) {
1398 // just enough to get screen size chunk on
1399 scrollYDelta += (rect.top - screenTop);
1400 } else {
1401 // get entire rect at bottom of screen
1402 scrollYDelta += (rect.bottom - screenBottom);
1403 }
1404
1405 // make sure we aren't scrolling beyond the end of our content
Romain Guyef0e9ae2009-07-10 14:11:26 -07001406 int bottom = getChildAt(0).getBottom();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001407 int distanceToBottom = bottom - screenBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001408 scrollYDelta = Math.min(scrollYDelta, distanceToBottom);
1409
1410 } else if (rect.top < screenTop && rect.bottom < screenBottom) {
1411 // need to move up to get it in view: move up just enough so that
1412 // entire rectangle is in view (or at least the first screen
1413 // size chunk of it).
1414
1415 if (rect.height() > height) {
1416 // screen size chunk
1417 scrollYDelta -= (screenBottom - rect.bottom);
1418 } else {
1419 // entire rect at top
1420 scrollYDelta -= (screenTop - rect.top);
1421 }
1422
1423 // make sure we aren't scrolling any further than the top our content
1424 scrollYDelta = Math.max(scrollYDelta, -getScrollY());
1425 }
1426 return scrollYDelta;
1427 }
1428
1429 @Override
1430 public void requestChildFocus(View child, View focused) {
Gilles Debunne2ed2eac2011-02-24 16:29:48 -08001431 if (!mIsLayoutDirty) {
1432 scrollToChild(focused);
1433 } else {
1434 // The child may not be laid out yet, we can't compute the scroll yet
1435 mChildToScrollTo = focused;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001436 }
1437 super.requestChildFocus(child, focused);
1438 }
1439
1440
1441 /**
1442 * When looking for focus in children of a scroll view, need to be a little
1443 * more careful not to give focus to something that is scrolled off screen.
1444 *
1445 * This is more expensive than the default {@link android.view.ViewGroup}
1446 * implementation, otherwise this behavior might have been made the default.
1447 */
1448 @Override
1449 protected boolean onRequestFocusInDescendants(int direction,
1450 Rect previouslyFocusedRect) {
1451
1452 // convert from forward / backward notation to up / down / left / right
1453 // (ugh).
1454 if (direction == View.FOCUS_FORWARD) {
1455 direction = View.FOCUS_DOWN;
1456 } else if (direction == View.FOCUS_BACKWARD) {
1457 direction = View.FOCUS_UP;
1458 }
1459
1460 final View nextFocus = previouslyFocusedRect == null ?
1461 FocusFinder.getInstance().findNextFocus(this, null, direction) :
1462 FocusFinder.getInstance().findNextFocusFromRect(this,
1463 previouslyFocusedRect, direction);
1464
1465 if (nextFocus == null) {
1466 return false;
1467 }
1468
1469 if (isOffScreen(nextFocus)) {
1470 return false;
1471 }
1472
1473 return nextFocus.requestFocus(direction, previouslyFocusedRect);
Mindy Pereira4e30d892010-11-24 15:32:39 -08001474 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001475
1476 @Override
1477 public boolean requestChildRectangleOnScreen(View child, Rect rectangle,
1478 boolean immediate) {
1479 // offset into coordinate space of this scroll view
1480 rectangle.offset(child.getLeft() - child.getScrollX(),
1481 child.getTop() - child.getScrollY());
1482
1483 return scrollToChildRect(rectangle, immediate);
1484 }
1485
1486 @Override
1487 public void requestLayout() {
1488 mIsLayoutDirty = true;
1489 super.requestLayout();
1490 }
1491
1492 @Override
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -08001493 protected void onDetachedFromWindow() {
1494 super.onDetachedFromWindow();
1495
1496 if (mScrollStrictSpan != null) {
1497 mScrollStrictSpan.finish();
1498 mScrollStrictSpan = null;
1499 }
1500 if (mFlingStrictSpan != null) {
1501 mFlingStrictSpan.finish();
1502 mFlingStrictSpan = null;
1503 }
1504 }
1505
1506 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001507 protected void onLayout(boolean changed, int l, int t, int r, int b) {
1508 super.onLayout(changed, l, t, r, b);
1509 mIsLayoutDirty = false;
Mindy Pereira4e30d892010-11-24 15:32:39 -08001510 // Give a child focus if it needs it
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001511 if (mChildToScrollTo != null && isViewDescendantOf(mChildToScrollTo, this)) {
Romain Guy9c957372011-01-04 17:39:43 -08001512 scrollToChild(mChildToScrollTo);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001513 }
1514 mChildToScrollTo = null;
1515
Chet Haase7a46dde2013-07-17 10:22:53 -07001516 if (!isLaidOut()) {
Fabrice Di Megliod6befbd2013-06-12 16:01:48 -07001517 if (mSavedState != null) {
1518 mScrollY = mSavedState.scrollPosition;
1519 mSavedState = null;
1520 } // mScrollY default value is "0"
Fabrice Di Megliod6d54392013-06-18 14:09:07 -07001521
1522 final int childHeight = (getChildCount() > 0) ? getChildAt(0).getMeasuredHeight() : 0;
1523 final int scrollRange = Math.max(0,
1524 childHeight - (b - t - mPaddingBottom - mPaddingTop));
1525
Fabrice Di Megliod6befbd2013-06-12 16:01:48 -07001526 // Don't forget to clamp
1527 if (mScrollY > scrollRange) {
1528 mScrollY = scrollRange;
1529 } else if (mScrollY < 0) {
1530 mScrollY = 0;
1531 }
1532 }
1533
Ken Wakasaf76a50c2012-03-09 19:56:35 +09001534 // Calling this with the present values causes it to re-claim them
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001535 scrollTo(mScrollX, mScrollY);
1536 }
1537
1538 @Override
1539 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
1540 super.onSizeChanged(w, h, oldw, oldh);
1541
1542 View currentFocused = findFocus();
1543 if (null == currentFocused || this == currentFocused)
1544 return;
1545
Jack Veenstra7d4200d2009-09-21 19:45:14 -07001546 // If the currently-focused view was visible on the screen when the
1547 // screen was at the old height, then scroll the screen to make that
1548 // view visible with the new screen height.
1549 if (isWithinDeltaOfScreen(currentFocused, 0, oldh)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001550 currentFocused.getDrawingRect(mTempRect);
1551 offsetDescendantRectToMyCoords(currentFocused, mTempRect);
1552 int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
1553 doScrollY(scrollDelta);
1554 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08001555 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001556
1557 /**
Ken Wakasaf76a50c2012-03-09 19:56:35 +09001558 * Return true if child is a descendant of parent, (or equal to the parent).
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001559 */
Romain Guye979e622012-03-20 13:50:27 -07001560 private static boolean isViewDescendantOf(View child, View parent) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001561 if (child == parent) {
1562 return true;
1563 }
1564
1565 final ViewParent theParent = child.getParent();
1566 return (theParent instanceof ViewGroup) && isViewDescendantOf((View) theParent, parent);
Mindy Pereira4e30d892010-11-24 15:32:39 -08001567 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001568
1569 /**
1570 * Fling the scroll view
1571 *
1572 * @param velocityY The initial velocity in the Y direction. Positive
Gilles Debunne52964242010-02-24 11:05:19 -08001573 * numbers mean that the finger/cursor is moving down the screen,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001574 * which means we want to scroll towards the top.
1575 */
1576 public void fling(int velocityY) {
Romain Guyef0e9ae2009-07-10 14:11:26 -07001577 if (getChildCount() > 0) {
1578 int height = getHeight() - mPaddingBottom - mPaddingTop;
1579 int bottom = getChildAt(0).getHeight();
Mindy Pereira4e30d892010-11-24 15:32:39 -08001580
1581 mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0,
Adam Powell637d3372010-08-25 14:37:03 -07001582 Math.max(0, bottom - height), 0, height/2);
Mindy Pereira4e30d892010-11-24 15:32:39 -08001583
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -08001584 if (mFlingStrictSpan == null) {
1585 mFlingStrictSpan = StrictMode.enterCriticalSpan("ScrollView-fling");
1586 }
1587
Adam Powelldf3ae4f2012-04-10 18:55:22 -07001588 postInvalidateOnAnimation();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001589 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001590 }
1591
Adam Powell10ba2772014-04-15 09:46:51 -07001592 private void flingWithNestedDispatch(int velocityY) {
Adam Powellb36e4f92014-05-01 10:23:33 -07001593 final boolean canFling = (mScrollY > 0 || velocityY > 0) &&
1594 (mScrollY < getScrollRange() || velocityY < 0);
Adam Powell9413b242014-08-06 17:34:24 -07001595 if (!dispatchNestedPreFling(0, velocityY)) {
1596 dispatchNestedFling(0, velocityY, canFling);
1597 if (canFling) {
1598 fling(velocityY);
1599 }
Adam Powell10ba2772014-04-15 09:46:51 -07001600 }
1601 }
1602
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -08001603 private void endDrag() {
1604 mIsBeingDragged = false;
1605
Michael Jurka13451a42011-08-22 15:54:21 -07001606 recycleVelocityTracker();
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -08001607
1608 if (mEdgeGlowTop != null) {
1609 mEdgeGlowTop.onRelease();
1610 mEdgeGlowBottom.onRelease();
1611 }
1612
1613 if (mScrollStrictSpan != null) {
1614 mScrollStrictSpan.finish();
1615 mScrollStrictSpan = null;
1616 }
1617 }
1618
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001619 /**
1620 * {@inheritDoc}
1621 *
1622 * <p>This version also clamps the scrolling to the bounds of our child.
1623 */
Gilles Debunne52964242010-02-24 11:05:19 -08001624 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001625 public void scrollTo(int x, int y) {
1626 // we rely on the fact the View.scrollBy calls scrollTo.
1627 if (getChildCount() > 0) {
1628 View child = getChildAt(0);
1629 x = clamp(x, getWidth() - mPaddingRight - mPaddingLeft, child.getWidth());
1630 y = clamp(y, getHeight() - mPaddingBottom - mPaddingTop, child.getHeight());
1631 if (x != mScrollX || y != mScrollY) {
1632 super.scrollTo(x, y);
1633 }
1634 }
1635 }
1636
Adam Powell637d3372010-08-25 14:37:03 -07001637 @Override
1638 public void setOverScrollMode(int mode) {
1639 if (mode != OVER_SCROLL_NEVER) {
1640 if (mEdgeGlowTop == null) {
Mindy Pereira4e30d892010-11-24 15:32:39 -08001641 Context context = getContext();
Adam Powell89935e42011-08-31 14:26:12 -07001642 mEdgeGlowTop = new EdgeEffect(context);
1643 mEdgeGlowBottom = new EdgeEffect(context);
Adam Powell637d3372010-08-25 14:37:03 -07001644 }
1645 } else {
1646 mEdgeGlowTop = null;
1647 mEdgeGlowBottom = null;
1648 }
1649 super.setOverScrollMode(mode);
1650 }
1651
1652 @Override
Adam Powell10ba2772014-04-15 09:46:51 -07001653 public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
1654 return (nestedScrollAxes & SCROLL_AXIS_VERTICAL) != 0;
1655 }
1656
Adam Powellb36e4f92014-05-01 10:23:33 -07001657 @Override
1658 public void onNestedScrollAccepted(View child, View target, int axes) {
1659 super.onNestedScrollAccepted(child, target, axes);
1660 startNestedScroll(SCROLL_AXIS_VERTICAL);
1661 }
1662
Adam Powell10ba2772014-04-15 09:46:51 -07001663 /**
1664 * @inheritDoc
1665 */
1666 @Override
1667 public void onStopNestedScroll(View target) {
1668 super.onStopNestedScroll(target);
1669 }
1670
1671 @Override
1672 public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
1673 int dxUnconsumed, int dyUnconsumed) {
Adam Powellb36e4f92014-05-01 10:23:33 -07001674 final int oldScrollY = mScrollY;
Adam Powell10ba2772014-04-15 09:46:51 -07001675 scrollBy(0, dyUnconsumed);
Adam Powellb36e4f92014-05-01 10:23:33 -07001676 final int myConsumed = mScrollY - oldScrollY;
1677 final int myUnconsumed = dyUnconsumed - myConsumed;
1678 dispatchNestedScroll(0, myConsumed, 0, myUnconsumed, null);
Adam Powell10ba2772014-04-15 09:46:51 -07001679 }
1680
1681 /**
1682 * @inheritDoc
1683 */
1684 @Override
Adam Powellb36e4f92014-05-01 10:23:33 -07001685 public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
1686 if (!consumed) {
1687 flingWithNestedDispatch((int) velocityY);
1688 return true;
1689 }
1690 return false;
Adam Powell10ba2772014-04-15 09:46:51 -07001691 }
1692
1693 @Override
Adam Powell637d3372010-08-25 14:37:03 -07001694 public void draw(Canvas canvas) {
1695 super.draw(canvas);
1696 if (mEdgeGlowTop != null) {
1697 final int scrollY = mScrollY;
1698 if (!mEdgeGlowTop.isFinished()) {
1699 final int restoreCount = canvas.save();
Adam Powell7d863782011-02-15 15:05:03 -08001700 final int width = getWidth() - mPaddingLeft - mPaddingRight;
Adam Powell637d3372010-08-25 14:37:03 -07001701
Adam Powell7d863782011-02-15 15:05:03 -08001702 canvas.translate(mPaddingLeft, Math.min(0, scrollY));
Mindy Pereirab1297f72010-12-07 15:06:47 -08001703 mEdgeGlowTop.setSize(width, getHeight());
Adam Powell637d3372010-08-25 14:37:03 -07001704 if (mEdgeGlowTop.draw(canvas)) {
Adam Powelldf3ae4f2012-04-10 18:55:22 -07001705 postInvalidateOnAnimation();
Adam Powell637d3372010-08-25 14:37:03 -07001706 }
1707 canvas.restoreToCount(restoreCount);
1708 }
1709 if (!mEdgeGlowBottom.isFinished()) {
1710 final int restoreCount = canvas.save();
Adam Powell7d863782011-02-15 15:05:03 -08001711 final int width = getWidth() - mPaddingLeft - mPaddingRight;
Adam Powell637d3372010-08-25 14:37:03 -07001712 final int height = getHeight();
1713
Adam Powell7d863782011-02-15 15:05:03 -08001714 canvas.translate(-width + mPaddingLeft,
1715 Math.max(getScrollRange(), scrollY) + height);
Adam Powell637d3372010-08-25 14:37:03 -07001716 canvas.rotate(180, width, 0);
Mindy Pereirab1297f72010-12-07 15:06:47 -08001717 mEdgeGlowBottom.setSize(width, height);
Adam Powell637d3372010-08-25 14:37:03 -07001718 if (mEdgeGlowBottom.draw(canvas)) {
Adam Powelldf3ae4f2012-04-10 18:55:22 -07001719 postInvalidateOnAnimation();
Adam Powell637d3372010-08-25 14:37:03 -07001720 }
1721 canvas.restoreToCount(restoreCount);
1722 }
1723 }
1724 }
1725
Romain Guye979e622012-03-20 13:50:27 -07001726 private static int clamp(int n, int my, int child) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001727 if (my >= child || n < 0) {
1728 /* my >= child is this case:
1729 * |--------------- me ---------------|
1730 * |------ child ------|
1731 * or
1732 * |--------------- me ---------------|
1733 * |------ child ------|
1734 * or
1735 * |--------------- me ---------------|
1736 * |------ child ------|
1737 *
1738 * n < 0 is this case:
1739 * |------ me ------|
1740 * |-------- child --------|
1741 * |-- mScrollX --|
1742 */
1743 return 0;
1744 }
1745 if ((my+n) > child) {
1746 /* this case:
1747 * |------ me ------|
1748 * |------ child ------|
1749 * |-- mScrollX --|
1750 */
1751 return child-my;
1752 }
1753 return n;
1754 }
Fabrice Di Megliod6befbd2013-06-12 16:01:48 -07001755
1756 @Override
1757 protected void onRestoreInstanceState(Parcelable state) {
Adam Powell90f339a2013-06-13 17:44:04 -07001758 if (mContext.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR2) {
1759 // Some old apps reused IDs in ways they shouldn't have.
1760 // Don't break them, but they don't get scroll state restoration.
1761 super.onRestoreInstanceState(state);
1762 return;
1763 }
Fabrice Di Megliod6befbd2013-06-12 16:01:48 -07001764 SavedState ss = (SavedState) state;
1765 super.onRestoreInstanceState(ss.getSuperState());
1766 mSavedState = ss;
1767 requestLayout();
1768 }
1769
1770 @Override
1771 protected Parcelable onSaveInstanceState() {
Adam Powell90f339a2013-06-13 17:44:04 -07001772 if (mContext.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR2) {
1773 // Some old apps reused IDs in ways they shouldn't have.
1774 // Don't break them, but they don't get scroll state restoration.
1775 return super.onSaveInstanceState();
1776 }
Fabrice Di Megliod6befbd2013-06-12 16:01:48 -07001777 Parcelable superState = super.onSaveInstanceState();
1778 SavedState ss = new SavedState(superState);
1779 ss.scrollPosition = mScrollY;
1780 return ss;
1781 }
1782
1783 static class SavedState extends BaseSavedState {
1784 public int scrollPosition;
1785
1786 SavedState(Parcelable superState) {
1787 super(superState);
1788 }
1789
1790 public SavedState(Parcel source) {
1791 super(source);
1792 scrollPosition = source.readInt();
1793 }
1794
1795 @Override
1796 public void writeToParcel(Parcel dest, int flags) {
1797 super.writeToParcel(dest, flags);
1798 dest.writeInt(scrollPosition);
1799 }
1800
1801 @Override
1802 public String toString() {
1803 return "HorizontalScrollView.SavedState{"
1804 + Integer.toHexString(System.identityHashCode(this))
1805 + " scrollPosition=" + scrollPosition + "}";
1806 }
1807
1808 public static final Parcelable.Creator<SavedState> CREATOR
1809 = new Parcelable.Creator<SavedState>() {
1810 public SavedState createFromParcel(Parcel in) {
1811 return new SavedState(in);
1812 }
1813
1814 public SavedState[] newArray(int size) {
1815 return new SavedState[size];
1816 }
1817 };
1818 }
1819
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001820}