blob: 082d7288c9c445d4f17226523fc888b3552954e0 [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
Alan Viverette617feb92013-09-09 18:09:13 -0700165 public ScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
166 this(context, attrs, defStyleAttr, 0);
167 }
168
169 public ScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
170 super(context, attrs, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800171 initScrollView();
172
Alan Viverette617feb92013-09-09 18:09:13 -0700173 final TypedArray a = context.obtainStyledAttributes(
174 attrs, com.android.internal.R.styleable.ScrollView, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800175
176 setFillViewport(a.getBoolean(R.styleable.ScrollView_fillViewport, false));
177
178 a.recycle();
179 }
180
181 @Override
Patrick Dubroye0a799a2011-05-04 16:19:22 -0700182 public boolean shouldDelayChildPressedState() {
183 return true;
184 }
185
186 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800187 protected float getTopFadingEdgeStrength() {
188 if (getChildCount() == 0) {
189 return 0.0f;
190 }
191
192 final int length = getVerticalFadingEdgeLength();
193 if (mScrollY < length) {
194 return mScrollY / (float) length;
195 }
196
197 return 1.0f;
198 }
199
200 @Override
201 protected float getBottomFadingEdgeStrength() {
202 if (getChildCount() == 0) {
203 return 0.0f;
204 }
205
206 final int length = getVerticalFadingEdgeLength();
207 final int bottomEdge = getHeight() - mPaddingBottom;
208 final int span = getChildAt(0).getBottom() - mScrollY - bottomEdge;
209 if (span < length) {
210 return span / (float) length;
211 }
212
213 return 1.0f;
214 }
215
216 /**
217 * @return The maximum amount this scroll view will scroll in response to
218 * an arrow event.
219 */
220 public int getMaxScrollAmount() {
221 return (int) (MAX_SCROLL_FACTOR * (mBottom - mTop));
222 }
223
224
225 private void initScrollView() {
Adam Powell637d3372010-08-25 14:37:03 -0700226 mScroller = new OverScroller(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800227 setFocusable(true);
228 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
229 setWillNotDraw(false);
Romain Guy4296fc42009-07-06 11:48:52 -0700230 final ViewConfiguration configuration = ViewConfiguration.get(mContext);
231 mTouchSlop = configuration.getScaledTouchSlop();
232 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
233 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
Adam Powell637d3372010-08-25 14:37:03 -0700234 mOverscrollDistance = configuration.getScaledOverscrollDistance();
235 mOverflingDistance = configuration.getScaledOverflingDistance();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800236 }
237
238 @Override
239 public void addView(View child) {
240 if (getChildCount() > 0) {
241 throw new IllegalStateException("ScrollView can host only one direct child");
242 }
243
244 super.addView(child);
245 }
246
247 @Override
248 public void addView(View child, int index) {
249 if (getChildCount() > 0) {
250 throw new IllegalStateException("ScrollView can host only one direct child");
251 }
252
253 super.addView(child, index);
254 }
255
256 @Override
257 public void addView(View child, ViewGroup.LayoutParams params) {
258 if (getChildCount() > 0) {
259 throw new IllegalStateException("ScrollView can host only one direct child");
260 }
261
262 super.addView(child, params);
263 }
264
265 @Override
266 public void addView(View child, int index, ViewGroup.LayoutParams params) {
267 if (getChildCount() > 0) {
268 throw new IllegalStateException("ScrollView can host only one direct child");
269 }
270
271 super.addView(child, index, params);
272 }
273
274 /**
275 * @return Returns true this ScrollView can be scrolled
276 */
277 private boolean canScroll() {
278 View child = getChildAt(0);
279 if (child != null) {
280 int childHeight = child.getHeight();
281 return getHeight() < childHeight + mPaddingTop + mPaddingBottom;
282 }
283 return false;
284 }
285
286 /**
287 * Indicates whether this ScrollView's content is stretched to fill the viewport.
288 *
289 * @return True if the content fills the viewport, false otherwise.
Mindy Pereira4e30d892010-11-24 15:32:39 -0800290 *
Romain Guyfdbf4842010-08-16 10:55:49 -0700291 * @attr ref android.R.styleable#ScrollView_fillViewport
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800292 */
293 public boolean isFillViewport() {
294 return mFillViewport;
295 }
296
297 /**
298 * Indicates this ScrollView whether it should stretch its content height to fill
299 * the viewport or not.
300 *
301 * @param fillViewport True to stretch the content's height to the viewport's
302 * boundaries, false otherwise.
Mindy Pereira4e30d892010-11-24 15:32:39 -0800303 *
Romain Guyfdbf4842010-08-16 10:55:49 -0700304 * @attr ref android.R.styleable#ScrollView_fillViewport
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800305 */
306 public void setFillViewport(boolean fillViewport) {
307 if (fillViewport != mFillViewport) {
308 mFillViewport = fillViewport;
309 requestLayout();
310 }
311 }
312
313 /**
314 * @return Whether arrow scrolling will animate its transition.
315 */
316 public boolean isSmoothScrollingEnabled() {
317 return mSmoothScrollingEnabled;
318 }
319
320 /**
321 * Set whether arrow scrolling will animate its transition.
322 * @param smoothScrollingEnabled whether arrow scrolling will animate its transition
323 */
324 public void setSmoothScrollingEnabled(boolean smoothScrollingEnabled) {
325 mSmoothScrollingEnabled = smoothScrollingEnabled;
326 }
327
328 @Override
329 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
330 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
331
332 if (!mFillViewport) {
333 return;
334 }
335
336 final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
337 if (heightMode == MeasureSpec.UNSPECIFIED) {
338 return;
339 }
340
Romain Guyef0e9ae2009-07-10 14:11:26 -0700341 if (getChildCount() > 0) {
342 final View child = getChildAt(0);
343 int height = getMeasuredHeight();
344 if (child.getMeasuredHeight() < height) {
345 final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
Mindy Pereira4e30d892010-11-24 15:32:39 -0800346
Romain Guy9c957372011-01-04 17:39:43 -0800347 int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
348 mPaddingLeft + mPaddingRight, lp.width);
Romain Guyef0e9ae2009-07-10 14:11:26 -0700349 height -= mPaddingTop;
350 height -= mPaddingBottom;
351 int childHeightMeasureSpec =
352 MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
Mindy Pereira4e30d892010-11-24 15:32:39 -0800353
Romain Guyef0e9ae2009-07-10 14:11:26 -0700354 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
355 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800356 }
357 }
358
359 @Override
360 public boolean dispatchKeyEvent(KeyEvent event) {
361 // Let the focused view and/or our descendants get the key first
Romain Guy8e618e52010-03-08 12:18:20 -0800362 return super.dispatchKeyEvent(event) || executeKeyEvent(event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800363 }
364
365 /**
366 * You can call this function yourself to have the scroll view perform
367 * scrolling from a key event, just as if the event had been dispatched to
368 * it by the view hierarchy.
369 *
370 * @param event The key event to execute.
371 * @return Return true if the event was handled, else false.
372 */
373 public boolean executeKeyEvent(KeyEvent event) {
374 mTempRect.setEmpty();
375
376 if (!canScroll()) {
Romain Guy2d4cff62010-04-09 15:39:00 -0700377 if (isFocused() && event.getKeyCode() != KeyEvent.KEYCODE_BACK) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800378 View currentFocused = findFocus();
379 if (currentFocused == this) currentFocused = null;
380 View nextFocused = FocusFinder.getInstance().findNextFocus(this,
381 currentFocused, View.FOCUS_DOWN);
382 return nextFocused != null
383 && nextFocused != this
384 && nextFocused.requestFocus(View.FOCUS_DOWN);
385 }
386 return false;
387 }
388
389 boolean handled = false;
390 if (event.getAction() == KeyEvent.ACTION_DOWN) {
391 switch (event.getKeyCode()) {
392 case KeyEvent.KEYCODE_DPAD_UP:
393 if (!event.isAltPressed()) {
394 handled = arrowScroll(View.FOCUS_UP);
395 } else {
396 handled = fullScroll(View.FOCUS_UP);
397 }
398 break;
399 case KeyEvent.KEYCODE_DPAD_DOWN:
400 if (!event.isAltPressed()) {
401 handled = arrowScroll(View.FOCUS_DOWN);
402 } else {
403 handled = fullScroll(View.FOCUS_DOWN);
404 }
405 break;
406 case KeyEvent.KEYCODE_SPACE:
407 pageScroll(event.isShiftPressed() ? View.FOCUS_UP : View.FOCUS_DOWN);
408 break;
409 }
410 }
411
412 return handled;
413 }
414
Joe Onorato9f0e8ee2010-02-23 19:19:22 -0800415 private boolean inChild(int x, int y) {
416 if (getChildCount() > 0) {
Adam Powell352b9782010-03-24 14:23:43 -0700417 final int scrollY = mScrollY;
Joe Onorato9f0e8ee2010-02-23 19:19:22 -0800418 final View child = getChildAt(0);
Adam Powell352b9782010-03-24 14:23:43 -0700419 return !(y < child.getTop() - scrollY
420 || y >= child.getBottom() - scrollY
Joe Onorato9f0e8ee2010-02-23 19:19:22 -0800421 || x < child.getLeft()
422 || x >= child.getRight());
423 }
424 return false;
425 }
426
Michael Jurka13451a42011-08-22 15:54:21 -0700427 private void initOrResetVelocityTracker() {
428 if (mVelocityTracker == null) {
429 mVelocityTracker = VelocityTracker.obtain();
430 } else {
431 mVelocityTracker.clear();
432 }
433 }
434
435 private void initVelocityTrackerIfNotExists() {
436 if (mVelocityTracker == null) {
437 mVelocityTracker = VelocityTracker.obtain();
438 }
439 }
440
441 private void recycleVelocityTracker() {
442 if (mVelocityTracker != null) {
443 mVelocityTracker.recycle();
444 mVelocityTracker = null;
445 }
446 }
447
448 @Override
449 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
450 if (disallowIntercept) {
451 recycleVelocityTracker();
452 }
453 super.requestDisallowInterceptTouchEvent(disallowIntercept);
454 }
455
456
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800457 @Override
458 public boolean onInterceptTouchEvent(MotionEvent ev) {
459 /*
460 * This method JUST determines whether we want to intercept the motion.
461 * If we return true, onMotionEvent will be called and we do the actual
462 * scrolling there.
463 */
464
465 /*
466 * Shortcut the most recurring case: the user is in the dragging
467 * state and he is moving his finger. We want to intercept this
468 * motion.
469 */
470 final int action = ev.getAction();
471 if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
472 return true;
473 }
474
Adam Powell0278c2f2012-07-31 16:39:32 -0700475 /*
476 * Don't try to intercept touch if we can't scroll anyway.
477 */
478 if (getScrollY() == 0 && !canScrollVertically(1)) {
479 return false;
480 }
481
Adam Powellbc4e7532010-02-23 14:49:01 -0800482 switch (action & MotionEvent.ACTION_MASK) {
483 case MotionEvent.ACTION_MOVE: {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800484 /*
485 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
486 * whether the user has moved far enough from his original down touch.
487 */
488
489 /*
490 * Locally do absolute value. mLastMotionY is set to the y value
491 * of the down event.
492 */
Adam Powell9d0335b2010-03-24 13:42:51 -0700493 final int activePointerId = mActivePointerId;
494 if (activePointerId == INVALID_POINTER) {
495 // If we don't have a valid id, the touch down wasn't on content.
496 break;
497 }
498
499 final int pointerIndex = ev.findPointerIndex(activePointerId);
Johan Rosengren0dc291e2011-02-21 09:49:45 +0100500 if (pointerIndex == -1) {
501 Log.e(TAG, "Invalid pointerId=" + activePointerId
502 + " in onInterceptTouchEvent");
503 break;
504 }
505
Adam Powelldf3ae4f2012-04-10 18:55:22 -0700506 final int y = (int) ev.getY(pointerIndex);
507 final int yDiff = Math.abs(y - mLastMotionY);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800508 if (yDiff > mTouchSlop) {
509 mIsBeingDragged = true;
Romain Guyf7b4acc2009-12-01 16:24:45 -0800510 mLastMotionY = y;
Michael Jurka13451a42011-08-22 15:54:21 -0700511 initVelocityTrackerIfNotExists();
512 mVelocityTracker.addMovement(ev);
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -0800513 if (mScrollStrictSpan == null) {
514 mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll");
515 }
Adam Powellb3e02c42012-05-02 22:05:46 -0700516 final ViewParent parent = getParent();
517 if (parent != null) {
518 parent.requestDisallowInterceptTouchEvent(true);
519 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800520 }
521 break;
Adam Powellbc4e7532010-02-23 14:49:01 -0800522 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800523
Adam Powellbc4e7532010-02-23 14:49:01 -0800524 case MotionEvent.ACTION_DOWN: {
Adam Powelldf3ae4f2012-04-10 18:55:22 -0700525 final int y = (int) ev.getY();
Adam Powell4cd47702010-02-25 11:21:14 -0800526 if (!inChild((int) ev.getX(), (int) y)) {
Joe Onorato9f0e8ee2010-02-23 19:19:22 -0800527 mIsBeingDragged = false;
Michael Jurka13451a42011-08-22 15:54:21 -0700528 recycleVelocityTracker();
Joe Onorato9f0e8ee2010-02-23 19:19:22 -0800529 break;
530 }
531
Adam Powellbc4e7532010-02-23 14:49:01 -0800532 /*
533 * Remember location of down touch.
534 * ACTION_DOWN always refers to pointer index 0.
535 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800536 mLastMotionY = y;
Adam Powellbc4e7532010-02-23 14:49:01 -0800537 mActivePointerId = ev.getPointerId(0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800538
Michael Jurka13451a42011-08-22 15:54:21 -0700539 initOrResetVelocityTracker();
540 mVelocityTracker.addMovement(ev);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800541 /*
542 * If being flinged and user touches the screen, initiate drag;
543 * otherwise don't. mScroller.isFinished should be false when
544 * being flinged.
545 */
546 mIsBeingDragged = !mScroller.isFinished();
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -0800547 if (mIsBeingDragged && mScrollStrictSpan == null) {
548 mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll");
549 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800550 break;
Adam Powellbc4e7532010-02-23 14:49:01 -0800551 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800552
553 case MotionEvent.ACTION_CANCEL:
554 case MotionEvent.ACTION_UP:
555 /* Release the drag */
556 mIsBeingDragged = false;
Adam Powellbc4e7532010-02-23 14:49:01 -0800557 mActivePointerId = INVALID_POINTER;
Michael Jurka13451a42011-08-22 15:54:21 -0700558 recycleVelocityTracker();
Adam Powell637d3372010-08-25 14:37:03 -0700559 if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) {
Adam Powelldf3ae4f2012-04-10 18:55:22 -0700560 postInvalidateOnAnimation();
Adam Powell637d3372010-08-25 14:37:03 -0700561 }
Adam Powellbc4e7532010-02-23 14:49:01 -0800562 break;
563 case MotionEvent.ACTION_POINTER_UP:
564 onSecondaryPointerUp(ev);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800565 break;
566 }
567
568 /*
569 * The only time we want to intercept motion events is if we are in the
570 * drag mode.
571 */
572 return mIsBeingDragged;
573 }
574
575 @Override
576 public boolean onTouchEvent(MotionEvent ev) {
Michael Jurka13451a42011-08-22 15:54:21 -0700577 initVelocityTrackerIfNotExists();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800578 mVelocityTracker.addMovement(ev);
579
580 final int action = ev.getAction();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800581
Adam Powellbc4e7532010-02-23 14:49:01 -0800582 switch (action & MotionEvent.ACTION_MASK) {
583 case MotionEvent.ACTION_DOWN: {
Adam Powellb3e02c42012-05-02 22:05:46 -0700584 if (getChildCount() == 0) {
Jeff Brownfb757382011-01-18 18:42:33 -0800585 return false;
586 }
Adam Powellb3e02c42012-05-02 22:05:46 -0700587 if ((mIsBeingDragged = !mScroller.isFinished())) {
588 final ViewParent parent = getParent();
589 if (parent != null) {
590 parent.requestDisallowInterceptTouchEvent(true);
591 }
592 }
Mindy Pereira4e30d892010-11-24 15:32:39 -0800593
Adam Powell352b9782010-03-24 14:23:43 -0700594 /*
595 * If being flinged and user touches, stop the fling. isFinished
596 * will be false if being flinged.
597 */
598 if (!mScroller.isFinished()) {
599 mScroller.abortAnimation();
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -0800600 if (mFlingStrictSpan != null) {
601 mFlingStrictSpan.finish();
602 mFlingStrictSpan = null;
603 }
Adam Powell352b9782010-03-24 14:23:43 -0700604 }
605
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800606 // Remember where the motion event started
Adam Powelldf3ae4f2012-04-10 18:55:22 -0700607 mLastMotionY = (int) ev.getY();
Adam Powellbc4e7532010-02-23 14:49:01 -0800608 mActivePointerId = ev.getPointerId(0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800609 break;
Adam Powellbc4e7532010-02-23 14:49:01 -0800610 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800611 case MotionEvent.ACTION_MOVE:
Adam Powellb3e02c42012-05-02 22:05:46 -0700612 final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
Johan Rosengren0dc291e2011-02-21 09:49:45 +0100613 if (activePointerIndex == -1) {
614 Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
615 break;
616 }
617
Adam Powellb3e02c42012-05-02 22:05:46 -0700618 final int y = (int) ev.getY(activePointerIndex);
619 int deltaY = mLastMotionY - y;
620 if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) {
621 final ViewParent parent = getParent();
622 if (parent != null) {
623 parent.requestDisallowInterceptTouchEvent(true);
624 }
625 mIsBeingDragged = true;
626 if (deltaY > 0) {
627 deltaY -= mTouchSlop;
628 } else {
629 deltaY += mTouchSlop;
630 }
631 }
Joe Onorato9f0e8ee2010-02-23 19:19:22 -0800632 if (mIsBeingDragged) {
633 // Scroll to follow the motion event
Joe Onorato9f0e8ee2010-02-23 19:19:22 -0800634 mLastMotionY = y;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800635
Adam Powell637d3372010-08-25 14:37:03 -0700636 final int oldX = mScrollX;
637 final int oldY = mScrollY;
638 final int range = getScrollRange();
Fabrice Di Meglioe9dbef82011-09-12 16:36:46 -0700639 final int overscrollMode = getOverScrollMode();
640 final boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
641 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
642
Alan Viverettecb25bd82013-06-03 17:10:44 -0700643 // Calling overScrollBy will call onOverScrolled, which
644 // calls onScrollChanged if applicable.
Adam Powellf6a6c972011-09-28 23:30:20 -0700645 if (overScrollBy(0, deltaY, 0, mScrollY,
Fabrice Di Meglioe9dbef82011-09-12 16:36:46 -0700646 0, range, 0, mOverscrollDistance, true)) {
Adam Powell637d3372010-08-25 14:37:03 -0700647 // Break our velocity if we hit a scroll barrier.
648 mVelocityTracker.clear();
649 }
Adam Powell637d3372010-08-25 14:37:03 -0700650
Fabrice Di Meglioe9dbef82011-09-12 16:36:46 -0700651 if (canOverscroll) {
Adam Powell637d3372010-08-25 14:37:03 -0700652 final int pulledToY = oldY + deltaY;
653 if (pulledToY < 0) {
654 mEdgeGlowTop.onPull((float) deltaY / getHeight());
655 if (!mEdgeGlowBottom.isFinished()) {
656 mEdgeGlowBottom.onRelease();
657 }
658 } else if (pulledToY > range) {
659 mEdgeGlowBottom.onPull((float) deltaY / getHeight());
660 if (!mEdgeGlowTop.isFinished()) {
661 mEdgeGlowTop.onRelease();
662 }
663 }
664 if (mEdgeGlowTop != null
665 && (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished())) {
Adam Powelldf3ae4f2012-04-10 18:55:22 -0700666 postInvalidateOnAnimation();
Adam Powell637d3372010-08-25 14:37:03 -0700667 }
668 }
Joe Onorato9f0e8ee2010-02-23 19:19:22 -0800669 }
Adam Powellbc4e7532010-02-23 14:49:01 -0800670 break;
Mindy Pereira4e30d892010-11-24 15:32:39 -0800671 case MotionEvent.ACTION_UP:
Joe Onorato9f0e8ee2010-02-23 19:19:22 -0800672 if (mIsBeingDragged) {
673 final VelocityTracker velocityTracker = mVelocityTracker;
674 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
Adam Powellbc4e7532010-02-23 14:49:01 -0800675 int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800676
Adam Powellf6a6c972011-09-28 23:30:20 -0700677 if (getChildCount() > 0) {
Adam Powell637d3372010-08-25 14:37:03 -0700678 if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
679 fling(-initialVelocity);
680 } else {
Adam Powellf6a6c972011-09-28 23:30:20 -0700681 if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0,
682 getScrollRange())) {
Adam Powelldf3ae4f2012-04-10 18:55:22 -0700683 postInvalidateOnAnimation();
Adam Powell637d3372010-08-25 14:37:03 -0700684 }
685 }
Adam Powell17dfce12010-01-25 18:38:22 -0800686 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800687
Adam Powellbc4e7532010-02-23 14:49:01 -0800688 mActivePointerId = INVALID_POINTER;
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -0800689 endDrag();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800690 }
Adam Powellbc4e7532010-02-23 14:49:01 -0800691 break;
Adam Powell352b9782010-03-24 14:23:43 -0700692 case MotionEvent.ACTION_CANCEL:
693 if (mIsBeingDragged && getChildCount() > 0) {
Adam Powell637d3372010-08-25 14:37:03 -0700694 if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) {
Adam Powelldf3ae4f2012-04-10 18:55:22 -0700695 postInvalidateOnAnimation();
Adam Powell637d3372010-08-25 14:37:03 -0700696 }
Adam Powell352b9782010-03-24 14:23:43 -0700697 mActivePointerId = INVALID_POINTER;
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -0800698 endDrag();
Adam Powell352b9782010-03-24 14:23:43 -0700699 }
700 break;
Adam Powell9bc30d32011-02-28 10:27:49 -0800701 case MotionEvent.ACTION_POINTER_DOWN: {
702 final int index = ev.getActionIndex();
Adam Powelldf3ae4f2012-04-10 18:55:22 -0700703 mLastMotionY = (int) ev.getY(index);
Adam Powell9bc30d32011-02-28 10:27:49 -0800704 mActivePointerId = ev.getPointerId(index);
705 break;
706 }
Adam Powellbc4e7532010-02-23 14:49:01 -0800707 case MotionEvent.ACTION_POINTER_UP:
708 onSecondaryPointerUp(ev);
Adam Powelldf3ae4f2012-04-10 18:55:22 -0700709 mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
Adam Powellbc4e7532010-02-23 14:49:01 -0800710 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800711 }
712 return true;
713 }
Mindy Pereira4e30d892010-11-24 15:32:39 -0800714
Adam Powellbc4e7532010-02-23 14:49:01 -0800715 private void onSecondaryPointerUp(MotionEvent ev) {
716 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
717 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
718 final int pointerId = ev.getPointerId(pointerIndex);
719 if (pointerId == mActivePointerId) {
720 // This was our active pointer going up. Choose a new
721 // active pointer and adjust accordingly.
722 // TODO: Make this decision more intelligent.
723 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
Adam Powelldf3ae4f2012-04-10 18:55:22 -0700724 mLastMotionY = (int) ev.getY(newPointerIndex);
Adam Powellbc4e7532010-02-23 14:49:01 -0800725 mActivePointerId = ev.getPointerId(newPointerIndex);
726 if (mVelocityTracker != null) {
727 mVelocityTracker.clear();
728 }
729 }
730 }
Mindy Pereira4e30d892010-11-24 15:32:39 -0800731
Adam Powell637d3372010-08-25 14:37:03 -0700732 @Override
Jeff Brown33bbfd22011-02-24 20:55:35 -0800733 public boolean onGenericMotionEvent(MotionEvent event) {
734 if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
735 switch (event.getAction()) {
736 case MotionEvent.ACTION_SCROLL: {
737 if (!mIsBeingDragged) {
738 final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
739 if (vscroll != 0) {
740 final int delta = (int) (vscroll * getVerticalScrollFactor());
741 final int range = getScrollRange();
742 int oldScrollY = mScrollY;
743 int newScrollY = oldScrollY - delta;
744 if (newScrollY < 0) {
745 newScrollY = 0;
746 } else if (newScrollY > range) {
747 newScrollY = range;
748 }
749 if (newScrollY != oldScrollY) {
750 super.scrollTo(mScrollX, newScrollY);
751 return true;
752 }
753 }
754 }
755 }
756 }
757 }
758 return super.onGenericMotionEvent(event);
759 }
760
761 @Override
Adam Powell637d3372010-08-25 14:37:03 -0700762 protected void onOverScrolled(int scrollX, int scrollY,
763 boolean clampedX, boolean clampedY) {
764 // Treat animating scrolls differently; see #computeScroll() for why.
765 if (!mScroller.isFinished()) {
Alan Viverettecb25bd82013-06-03 17:10:44 -0700766 final int oldX = mScrollX;
767 final int oldY = mScrollY;
Adam Powell637d3372010-08-25 14:37:03 -0700768 mScrollX = scrollX;
769 mScrollY = scrollY;
Romain Guy0fd89bf2011-01-26 15:41:30 -0800770 invalidateParentIfNeeded();
Alan Viverettecb25bd82013-06-03 17:10:44 -0700771 onScrollChanged(mScrollX, mScrollY, oldX, oldY);
Adam Powell637d3372010-08-25 14:37:03 -0700772 if (clampedY) {
773 mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange());
774 }
775 } else {
776 super.scrollTo(scrollX, scrollY);
777 }
Romain Guye979e622012-03-20 13:50:27 -0700778
Romain Guye72cf732012-03-20 14:23:09 -0700779 awakenScrollBars();
Adam Powell637d3372010-08-25 14:37:03 -0700780 }
781
Svetoslav Ganova0156172011-06-26 17:55:44 -0700782 @Override
Svetoslav Ganova1dc7612012-05-10 04:14:53 -0700783 public boolean performAccessibilityAction(int action, Bundle arguments) {
Svetoslav Ganov48d15862012-05-15 10:10:00 -0700784 if (super.performAccessibilityAction(action, arguments)) {
785 return true;
786 }
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -0700787 if (!isEnabled()) {
788 return false;
789 }
Svetoslav Ganova1dc7612012-05-10 04:14:53 -0700790 switch (action) {
791 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
792 final int viewportHeight = getHeight() - mPaddingBottom - mPaddingTop;
793 final int targetScrollY = Math.min(mScrollY + viewportHeight, getScrollRange());
794 if (targetScrollY != mScrollY) {
795 smoothScrollTo(0, targetScrollY);
796 return true;
797 }
798 } return false;
799 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
800 final int viewportHeight = getHeight() - mPaddingBottom - mPaddingTop;
801 final int targetScrollY = Math.max(mScrollY - viewportHeight, 0);
802 if (targetScrollY != mScrollY) {
803 smoothScrollTo(0, targetScrollY);
804 return true;
805 }
806 } return false;
807 }
Svetoslav Ganov48d15862012-05-15 10:10:00 -0700808 return false;
Svetoslav Ganova1dc7612012-05-10 04:14:53 -0700809 }
810
811 @Override
Svetoslav Ganova0156172011-06-26 17:55:44 -0700812 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
813 super.onInitializeAccessibilityNodeInfo(info);
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800814 info.setClassName(ScrollView.class.getName());
Svetoslav Ganovfb1e80a2012-05-16 17:33:19 -0700815 if (isEnabled()) {
816 final int scrollRange = getScrollRange();
817 if (scrollRange > 0) {
818 info.setScrollable(true);
819 if (mScrollY > 0) {
820 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
821 }
822 if (mScrollY < scrollRange) {
823 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
824 }
Svetoslav Ganova1dc7612012-05-10 04:14:53 -0700825 }
826 }
Svetoslav Ganova0156172011-06-26 17:55:44 -0700827 }
828
829 @Override
830 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
831 super.onInitializeAccessibilityEvent(event);
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800832 event.setClassName(ScrollView.class.getName());
Svetoslav Ganovd9ee72f2011-10-05 22:26:05 -0700833 final boolean scrollable = getScrollRange() > 0;
834 event.setScrollable(scrollable);
835 event.setScrollX(mScrollX);
836 event.setScrollY(mScrollY);
837 event.setMaxScrollX(mScrollX);
838 event.setMaxScrollY(getScrollRange());
Svetoslav Ganova0156172011-06-26 17:55:44 -0700839 }
840
Adam Powell637d3372010-08-25 14:37:03 -0700841 private int getScrollRange() {
842 int scrollRange = 0;
843 if (getChildCount() > 0) {
844 View child = getChildAt(0);
845 scrollRange = Math.max(0,
846 child.getHeight() - (getHeight() - mPaddingBottom - mPaddingTop));
847 }
848 return scrollRange;
849 }
850
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800851 /**
852 * <p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800853 * Finds the next focusable component that fits in the specified bounds.
854 * </p>
855 *
856 * @param topFocus look for a candidate is the one at the top of the bounds
857 * if topFocus is true, or at the bottom of the bounds if topFocus is
858 * false
859 * @param top the top offset of the bounds in which a focusable must be
860 * found
861 * @param bottom the bottom offset of the bounds in which a focusable must
862 * be found
863 * @return the next focusable component in the bounds or null if none can
864 * be found
865 */
866 private View findFocusableViewInBounds(boolean topFocus, int top, int bottom) {
867
868 List<View> focusables = getFocusables(View.FOCUS_FORWARD);
869 View focusCandidate = null;
870
871 /*
872 * A fully contained focusable is one where its top is below the bound's
873 * top, and its bottom is above the bound's bottom. A partially
874 * contained focusable is one where some part of it is within the
875 * bounds, but it also has some part that is not within bounds. A fully contained
876 * focusable is preferred to a partially contained focusable.
877 */
878 boolean foundFullyContainedFocusable = false;
879
880 int count = focusables.size();
881 for (int i = 0; i < count; i++) {
882 View view = focusables.get(i);
883 int viewTop = view.getTop();
884 int viewBottom = view.getBottom();
885
886 if (top < viewBottom && viewTop < bottom) {
887 /*
888 * the focusable is in the target area, it is a candidate for
889 * focusing
890 */
891
892 final boolean viewIsFullyContained = (top < viewTop) &&
893 (viewBottom < bottom);
894
895 if (focusCandidate == null) {
896 /* No candidate, take this one */
897 focusCandidate = view;
898 foundFullyContainedFocusable = viewIsFullyContained;
899 } else {
900 final boolean viewIsCloserToBoundary =
901 (topFocus && viewTop < focusCandidate.getTop()) ||
902 (!topFocus && viewBottom > focusCandidate
903 .getBottom());
904
905 if (foundFullyContainedFocusable) {
906 if (viewIsFullyContained && viewIsCloserToBoundary) {
907 /*
908 * We're dealing with only fully contained views, so
909 * it has to be closer to the boundary to beat our
910 * candidate
911 */
912 focusCandidate = view;
913 }
914 } else {
915 if (viewIsFullyContained) {
916 /* Any fully contained view beats a partially contained view */
917 focusCandidate = view;
918 foundFullyContainedFocusable = true;
919 } else if (viewIsCloserToBoundary) {
920 /*
921 * Partially contained view beats another partially
922 * contained view if it's closer
923 */
924 focusCandidate = view;
925 }
926 }
927 }
928 }
929 }
930
931 return focusCandidate;
932 }
933
934 /**
935 * <p>Handles scrolling in response to a "page up/down" shortcut press. This
936 * method will scroll the view by one page up or down and give the focus
937 * to the topmost/bottommost component in the new visible area. If no
938 * component is a good candidate for focus, this scrollview reclaims the
939 * focus.</p>
940 *
941 * @param direction the scroll direction: {@link android.view.View#FOCUS_UP}
942 * to go one page up or
943 * {@link android.view.View#FOCUS_DOWN} to go one page down
944 * @return true if the key event is consumed by this method, false otherwise
945 */
946 public boolean pageScroll(int direction) {
947 boolean down = direction == View.FOCUS_DOWN;
948 int height = getHeight();
949
950 if (down) {
951 mTempRect.top = getScrollY() + height;
952 int count = getChildCount();
953 if (count > 0) {
954 View view = getChildAt(count - 1);
955 if (mTempRect.top + height > view.getBottom()) {
956 mTempRect.top = view.getBottom() - height;
957 }
958 }
959 } else {
960 mTempRect.top = getScrollY() - height;
961 if (mTempRect.top < 0) {
962 mTempRect.top = 0;
963 }
964 }
965 mTempRect.bottom = mTempRect.top + height;
966
967 return scrollAndFocus(direction, mTempRect.top, mTempRect.bottom);
968 }
969
970 /**
971 * <p>Handles scrolling in response to a "home/end" shortcut press. This
972 * method will scroll the view to the top or bottom 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 the top of the view or
979 * {@link android.view.View#FOCUS_DOWN} to go the bottom
980 * @return true if the key event is consumed by this method, false otherwise
981 */
982 public boolean fullScroll(int direction) {
983 boolean down = direction == View.FOCUS_DOWN;
984 int height = getHeight();
985
986 mTempRect.top = 0;
987 mTempRect.bottom = height;
988
989 if (down) {
990 int count = getChildCount();
991 if (count > 0) {
992 View view = getChildAt(count - 1);
Mattias Petersson5435a062011-04-07 15:46:56 +0200993 mTempRect.bottom = view.getBottom() + mPaddingBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800994 mTempRect.top = mTempRect.bottom - height;
995 }
996 }
997
998 return scrollAndFocus(direction, mTempRect.top, mTempRect.bottom);
999 }
1000
1001 /**
1002 * <p>Scrolls the view to make the area defined by <code>top</code> and
1003 * <code>bottom</code> visible. This method attempts to give the focus
1004 * to a component visible in this area. If no component can be focused in
Gilles Debunne2ed2eac2011-02-24 16:29:48 -08001005 * the new visible area, the focus is reclaimed by this ScrollView.</p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001006 *
1007 * @param direction the scroll direction: {@link android.view.View#FOCUS_UP}
Gilles Debunne2ed2eac2011-02-24 16:29:48 -08001008 * to go upward, {@link android.view.View#FOCUS_DOWN} to downward
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001009 * @param top the top offset of the new area to be made visible
1010 * @param bottom the bottom offset of the new area to be made visible
1011 * @return true if the key event is consumed by this method, false otherwise
1012 */
1013 private boolean scrollAndFocus(int direction, int top, int bottom) {
1014 boolean handled = true;
1015
1016 int height = getHeight();
1017 int containerTop = getScrollY();
1018 int containerBottom = containerTop + height;
1019 boolean up = direction == View.FOCUS_UP;
1020
1021 View newFocused = findFocusableViewInBounds(up, top, bottom);
1022 if (newFocused == null) {
1023 newFocused = this;
1024 }
1025
1026 if (top >= containerTop && bottom <= containerBottom) {
1027 handled = false;
1028 } else {
1029 int delta = up ? (top - containerTop) : (bottom - containerBottom);
1030 doScrollY(delta);
1031 }
1032
Gilles Debunne2ed2eac2011-02-24 16:29:48 -08001033 if (newFocused != findFocus()) newFocused.requestFocus(direction);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001034
1035 return handled;
1036 }
1037
1038 /**
1039 * Handle scrolling in response to an up or down arrow click.
1040 *
1041 * @param direction The direction corresponding to the arrow key that was
1042 * pressed
1043 * @return True if we consumed the event, false otherwise
1044 */
1045 public boolean arrowScroll(int direction) {
1046
1047 View currentFocused = findFocus();
1048 if (currentFocused == this) currentFocused = null;
1049
1050 View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, direction);
1051
1052 final int maxJump = getMaxScrollAmount();
1053
Jack Veenstra7d4200d2009-09-21 19:45:14 -07001054 if (nextFocused != null && isWithinDeltaOfScreen(nextFocused, maxJump, getHeight())) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001055 nextFocused.getDrawingRect(mTempRect);
1056 offsetDescendantRectToMyCoords(nextFocused, mTempRect);
1057 int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
1058 doScrollY(scrollDelta);
1059 nextFocused.requestFocus(direction);
1060 } else {
1061 // no new focus
1062 int scrollDelta = maxJump;
1063
1064 if (direction == View.FOCUS_UP && getScrollY() < scrollDelta) {
1065 scrollDelta = getScrollY();
1066 } else if (direction == View.FOCUS_DOWN) {
Romain Guyef0e9ae2009-07-10 14:11:26 -07001067 if (getChildCount() > 0) {
1068 int daBottom = getChildAt(0).getBottom();
Mattias Petersson5435a062011-04-07 15:46:56 +02001069 int screenBottom = getScrollY() + getHeight() - mPaddingBottom;
Romain Guyef0e9ae2009-07-10 14:11:26 -07001070 if (daBottom - screenBottom < maxJump) {
1071 scrollDelta = daBottom - screenBottom;
1072 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001073 }
1074 }
1075 if (scrollDelta == 0) {
1076 return false;
1077 }
1078 doScrollY(direction == View.FOCUS_DOWN ? scrollDelta : -scrollDelta);
1079 }
1080
1081 if (currentFocused != null && currentFocused.isFocused()
1082 && isOffScreen(currentFocused)) {
1083 // previously focused item still has focus and is off screen, give
1084 // it up (take it back to ourselves)
1085 // (also, need to temporarily force FOCUS_BEFORE_DESCENDANTS so we are
1086 // sure to
1087 // get it)
1088 final int descendantFocusability = getDescendantFocusability(); // save
1089 setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
1090 requestFocus();
1091 setDescendantFocusability(descendantFocusability); // restore
1092 }
1093 return true;
1094 }
1095
1096 /**
1097 * @return whether the descendant of this scroll view is scrolled off
1098 * screen.
1099 */
1100 private boolean isOffScreen(View descendant) {
Jack Veenstra7d4200d2009-09-21 19:45:14 -07001101 return !isWithinDeltaOfScreen(descendant, 0, getHeight());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001102 }
1103
1104 /**
1105 * @return whether the descendant of this scroll view is within delta
1106 * pixels of being on the screen.
1107 */
Jack Veenstra7d4200d2009-09-21 19:45:14 -07001108 private boolean isWithinDeltaOfScreen(View descendant, int delta, int height) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001109 descendant.getDrawingRect(mTempRect);
1110 offsetDescendantRectToMyCoords(descendant, mTempRect);
1111
1112 return (mTempRect.bottom + delta) >= getScrollY()
Jack Veenstra7d4200d2009-09-21 19:45:14 -07001113 && (mTempRect.top - delta) <= (getScrollY() + height);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001114 }
1115
1116 /**
1117 * Smooth scroll by a Y delta
1118 *
1119 * @param delta the number of pixels to scroll by on the Y axis
1120 */
1121 private void doScrollY(int delta) {
1122 if (delta != 0) {
1123 if (mSmoothScrollingEnabled) {
1124 smoothScrollBy(0, delta);
1125 } else {
1126 scrollBy(0, delta);
1127 }
1128 }
1129 }
1130
1131 /**
1132 * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
1133 *
1134 * @param dx the number of pixels to scroll by on the X axis
1135 * @param dy the number of pixels to scroll by on the Y axis
1136 */
1137 public final void smoothScrollBy(int dx, int dy) {
Adam Powell3fc37372010-01-28 13:52:24 -08001138 if (getChildCount() == 0) {
1139 // Nothing to do.
1140 return;
1141 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001142 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
1143 if (duration > ANIMATED_SCROLL_GAP) {
Adam Powellf5446052010-01-28 17:24:56 -08001144 final int height = getHeight() - mPaddingBottom - mPaddingTop;
1145 final int bottom = getChildAt(0).getHeight();
1146 final int maxY = Math.max(0, bottom - height);
1147 final int scrollY = mScrollY;
1148 dy = Math.max(0, Math.min(scrollY + dy, maxY)) - scrollY;
1149
1150 mScroller.startScroll(mScrollX, scrollY, 0, dy);
Adam Powelldf3ae4f2012-04-10 18:55:22 -07001151 postInvalidateOnAnimation();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001152 } else {
1153 if (!mScroller.isFinished()) {
1154 mScroller.abortAnimation();
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -08001155 if (mFlingStrictSpan != null) {
1156 mFlingStrictSpan.finish();
1157 mFlingStrictSpan = null;
1158 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001159 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001160 scrollBy(dx, dy);
1161 }
1162 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
1163 }
1164
1165 /**
1166 * Like {@link #scrollTo}, but scroll smoothly instead of immediately.
1167 *
1168 * @param x the position where to scroll on the X axis
1169 * @param y the position where to scroll on the Y axis
1170 */
1171 public final void smoothScrollTo(int x, int y) {
1172 smoothScrollBy(x - mScrollX, y - mScrollY);
1173 }
1174
1175 /**
1176 * <p>The scroll range of a scroll view is the overall height of all of its
1177 * children.</p>
1178 */
1179 @Override
1180 protected int computeVerticalScrollRange() {
Adam Powella2f91012010-02-16 12:26:33 -08001181 final int count = getChildCount();
1182 final int contentHeight = getHeight() - mPaddingBottom - mPaddingTop;
Adam Powell0b8bb422010-02-08 14:30:45 -08001183 if (count == 0) {
Adam Powella2f91012010-02-16 12:26:33 -08001184 return contentHeight;
Adam Powell0b8bb422010-02-08 14:30:45 -08001185 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08001186
Adam Powell637d3372010-08-25 14:37:03 -07001187 int scrollRange = getChildAt(0).getBottom();
1188 final int scrollY = mScrollY;
1189 final int overscrollBottom = Math.max(0, scrollRange - contentHeight);
1190 if (scrollY < 0) {
1191 scrollRange -= scrollY;
1192 } else if (scrollY > overscrollBottom) {
1193 scrollRange += scrollY - overscrollBottom;
1194 }
1195
1196 return scrollRange;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001197 }
1198
Adam Powell0b8bb422010-02-08 14:30:45 -08001199 @Override
1200 protected int computeVerticalScrollOffset() {
1201 return Math.max(0, super.computeVerticalScrollOffset());
1202 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001203
1204 @Override
1205 protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
1206 ViewGroup.LayoutParams lp = child.getLayoutParams();
1207
1208 int childWidthMeasureSpec;
1209 int childHeightMeasureSpec;
1210
1211 childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft
1212 + mPaddingRight, lp.width);
1213
1214 childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
1215
1216 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
1217 }
1218
1219 @Override
1220 protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
1221 int parentHeightMeasureSpec, int heightUsed) {
1222 final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
1223
1224 final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
1225 mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
1226 + widthUsed, lp.width);
1227 final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
1228 lp.topMargin + lp.bottomMargin, MeasureSpec.UNSPECIFIED);
1229
1230 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
1231 }
1232
1233 @Override
1234 public void computeScroll() {
1235 if (mScroller.computeScrollOffset()) {
1236 // This is called at drawing time by ViewGroup. We don't want to
1237 // re-show the scrollbars at this point, which scrollTo will do,
1238 // so we replicate most of scrollTo here.
1239 //
1240 // It's a little odd to call onScrollChanged from inside the drawing.
1241 //
1242 // It is, except when you remember that computeScroll() is used to
1243 // animate scrolling. So unless we want to defer the onScrollChanged()
1244 // until the end of the animated scrolling, we don't really have a
1245 // choice here.
1246 //
1247 // I agree. The alternative, which I think would be worse, is to post
1248 // something and tell the subclasses later. This is bad because there
1249 // will be a window where mScrollX/Y is different from what the app
1250 // thinks it is.
1251 //
1252 int oldX = mScrollX;
1253 int oldY = mScrollY;
1254 int x = mScroller.getCurrX();
1255 int y = mScroller.getCurrY();
Adam Powell17dfce12010-01-25 18:38:22 -08001256
Adam Powell637d3372010-08-25 14:37:03 -07001257 if (oldX != x || oldY != y) {
Fabrice Di Meglioe9dbef82011-09-12 16:36:46 -07001258 final int range = getScrollRange();
1259 final int overscrollMode = getOverScrollMode();
1260 final boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
1261 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
1262
1263 overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range,
Adam Powell637d3372010-08-25 14:37:03 -07001264 0, mOverflingDistance, false);
1265 onScrollChanged(mScrollX, mScrollY, oldX, oldY);
1266
Fabrice Di Meglioe9dbef82011-09-12 16:36:46 -07001267 if (canOverscroll) {
Adam Powell637d3372010-08-25 14:37:03 -07001268 if (y < 0 && oldY >= 0) {
1269 mEdgeGlowTop.onAbsorb((int) mScroller.getCurrVelocity());
1270 } else if (y > range && oldY <= range) {
1271 mEdgeGlowBottom.onAbsorb((int) mScroller.getCurrVelocity());
1272 }
Adam Powell9d32d242010-03-29 16:02:07 -07001273 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001274 }
Fabrice Di Meglioe9dbef82011-09-12 16:36:46 -07001275
Romain Guye979e622012-03-20 13:50:27 -07001276 if (!awakenScrollBars()) {
1277 // Keep on drawing until the animation has finished.
Adam Powelldf3ae4f2012-04-10 18:55:22 -07001278 postInvalidateOnAnimation();
Romain Guye979e622012-03-20 13:50:27 -07001279 }
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -08001280 } else {
1281 if (mFlingStrictSpan != null) {
1282 mFlingStrictSpan.finish();
1283 mFlingStrictSpan = null;
1284 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001285 }
1286 }
1287
1288 /**
1289 * Scrolls the view to the given child.
1290 *
1291 * @param child the View to scroll to
1292 */
1293 private void scrollToChild(View child) {
1294 child.getDrawingRect(mTempRect);
1295
1296 /* Offset from child's local coordinates to ScrollView coordinates */
1297 offsetDescendantRectToMyCoords(child, mTempRect);
1298
1299 int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
1300
1301 if (scrollDelta != 0) {
1302 scrollBy(0, scrollDelta);
1303 }
1304 }
1305
1306 /**
1307 * If rect is off screen, scroll just enough to get it (or at least the
1308 * first screen size chunk of it) on screen.
1309 *
1310 * @param rect The rectangle.
1311 * @param immediate True to scroll immediately without animation
1312 * @return true if scrolling was performed
1313 */
1314 private boolean scrollToChildRect(Rect rect, boolean immediate) {
1315 final int delta = computeScrollDeltaToGetChildRectOnScreen(rect);
1316 final boolean scroll = delta != 0;
1317 if (scroll) {
1318 if (immediate) {
1319 scrollBy(0, delta);
1320 } else {
1321 smoothScrollBy(0, delta);
1322 }
1323 }
1324 return scroll;
1325 }
1326
1327 /**
1328 * Compute the amount to scroll in the Y direction in order to get
1329 * a rectangle completely on the screen (or, if taller than the screen,
1330 * at least the first screen size chunk of it).
1331 *
1332 * @param rect The rect.
1333 * @return The scroll delta.
1334 */
1335 protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) {
Romain Guyef0e9ae2009-07-10 14:11:26 -07001336 if (getChildCount() == 0) return 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001337
1338 int height = getHeight();
1339 int screenTop = getScrollY();
1340 int screenBottom = screenTop + height;
1341
1342 int fadingEdge = getVerticalFadingEdgeLength();
1343
1344 // leave room for top fading edge as long as rect isn't at very top
1345 if (rect.top > 0) {
1346 screenTop += fadingEdge;
1347 }
1348
1349 // leave room for bottom fading edge as long as rect isn't at very bottom
1350 if (rect.bottom < getChildAt(0).getHeight()) {
1351 screenBottom -= fadingEdge;
1352 }
1353
1354 int scrollYDelta = 0;
1355
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001356 if (rect.bottom > screenBottom && rect.top > screenTop) {
1357 // need to move down to get it in view: move down just enough so
1358 // that the entire rectangle is in view (or at least the first
1359 // screen size chunk).
1360
1361 if (rect.height() > height) {
1362 // just enough to get screen size chunk on
1363 scrollYDelta += (rect.top - screenTop);
1364 } else {
1365 // get entire rect at bottom of screen
1366 scrollYDelta += (rect.bottom - screenBottom);
1367 }
1368
1369 // make sure we aren't scrolling beyond the end of our content
Romain Guyef0e9ae2009-07-10 14:11:26 -07001370 int bottom = getChildAt(0).getBottom();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001371 int distanceToBottom = bottom - screenBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001372 scrollYDelta = Math.min(scrollYDelta, distanceToBottom);
1373
1374 } else if (rect.top < screenTop && rect.bottom < screenBottom) {
1375 // need to move up to get it in view: move up just enough so that
1376 // entire rectangle is in view (or at least the first screen
1377 // size chunk of it).
1378
1379 if (rect.height() > height) {
1380 // screen size chunk
1381 scrollYDelta -= (screenBottom - rect.bottom);
1382 } else {
1383 // entire rect at top
1384 scrollYDelta -= (screenTop - rect.top);
1385 }
1386
1387 // make sure we aren't scrolling any further than the top our content
1388 scrollYDelta = Math.max(scrollYDelta, -getScrollY());
1389 }
1390 return scrollYDelta;
1391 }
1392
1393 @Override
1394 public void requestChildFocus(View child, View focused) {
Gilles Debunne2ed2eac2011-02-24 16:29:48 -08001395 if (!mIsLayoutDirty) {
1396 scrollToChild(focused);
1397 } else {
1398 // The child may not be laid out yet, we can't compute the scroll yet
1399 mChildToScrollTo = focused;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001400 }
1401 super.requestChildFocus(child, focused);
1402 }
1403
1404
1405 /**
1406 * When looking for focus in children of a scroll view, need to be a little
1407 * more careful not to give focus to something that is scrolled off screen.
1408 *
1409 * This is more expensive than the default {@link android.view.ViewGroup}
1410 * implementation, otherwise this behavior might have been made the default.
1411 */
1412 @Override
1413 protected boolean onRequestFocusInDescendants(int direction,
1414 Rect previouslyFocusedRect) {
1415
1416 // convert from forward / backward notation to up / down / left / right
1417 // (ugh).
1418 if (direction == View.FOCUS_FORWARD) {
1419 direction = View.FOCUS_DOWN;
1420 } else if (direction == View.FOCUS_BACKWARD) {
1421 direction = View.FOCUS_UP;
1422 }
1423
1424 final View nextFocus = previouslyFocusedRect == null ?
1425 FocusFinder.getInstance().findNextFocus(this, null, direction) :
1426 FocusFinder.getInstance().findNextFocusFromRect(this,
1427 previouslyFocusedRect, direction);
1428
1429 if (nextFocus == null) {
1430 return false;
1431 }
1432
1433 if (isOffScreen(nextFocus)) {
1434 return false;
1435 }
1436
1437 return nextFocus.requestFocus(direction, previouslyFocusedRect);
Mindy Pereira4e30d892010-11-24 15:32:39 -08001438 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001439
1440 @Override
1441 public boolean requestChildRectangleOnScreen(View child, Rect rectangle,
1442 boolean immediate) {
1443 // offset into coordinate space of this scroll view
1444 rectangle.offset(child.getLeft() - child.getScrollX(),
1445 child.getTop() - child.getScrollY());
1446
1447 return scrollToChildRect(rectangle, immediate);
1448 }
1449
1450 @Override
1451 public void requestLayout() {
1452 mIsLayoutDirty = true;
1453 super.requestLayout();
1454 }
1455
1456 @Override
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -08001457 protected void onDetachedFromWindow() {
1458 super.onDetachedFromWindow();
1459
1460 if (mScrollStrictSpan != null) {
1461 mScrollStrictSpan.finish();
1462 mScrollStrictSpan = null;
1463 }
1464 if (mFlingStrictSpan != null) {
1465 mFlingStrictSpan.finish();
1466 mFlingStrictSpan = null;
1467 }
1468 }
1469
1470 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001471 protected void onLayout(boolean changed, int l, int t, int r, int b) {
1472 super.onLayout(changed, l, t, r, b);
1473 mIsLayoutDirty = false;
Mindy Pereira4e30d892010-11-24 15:32:39 -08001474 // Give a child focus if it needs it
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001475 if (mChildToScrollTo != null && isViewDescendantOf(mChildToScrollTo, this)) {
Romain Guy9c957372011-01-04 17:39:43 -08001476 scrollToChild(mChildToScrollTo);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001477 }
1478 mChildToScrollTo = null;
1479
Chet Haase7a46dde2013-07-17 10:22:53 -07001480 if (!isLaidOut()) {
Fabrice Di Megliod6befbd2013-06-12 16:01:48 -07001481 if (mSavedState != null) {
1482 mScrollY = mSavedState.scrollPosition;
1483 mSavedState = null;
1484 } // mScrollY default value is "0"
Fabrice Di Megliod6d54392013-06-18 14:09:07 -07001485
1486 final int childHeight = (getChildCount() > 0) ? getChildAt(0).getMeasuredHeight() : 0;
1487 final int scrollRange = Math.max(0,
1488 childHeight - (b - t - mPaddingBottom - mPaddingTop));
1489
Fabrice Di Megliod6befbd2013-06-12 16:01:48 -07001490 // Don't forget to clamp
1491 if (mScrollY > scrollRange) {
1492 mScrollY = scrollRange;
1493 } else if (mScrollY < 0) {
1494 mScrollY = 0;
1495 }
1496 }
1497
Ken Wakasaf76a50c2012-03-09 19:56:35 +09001498 // Calling this with the present values causes it to re-claim them
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001499 scrollTo(mScrollX, mScrollY);
1500 }
1501
1502 @Override
1503 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
1504 super.onSizeChanged(w, h, oldw, oldh);
1505
1506 View currentFocused = findFocus();
1507 if (null == currentFocused || this == currentFocused)
1508 return;
1509
Jack Veenstra7d4200d2009-09-21 19:45:14 -07001510 // If the currently-focused view was visible on the screen when the
1511 // screen was at the old height, then scroll the screen to make that
1512 // view visible with the new screen height.
1513 if (isWithinDeltaOfScreen(currentFocused, 0, oldh)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001514 currentFocused.getDrawingRect(mTempRect);
1515 offsetDescendantRectToMyCoords(currentFocused, mTempRect);
1516 int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
1517 doScrollY(scrollDelta);
1518 }
Mindy Pereira4e30d892010-11-24 15:32:39 -08001519 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001520
1521 /**
Ken Wakasaf76a50c2012-03-09 19:56:35 +09001522 * Return true if child is a descendant of parent, (or equal to the parent).
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001523 */
Romain Guye979e622012-03-20 13:50:27 -07001524 private static boolean isViewDescendantOf(View child, View parent) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001525 if (child == parent) {
1526 return true;
1527 }
1528
1529 final ViewParent theParent = child.getParent();
1530 return (theParent instanceof ViewGroup) && isViewDescendantOf((View) theParent, parent);
Mindy Pereira4e30d892010-11-24 15:32:39 -08001531 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001532
1533 /**
1534 * Fling the scroll view
1535 *
1536 * @param velocityY The initial velocity in the Y direction. Positive
Gilles Debunne52964242010-02-24 11:05:19 -08001537 * numbers mean that the finger/cursor is moving down the screen,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001538 * which means we want to scroll towards the top.
1539 */
1540 public void fling(int velocityY) {
Romain Guyef0e9ae2009-07-10 14:11:26 -07001541 if (getChildCount() > 0) {
1542 int height = getHeight() - mPaddingBottom - mPaddingTop;
1543 int bottom = getChildAt(0).getHeight();
Mindy Pereira4e30d892010-11-24 15:32:39 -08001544
1545 mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0,
Adam Powell637d3372010-08-25 14:37:03 -07001546 Math.max(0, bottom - height), 0, height/2);
Mindy Pereira4e30d892010-11-24 15:32:39 -08001547
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -08001548 if (mFlingStrictSpan == null) {
1549 mFlingStrictSpan = StrictMode.enterCriticalSpan("ScrollView-fling");
1550 }
1551
Adam Powelldf3ae4f2012-04-10 18:55:22 -07001552 postInvalidateOnAnimation();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001553 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001554 }
1555
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -08001556 private void endDrag() {
1557 mIsBeingDragged = false;
1558
Michael Jurka13451a42011-08-22 15:54:21 -07001559 recycleVelocityTracker();
Brad Fitzpatrickce81f3a2010-11-21 18:46:08 -08001560
1561 if (mEdgeGlowTop != null) {
1562 mEdgeGlowTop.onRelease();
1563 mEdgeGlowBottom.onRelease();
1564 }
1565
1566 if (mScrollStrictSpan != null) {
1567 mScrollStrictSpan.finish();
1568 mScrollStrictSpan = null;
1569 }
1570 }
1571
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001572 /**
1573 * {@inheritDoc}
1574 *
1575 * <p>This version also clamps the scrolling to the bounds of our child.
1576 */
Gilles Debunne52964242010-02-24 11:05:19 -08001577 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001578 public void scrollTo(int x, int y) {
1579 // we rely on the fact the View.scrollBy calls scrollTo.
1580 if (getChildCount() > 0) {
1581 View child = getChildAt(0);
1582 x = clamp(x, getWidth() - mPaddingRight - mPaddingLeft, child.getWidth());
1583 y = clamp(y, getHeight() - mPaddingBottom - mPaddingTop, child.getHeight());
1584 if (x != mScrollX || y != mScrollY) {
1585 super.scrollTo(x, y);
1586 }
1587 }
1588 }
1589
Adam Powell637d3372010-08-25 14:37:03 -07001590 @Override
1591 public void setOverScrollMode(int mode) {
1592 if (mode != OVER_SCROLL_NEVER) {
1593 if (mEdgeGlowTop == null) {
Mindy Pereira4e30d892010-11-24 15:32:39 -08001594 Context context = getContext();
Adam Powell89935e42011-08-31 14:26:12 -07001595 mEdgeGlowTop = new EdgeEffect(context);
1596 mEdgeGlowBottom = new EdgeEffect(context);
Adam Powell637d3372010-08-25 14:37:03 -07001597 }
1598 } else {
1599 mEdgeGlowTop = null;
1600 mEdgeGlowBottom = null;
1601 }
1602 super.setOverScrollMode(mode);
1603 }
1604
1605 @Override
1606 public void draw(Canvas canvas) {
1607 super.draw(canvas);
1608 if (mEdgeGlowTop != null) {
1609 final int scrollY = mScrollY;
1610 if (!mEdgeGlowTop.isFinished()) {
1611 final int restoreCount = canvas.save();
Adam Powell7d863782011-02-15 15:05:03 -08001612 final int width = getWidth() - mPaddingLeft - mPaddingRight;
Adam Powell637d3372010-08-25 14:37:03 -07001613
Adam Powell7d863782011-02-15 15:05:03 -08001614 canvas.translate(mPaddingLeft, Math.min(0, scrollY));
Mindy Pereirab1297f72010-12-07 15:06:47 -08001615 mEdgeGlowTop.setSize(width, getHeight());
Adam Powell637d3372010-08-25 14:37:03 -07001616 if (mEdgeGlowTop.draw(canvas)) {
Adam Powelldf3ae4f2012-04-10 18:55:22 -07001617 postInvalidateOnAnimation();
Adam Powell637d3372010-08-25 14:37:03 -07001618 }
1619 canvas.restoreToCount(restoreCount);
1620 }
1621 if (!mEdgeGlowBottom.isFinished()) {
1622 final int restoreCount = canvas.save();
Adam Powell7d863782011-02-15 15:05:03 -08001623 final int width = getWidth() - mPaddingLeft - mPaddingRight;
Adam Powell637d3372010-08-25 14:37:03 -07001624 final int height = getHeight();
1625
Adam Powell7d863782011-02-15 15:05:03 -08001626 canvas.translate(-width + mPaddingLeft,
1627 Math.max(getScrollRange(), scrollY) + height);
Adam Powell637d3372010-08-25 14:37:03 -07001628 canvas.rotate(180, width, 0);
Mindy Pereirab1297f72010-12-07 15:06:47 -08001629 mEdgeGlowBottom.setSize(width, height);
Adam Powell637d3372010-08-25 14:37:03 -07001630 if (mEdgeGlowBottom.draw(canvas)) {
Adam Powelldf3ae4f2012-04-10 18:55:22 -07001631 postInvalidateOnAnimation();
Adam Powell637d3372010-08-25 14:37:03 -07001632 }
1633 canvas.restoreToCount(restoreCount);
1634 }
1635 }
1636 }
1637
Romain Guye979e622012-03-20 13:50:27 -07001638 private static int clamp(int n, int my, int child) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001639 if (my >= child || n < 0) {
1640 /* my >= child is this case:
1641 * |--------------- me ---------------|
1642 * |------ child ------|
1643 * or
1644 * |--------------- me ---------------|
1645 * |------ child ------|
1646 * or
1647 * |--------------- me ---------------|
1648 * |------ child ------|
1649 *
1650 * n < 0 is this case:
1651 * |------ me ------|
1652 * |-------- child --------|
1653 * |-- mScrollX --|
1654 */
1655 return 0;
1656 }
1657 if ((my+n) > child) {
1658 /* this case:
1659 * |------ me ------|
1660 * |------ child ------|
1661 * |-- mScrollX --|
1662 */
1663 return child-my;
1664 }
1665 return n;
1666 }
Fabrice Di Megliod6befbd2013-06-12 16:01:48 -07001667
1668 @Override
1669 protected void onRestoreInstanceState(Parcelable state) {
Adam Powell90f339a2013-06-13 17:44:04 -07001670 if (mContext.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR2) {
1671 // Some old apps reused IDs in ways they shouldn't have.
1672 // Don't break them, but they don't get scroll state restoration.
1673 super.onRestoreInstanceState(state);
1674 return;
1675 }
Fabrice Di Megliod6befbd2013-06-12 16:01:48 -07001676 SavedState ss = (SavedState) state;
1677 super.onRestoreInstanceState(ss.getSuperState());
1678 mSavedState = ss;
1679 requestLayout();
1680 }
1681
1682 @Override
1683 protected Parcelable onSaveInstanceState() {
Adam Powell90f339a2013-06-13 17:44:04 -07001684 if (mContext.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR2) {
1685 // Some old apps reused IDs in ways they shouldn't have.
1686 // Don't break them, but they don't get scroll state restoration.
1687 return super.onSaveInstanceState();
1688 }
Fabrice Di Megliod6befbd2013-06-12 16:01:48 -07001689 Parcelable superState = super.onSaveInstanceState();
1690 SavedState ss = new SavedState(superState);
1691 ss.scrollPosition = mScrollY;
1692 return ss;
1693 }
1694
1695 static class SavedState extends BaseSavedState {
1696 public int scrollPosition;
1697
1698 SavedState(Parcelable superState) {
1699 super(superState);
1700 }
1701
1702 public SavedState(Parcel source) {
1703 super(source);
1704 scrollPosition = source.readInt();
1705 }
1706
1707 @Override
1708 public void writeToParcel(Parcel dest, int flags) {
1709 super.writeToParcel(dest, flags);
1710 dest.writeInt(scrollPosition);
1711 }
1712
1713 @Override
1714 public String toString() {
1715 return "HorizontalScrollView.SavedState{"
1716 + Integer.toHexString(System.identityHashCode(this))
1717 + " scrollPosition=" + scrollPosition + "}";
1718 }
1719
1720 public static final Parcelable.Creator<SavedState> CREATOR
1721 = new Parcelable.Creator<SavedState>() {
1722 public SavedState createFromParcel(Parcel in) {
1723 return new SavedState(in);
1724 }
1725
1726 public SavedState[] newArray(int size) {
1727 return new SavedState[size];
1728 }
1729 };
1730 }
1731
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001732}