blob: 668039384546367d22872afa3fae46ce670eea0a [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 /**
141 * The StrictMode "critical time span" objects to catch animation
142 * stutters. Non-null when a time-sensitive animation is
143 * in-flight. Must call finish() on them when done animating.
144 * These are no-ops on user builds.
145 */
146 private StrictMode.Span mScrollStrictSpan = null; // aka "drag"
147 private StrictMode.Span mFlingStrictSpan = null;
148
Adam Powellbc4e7532010-02-23 14:49:01 -0800149 /**
150 * Sentinel value for no current active pointer.
151 * Used by {@link #mActivePointerId}.
152 */
153 private static final int INVALID_POINTER = -1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800154
Fabrice Di Megliod6befbd2013-06-12 16:01:48 -0700155 private SavedState mSavedState;
156
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800157 public ScrollView(Context context) {
158 this(context, null);
159 }
160
161 public ScrollView(Context context, AttributeSet attrs) {
162 this(context, attrs, com.android.internal.R.attr.scrollViewStyle);
163 }
164
165 public ScrollView(Context context, AttributeSet attrs, int defStyle) {
166 super(context, attrs, defStyle);
167 initScrollView();
168
169 TypedArray a =
170 context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.ScrollView, defStyle, 0);
171
172 setFillViewport(a.getBoolean(R.styleable.ScrollView_fillViewport, false));
173
174 a.recycle();
175 }
176
177 @Override
Patrick Dubroye0a799a2011-05-04 16:19:22 -0700178 public boolean shouldDelayChildPressedState() {
179 return true;
180 }
181
182 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800183 protected float getTopFadingEdgeStrength() {
184 if (getChildCount() == 0) {
185 return 0.0f;
186 }
187
188 final int length = getVerticalFadingEdgeLength();
189 if (mScrollY < length) {
190 return mScrollY / (float) length;
191 }
192
193 return 1.0f;
194 }
195
196 @Override
197 protected float getBottomFadingEdgeStrength() {
198 if (getChildCount() == 0) {
199 return 0.0f;
200 }
201
202 final int length = getVerticalFadingEdgeLength();
203 final int bottomEdge = getHeight() - mPaddingBottom;
204 final int span = getChildAt(0).getBottom() - mScrollY - bottomEdge;
205 if (span < length) {
206 return span / (float) length;
207 }
208
209 return 1.0f;
210 }
211
212 /**
213 * @return The maximum amount this scroll view will scroll in response to
214 * an arrow event.
215 */
216 public int getMaxScrollAmount() {
217 return (int) (MAX_SCROLL_FACTOR * (mBottom - mTop));
218 }
219
220
221 private void initScrollView() {
Adam Powell637d3372010-08-25 14:37:03 -0700222 mScroller = new OverScroller(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800223 setFocusable(true);
224 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
225 setWillNotDraw(false);
Romain Guy4296fc42009-07-06 11:48:52 -0700226 final ViewConfiguration configuration = ViewConfiguration.get(mContext);
227 mTouchSlop = configuration.getScaledTouchSlop();
228 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
229 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
Adam Powell637d3372010-08-25 14:37:03 -0700230 mOverscrollDistance = configuration.getScaledOverscrollDistance();
231 mOverflingDistance = configuration.getScaledOverflingDistance();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800232 }
233
234 @Override
235 public void addView(View child) {
236 if (getChildCount() > 0) {
237 throw new IllegalStateException("ScrollView can host only one direct child");
238 }
239
240 super.addView(child);
241 }
242
243 @Override
244 public void addView(View child, int index) {
245 if (getChildCount() > 0) {
246 throw new IllegalStateException("ScrollView can host only one direct child");
247 }
248
249 super.addView(child, index);
250 }
251
252 @Override
253 public void addView(View child, ViewGroup.LayoutParams params) {
254 if (getChildCount() > 0) {
255 throw new IllegalStateException("ScrollView can host only one direct child");
256 }
257
258 super.addView(child, params);
259 }
260
261 @Override
262 public void addView(View child, int index, ViewGroup.LayoutParams params) {
263 if (getChildCount() > 0) {
264 throw new IllegalStateException("ScrollView can host only one direct child");
265 }
266
267 super.addView(child, index, params);
268 }
269
270 /**
271 * @return Returns true this ScrollView can be scrolled
272 */
273 private boolean canScroll() {
274 View child = getChildAt(0);
275 if (child != null) {
276 int childHeight = child.getHeight();
277 return getHeight() < childHeight + mPaddingTop + mPaddingBottom;
278 }
279 return false;
280 }
281
282 /**
283 * Indicates whether this ScrollView's content is stretched to fill the viewport.
284 *
285 * @return True if the content fills the viewport, false otherwise.
Mindy Pereira4e30d892010-11-24 15:32:39 -0800286 *
Romain Guyfdbf4842010-08-16 10:55:49 -0700287 * @attr ref android.R.styleable#ScrollView_fillViewport
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800288 */
289 public boolean isFillViewport() {
290 return mFillViewport;
291 }
292
293 /**
294 * Indicates this ScrollView whether it should stretch its content height to fill
295 * the viewport or not.
296 *
297 * @param fillViewport True to stretch the content's height to the viewport's
298 * boundaries, false otherwise.
Mindy Pereira4e30d892010-11-24 15:32:39 -0800299 *
Romain Guyfdbf4842010-08-16 10:55:49 -0700300 * @attr ref android.R.styleable#ScrollView_fillViewport
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800301 */
302 public void setFillViewport(boolean fillViewport) {
303 if (fillViewport != mFillViewport) {
304 mFillViewport = fillViewport;
305 requestLayout();
306 }
307 }
308
309 /**
310 * @return Whether arrow scrolling will animate its transition.
311 */
312 public boolean isSmoothScrollingEnabled() {
313 return mSmoothScrollingEnabled;
314 }
315
316 /**
317 * Set whether arrow scrolling will animate its transition.
318 * @param smoothScrollingEnabled whether arrow scrolling will animate its transition
319 */
320 public void setSmoothScrollingEnabled(boolean smoothScrollingEnabled) {
321 mSmoothScrollingEnabled = smoothScrollingEnabled;
322 }
323
324 @Override
325 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
326 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
327
328 if (!mFillViewport) {
329 return;
330 }
331
332 final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
333 if (heightMode == MeasureSpec.UNSPECIFIED) {
334 return;
335 }
336
Romain Guyef0e9ae2009-07-10 14:11:26 -0700337 if (getChildCount() > 0) {
338 final View child = getChildAt(0);
339 int height = getMeasuredHeight();
340 if (child.getMeasuredHeight() < height) {
341 final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
Mindy Pereira4e30d892010-11-24 15:32:39 -0800342
Romain Guy9c957372011-01-04 17:39:43 -0800343 int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
344 mPaddingLeft + mPaddingRight, lp.width);
Romain Guyef0e9ae2009-07-10 14:11:26 -0700345 height -= mPaddingTop;
346 height -= mPaddingBottom;
347 int childHeightMeasureSpec =
348 MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
Mindy Pereira4e30d892010-11-24 15:32:39 -0800349
Romain Guyef0e9ae2009-07-10 14:11:26 -0700350 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
351 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800352 }
353 }
354
355 @Override
356 public boolean dispatchKeyEvent(KeyEvent event) {
357 // Let the focused view and/or our descendants get the key first
Romain Guy8e618e52010-03-08 12:18:20 -0800358 return super.dispatchKeyEvent(event) || executeKeyEvent(event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800359 }
360
361 /**
362 * You can call this function yourself to have the scroll view perform
363 * scrolling from a key event, just as if the event had been dispatched to
364 * it by the view hierarchy.
365 *
366 * @param event The key event to execute.
367 * @return Return true if the event was handled, else false.
368 */
369 public boolean executeKeyEvent(KeyEvent event) {
370 mTempRect.setEmpty();
371
372 if (!canScroll()) {
Romain Guy2d4cff62010-04-09 15:39:00 -0700373 if (isFocused() && event.getKeyCode() != KeyEvent.KEYCODE_BACK) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800374 View currentFocused = findFocus();
375 if (currentFocused == this) currentFocused = null;
376 View nextFocused = FocusFinder.getInstance().findNextFocus(this,
377 currentFocused, View.FOCUS_DOWN);
378 return nextFocused != null
379 && nextFocused != this
380 && nextFocused.requestFocus(View.FOCUS_DOWN);
381 }
382 return false;
383 }
384
385 boolean handled = false;
386 if (event.getAction() == KeyEvent.ACTION_DOWN) {
387 switch (event.getKeyCode()) {
388 case KeyEvent.KEYCODE_DPAD_UP:
389 if (!event.isAltPressed()) {
390 handled = arrowScroll(View.FOCUS_UP);
391 } else {
392 handled = fullScroll(View.FOCUS_UP);
393 }
394 break;
395 case KeyEvent.KEYCODE_DPAD_DOWN:
396 if (!event.isAltPressed()) {
397 handled = arrowScroll(View.FOCUS_DOWN);
398 } else {
399 handled = fullScroll(View.FOCUS_DOWN);
400 }
401 break;
402 case KeyEvent.KEYCODE_SPACE:
403 pageScroll(event.isShiftPressed() ? View.FOCUS_UP : View.FOCUS_DOWN);
404 break;
405 }
406 }
407
408 return handled;
409 }
410
Joe Onorato9f0e8ee2010-02-23 19:19:22 -0800411 private boolean inChild(int x, int y) {
412 if (getChildCount() > 0) {
Adam Powell352b9782010-03-24 14:23:43 -0700413 final int scrollY = mScrollY;
Joe Onorato9f0e8ee2010-02-23 19:19:22 -0800414 final View child = getChildAt(0);
Adam Powell352b9782010-03-24 14:23:43 -0700415 return !(y < child.getTop() - scrollY
416 || y >= child.getBottom() - scrollY
Joe Onorato9f0e8ee2010-02-23 19:19:22 -0800417 || x < child.getLeft()
418 || x >= child.getRight());
419 }
420 return false;
421 }
422
Michael Jurka13451a42011-08-22 15:54:21 -0700423 private void initOrResetVelocityTracker() {
424 if (mVelocityTracker == null) {
425 mVelocityTracker = VelocityTracker.obtain();
426 } else {
427 mVelocityTracker.clear();
428 }
429 }
430
431 private void initVelocityTrackerIfNotExists() {
432 if (mVelocityTracker == null) {
433 mVelocityTracker = VelocityTracker.obtain();
434 }
435 }
436
437 private void recycleVelocityTracker() {
438 if (mVelocityTracker != null) {
439 mVelocityTracker.recycle();
440 mVelocityTracker = null;
441 }
442 }
443
444 @Override
445 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
446 if (disallowIntercept) {
447 recycleVelocityTracker();
448 }
449 super.requestDisallowInterceptTouchEvent(disallowIntercept);
450 }
451
452
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800453 @Override
454 public boolean onInterceptTouchEvent(MotionEvent ev) {
455 /*
456 * This method JUST determines whether we want to intercept the motion.
457 * If we return true, onMotionEvent will be called and we do the actual
458 * scrolling there.
459 */
460
461 /*
462 * Shortcut the most recurring case: the user is in the dragging
463 * state and he is moving his finger. We want to intercept this
464 * motion.
465 */
466 final int action = ev.getAction();
467 if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
468 return true;
469 }
470
Adam Powell0278c2f2012-07-31 16:39:32 -0700471 /*
472 * Don't try to intercept touch if we can't scroll anyway.
473 */
474 if (getScrollY() == 0 && !canScrollVertically(1)) {
475 return false;
476 }
477
Adam Powellbc4e7532010-02-23 14:49:01 -0800478 switch (action & MotionEvent.ACTION_MASK) {
479 case MotionEvent.ACTION_MOVE: {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800480 /*
481 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
482 * whether the user has moved far enough from his original down touch.
483 */
484
485 /*
486 * Locally do absolute value. mLastMotionY is set to the y value
487 * of the down event.
488 */
Adam Powell9d0335b2010-03-24 13:42:51 -0700489 final int activePointerId = mActivePointerId;
490 if (activePointerId == INVALID_POINTER) {
491 // If we don't have a valid id, the touch down wasn't on content.
492 break;
493 }
494
495 final int pointerIndex = ev.findPointerIndex(activePointerId);
Johan Rosengren0dc291e2011-02-21 09:49:45 +0100496 if (pointerIndex == -1) {
497 Log.e(TAG, "Invalid pointerId=" + activePointerId
498 + " in onInterceptTouchEvent");
499 break;
500 }
501
Adam Powelldf3ae4f2012-04-10 18:55:22 -0700502 final int y = (int) ev.getY(pointerIndex);
503 final int yDiff = Math.abs(y - mLastMotionY);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800504 if (yDiff > mTouchSlop) {
505 mIsBeingDragged = true;
Romain Guyf7b4acc2009-12-01 16:24:45 -0800506 mLastMotionY = y;
Michael Jurka13451a42011-08-22 15:54:21 -0700507 initVelocityTrackerIfNotExists();
508 mVelocityTracker.addMovement(ev);
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -0800509 if (mScrollStrictSpan == null) {
510 mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll");
511 }
Adam Powellb3e02c42012-05-02 22:05:46 -0700512 final ViewParent parent = getParent();
513 if (parent != null) {
514 parent.requestDisallowInterceptTouchEvent(true);
515 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800516 }
517 break;
Adam Powellbc4e7532010-02-23 14:49:01 -0800518 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800519
Adam Powellbc4e7532010-02-23 14:49:01 -0800520 case MotionEvent.ACTION_DOWN: {
Adam Powelldf3ae4f2012-04-10 18:55:22 -0700521 final int y = (int) ev.getY();
Adam Powell4cd47702010-02-25 11:21:14 -0800522 if (!inChild((int) ev.getX(), (int) y)) {
Joe Onorato9f0e8ee2010-02-23 19:19:22 -0800523 mIsBeingDragged = false;
Michael Jurka13451a42011-08-22 15:54:21 -0700524 recycleVelocityTracker();
Joe Onorato9f0e8ee2010-02-23 19:19:22 -0800525 break;
526 }
527
Adam Powellbc4e7532010-02-23 14:49:01 -0800528 /*
529 * Remember location of down touch.
530 * ACTION_DOWN always refers to pointer index 0.
531 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800532 mLastMotionY = y;
Adam Powellbc4e7532010-02-23 14:49:01 -0800533 mActivePointerId = ev.getPointerId(0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800534
Michael Jurka13451a42011-08-22 15:54:21 -0700535 initOrResetVelocityTracker();
536 mVelocityTracker.addMovement(ev);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800537 /*
538 * If being flinged and user touches the screen, initiate drag;
539 * otherwise don't. mScroller.isFinished should be false when
540 * being flinged.
541 */
542 mIsBeingDragged = !mScroller.isFinished();
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -0800543 if (mIsBeingDragged && mScrollStrictSpan == null) {
544 mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll");
545 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800546 break;
Adam Powellbc4e7532010-02-23 14:49:01 -0800547 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800548
549 case MotionEvent.ACTION_CANCEL:
550 case MotionEvent.ACTION_UP:
551 /* Release the drag */
552 mIsBeingDragged = false;
Adam Powellbc4e7532010-02-23 14:49:01 -0800553 mActivePointerId = INVALID_POINTER;
Michael Jurka13451a42011-08-22 15:54:21 -0700554 recycleVelocityTracker();
Adam Powell637d3372010-08-25 14:37:03 -0700555 if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) {
Adam Powelldf3ae4f2012-04-10 18:55:22 -0700556 postInvalidateOnAnimation();
Adam Powell637d3372010-08-25 14:37:03 -0700557 }
Adam Powellbc4e7532010-02-23 14:49:01 -0800558 break;
559 case MotionEvent.ACTION_POINTER_UP:
560 onSecondaryPointerUp(ev);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800561 break;
562 }
563
564 /*
565 * The only time we want to intercept motion events is if we are in the
566 * drag mode.
567 */
568 return mIsBeingDragged;
569 }
570
571 @Override
572 public boolean onTouchEvent(MotionEvent ev) {
Michael Jurka13451a42011-08-22 15:54:21 -0700573 initVelocityTrackerIfNotExists();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800574 mVelocityTracker.addMovement(ev);
575
576 final int action = ev.getAction();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800577
Adam Powellbc4e7532010-02-23 14:49:01 -0800578 switch (action & MotionEvent.ACTION_MASK) {
579 case MotionEvent.ACTION_DOWN: {
Adam Powellb3e02c42012-05-02 22:05:46 -0700580 if (getChildCount() == 0) {
Jeff Brownfb757382011-01-18 18:42:33 -0800581 return false;
582 }
Adam Powellb3e02c42012-05-02 22:05:46 -0700583 if ((mIsBeingDragged = !mScroller.isFinished())) {
584 final ViewParent parent = getParent();
585 if (parent != null) {
586 parent.requestDisallowInterceptTouchEvent(true);
587 }
588 }
Mindy Pereira4e30d892010-11-24 15:32:39 -0800589
Adam Powell352b9782010-03-24 14:23:43 -0700590 /*
591 * If being flinged and user touches, stop the fling. isFinished
592 * will be false if being flinged.
593 */
594 if (!mScroller.isFinished()) {
595 mScroller.abortAnimation();
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -0800596 if (mFlingStrictSpan != null) {
597 mFlingStrictSpan.finish();
598 mFlingStrictSpan = null;
599 }
Adam Powell352b9782010-03-24 14:23:43 -0700600 }
601
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800602 // Remember where the motion event started
Adam Powelldf3ae4f2012-04-10 18:55:22 -0700603 mLastMotionY = (int) ev.getY();
Adam Powellbc4e7532010-02-23 14:49:01 -0800604 mActivePointerId = ev.getPointerId(0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800605 break;
Adam Powellbc4e7532010-02-23 14:49:01 -0800606 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800607 case MotionEvent.ACTION_MOVE:
Adam Powellb3e02c42012-05-02 22:05:46 -0700608 final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
Johan Rosengren0dc291e2011-02-21 09:49:45 +0100609 if (activePointerIndex == -1) {
610 Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
611 break;
612 }
613
Adam Powellb3e02c42012-05-02 22:05:46 -0700614 final int y = (int) ev.getY(activePointerIndex);
615 int deltaY = mLastMotionY - y;
616 if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) {
617 final ViewParent parent = getParent();
618 if (parent != null) {
619 parent.requestDisallowInterceptTouchEvent(true);
620 }
621 mIsBeingDragged = true;
622 if (deltaY > 0) {
623 deltaY -= mTouchSlop;
624 } else {
625 deltaY += mTouchSlop;
626 }
627 }
Joe Onorato9f0e8ee2010-02-23 19:19:22 -0800628 if (mIsBeingDragged) {
629 // Scroll to follow the motion event
Joe Onorato9f0e8ee2010-02-23 19:19:22 -0800630 mLastMotionY = y;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800631
Adam Powell637d3372010-08-25 14:37:03 -0700632 final int oldX = mScrollX;
633 final int oldY = mScrollY;
634 final int range = getScrollRange();
Fabrice Di Meglioe9dbef82011-09-12 16:36:46 -0700635 final int overscrollMode = getOverScrollMode();
636 final boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
637 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
638
Alan Viverettecb25bd82013-06-03 17:10:44 -0700639 // Calling overScrollBy will call onOverScrolled, which
640 // calls onScrollChanged if applicable.
Adam Powellf6a6c972011-09-28 23:30:20 -0700641 if (overScrollBy(0, deltaY, 0, mScrollY,
Fabrice Di Meglioe9dbef82011-09-12 16:36:46 -0700642 0, range, 0, mOverscrollDistance, true)) {
Adam Powell637d3372010-08-25 14:37:03 -0700643 // Break our velocity if we hit a scroll barrier.
644 mVelocityTracker.clear();
645 }
Adam Powell637d3372010-08-25 14:37:03 -0700646
Fabrice Di Meglioe9dbef82011-09-12 16:36:46 -0700647 if (canOverscroll) {
Adam Powell637d3372010-08-25 14:37:03 -0700648 final int pulledToY = oldY + deltaY;
649 if (pulledToY < 0) {
650 mEdgeGlowTop.onPull((float) deltaY / getHeight());
651 if (!mEdgeGlowBottom.isFinished()) {
652 mEdgeGlowBottom.onRelease();
653 }
654 } else if (pulledToY > range) {
655 mEdgeGlowBottom.onPull((float) deltaY / getHeight());
656 if (!mEdgeGlowTop.isFinished()) {
657 mEdgeGlowTop.onRelease();
658 }
659 }
660 if (mEdgeGlowTop != null
661 && (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished())) {
Adam Powelldf3ae4f2012-04-10 18:55:22 -0700662 postInvalidateOnAnimation();
Adam Powell637d3372010-08-25 14:37:03 -0700663 }
664 }
Joe Onorato9f0e8ee2010-02-23 19:19:22 -0800665 }
Adam Powellbc4e7532010-02-23 14:49:01 -0800666 break;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800667 case MotionEvent.ACTION_UP:
Joe Onorato9f0e8ee2010-02-23 19:19:22 -0800668 if (mIsBeingDragged) {
669 final VelocityTracker velocityTracker = mVelocityTracker;
670 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
Adam Powellbc4e7532010-02-23 14:49:01 -0800671 int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800672
Adam Powellf6a6c972011-09-28 23:30:20 -0700673 if (getChildCount() > 0) {
Adam Powell637d3372010-08-25 14:37:03 -0700674 if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
675 fling(-initialVelocity);
676 } else {
Adam Powellf6a6c972011-09-28 23:30:20 -0700677 if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0,
678 getScrollRange())) {
Adam Powelldf3ae4f2012-04-10 18:55:22 -0700679 postInvalidateOnAnimation();
Adam Powell637d3372010-08-25 14:37:03 -0700680 }
681 }
Adam Powell17dfce12010-01-25 18:38:22 -0800682 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800683
Adam Powellbc4e7532010-02-23 14:49:01 -0800684 mActivePointerId = INVALID_POINTER;
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -0800685 endDrag();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800686 }
Adam Powellbc4e7532010-02-23 14:49:01 -0800687 break;
Adam Powell352b9782010-03-24 14:23:43 -0700688 case MotionEvent.ACTION_CANCEL:
689 if (mIsBeingDragged && getChildCount() > 0) {
Adam Powell637d3372010-08-25 14:37:03 -0700690 if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) {
Adam Powelldf3ae4f2012-04-10 18:55:22 -0700691 postInvalidateOnAnimation();
Adam Powell637d3372010-08-25 14:37:03 -0700692 }
Adam Powell352b9782010-03-24 14:23:43 -0700693 mActivePointerId = INVALID_POINTER;
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -0800694 endDrag();
Adam Powell352b9782010-03-24 14:23:43 -0700695 }
696 break;
Adam Powell9bc30d32011-02-28 10:27:49 -0800697 case MotionEvent.ACTION_POINTER_DOWN: {
698 final int index = ev.getActionIndex();
Adam Powelldf3ae4f2012-04-10 18:55:22 -0700699 mLastMotionY = (int) ev.getY(index);
Adam Powell9bc30d32011-02-28 10:27:49 -0800700 mActivePointerId = ev.getPointerId(index);
701 break;
702 }
Adam Powellbc4e7532010-02-23 14:49:01 -0800703 case MotionEvent.ACTION_POINTER_UP:
704 onSecondaryPointerUp(ev);
Adam Powelldf3ae4f2012-04-10 18:55:22 -0700705 mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
Adam Powellbc4e7532010-02-23 14:49:01 -0800706 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800707 }
708 return true;
709 }
Mindy Pereira4e30d892010-11-24 15:32:39 -0800710
Adam Powellbc4e7532010-02-23 14:49:01 -0800711 private void onSecondaryPointerUp(MotionEvent ev) {
712 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
713 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
714 final int pointerId = ev.getPointerId(pointerIndex);
715 if (pointerId == mActivePointerId) {
716 // This was our active pointer going up. Choose a new
717 // active pointer and adjust accordingly.
718 // TODO: Make this decision more intelligent.
719 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
Adam Powelldf3ae4f2012-04-10 18:55:22 -0700720 mLastMotionY = (int) ev.getY(newPointerIndex);
Adam Powellbc4e7532010-02-23 14:49:01 -0800721 mActivePointerId = ev.getPointerId(newPointerIndex);
722 if (mVelocityTracker != null) {
723 mVelocityTracker.clear();
724 }
725 }
726 }
Mindy Pereira4e30d892010-11-24 15:32:39 -0800727
Adam Powell637d3372010-08-25 14:37:03 -0700728 @Override
Jeff Brown33bbfd22011-02-24 20:55:35 -0800729 public boolean onGenericMotionEvent(MotionEvent event) {
730 if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
731 switch (event.getAction()) {
732 case MotionEvent.ACTION_SCROLL: {
733 if (!mIsBeingDragged) {
734 final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
735 if (vscroll != 0) {
736 final int delta = (int) (vscroll * getVerticalScrollFactor());
737 final int range = getScrollRange();
738 int oldScrollY = mScrollY;
739 int newScrollY = oldScrollY - delta;
740 if (newScrollY < 0) {
741 newScrollY = 0;
742 } else if (newScrollY > range) {
743 newScrollY = range;
744 }
745 if (newScrollY != oldScrollY) {
746 super.scrollTo(mScrollX, newScrollY);
747 return true;
748 }
749 }
750 }
751 }
752 }
753 }
754 return super.onGenericMotionEvent(event);
755 }
756
757 @Override
Adam Powell637d3372010-08-25 14:37:03 -0700758 protected void onOverScrolled(int scrollX, int scrollY,
759 boolean clampedX, boolean clampedY) {
760 // Treat animating scrolls differently; see #computeScroll() for why.
761 if (!mScroller.isFinished()) {
Alan Viverettecb25bd82013-06-03 17:10:44 -0700762 final int oldX = mScrollX;
763 final int oldY = mScrollY;
Adam Powell637d3372010-08-25 14:37:03 -0700764 mScrollX = scrollX;
765 mScrollY = scrollY;
Romain Guy0fd89bf2011-01-26 15:41:30 -0800766 invalidateParentIfNeeded();
Alan Viverettecb25bd82013-06-03 17:10:44 -0700767 onScrollChanged(mScrollX, mScrollY, oldX, oldY);
Adam Powell637d3372010-08-25 14:37:03 -0700768 if (clampedY) {
769 mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange());
770 }
771 } else {
772 super.scrollTo(scrollX, scrollY);
773 }
Romain Guye979e622012-03-20 13:50:27 -0700774
Romain Guye72cf732012-03-20 14:23:09 -0700775 awakenScrollBars();
Adam Powell637d3372010-08-25 14:37:03 -0700776 }
777
Svetoslav Ganova0156172011-06-26 17:55:44 -0700778 @Override
Svetoslav Ganova1dc7612012-05-10 04:14:53 -0700779 public boolean performAccessibilityAction(int action, Bundle arguments) {
Svetoslav Ganov48d15862012-05-15 10:10:00 -0700780 if (super.performAccessibilityAction(action, arguments)) {
781 return true;
782 }
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -0700783 if (!isEnabled()) {
784 return false;
785 }
Svetoslav Ganova1dc7612012-05-10 04:14:53 -0700786 switch (action) {
787 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
788 final int viewportHeight = getHeight() - mPaddingBottom - mPaddingTop;
789 final int targetScrollY = Math.min(mScrollY + viewportHeight, getScrollRange());
790 if (targetScrollY != mScrollY) {
791 smoothScrollTo(0, targetScrollY);
792 return true;
793 }
794 } return false;
795 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
796 final int viewportHeight = getHeight() - mPaddingBottom - mPaddingTop;
797 final int targetScrollY = Math.max(mScrollY - viewportHeight, 0);
798 if (targetScrollY != mScrollY) {
799 smoothScrollTo(0, targetScrollY);
800 return true;
801 }
802 } return false;
803 }
Svetoslav Ganov48d15862012-05-15 10:10:00 -0700804 return false;
Svetoslav Ganova1dc7612012-05-10 04:14:53 -0700805 }
806
807 @Override
Svetoslav Ganova0156172011-06-26 17:55:44 -0700808 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
809 super.onInitializeAccessibilityNodeInfo(info);
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800810 info.setClassName(ScrollView.class.getName());
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -0700811 if (isEnabled()) {
812 final int scrollRange = getScrollRange();
813 if (scrollRange > 0) {
814 info.setScrollable(true);
815 if (mScrollY > 0) {
816 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
817 }
818 if (mScrollY < scrollRange) {
819 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
820 }
Svetoslav Ganova1dc7612012-05-10 04:14:53 -0700821 }
822 }
Svetoslav Ganova0156172011-06-26 17:55:44 -0700823 }
824
825 @Override
826 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
827 super.onInitializeAccessibilityEvent(event);
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800828 event.setClassName(ScrollView.class.getName());
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -0700829 final boolean scrollable = getScrollRange() > 0;
830 event.setScrollable(scrollable);
831 event.setScrollX(mScrollX);
832 event.setScrollY(mScrollY);
833 event.setMaxScrollX(mScrollX);
834 event.setMaxScrollY(getScrollRange());
Svetoslav Ganova0156172011-06-26 17:55:44 -0700835 }
836
Adam Powell637d3372010-08-25 14:37:03 -0700837 private int getScrollRange() {
838 int scrollRange = 0;
839 if (getChildCount() > 0) {
840 View child = getChildAt(0);
841 scrollRange = Math.max(0,
842 child.getHeight() - (getHeight() - mPaddingBottom - mPaddingTop));
843 }
844 return scrollRange;
845 }
846
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800847 /**
848 * <p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800849 * Finds the next focusable component that fits in the specified bounds.
850 * </p>
851 *
852 * @param topFocus look for a candidate is the one at the top of the bounds
853 * if topFocus is true, or at the bottom of the bounds if topFocus is
854 * false
855 * @param top the top offset of the bounds in which a focusable must be
856 * found
857 * @param bottom the bottom offset of the bounds in which a focusable must
858 * be found
859 * @return the next focusable component in the bounds or null if none can
860 * be found
861 */
862 private View findFocusableViewInBounds(boolean topFocus, int top, int bottom) {
863
864 List<View> focusables = getFocusables(View.FOCUS_FORWARD);
865 View focusCandidate = null;
866
867 /*
868 * A fully contained focusable is one where its top is below the bound's
869 * top, and its bottom is above the bound's bottom. A partially
870 * contained focusable is one where some part of it is within the
871 * bounds, but it also has some part that is not within bounds. A fully contained
872 * focusable is preferred to a partially contained focusable.
873 */
874 boolean foundFullyContainedFocusable = false;
875
876 int count = focusables.size();
877 for (int i = 0; i < count; i++) {
878 View view = focusables.get(i);
879 int viewTop = view.getTop();
880 int viewBottom = view.getBottom();
881
882 if (top < viewBottom && viewTop < bottom) {
883 /*
884 * the focusable is in the target area, it is a candidate for
885 * focusing
886 */
887
888 final boolean viewIsFullyContained = (top < viewTop) &&
889 (viewBottom < bottom);
890
891 if (focusCandidate == null) {
892 /* No candidate, take this one */
893 focusCandidate = view;
894 foundFullyContainedFocusable = viewIsFullyContained;
895 } else {
896 final boolean viewIsCloserToBoundary =
897 (topFocus && viewTop < focusCandidate.getTop()) ||
898 (!topFocus && viewBottom > focusCandidate
899 .getBottom());
900
901 if (foundFullyContainedFocusable) {
902 if (viewIsFullyContained && viewIsCloserToBoundary) {
903 /*
904 * We're dealing with only fully contained views, so
905 * it has to be closer to the boundary to beat our
906 * candidate
907 */
908 focusCandidate = view;
909 }
910 } else {
911 if (viewIsFullyContained) {
912 /* Any fully contained view beats a partially contained view */
913 focusCandidate = view;
914 foundFullyContainedFocusable = true;
915 } else if (viewIsCloserToBoundary) {
916 /*
917 * Partially contained view beats another partially
918 * contained view if it's closer
919 */
920 focusCandidate = view;
921 }
922 }
923 }
924 }
925 }
926
927 return focusCandidate;
928 }
929
930 /**
931 * <p>Handles scrolling in response to a "page up/down" shortcut press. This
932 * method will scroll the view by one page up or down and give the focus
933 * to the topmost/bottommost component in the new visible area. If no
934 * component is a good candidate for focus, this scrollview reclaims the
935 * focus.</p>
936 *
937 * @param direction the scroll direction: {@link android.view.View#FOCUS_UP}
938 * to go one page up or
939 * {@link android.view.View#FOCUS_DOWN} to go one page down
940 * @return true if the key event is consumed by this method, false otherwise
941 */
942 public boolean pageScroll(int direction) {
943 boolean down = direction == View.FOCUS_DOWN;
944 int height = getHeight();
945
946 if (down) {
947 mTempRect.top = getScrollY() + height;
948 int count = getChildCount();
949 if (count > 0) {
950 View view = getChildAt(count - 1);
951 if (mTempRect.top + height > view.getBottom()) {
952 mTempRect.top = view.getBottom() - height;
953 }
954 }
955 } else {
956 mTempRect.top = getScrollY() - height;
957 if (mTempRect.top < 0) {
958 mTempRect.top = 0;
959 }
960 }
961 mTempRect.bottom = mTempRect.top + height;
962
963 return scrollAndFocus(direction, mTempRect.top, mTempRect.bottom);
964 }
965
966 /**
967 * <p>Handles scrolling in response to a "home/end" shortcut press. This
968 * method will scroll the view to the top or bottom and give the focus
969 * to the topmost/bottommost component in the new visible area. If no
970 * component is a good candidate for focus, this scrollview reclaims the
971 * focus.</p>
972 *
973 * @param direction the scroll direction: {@link android.view.View#FOCUS_UP}
974 * to go the top of the view or
975 * {@link android.view.View#FOCUS_DOWN} to go the bottom
976 * @return true if the key event is consumed by this method, false otherwise
977 */
978 public boolean fullScroll(int direction) {
979 boolean down = direction == View.FOCUS_DOWN;
980 int height = getHeight();
981
982 mTempRect.top = 0;
983 mTempRect.bottom = height;
984
985 if (down) {
986 int count = getChildCount();
987 if (count > 0) {
988 View view = getChildAt(count - 1);
Mattias Petersson5435a062011-04-07 15:46:56 +0200989 mTempRect.bottom = view.getBottom() + mPaddingBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800990 mTempRect.top = mTempRect.bottom - height;
991 }
992 }
993
994 return scrollAndFocus(direction, mTempRect.top, mTempRect.bottom);
995 }
996
997 /**
998 * <p>Scrolls the view to make the area defined by <code>top</code> and
999 * <code>bottom</code> visible. This method attempts to give the focus
1000 * to a component visible in this area. If no component can be focused in
Gilles Debunne2ed2eac2011-02-24 16:29:48 -08001001 * the new visible area, the focus is reclaimed by this ScrollView.</p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001002 *
1003 * @param direction the scroll direction: {@link android.view.View#FOCUS_UP}
Gilles Debunne2ed2eac2011-02-24 16:29:48 -08001004 * to go upward, {@link android.view.View#FOCUS_DOWN} to downward
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001005 * @param top the top offset of the new area to be made visible
1006 * @param bottom the bottom offset of the new area to be made visible
1007 * @return true if the key event is consumed by this method, false otherwise
1008 */
1009 private boolean scrollAndFocus(int direction, int top, int bottom) {
1010 boolean handled = true;
1011
1012 int height = getHeight();
1013 int containerTop = getScrollY();
1014 int containerBottom = containerTop + height;
1015 boolean up = direction == View.FOCUS_UP;
1016
1017 View newFocused = findFocusableViewInBounds(up, top, bottom);
1018 if (newFocused == null) {
1019 newFocused = this;
1020 }
1021
1022 if (top >= containerTop && bottom <= containerBottom) {
1023 handled = false;
1024 } else {
1025 int delta = up ? (top - containerTop) : (bottom - containerBottom);
1026 doScrollY(delta);
1027 }
1028
Gilles Debunne2ed2eac2011-02-24 16:29:48 -08001029 if (newFocused != findFocus()) newFocused.requestFocus(direction);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001030
1031 return handled;
1032 }
1033
1034 /**
1035 * Handle scrolling in response to an up or down arrow click.
1036 *
1037 * @param direction The direction corresponding to the arrow key that was
1038 * pressed
1039 * @return True if we consumed the event, false otherwise
1040 */
1041 public boolean arrowScroll(int direction) {
1042
1043 View currentFocused = findFocus();
1044 if (currentFocused == this) currentFocused = null;
1045
1046 View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, direction);
1047
1048 final int maxJump = getMaxScrollAmount();
1049
Jack Veenstra7d4200d2009-09-21 19:45:14 -07001050 if (nextFocused != null && isWithinDeltaOfScreen(nextFocused, maxJump, getHeight())) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001051 nextFocused.getDrawingRect(mTempRect);
1052 offsetDescendantRectToMyCoords(nextFocused, mTempRect);
1053 int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
1054 doScrollY(scrollDelta);
1055 nextFocused.requestFocus(direction);
1056 } else {
1057 // no new focus
1058 int scrollDelta = maxJump;
1059
1060 if (direction == View.FOCUS_UP && getScrollY() < scrollDelta) {
1061 scrollDelta = getScrollY();
1062 } else if (direction == View.FOCUS_DOWN) {
Romain Guyef0e9ae2009-07-10 14:11:26 -07001063 if (getChildCount() > 0) {
1064 int daBottom = getChildAt(0).getBottom();
Mattias Petersson5435a062011-04-07 15:46:56 +02001065 int screenBottom = getScrollY() + getHeight() - mPaddingBottom;
Romain Guyef0e9ae2009-07-10 14:11:26 -07001066 if (daBottom - screenBottom < maxJump) {
1067 scrollDelta = daBottom - screenBottom;
1068 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001069 }
1070 }
1071 if (scrollDelta == 0) {
1072 return false;
1073 }
1074 doScrollY(direction == View.FOCUS_DOWN ? scrollDelta : -scrollDelta);
1075 }
1076
1077 if (currentFocused != null && currentFocused.isFocused()
1078 && isOffScreen(currentFocused)) {
1079 // previously focused item still has focus and is off screen, give
1080 // it up (take it back to ourselves)
1081 // (also, need to temporarily force FOCUS_BEFORE_DESCENDANTS so we are
1082 // sure to
1083 // get it)
1084 final int descendantFocusability = getDescendantFocusability(); // save
1085 setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
1086 requestFocus();
1087 setDescendantFocusability(descendantFocusability); // restore
1088 }
1089 return true;
1090 }
1091
1092 /**
1093 * @return whether the descendant of this scroll view is scrolled off
1094 * screen.
1095 */
1096 private boolean isOffScreen(View descendant) {
Jack Veenstra7d4200d2009-09-21 19:45:14 -07001097 return !isWithinDeltaOfScreen(descendant, 0, getHeight());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001098 }
1099
1100 /**
1101 * @return whether the descendant of this scroll view is within delta
1102 * pixels of being on the screen.
1103 */
Jack Veenstra7d4200d2009-09-21 19:45:14 -07001104 private boolean isWithinDeltaOfScreen(View descendant, int delta, int height) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001105 descendant.getDrawingRect(mTempRect);
1106 offsetDescendantRectToMyCoords(descendant, mTempRect);
1107
1108 return (mTempRect.bottom + delta) >= getScrollY()
Jack Veenstra7d4200d2009-09-21 19:45:14 -07001109 && (mTempRect.top - delta) <= (getScrollY() + height);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001110 }
1111
1112 /**
1113 * Smooth scroll by a Y delta
1114 *
1115 * @param delta the number of pixels to scroll by on the Y axis
1116 */
1117 private void doScrollY(int delta) {
1118 if (delta != 0) {
1119 if (mSmoothScrollingEnabled) {
1120 smoothScrollBy(0, delta);
1121 } else {
1122 scrollBy(0, delta);
1123 }
1124 }
1125 }
1126
1127 /**
1128 * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
1129 *
1130 * @param dx the number of pixels to scroll by on the X axis
1131 * @param dy the number of pixels to scroll by on the Y axis
1132 */
1133 public final void smoothScrollBy(int dx, int dy) {
Adam Powell3fc37372010-01-28 13:52:24 -08001134 if (getChildCount() == 0) {
1135 // Nothing to do.
1136 return;
1137 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001138 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
1139 if (duration > ANIMATED_SCROLL_GAP) {
Adam Powellf5446052010-01-28 17:24:56 -08001140 final int height = getHeight() - mPaddingBottom - mPaddingTop;
1141 final int bottom = getChildAt(0).getHeight();
1142 final int maxY = Math.max(0, bottom - height);
1143 final int scrollY = mScrollY;
1144 dy = Math.max(0, Math.min(scrollY + dy, maxY)) - scrollY;
1145
1146 mScroller.startScroll(mScrollX, scrollY, 0, dy);
Adam Powelldf3ae4f2012-04-10 18:55:22 -07001147 postInvalidateOnAnimation();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001148 } else {
1149 if (!mScroller.isFinished()) {
1150 mScroller.abortAnimation();
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -08001151 if (mFlingStrictSpan != null) {
1152 mFlingStrictSpan.finish();
1153 mFlingStrictSpan = null;
1154 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001155 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001156 scrollBy(dx, dy);
1157 }
1158 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
1159 }
1160
1161 /**
1162 * Like {@link #scrollTo}, but scroll smoothly instead of immediately.
1163 *
1164 * @param x the position where to scroll on the X axis
1165 * @param y the position where to scroll on the Y axis
1166 */
1167 public final void smoothScrollTo(int x, int y) {
1168 smoothScrollBy(x - mScrollX, y - mScrollY);
1169 }
1170
1171 /**
1172 * <p>The scroll range of a scroll view is the overall height of all of its
1173 * children.</p>
1174 */
1175 @Override
1176 protected int computeVerticalScrollRange() {
Adam Powella2f91012010-02-16 12:26:33 -08001177 final int count = getChildCount();
1178 final int contentHeight = getHeight() - mPaddingBottom - mPaddingTop;
Adam Powell0b8bb422010-02-08 14:30:45 -08001179 if (count == 0) {
Adam Powella2f91012010-02-16 12:26:33 -08001180 return contentHeight;
Adam Powell0b8bb422010-02-08 14:30:45 -08001181 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08001182
Adam Powell637d3372010-08-25 14:37:03 -07001183 int scrollRange = getChildAt(0).getBottom();
1184 final int scrollY = mScrollY;
1185 final int overscrollBottom = Math.max(0, scrollRange - contentHeight);
1186 if (scrollY < 0) {
1187 scrollRange -= scrollY;
1188 } else if (scrollY > overscrollBottom) {
1189 scrollRange += scrollY - overscrollBottom;
1190 }
1191
1192 return scrollRange;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001193 }
1194
Adam Powell0b8bb422010-02-08 14:30:45 -08001195 @Override
1196 protected int computeVerticalScrollOffset() {
1197 return Math.max(0, super.computeVerticalScrollOffset());
1198 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001199
1200 @Override
1201 protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
1202 ViewGroup.LayoutParams lp = child.getLayoutParams();
1203
1204 int childWidthMeasureSpec;
1205 int childHeightMeasureSpec;
1206
1207 childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft
1208 + mPaddingRight, lp.width);
1209
1210 childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
1211
1212 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
1213 }
1214
1215 @Override
1216 protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
1217 int parentHeightMeasureSpec, int heightUsed) {
1218 final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
1219
1220 final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
1221 mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
1222 + widthUsed, lp.width);
1223 final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
1224 lp.topMargin + lp.bottomMargin, MeasureSpec.UNSPECIFIED);
1225
1226 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
1227 }
1228
1229 @Override
1230 public void computeScroll() {
1231 if (mScroller.computeScrollOffset()) {
1232 // This is called at drawing time by ViewGroup. We don't want to
1233 // re-show the scrollbars at this point, which scrollTo will do,
1234 // so we replicate most of scrollTo here.
1235 //
1236 // It's a little odd to call onScrollChanged from inside the drawing.
1237 //
1238 // It is, except when you remember that computeScroll() is used to
1239 // animate scrolling. So unless we want to defer the onScrollChanged()
1240 // until the end of the animated scrolling, we don't really have a
1241 // choice here.
1242 //
1243 // I agree. The alternative, which I think would be worse, is to post
1244 // something and tell the subclasses later. This is bad because there
1245 // will be a window where mScrollX/Y is different from what the app
1246 // thinks it is.
1247 //
1248 int oldX = mScrollX;
1249 int oldY = mScrollY;
1250 int x = mScroller.getCurrX();
1251 int y = mScroller.getCurrY();
Adam Powell17dfce12010-01-25 18:38:22 -08001252
Adam Powell637d3372010-08-25 14:37:03 -07001253 if (oldX != x || oldY != y) {
Fabrice Di Meglioe9dbef82011-09-12 16:36:46 -07001254 final int range = getScrollRange();
1255 final int overscrollMode = getOverScrollMode();
1256 final boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
1257 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
1258
1259 overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range,
Adam Powell637d3372010-08-25 14:37:03 -07001260 0, mOverflingDistance, false);
1261 onScrollChanged(mScrollX, mScrollY, oldX, oldY);
1262
Fabrice Di Meglioe9dbef82011-09-12 16:36:46 -07001263 if (canOverscroll) {
Adam Powell637d3372010-08-25 14:37:03 -07001264 if (y < 0 && oldY >= 0) {
1265 mEdgeGlowTop.onAbsorb((int) mScroller.getCurrVelocity());
1266 } else if (y > range && oldY <= range) {
1267 mEdgeGlowBottom.onAbsorb((int) mScroller.getCurrVelocity());
1268 }
Adam Powell9d32d242010-03-29 16:02:07 -07001269 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001270 }
Fabrice Di Meglioe9dbef82011-09-12 16:36:46 -07001271
Romain Guye979e622012-03-20 13:50:27 -07001272 if (!awakenScrollBars()) {
1273 // Keep on drawing until the animation has finished.
Adam Powelldf3ae4f2012-04-10 18:55:22 -07001274 postInvalidateOnAnimation();
Romain Guye979e622012-03-20 13:50:27 -07001275 }
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -08001276 } else {
1277 if (mFlingStrictSpan != null) {
1278 mFlingStrictSpan.finish();
1279 mFlingStrictSpan = null;
1280 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001281 }
1282 }
1283
1284 /**
1285 * Scrolls the view to the given child.
1286 *
1287 * @param child the View to scroll to
1288 */
1289 private void scrollToChild(View child) {
1290 child.getDrawingRect(mTempRect);
1291
1292 /* Offset from child's local coordinates to ScrollView coordinates */
1293 offsetDescendantRectToMyCoords(child, mTempRect);
1294
1295 int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
1296
1297 if (scrollDelta != 0) {
1298 scrollBy(0, scrollDelta);
1299 }
1300 }
1301
1302 /**
1303 * If rect is off screen, scroll just enough to get it (or at least the
1304 * first screen size chunk of it) on screen.
1305 *
1306 * @param rect The rectangle.
1307 * @param immediate True to scroll immediately without animation
1308 * @return true if scrolling was performed
1309 */
1310 private boolean scrollToChildRect(Rect rect, boolean immediate) {
1311 final int delta = computeScrollDeltaToGetChildRectOnScreen(rect);
1312 final boolean scroll = delta != 0;
1313 if (scroll) {
1314 if (immediate) {
1315 scrollBy(0, delta);
1316 } else {
1317 smoothScrollBy(0, delta);
1318 }
1319 }
1320 return scroll;
1321 }
1322
1323 /**
1324 * Compute the amount to scroll in the Y direction in order to get
1325 * a rectangle completely on the screen (or, if taller than the screen,
1326 * at least the first screen size chunk of it).
1327 *
1328 * @param rect The rect.
1329 * @return The scroll delta.
1330 */
1331 protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) {
Romain Guyef0e9ae2009-07-10 14:11:26 -07001332 if (getChildCount() == 0) return 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001333
1334 int height = getHeight();
1335 int screenTop = getScrollY();
1336 int screenBottom = screenTop + height;
1337
1338 int fadingEdge = getVerticalFadingEdgeLength();
1339
1340 // leave room for top fading edge as long as rect isn't at very top
1341 if (rect.top > 0) {
1342 screenTop += fadingEdge;
1343 }
1344
1345 // leave room for bottom fading edge as long as rect isn't at very bottom
1346 if (rect.bottom < getChildAt(0).getHeight()) {
1347 screenBottom -= fadingEdge;
1348 }
1349
1350 int scrollYDelta = 0;
1351
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001352 if (rect.bottom > screenBottom && rect.top > screenTop) {
1353 // need to move down to get it in view: move down just enough so
1354 // that the entire rectangle is in view (or at least the first
1355 // screen size chunk).
1356
1357 if (rect.height() > height) {
1358 // just enough to get screen size chunk on
1359 scrollYDelta += (rect.top - screenTop);
1360 } else {
1361 // get entire rect at bottom of screen
1362 scrollYDelta += (rect.bottom - screenBottom);
1363 }
1364
1365 // make sure we aren't scrolling beyond the end of our content
Romain Guyef0e9ae2009-07-10 14:11:26 -07001366 int bottom = getChildAt(0).getBottom();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001367 int distanceToBottom = bottom - screenBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001368 scrollYDelta = Math.min(scrollYDelta, distanceToBottom);
1369
1370 } else if (rect.top < screenTop && rect.bottom < screenBottom) {
1371 // need to move up to get it in view: move up just enough so that
1372 // entire rectangle is in view (or at least the first screen
1373 // size chunk of it).
1374
1375 if (rect.height() > height) {
1376 // screen size chunk
1377 scrollYDelta -= (screenBottom - rect.bottom);
1378 } else {
1379 // entire rect at top
1380 scrollYDelta -= (screenTop - rect.top);
1381 }
1382
1383 // make sure we aren't scrolling any further than the top our content
1384 scrollYDelta = Math.max(scrollYDelta, -getScrollY());
1385 }
1386 return scrollYDelta;
1387 }
1388
1389 @Override
1390 public void requestChildFocus(View child, View focused) {
Gilles Debunne2ed2eac2011-02-24 16:29:48 -08001391 if (!mIsLayoutDirty) {
1392 scrollToChild(focused);
1393 } else {
1394 // The child may not be laid out yet, we can't compute the scroll yet
1395 mChildToScrollTo = focused;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001396 }
1397 super.requestChildFocus(child, focused);
1398 }
1399
1400
1401 /**
1402 * When looking for focus in children of a scroll view, need to be a little
1403 * more careful not to give focus to something that is scrolled off screen.
1404 *
1405 * This is more expensive than the default {@link android.view.ViewGroup}
1406 * implementation, otherwise this behavior might have been made the default.
1407 */
1408 @Override
1409 protected boolean onRequestFocusInDescendants(int direction,
1410 Rect previouslyFocusedRect) {
1411
1412 // convert from forward / backward notation to up / down / left / right
1413 // (ugh).
1414 if (direction == View.FOCUS_FORWARD) {
1415 direction = View.FOCUS_DOWN;
1416 } else if (direction == View.FOCUS_BACKWARD) {
1417 direction = View.FOCUS_UP;
1418 }
1419
1420 final View nextFocus = previouslyFocusedRect == null ?
1421 FocusFinder.getInstance().findNextFocus(this, null, direction) :
1422 FocusFinder.getInstance().findNextFocusFromRect(this,
1423 previouslyFocusedRect, direction);
1424
1425 if (nextFocus == null) {
1426 return false;
1427 }
1428
1429 if (isOffScreen(nextFocus)) {
1430 return false;
1431 }
1432
1433 return nextFocus.requestFocus(direction, previouslyFocusedRect);
Mindy Pereira4e30d892010-11-24 15:32:39 -08001434 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001435
1436 @Override
1437 public boolean requestChildRectangleOnScreen(View child, Rect rectangle,
1438 boolean immediate) {
1439 // offset into coordinate space of this scroll view
1440 rectangle.offset(child.getLeft() - child.getScrollX(),
1441 child.getTop() - child.getScrollY());
1442
1443 return scrollToChildRect(rectangle, immediate);
1444 }
1445
1446 @Override
1447 public void requestLayout() {
1448 mIsLayoutDirty = true;
1449 super.requestLayout();
1450 }
1451
1452 @Override
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -08001453 protected void onDetachedFromWindow() {
1454 super.onDetachedFromWindow();
1455
1456 if (mScrollStrictSpan != null) {
1457 mScrollStrictSpan.finish();
1458 mScrollStrictSpan = null;
1459 }
1460 if (mFlingStrictSpan != null) {
1461 mFlingStrictSpan.finish();
1462 mFlingStrictSpan = null;
1463 }
1464 }
1465
1466 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001467 protected void onLayout(boolean changed, int l, int t, int r, int b) {
1468 super.onLayout(changed, l, t, r, b);
1469 mIsLayoutDirty = false;
Mindy Pereira4e30d892010-11-24 15:32:39 -08001470 // Give a child focus if it needs it
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001471 if (mChildToScrollTo != null && isViewDescendantOf(mChildToScrollTo, this)) {
Romain Guy9c957372011-01-04 17:39:43 -08001472 scrollToChild(mChildToScrollTo);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001473 }
1474 mChildToScrollTo = null;
1475
Chet Haase7a46dde2013-07-17 10:22:53 -07001476 if (!isLaidOut()) {
Fabrice Di Megliod6befbd2013-06-12 16:01:48 -07001477 if (mSavedState != null) {
1478 mScrollY = mSavedState.scrollPosition;
1479 mSavedState = null;
1480 } // mScrollY default value is "0"
Fabrice Di Megliod6d54392013-06-18 14:09:07 -07001481
1482 final int childHeight = (getChildCount() > 0) ? getChildAt(0).getMeasuredHeight() : 0;
1483 final int scrollRange = Math.max(0,
1484 childHeight - (b - t - mPaddingBottom - mPaddingTop));
1485
Fabrice Di Megliod6befbd2013-06-12 16:01:48 -07001486 // Don't forget to clamp
1487 if (mScrollY > scrollRange) {
1488 mScrollY = scrollRange;
1489 } else if (mScrollY < 0) {
1490 mScrollY = 0;
1491 }
1492 }
1493
Ken Wakasaf76a50c2012-03-09 19:56:35 +09001494 // Calling this with the present values causes it to re-claim them
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001495 scrollTo(mScrollX, mScrollY);
1496 }
1497
1498 @Override
1499 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
1500 super.onSizeChanged(w, h, oldw, oldh);
1501
1502 View currentFocused = findFocus();
1503 if (null == currentFocused || this == currentFocused)
1504 return;
1505
Jack Veenstra7d4200d2009-09-21 19:45:14 -07001506 // If the currently-focused view was visible on the screen when the
1507 // screen was at the old height, then scroll the screen to make that
1508 // view visible with the new screen height.
1509 if (isWithinDeltaOfScreen(currentFocused, 0, oldh)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001510 currentFocused.getDrawingRect(mTempRect);
1511 offsetDescendantRectToMyCoords(currentFocused, mTempRect);
1512 int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
1513 doScrollY(scrollDelta);
1514 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08001515 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001516
1517 /**
Ken Wakasaf76a50c2012-03-09 19:56:35 +09001518 * Return true if child is a descendant of parent, (or equal to the parent).
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001519 */
Romain Guye979e622012-03-20 13:50:27 -07001520 private static boolean isViewDescendantOf(View child, View parent) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001521 if (child == parent) {
1522 return true;
1523 }
1524
1525 final ViewParent theParent = child.getParent();
1526 return (theParent instanceof ViewGroup) && isViewDescendantOf((View) theParent, parent);
Mindy Pereira4e30d892010-11-24 15:32:39 -08001527 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001528
1529 /**
1530 * Fling the scroll view
1531 *
1532 * @param velocityY The initial velocity in the Y direction. Positive
Gilles Debunne52964242010-02-24 11:05:19 -08001533 * numbers mean that the finger/cursor is moving down the screen,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001534 * which means we want to scroll towards the top.
1535 */
1536 public void fling(int velocityY) {
Romain Guyef0e9ae2009-07-10 14:11:26 -07001537 if (getChildCount() > 0) {
1538 int height = getHeight() - mPaddingBottom - mPaddingTop;
1539 int bottom = getChildAt(0).getHeight();
Mindy Pereira4e30d892010-11-24 15:32:39 -08001540
1541 mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0,
Adam Powell637d3372010-08-25 14:37:03 -07001542 Math.max(0, bottom - height), 0, height/2);
Mindy Pereira4e30d892010-11-24 15:32:39 -08001543
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -08001544 if (mFlingStrictSpan == null) {
1545 mFlingStrictSpan = StrictMode.enterCriticalSpan("ScrollView-fling");
1546 }
1547
Adam Powelldf3ae4f2012-04-10 18:55:22 -07001548 postInvalidateOnAnimation();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001549 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001550 }
1551
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -08001552 private void endDrag() {
1553 mIsBeingDragged = false;
1554
Michael Jurka13451a42011-08-22 15:54:21 -07001555 recycleVelocityTracker();
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -08001556
1557 if (mEdgeGlowTop != null) {
1558 mEdgeGlowTop.onRelease();
1559 mEdgeGlowBottom.onRelease();
1560 }
1561
1562 if (mScrollStrictSpan != null) {
1563 mScrollStrictSpan.finish();
1564 mScrollStrictSpan = null;
1565 }
1566 }
1567
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001568 /**
1569 * {@inheritDoc}
1570 *
1571 * <p>This version also clamps the scrolling to the bounds of our child.
1572 */
Gilles Debunne52964242010-02-24 11:05:19 -08001573 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001574 public void scrollTo(int x, int y) {
1575 // we rely on the fact the View.scrollBy calls scrollTo.
1576 if (getChildCount() > 0) {
1577 View child = getChildAt(0);
1578 x = clamp(x, getWidth() - mPaddingRight - mPaddingLeft, child.getWidth());
1579 y = clamp(y, getHeight() - mPaddingBottom - mPaddingTop, child.getHeight());
1580 if (x != mScrollX || y != mScrollY) {
1581 super.scrollTo(x, y);
1582 }
1583 }
1584 }
1585
Adam Powell637d3372010-08-25 14:37:03 -07001586 @Override
1587 public void setOverScrollMode(int mode) {
1588 if (mode != OVER_SCROLL_NEVER) {
1589 if (mEdgeGlowTop == null) {
Mindy Pereira4e30d892010-11-24 15:32:39 -08001590 Context context = getContext();
Adam Powell89935e42011-08-31 14:26:12 -07001591 mEdgeGlowTop = new EdgeEffect(context);
1592 mEdgeGlowBottom = new EdgeEffect(context);
Adam Powell637d3372010-08-25 14:37:03 -07001593 }
1594 } else {
1595 mEdgeGlowTop = null;
1596 mEdgeGlowBottom = null;
1597 }
1598 super.setOverScrollMode(mode);
1599 }
1600
1601 @Override
1602 public void draw(Canvas canvas) {
1603 super.draw(canvas);
1604 if (mEdgeGlowTop != null) {
1605 final int scrollY = mScrollY;
1606 if (!mEdgeGlowTop.isFinished()) {
1607 final int restoreCount = canvas.save();
Adam Powell7d863782011-02-15 15:05:03 -08001608 final int width = getWidth() - mPaddingLeft - mPaddingRight;
Adam Powell637d3372010-08-25 14:37:03 -07001609
Adam Powell7d863782011-02-15 15:05:03 -08001610 canvas.translate(mPaddingLeft, Math.min(0, scrollY));
Mindy Pereirab1297f72010-12-07 15:06:47 -08001611 mEdgeGlowTop.setSize(width, getHeight());
Adam Powell637d3372010-08-25 14:37:03 -07001612 if (mEdgeGlowTop.draw(canvas)) {
Adam Powelldf3ae4f2012-04-10 18:55:22 -07001613 postInvalidateOnAnimation();
Adam Powell637d3372010-08-25 14:37:03 -07001614 }
1615 canvas.restoreToCount(restoreCount);
1616 }
1617 if (!mEdgeGlowBottom.isFinished()) {
1618 final int restoreCount = canvas.save();
Adam Powell7d863782011-02-15 15:05:03 -08001619 final int width = getWidth() - mPaddingLeft - mPaddingRight;
Adam Powell637d3372010-08-25 14:37:03 -07001620 final int height = getHeight();
1621
Adam Powell7d863782011-02-15 15:05:03 -08001622 canvas.translate(-width + mPaddingLeft,
1623 Math.max(getScrollRange(), scrollY) + height);
Adam Powell637d3372010-08-25 14:37:03 -07001624 canvas.rotate(180, width, 0);
Mindy Pereirab1297f72010-12-07 15:06:47 -08001625 mEdgeGlowBottom.setSize(width, height);
Adam Powell637d3372010-08-25 14:37:03 -07001626 if (mEdgeGlowBottom.draw(canvas)) {
Adam Powelldf3ae4f2012-04-10 18:55:22 -07001627 postInvalidateOnAnimation();
Adam Powell637d3372010-08-25 14:37:03 -07001628 }
1629 canvas.restoreToCount(restoreCount);
1630 }
1631 }
1632 }
1633
Romain Guye979e622012-03-20 13:50:27 -07001634 private static int clamp(int n, int my, int child) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001635 if (my >= child || n < 0) {
1636 /* my >= child is this case:
1637 * |--------------- me ---------------|
1638 * |------ child ------|
1639 * or
1640 * |--------------- me ---------------|
1641 * |------ child ------|
1642 * or
1643 * |--------------- me ---------------|
1644 * |------ child ------|
1645 *
1646 * n < 0 is this case:
1647 * |------ me ------|
1648 * |-------- child --------|
1649 * |-- mScrollX --|
1650 */
1651 return 0;
1652 }
1653 if ((my+n) > child) {
1654 /* this case:
1655 * |------ me ------|
1656 * |------ child ------|
1657 * |-- mScrollX --|
1658 */
1659 return child-my;
1660 }
1661 return n;
1662 }
Fabrice Di Megliod6befbd2013-06-12 16:01:48 -07001663
1664 @Override
1665 protected void onRestoreInstanceState(Parcelable state) {
Adam Powell90f339a2013-06-13 17:44:04 -07001666 if (mContext.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR2) {
1667 // Some old apps reused IDs in ways they shouldn't have.
1668 // Don't break them, but they don't get scroll state restoration.
1669 super.onRestoreInstanceState(state);
1670 return;
1671 }
Fabrice Di Megliod6befbd2013-06-12 16:01:48 -07001672 SavedState ss = (SavedState) state;
1673 super.onRestoreInstanceState(ss.getSuperState());
1674 mSavedState = ss;
1675 requestLayout();
1676 }
1677
1678 @Override
1679 protected Parcelable onSaveInstanceState() {
Adam Powell90f339a2013-06-13 17:44:04 -07001680 if (mContext.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR2) {
1681 // Some old apps reused IDs in ways they shouldn't have.
1682 // Don't break them, but they don't get scroll state restoration.
1683 return super.onSaveInstanceState();
1684 }
Fabrice Di Megliod6befbd2013-06-12 16:01:48 -07001685 Parcelable superState = super.onSaveInstanceState();
1686 SavedState ss = new SavedState(superState);
1687 ss.scrollPosition = mScrollY;
1688 return ss;
1689 }
1690
1691 static class SavedState extends BaseSavedState {
1692 public int scrollPosition;
1693
1694 SavedState(Parcelable superState) {
1695 super(superState);
1696 }
1697
1698 public SavedState(Parcel source) {
1699 super(source);
1700 scrollPosition = source.readInt();
1701 }
1702
1703 @Override
1704 public void writeToParcel(Parcel dest, int flags) {
1705 super.writeToParcel(dest, flags);
1706 dest.writeInt(scrollPosition);
1707 }
1708
1709 @Override
1710 public String toString() {
1711 return "HorizontalScrollView.SavedState{"
1712 + Integer.toHexString(System.identityHashCode(this))
1713 + " scrollPosition=" + scrollPosition + "}";
1714 }
1715
1716 public static final Parcelable.Creator<SavedState> CREATOR
1717 = new Parcelable.Creator<SavedState>() {
1718 public SavedState createFromParcel(Parcel in) {
1719 return new SavedState(in);
1720 }
1721
1722 public SavedState[] newArray(int size) {
1723 return new SavedState[size];
1724 }
1725 };
1726 }
1727
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001728}