ScrollViewpublic class ScrollView extends FrameLayout Layout container for a view hierarchy that can be scrolled by the user,
allowing it to be larger than the physical display. A ScrollView
is a {@link FrameLayout}, meaning you should place one child in it
containing the entire contents to scroll; this child may itself be a layout
manager with a complex hierarchy of objects. A child that is often used
is a {@link LinearLayout} in a vertical orientation, presenting a vertical
array of top-level items that the user can scroll through.
You should never use a ScrollView with a {@link ListView}, because
ListView takes care of its own vertical scrolling. Most importantly, doing this
defeats all of the important optimizations in ListView for dealing with
large lists, since it effectively forces the ListView to display its entire
list of items to fill up the infinite container supplied by ScrollView.
The {@link TextView} class also
takes care of its own scrolling, so does not require a ScrollView, but
using the two together is possible to achieve the effect of a text view
within a larger container.
ScrollView only supports vertical scrolling. For horizontal scrolling,
use {@link HorizontalScrollView}. |
Fields Summary |
---|
static final int | ANIMATED_SCROLL_GAP | static final float | MAX_SCROLL_FACTOR | private static final String | TAG | private long | mLastScroll | private final android.graphics.Rect | mTempRect | private OverScroller | mScroller | private EdgeEffect | mEdgeGlowTop | private EdgeEffect | mEdgeGlowBottom | private int | mLastMotionYPosition of the last motion event. | private boolean | mIsLayoutDirtyTrue when the layout has changed but the traversal has not come through yet.
Ideally the view hierarchy would keep track of this for us. | private android.view.View | mChildToScrollToThe child to give focus to in the event that a child has requested focus while the
layout is dirty. This prevents the scroll from being wrong if the child has not been
laid out before requesting focus. | private boolean | mIsBeingDraggedTrue if the user is currently dragging this ScrollView around. This is
not the same as 'is being flinged', which can be checked by
mScroller.isFinished() (flinging begins when the user lifts his finger). | private android.view.VelocityTracker | mVelocityTrackerDetermines speed during touch scrolling | private boolean | mFillViewportWhen set to true, the scroll view measure its child to make it fill the currently
visible area. | private boolean | mSmoothScrollingEnabledWhether arrow scrolling is animated. | private int | mTouchSlop | private int | mMinimumVelocity | private int | mMaximumVelocity | private int | mOverscrollDistance | private int | mOverflingDistance | private int | mActivePointerIdID of the active pointer. This is used to retain consistency during
drags/flings if multiple pointers are used. | private final int[] | mScrollOffsetUsed during scrolling to retrieve the new offset within the window. | private final int[] | mScrollConsumed | private int | mNestedYOffset | private StrictMode.Span | mScrollStrictSpanThe StrictMode "critical time span" objects to catch animation
stutters. Non-null when a time-sensitive animation is
in-flight. Must call finish() on them when done animating.
These are no-ops on user builds. | private StrictMode.Span | mFlingStrictSpan | private static final int | INVALID_POINTERSentinel value for no current active pointer.
Used by {@link #mActivePointerId}. | private SavedState | mSavedState |
Constructors Summary |
---|
public ScrollView(android.content.Context context)
this(context, null);
| public ScrollView(android.content.Context context, android.util.AttributeSet attrs)
this(context, attrs, com.android.internal.R.attr.scrollViewStyle);
| public ScrollView(android.content.Context context, android.util.AttributeSet attrs, int defStyleAttr)
this(context, attrs, defStyleAttr, 0);
| public ScrollView(android.content.Context context, android.util.AttributeSet attrs, int defStyleAttr, int defStyleRes)
super(context, attrs, defStyleAttr, defStyleRes);
initScrollView();
final TypedArray a = context.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.ScrollView, defStyleAttr, defStyleRes);
setFillViewport(a.getBoolean(R.styleable.ScrollView_fillViewport, false));
a.recycle();
|
Methods Summary |
---|
public void | addView(android.view.View child)
if (getChildCount() > 0) {
throw new IllegalStateException("ScrollView can host only one direct child");
}
super.addView(child);
| public void | addView(android.view.View child, int index)
if (getChildCount() > 0) {
throw new IllegalStateException("ScrollView can host only one direct child");
}
super.addView(child, index);
| public void | addView(android.view.View child, ViewGroup.LayoutParams params)
if (getChildCount() > 0) {
throw new IllegalStateException("ScrollView can host only one direct child");
}
super.addView(child, params);
| public void | addView(android.view.View child, int index, ViewGroup.LayoutParams params)
if (getChildCount() > 0) {
throw new IllegalStateException("ScrollView can host only one direct child");
}
super.addView(child, index, params);
| public boolean | arrowScroll(int direction)Handle scrolling in response to an up or down arrow click.
View currentFocused = findFocus();
if (currentFocused == this) currentFocused = null;
View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, direction);
final int maxJump = getMaxScrollAmount();
if (nextFocused != null && isWithinDeltaOfScreen(nextFocused, maxJump, getHeight())) {
nextFocused.getDrawingRect(mTempRect);
offsetDescendantRectToMyCoords(nextFocused, mTempRect);
int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
doScrollY(scrollDelta);
nextFocused.requestFocus(direction);
} else {
// no new focus
int scrollDelta = maxJump;
if (direction == View.FOCUS_UP && getScrollY() < scrollDelta) {
scrollDelta = getScrollY();
} else if (direction == View.FOCUS_DOWN) {
if (getChildCount() > 0) {
int daBottom = getChildAt(0).getBottom();
int screenBottom = getScrollY() + getHeight() - mPaddingBottom;
if (daBottom - screenBottom < maxJump) {
scrollDelta = daBottom - screenBottom;
}
}
}
if (scrollDelta == 0) {
return false;
}
doScrollY(direction == View.FOCUS_DOWN ? scrollDelta : -scrollDelta);
}
if (currentFocused != null && currentFocused.isFocused()
&& isOffScreen(currentFocused)) {
// previously focused item still has focus and is off screen, give
// it up (take it back to ourselves)
// (also, need to temporarily force FOCUS_BEFORE_DESCENDANTS so we are
// sure to
// get it)
final int descendantFocusability = getDescendantFocusability(); // save
setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
requestFocus();
setDescendantFocusability(descendantFocusability); // restore
}
return true;
| private boolean | canScroll()
View child = getChildAt(0);
if (child != null) {
int childHeight = child.getHeight();
return getHeight() < childHeight + mPaddingTop + mPaddingBottom;
}
return false;
| private static int | clamp(int n, int my, int child)
if (my >= child || n < 0) {
/* my >= child is this case:
* |--------------- me ---------------|
* |------ child ------|
* or
* |--------------- me ---------------|
* |------ child ------|
* or
* |--------------- me ---------------|
* |------ child ------|
*
* n < 0 is this case:
* |------ me ------|
* |-------- child --------|
* |-- mScrollX --|
*/
return 0;
}
if ((my+n) > child) {
/* this case:
* |------ me ------|
* |------ child ------|
* |-- mScrollX --|
*/
return child-my;
}
return n;
| public void | computeScroll()
if (mScroller.computeScrollOffset()) {
// This is called at drawing time by ViewGroup. We don't want to
// re-show the scrollbars at this point, which scrollTo will do,
// so we replicate most of scrollTo here.
//
// It's a little odd to call onScrollChanged from inside the drawing.
//
// It is, except when you remember that computeScroll() is used to
// animate scrolling. So unless we want to defer the onScrollChanged()
// until the end of the animated scrolling, we don't really have a
// choice here.
//
// I agree. The alternative, which I think would be worse, is to post
// something and tell the subclasses later. This is bad because there
// will be a window where mScrollX/Y is different from what the app
// thinks it is.
//
int oldX = mScrollX;
int oldY = mScrollY;
int x = mScroller.getCurrX();
int y = mScroller.getCurrY();
if (oldX != x || oldY != y) {
final int range = getScrollRange();
final int overscrollMode = getOverScrollMode();
final boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
(overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range,
0, mOverflingDistance, false);
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
if (canOverscroll) {
if (y < 0 && oldY >= 0) {
mEdgeGlowTop.onAbsorb((int) mScroller.getCurrVelocity());
} else if (y > range && oldY <= range) {
mEdgeGlowBottom.onAbsorb((int) mScroller.getCurrVelocity());
}
}
}
if (!awakenScrollBars()) {
// Keep on drawing until the animation has finished.
postInvalidateOnAnimation();
}
} else {
if (mFlingStrictSpan != null) {
mFlingStrictSpan.finish();
mFlingStrictSpan = null;
}
}
| protected int | computeScrollDeltaToGetChildRectOnScreen(android.graphics.Rect rect)Compute the amount to scroll in the Y direction in order to get
a rectangle completely on the screen (or, if taller than the screen,
at least the first screen size chunk of it).
if (getChildCount() == 0) return 0;
int height = getHeight();
int screenTop = getScrollY();
int screenBottom = screenTop + height;
int fadingEdge = getVerticalFadingEdgeLength();
// leave room for top fading edge as long as rect isn't at very top
if (rect.top > 0) {
screenTop += fadingEdge;
}
// leave room for bottom fading edge as long as rect isn't at very bottom
if (rect.bottom < getChildAt(0).getHeight()) {
screenBottom -= fadingEdge;
}
int scrollYDelta = 0;
if (rect.bottom > screenBottom && rect.top > screenTop) {
// need to move down to get it in view: move down just enough so
// that the entire rectangle is in view (or at least the first
// screen size chunk).
if (rect.height() > height) {
// just enough to get screen size chunk on
scrollYDelta += (rect.top - screenTop);
} else {
// get entire rect at bottom of screen
scrollYDelta += (rect.bottom - screenBottom);
}
// make sure we aren't scrolling beyond the end of our content
int bottom = getChildAt(0).getBottom();
int distanceToBottom = bottom - screenBottom;
scrollYDelta = Math.min(scrollYDelta, distanceToBottom);
} else if (rect.top < screenTop && rect.bottom < screenBottom) {
// need to move up to get it in view: move up just enough so that
// entire rectangle is in view (or at least the first screen
// size chunk of it).
if (rect.height() > height) {
// screen size chunk
scrollYDelta -= (screenBottom - rect.bottom);
} else {
// entire rect at top
scrollYDelta -= (screenTop - rect.top);
}
// make sure we aren't scrolling any further than the top our content
scrollYDelta = Math.max(scrollYDelta, -getScrollY());
}
return scrollYDelta;
| protected int | computeVerticalScrollOffset()
return Math.max(0, super.computeVerticalScrollOffset());
| protected int | computeVerticalScrollRange()The scroll range of a scroll view is the overall height of all of its
children.
final int count = getChildCount();
final int contentHeight = getHeight() - mPaddingBottom - mPaddingTop;
if (count == 0) {
return contentHeight;
}
int scrollRange = getChildAt(0).getBottom();
final int scrollY = mScrollY;
final int overscrollBottom = Math.max(0, scrollRange - contentHeight);
if (scrollY < 0) {
scrollRange -= scrollY;
} else if (scrollY > overscrollBottom) {
scrollRange += scrollY - overscrollBottom;
}
return scrollRange;
| public boolean | dispatchKeyEvent(android.view.KeyEvent event)
// Let the focused view and/or our descendants get the key first
return super.dispatchKeyEvent(event) || executeKeyEvent(event);
| private void | doScrollY(int delta)Smooth scroll by a Y delta
if (delta != 0) {
if (mSmoothScrollingEnabled) {
smoothScrollBy(0, delta);
} else {
scrollBy(0, delta);
}
}
| public void | draw(android.graphics.Canvas canvas)
super.draw(canvas);
if (mEdgeGlowTop != null) {
final int scrollY = mScrollY;
if (!mEdgeGlowTop.isFinished()) {
final int restoreCount = canvas.save();
final int width = getWidth() - mPaddingLeft - mPaddingRight;
canvas.translate(mPaddingLeft, Math.min(0, scrollY));
mEdgeGlowTop.setSize(width, getHeight());
if (mEdgeGlowTop.draw(canvas)) {
postInvalidateOnAnimation();
}
canvas.restoreToCount(restoreCount);
}
if (!mEdgeGlowBottom.isFinished()) {
final int restoreCount = canvas.save();
final int width = getWidth() - mPaddingLeft - mPaddingRight;
final int height = getHeight();
canvas.translate(-width + mPaddingLeft,
Math.max(getScrollRange(), scrollY) + height);
canvas.rotate(180, width, 0);
mEdgeGlowBottom.setSize(width, height);
if (mEdgeGlowBottom.draw(canvas)) {
postInvalidateOnAnimation();
}
canvas.restoreToCount(restoreCount);
}
}
| private void | endDrag()
mIsBeingDragged = false;
recycleVelocityTracker();
if (mEdgeGlowTop != null) {
mEdgeGlowTop.onRelease();
mEdgeGlowBottom.onRelease();
}
if (mScrollStrictSpan != null) {
mScrollStrictSpan.finish();
mScrollStrictSpan = null;
}
| public boolean | executeKeyEvent(android.view.KeyEvent event)You can call this function yourself to have the scroll view perform
scrolling from a key event, just as if the event had been dispatched to
it by the view hierarchy.
mTempRect.setEmpty();
if (!canScroll()) {
if (isFocused() && event.getKeyCode() != KeyEvent.KEYCODE_BACK) {
View currentFocused = findFocus();
if (currentFocused == this) currentFocused = null;
View nextFocused = FocusFinder.getInstance().findNextFocus(this,
currentFocused, View.FOCUS_DOWN);
return nextFocused != null
&& nextFocused != this
&& nextFocused.requestFocus(View.FOCUS_DOWN);
}
return false;
}
boolean handled = false;
if (event.getAction() == KeyEvent.ACTION_DOWN) {
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_DPAD_UP:
if (!event.isAltPressed()) {
handled = arrowScroll(View.FOCUS_UP);
} else {
handled = fullScroll(View.FOCUS_UP);
}
break;
case KeyEvent.KEYCODE_DPAD_DOWN:
if (!event.isAltPressed()) {
handled = arrowScroll(View.FOCUS_DOWN);
} else {
handled = fullScroll(View.FOCUS_DOWN);
}
break;
case KeyEvent.KEYCODE_SPACE:
pageScroll(event.isShiftPressed() ? View.FOCUS_UP : View.FOCUS_DOWN);
break;
}
}
return handled;
| private android.view.View | findFocusableViewInBounds(boolean topFocus, int top, int bottom)
Finds the next focusable component that fits in the specified bounds.
List<View> focusables = getFocusables(View.FOCUS_FORWARD);
View focusCandidate = null;
/*
* A fully contained focusable is one where its top is below the bound's
* top, and its bottom is above the bound's bottom. A partially
* contained focusable is one where some part of it is within the
* bounds, but it also has some part that is not within bounds. A fully contained
* focusable is preferred to a partially contained focusable.
*/
boolean foundFullyContainedFocusable = false;
int count = focusables.size();
for (int i = 0; i < count; i++) {
View view = focusables.get(i);
int viewTop = view.getTop();
int viewBottom = view.getBottom();
if (top < viewBottom && viewTop < bottom) {
/*
* the focusable is in the target area, it is a candidate for
* focusing
*/
final boolean viewIsFullyContained = (top < viewTop) &&
(viewBottom < bottom);
if (focusCandidate == null) {
/* No candidate, take this one */
focusCandidate = view;
foundFullyContainedFocusable = viewIsFullyContained;
} else {
final boolean viewIsCloserToBoundary =
(topFocus && viewTop < focusCandidate.getTop()) ||
(!topFocus && viewBottom > focusCandidate
.getBottom());
if (foundFullyContainedFocusable) {
if (viewIsFullyContained && viewIsCloserToBoundary) {
/*
* We're dealing with only fully contained views, so
* it has to be closer to the boundary to beat our
* candidate
*/
focusCandidate = view;
}
} else {
if (viewIsFullyContained) {
/* Any fully contained view beats a partially contained view */
focusCandidate = view;
foundFullyContainedFocusable = true;
} else if (viewIsCloserToBoundary) {
/*
* Partially contained view beats another partially
* contained view if it's closer
*/
focusCandidate = view;
}
}
}
}
}
return focusCandidate;
| public void | fling(int velocityY)Fling the scroll view
if (getChildCount() > 0) {
int height = getHeight() - mPaddingBottom - mPaddingTop;
int bottom = getChildAt(0).getHeight();
mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0,
Math.max(0, bottom - height), 0, height/2);
if (mFlingStrictSpan == null) {
mFlingStrictSpan = StrictMode.enterCriticalSpan("ScrollView-fling");
}
postInvalidateOnAnimation();
}
| private void | flingWithNestedDispatch(int velocityY)
final boolean canFling = (mScrollY > 0 || velocityY > 0) &&
(mScrollY < getScrollRange() || velocityY < 0);
if (!dispatchNestedPreFling(0, velocityY)) {
dispatchNestedFling(0, velocityY, canFling);
if (canFling) {
fling(velocityY);
}
}
| public boolean | fullScroll(int direction)Handles scrolling in response to a "home/end" shortcut press. This
method will scroll the view to the top or bottom and give the focus
to the topmost/bottommost component in the new visible area. If no
component is a good candidate for focus, this scrollview reclaims the
focus.
boolean down = direction == View.FOCUS_DOWN;
int height = getHeight();
mTempRect.top = 0;
mTempRect.bottom = height;
if (down) {
int count = getChildCount();
if (count > 0) {
View view = getChildAt(count - 1);
mTempRect.bottom = view.getBottom() + mPaddingBottom;
mTempRect.top = mTempRect.bottom - height;
}
}
return scrollAndFocus(direction, mTempRect.top, mTempRect.bottom);
| protected float | getBottomFadingEdgeStrength()
if (getChildCount() == 0) {
return 0.0f;
}
final int length = getVerticalFadingEdgeLength();
final int bottomEdge = getHeight() - mPaddingBottom;
final int span = getChildAt(0).getBottom() - mScrollY - bottomEdge;
if (span < length) {
return span / (float) length;
}
return 1.0f;
| public int | getMaxScrollAmount()
return (int) (MAX_SCROLL_FACTOR * (mBottom - mTop));
| private int | getScrollRange()
int scrollRange = 0;
if (getChildCount() > 0) {
View child = getChildAt(0);
scrollRange = Math.max(0,
child.getHeight() - (getHeight() - mPaddingBottom - mPaddingTop));
}
return scrollRange;
| protected float | getTopFadingEdgeStrength()
if (getChildCount() == 0) {
return 0.0f;
}
final int length = getVerticalFadingEdgeLength();
if (mScrollY < length) {
return mScrollY / (float) length;
}
return 1.0f;
| private boolean | inChild(int x, int y)
if (getChildCount() > 0) {
final int scrollY = mScrollY;
final View child = getChildAt(0);
return !(y < child.getTop() - scrollY
|| y >= child.getBottom() - scrollY
|| x < child.getLeft()
|| x >= child.getRight());
}
return false;
| private void | initOrResetVelocityTracker()
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
} else {
mVelocityTracker.clear();
}
| private void | initScrollView()
mScroller = new OverScroller(getContext());
setFocusable(true);
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
setWillNotDraw(false);
final ViewConfiguration configuration = ViewConfiguration.get(mContext);
mTouchSlop = configuration.getScaledTouchSlop();
mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
mOverscrollDistance = configuration.getScaledOverscrollDistance();
mOverflingDistance = configuration.getScaledOverflingDistance();
| private void | initVelocityTrackerIfNotExists()
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
| public boolean | isFillViewport()Indicates whether this ScrollView's content is stretched to fill the viewport.
return mFillViewport;
| private boolean | isOffScreen(android.view.View descendant)
return !isWithinDeltaOfScreen(descendant, 0, getHeight());
| public boolean | isSmoothScrollingEnabled()
return mSmoothScrollingEnabled;
| private static boolean | isViewDescendantOf(android.view.View child, android.view.View parent)Return true if child is a descendant of parent, (or equal to the parent).
if (child == parent) {
return true;
}
final ViewParent theParent = child.getParent();
return (theParent instanceof ViewGroup) && isViewDescendantOf((View) theParent, parent);
| private boolean | isWithinDeltaOfScreen(android.view.View descendant, int delta, int height)
descendant.getDrawingRect(mTempRect);
offsetDescendantRectToMyCoords(descendant, mTempRect);
return (mTempRect.bottom + delta) >= getScrollY()
&& (mTempRect.top - delta) <= (getScrollY() + height);
| protected void | measureChild(android.view.View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec)
ViewGroup.LayoutParams lp = child.getLayoutParams();
int childWidthMeasureSpec;
int childHeightMeasureSpec;
childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft
+ mPaddingRight, lp.width);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
| protected void | measureChildWithMargins(android.view.View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed)
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
lp.topMargin + lp.bottomMargin, MeasureSpec.UNSPECIFIED);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
| protected void | onDetachedFromWindow()
super.onDetachedFromWindow();
if (mScrollStrictSpan != null) {
mScrollStrictSpan.finish();
mScrollStrictSpan = null;
}
if (mFlingStrictSpan != null) {
mFlingStrictSpan.finish();
mFlingStrictSpan = null;
}
| public boolean | onGenericMotionEvent(android.view.MotionEvent event)
if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
switch (event.getAction()) {
case MotionEvent.ACTION_SCROLL: {
if (!mIsBeingDragged) {
final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
if (vscroll != 0) {
final int delta = (int) (vscroll * getVerticalScrollFactor());
final int range = getScrollRange();
int oldScrollY = mScrollY;
int newScrollY = oldScrollY - delta;
if (newScrollY < 0) {
newScrollY = 0;
} else if (newScrollY > range) {
newScrollY = range;
}
if (newScrollY != oldScrollY) {
super.scrollTo(mScrollX, newScrollY);
return true;
}
}
}
}
}
}
return super.onGenericMotionEvent(event);
| public void | onInitializeAccessibilityEvent(android.view.accessibility.AccessibilityEvent event)
super.onInitializeAccessibilityEvent(event);
event.setClassName(ScrollView.class.getName());
final boolean scrollable = getScrollRange() > 0;
event.setScrollable(scrollable);
event.setScrollX(mScrollX);
event.setScrollY(mScrollY);
event.setMaxScrollX(mScrollX);
event.setMaxScrollY(getScrollRange());
| public void | onInitializeAccessibilityNodeInfo(android.view.accessibility.AccessibilityNodeInfo info)
super.onInitializeAccessibilityNodeInfo(info);
info.setClassName(ScrollView.class.getName());
if (isEnabled()) {
final int scrollRange = getScrollRange();
if (scrollRange > 0) {
info.setScrollable(true);
if (mScrollY > 0) {
info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
}
if (mScrollY < scrollRange) {
info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
}
}
}
| public boolean | onInterceptTouchEvent(android.view.MotionEvent ev)
/*
* This method JUST determines whether we want to intercept the motion.
* If we return true, onMotionEvent will be called and we do the actual
* scrolling there.
*/
/*
* Shortcut the most recurring case: the user is in the dragging
* state and he is moving his finger. We want to intercept this
* motion.
*/
final int action = ev.getAction();
if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
return true;
}
/*
* Don't try to intercept touch if we can't scroll anyway.
*/
if (getScrollY() == 0 && !canScrollVertically(1)) {
return false;
}
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_MOVE: {
/*
* mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
* whether the user has moved far enough from his original down touch.
*/
/*
* Locally do absolute value. mLastMotionY is set to the y value
* of the down event.
*/
final int activePointerId = mActivePointerId;
if (activePointerId == INVALID_POINTER) {
// If we don't have a valid id, the touch down wasn't on content.
break;
}
final int pointerIndex = ev.findPointerIndex(activePointerId);
if (pointerIndex == -1) {
Log.e(TAG, "Invalid pointerId=" + activePointerId
+ " in onInterceptTouchEvent");
break;
}
final int y = (int) ev.getY(pointerIndex);
final int yDiff = Math.abs(y - mLastMotionY);
if (yDiff > mTouchSlop && (getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) {
mIsBeingDragged = true;
mLastMotionY = y;
initVelocityTrackerIfNotExists();
mVelocityTracker.addMovement(ev);
mNestedYOffset = 0;
if (mScrollStrictSpan == null) {
mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll");
}
final ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
}
break;
}
case MotionEvent.ACTION_DOWN: {
final int y = (int) ev.getY();
if (!inChild((int) ev.getX(), (int) y)) {
mIsBeingDragged = false;
recycleVelocityTracker();
break;
}
/*
* Remember location of down touch.
* ACTION_DOWN always refers to pointer index 0.
*/
mLastMotionY = y;
mActivePointerId = ev.getPointerId(0);
initOrResetVelocityTracker();
mVelocityTracker.addMovement(ev);
/*
* If being flinged and user touches the screen, initiate drag;
* otherwise don't. mScroller.isFinished should be false when
* being flinged.
*/
mIsBeingDragged = !mScroller.isFinished();
if (mIsBeingDragged && mScrollStrictSpan == null) {
mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll");
}
startNestedScroll(SCROLL_AXIS_VERTICAL);
break;
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
/* Release the drag */
mIsBeingDragged = false;
mActivePointerId = INVALID_POINTER;
recycleVelocityTracker();
if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) {
postInvalidateOnAnimation();
}
stopNestedScroll();
break;
case MotionEvent.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
break;
}
/*
* The only time we want to intercept motion events is if we are in the
* drag mode.
*/
return mIsBeingDragged;
| protected void | onLayout(boolean changed, int l, int t, int r, int b)
super.onLayout(changed, l, t, r, b);
mIsLayoutDirty = false;
// Give a child focus if it needs it
if (mChildToScrollTo != null && isViewDescendantOf(mChildToScrollTo, this)) {
scrollToChild(mChildToScrollTo);
}
mChildToScrollTo = null;
if (!isLaidOut()) {
if (mSavedState != null) {
mScrollY = mSavedState.scrollPosition;
mSavedState = null;
} // mScrollY default value is "0"
final int childHeight = (getChildCount() > 0) ? getChildAt(0).getMeasuredHeight() : 0;
final int scrollRange = Math.max(0,
childHeight - (b - t - mPaddingBottom - mPaddingTop));
// Don't forget to clamp
if (mScrollY > scrollRange) {
mScrollY = scrollRange;
} else if (mScrollY < 0) {
mScrollY = 0;
}
}
// Calling this with the present values causes it to re-claim them
scrollTo(mScrollX, mScrollY);
| protected void | onMeasure(int widthMeasureSpec, int heightMeasureSpec)
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (!mFillViewport) {
return;
}
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (heightMode == MeasureSpec.UNSPECIFIED) {
return;
}
if (getChildCount() > 0) {
final View child = getChildAt(0);
int height = getMeasuredHeight();
if (child.getMeasuredHeight() < height) {
final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
height -= mPaddingTop;
height -= mPaddingBottom;
int childHeightMeasureSpec =
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
| public boolean | onNestedFling(android.view.View target, float velocityX, float velocityY, boolean consumed)
if (!consumed) {
flingWithNestedDispatch((int) velocityY);
return true;
}
return false;
| public void | onNestedScroll(android.view.View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed)
final int oldScrollY = mScrollY;
scrollBy(0, dyUnconsumed);
final int myConsumed = mScrollY - oldScrollY;
final int myUnconsumed = dyUnconsumed - myConsumed;
dispatchNestedScroll(0, myConsumed, 0, myUnconsumed, null);
| public void | onNestedScrollAccepted(android.view.View child, android.view.View target, int axes)
super.onNestedScrollAccepted(child, target, axes);
startNestedScroll(SCROLL_AXIS_VERTICAL);
| protected void | onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY)
// Treat animating scrolls differently; see #computeScroll() for why.
if (!mScroller.isFinished()) {
final int oldX = mScrollX;
final int oldY = mScrollY;
mScrollX = scrollX;
mScrollY = scrollY;
invalidateParentIfNeeded();
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
if (clampedY) {
mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange());
}
} else {
super.scrollTo(scrollX, scrollY);
}
awakenScrollBars();
| protected boolean | onRequestFocusInDescendants(int direction, android.graphics.Rect previouslyFocusedRect)When looking for focus in children of a scroll view, need to be a little
more careful not to give focus to something that is scrolled off screen.
This is more expensive than the default {@link android.view.ViewGroup}
implementation, otherwise this behavior might have been made the default.
// convert from forward / backward notation to up / down / left / right
// (ugh).
if (direction == View.FOCUS_FORWARD) {
direction = View.FOCUS_DOWN;
} else if (direction == View.FOCUS_BACKWARD) {
direction = View.FOCUS_UP;
}
final View nextFocus = previouslyFocusedRect == null ?
FocusFinder.getInstance().findNextFocus(this, null, direction) :
FocusFinder.getInstance().findNextFocusFromRect(this,
previouslyFocusedRect, direction);
if (nextFocus == null) {
return false;
}
if (isOffScreen(nextFocus)) {
return false;
}
return nextFocus.requestFocus(direction, previouslyFocusedRect);
| protected void | onRestoreInstanceState(android.os.Parcelable state)
if (mContext.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR2) {
// Some old apps reused IDs in ways they shouldn't have.
// Don't break them, but they don't get scroll state restoration.
super.onRestoreInstanceState(state);
return;
}
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
mSavedState = ss;
requestLayout();
| protected android.os.Parcelable | onSaveInstanceState()
if (mContext.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR2) {
// Some old apps reused IDs in ways they shouldn't have.
// Don't break them, but they don't get scroll state restoration.
return super.onSaveInstanceState();
}
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.scrollPosition = mScrollY;
return ss;
| private void | onSecondaryPointerUp(android.view.MotionEvent ev)
final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = ev.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
// TODO: Make this decision more intelligent.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mLastMotionY = (int) ev.getY(newPointerIndex);
mActivePointerId = ev.getPointerId(newPointerIndex);
if (mVelocityTracker != null) {
mVelocityTracker.clear();
}
}
| protected void | onSizeChanged(int w, int h, int oldw, int oldh)
super.onSizeChanged(w, h, oldw, oldh);
View currentFocused = findFocus();
if (null == currentFocused || this == currentFocused)
return;
// If the currently-focused view was visible on the screen when the
// screen was at the old height, then scroll the screen to make that
// view visible with the new screen height.
if (isWithinDeltaOfScreen(currentFocused, 0, oldh)) {
currentFocused.getDrawingRect(mTempRect);
offsetDescendantRectToMyCoords(currentFocused, mTempRect);
int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
doScrollY(scrollDelta);
}
| public boolean | onStartNestedScroll(android.view.View child, android.view.View target, int nestedScrollAxes)
return (nestedScrollAxes & SCROLL_AXIS_VERTICAL) != 0;
| public void | onStopNestedScroll(android.view.View target)
super.onStopNestedScroll(target);
| public boolean | onTouchEvent(android.view.MotionEvent ev)
initVelocityTrackerIfNotExists();
MotionEvent vtev = MotionEvent.obtain(ev);
final int actionMasked = ev.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
mNestedYOffset = 0;
}
vtev.offsetLocation(0, mNestedYOffset);
switch (actionMasked) {
case MotionEvent.ACTION_DOWN: {
if (getChildCount() == 0) {
return false;
}
if ((mIsBeingDragged = !mScroller.isFinished())) {
final ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
}
/*
* If being flinged and user touches, stop the fling. isFinished
* will be false if being flinged.
*/
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
if (mFlingStrictSpan != null) {
mFlingStrictSpan.finish();
mFlingStrictSpan = null;
}
}
// Remember where the motion event started
mLastMotionY = (int) ev.getY();
mActivePointerId = ev.getPointerId(0);
startNestedScroll(SCROLL_AXIS_VERTICAL);
break;
}
case MotionEvent.ACTION_MOVE:
final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
if (activePointerIndex == -1) {
Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
break;
}
final int y = (int) ev.getY(activePointerIndex);
int deltaY = mLastMotionY - y;
if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset)) {
deltaY -= mScrollConsumed[1];
vtev.offsetLocation(0, mScrollOffset[1]);
mNestedYOffset += mScrollOffset[1];
}
if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) {
final ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
mIsBeingDragged = true;
if (deltaY > 0) {
deltaY -= mTouchSlop;
} else {
deltaY += mTouchSlop;
}
}
if (mIsBeingDragged) {
// Scroll to follow the motion event
mLastMotionY = y - mScrollOffset[1];
final int oldY = mScrollY;
final int range = getScrollRange();
final int overscrollMode = getOverScrollMode();
boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
(overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
// Calling overScrollBy will call onOverScrolled, which
// calls onScrollChanged if applicable.
if (overScrollBy(0, deltaY, 0, mScrollY, 0, range, 0, mOverscrollDistance, true)
&& !hasNestedScrollingParent()) {
// Break our velocity if we hit a scroll barrier.
mVelocityTracker.clear();
}
final int scrolledDeltaY = mScrollY - oldY;
final int unconsumedY = deltaY - scrolledDeltaY;
if (dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, mScrollOffset)) {
mLastMotionY -= mScrollOffset[1];
vtev.offsetLocation(0, mScrollOffset[1]);
mNestedYOffset += mScrollOffset[1];
} else if (canOverscroll) {
final int pulledToY = oldY + deltaY;
if (pulledToY < 0) {
mEdgeGlowTop.onPull((float) deltaY / getHeight(),
ev.getX(activePointerIndex) / getWidth());
if (!mEdgeGlowBottom.isFinished()) {
mEdgeGlowBottom.onRelease();
}
} else if (pulledToY > range) {
mEdgeGlowBottom.onPull((float) deltaY / getHeight(),
1.f - ev.getX(activePointerIndex) / getWidth());
if (!mEdgeGlowTop.isFinished()) {
mEdgeGlowTop.onRelease();
}
}
if (mEdgeGlowTop != null
&& (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished())) {
postInvalidateOnAnimation();
}
}
}
break;
case MotionEvent.ACTION_UP:
if (mIsBeingDragged) {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
flingWithNestedDispatch(-initialVelocity);
} else if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0,
getScrollRange())) {
postInvalidateOnAnimation();
}
mActivePointerId = INVALID_POINTER;
endDrag();
}
break;
case MotionEvent.ACTION_CANCEL:
if (mIsBeingDragged && getChildCount() > 0) {
if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) {
postInvalidateOnAnimation();
}
mActivePointerId = INVALID_POINTER;
endDrag();
}
break;
case MotionEvent.ACTION_POINTER_DOWN: {
final int index = ev.getActionIndex();
mLastMotionY = (int) ev.getY(index);
mActivePointerId = ev.getPointerId(index);
break;
}
case MotionEvent.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
break;
}
if (mVelocityTracker != null) {
mVelocityTracker.addMovement(vtev);
}
vtev.recycle();
return true;
| public boolean | pageScroll(int direction)Handles scrolling in response to a "page up/down" shortcut press. This
method will scroll the view by one page up or down and give the focus
to the topmost/bottommost component in the new visible area. If no
component is a good candidate for focus, this scrollview reclaims the
focus.
boolean down = direction == View.FOCUS_DOWN;
int height = getHeight();
if (down) {
mTempRect.top = getScrollY() + height;
int count = getChildCount();
if (count > 0) {
View view = getChildAt(count - 1);
if (mTempRect.top + height > view.getBottom()) {
mTempRect.top = view.getBottom() - height;
}
}
} else {
mTempRect.top = getScrollY() - height;
if (mTempRect.top < 0) {
mTempRect.top = 0;
}
}
mTempRect.bottom = mTempRect.top + height;
return scrollAndFocus(direction, mTempRect.top, mTempRect.bottom);
| public boolean | performAccessibilityAction(int action, android.os.Bundle arguments)
if (super.performAccessibilityAction(action, arguments)) {
return true;
}
if (!isEnabled()) {
return false;
}
switch (action) {
case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
final int viewportHeight = getHeight() - mPaddingBottom - mPaddingTop;
final int targetScrollY = Math.min(mScrollY + viewportHeight, getScrollRange());
if (targetScrollY != mScrollY) {
smoothScrollTo(0, targetScrollY);
return true;
}
} return false;
case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
final int viewportHeight = getHeight() - mPaddingBottom - mPaddingTop;
final int targetScrollY = Math.max(mScrollY - viewportHeight, 0);
if (targetScrollY != mScrollY) {
smoothScrollTo(0, targetScrollY);
return true;
}
} return false;
}
return false;
| private void | recycleVelocityTracker()
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
| public void | requestChildFocus(android.view.View child, android.view.View focused)
if (!mIsLayoutDirty) {
scrollToChild(focused);
} else {
// The child may not be laid out yet, we can't compute the scroll yet
mChildToScrollTo = focused;
}
super.requestChildFocus(child, focused);
| public boolean | requestChildRectangleOnScreen(android.view.View child, android.graphics.Rect rectangle, boolean immediate)
// offset into coordinate space of this scroll view
rectangle.offset(child.getLeft() - child.getScrollX(),
child.getTop() - child.getScrollY());
return scrollToChildRect(rectangle, immediate);
| public void | requestDisallowInterceptTouchEvent(boolean disallowIntercept)
if (disallowIntercept) {
recycleVelocityTracker();
}
super.requestDisallowInterceptTouchEvent(disallowIntercept);
| public void | requestLayout()
mIsLayoutDirty = true;
super.requestLayout();
| private boolean | scrollAndFocus(int direction, int top, int bottom)Scrolls the view to make the area defined by top and
bottom visible. This method attempts to give the focus
to a component visible in this area. If no component can be focused in
the new visible area, the focus is reclaimed by this ScrollView.
boolean handled = true;
int height = getHeight();
int containerTop = getScrollY();
int containerBottom = containerTop + height;
boolean up = direction == View.FOCUS_UP;
View newFocused = findFocusableViewInBounds(up, top, bottom);
if (newFocused == null) {
newFocused = this;
}
if (top >= containerTop && bottom <= containerBottom) {
handled = false;
} else {
int delta = up ? (top - containerTop) : (bottom - containerBottom);
doScrollY(delta);
}
if (newFocused != findFocus()) newFocused.requestFocus(direction);
return handled;
| public void | scrollTo(int x, int y){@inheritDoc}
This version also clamps the scrolling to the bounds of our child.
// we rely on the fact the View.scrollBy calls scrollTo.
if (getChildCount() > 0) {
View child = getChildAt(0);
x = clamp(x, getWidth() - mPaddingRight - mPaddingLeft, child.getWidth());
y = clamp(y, getHeight() - mPaddingBottom - mPaddingTop, child.getHeight());
if (x != mScrollX || y != mScrollY) {
super.scrollTo(x, y);
}
}
| private void | scrollToChild(android.view.View child)Scrolls the view to the given child.
child.getDrawingRect(mTempRect);
/* Offset from child's local coordinates to ScrollView coordinates */
offsetDescendantRectToMyCoords(child, mTempRect);
int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
if (scrollDelta != 0) {
scrollBy(0, scrollDelta);
}
| private boolean | scrollToChildRect(android.graphics.Rect rect, boolean immediate)If rect is off screen, scroll just enough to get it (or at least the
first screen size chunk of it) on screen.
final int delta = computeScrollDeltaToGetChildRectOnScreen(rect);
final boolean scroll = delta != 0;
if (scroll) {
if (immediate) {
scrollBy(0, delta);
} else {
smoothScrollBy(0, delta);
}
}
return scroll;
| public void | setFillViewport(boolean fillViewport)Indicates this ScrollView whether it should stretch its content height to fill
the viewport or not.
if (fillViewport != mFillViewport) {
mFillViewport = fillViewport;
requestLayout();
}
| public void | setOverScrollMode(int mode)
if (mode != OVER_SCROLL_NEVER) {
if (mEdgeGlowTop == null) {
Context context = getContext();
mEdgeGlowTop = new EdgeEffect(context);
mEdgeGlowBottom = new EdgeEffect(context);
}
} else {
mEdgeGlowTop = null;
mEdgeGlowBottom = null;
}
super.setOverScrollMode(mode);
| public void | setSmoothScrollingEnabled(boolean smoothScrollingEnabled)Set whether arrow scrolling will animate its transition.
mSmoothScrollingEnabled = smoothScrollingEnabled;
| public boolean | shouldDelayChildPressedState()
return true;
| public final void | smoothScrollBy(int dx, int dy)Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
if (getChildCount() == 0) {
// Nothing to do.
return;
}
long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
if (duration > ANIMATED_SCROLL_GAP) {
final int height = getHeight() - mPaddingBottom - mPaddingTop;
final int bottom = getChildAt(0).getHeight();
final int maxY = Math.max(0, bottom - height);
final int scrollY = mScrollY;
dy = Math.max(0, Math.min(scrollY + dy, maxY)) - scrollY;
mScroller.startScroll(mScrollX, scrollY, 0, dy);
postInvalidateOnAnimation();
} else {
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
if (mFlingStrictSpan != null) {
mFlingStrictSpan.finish();
mFlingStrictSpan = null;
}
}
scrollBy(dx, dy);
}
mLastScroll = AnimationUtils.currentAnimationTimeMillis();
| public final void | smoothScrollTo(int x, int y)Like {@link #scrollTo}, but scroll smoothly instead of immediately.
smoothScrollBy(x - mScrollX, y - mScrollY);
|
|