FileDocCategorySizeDatePackage
HorizontalScrollView.javaAPI DocAndroid 5.1 API64239Thu Mar 12 22:22:10 GMT 2015android.widget

HorizontalScrollView

public class HorizontalScrollView 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 HorizontalScrollView 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 horizontal orientation, presenting a horizontal array of top-level items that the user can scroll through.

The {@link TextView} class also takes care of its own scrolling, so does not require a HorizontalScrollView, but using the two together is possible to achieve the effect of a text view within a larger container.

HorizontalScrollView only supports horizontal scrolling. For vertical scrolling, use either {@link ScrollView} or {@link ListView}.

attr
ref android.R.styleable#HorizontalScrollView_fillViewport

Fields Summary
private static final int
ANIMATED_SCROLL_GAP
private 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
mEdgeGlowLeft
private EdgeEffect
mEdgeGlowRight
private int
mLastMotionX
Position of the last motion event.
private boolean
mIsLayoutDirty
True 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
mChildToScrollTo
The 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
mIsBeingDragged
True 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
mVelocityTracker
Determines speed during touch scrolling
private boolean
mFillViewport
When set to true, the scroll view measure its child to make it fill the currently visible area.
private boolean
mSmoothScrollingEnabled
Whether arrow scrolling is animated.
private int
mTouchSlop
private int
mMinimumVelocity
private int
mMaximumVelocity
private int
mOverscrollDistance
private int
mOverflingDistance
private int
mActivePointerId
ID of the active pointer. This is used to retain consistency during drags/flings if multiple pointers are used.
private static final int
INVALID_POINTER
Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
private SavedState
mSavedState
Constructors Summary
public HorizontalScrollView(android.content.Context context)


       
        this(context, null);
    
public HorizontalScrollView(android.content.Context context, android.util.AttributeSet attrs)

        this(context, attrs, com.android.internal.R.attr.horizontalScrollViewStyle);
    
public HorizontalScrollView(android.content.Context context, android.util.AttributeSet attrs, int defStyleAttr)

        this(context, attrs, defStyleAttr, 0);
    
public HorizontalScrollView(android.content.Context context, android.util.AttributeSet attrs, int defStyleAttr, int defStyleRes)

        super(context, attrs, defStyleAttr, defStyleRes);
        initScrollView();

        final TypedArray a = context.obtainStyledAttributes(
                attrs, android.R.styleable.HorizontalScrollView, defStyleAttr, defStyleRes);

        setFillViewport(a.getBoolean(android.R.styleable.HorizontalScrollView_fillViewport, false));

        a.recycle();
    
Methods Summary
public voidaddView(android.view.View child, int index)

        if (getChildCount() > 0) {
            throw new IllegalStateException("HorizontalScrollView can host only one direct child");
        }

        super.addView(child, index);
    
public voidaddView(android.view.View child, ViewGroup.LayoutParams params)

        if (getChildCount() > 0) {
            throw new IllegalStateException("HorizontalScrollView can host only one direct child");
        }

        super.addView(child, params);
    
public voidaddView(android.view.View child, int index, ViewGroup.LayoutParams params)

        if (getChildCount() > 0) {
            throw new IllegalStateException("HorizontalScrollView can host only one direct child");
        }

        super.addView(child, index, params);
    
public voidaddView(android.view.View child)

        if (getChildCount() > 0) {
            throw new IllegalStateException("HorizontalScrollView can host only one direct child");
        }

        super.addView(child);
    
public booleanarrowScroll(int direction)
Handle scrolling in response to a left or right arrow click.

param
direction The direction corresponding to the arrow key that was pressed
return
True if we consumed the event, false otherwise


        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)) {
            nextFocused.getDrawingRect(mTempRect);
            offsetDescendantRectToMyCoords(nextFocused, mTempRect);
            int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
            doScrollX(scrollDelta);
            nextFocused.requestFocus(direction);
        } else {
            // no new focus
            int scrollDelta = maxJump;

            if (direction == View.FOCUS_LEFT && getScrollX() < scrollDelta) {
                scrollDelta = getScrollX();
            } else if (direction == View.FOCUS_RIGHT && getChildCount() > 0) {

                int daRight = getChildAt(0).getRight();

                int screenRight = getScrollX() + getWidth();

                if (daRight - screenRight < maxJump) {
                    scrollDelta = daRight - screenRight;
                }
            }
            if (scrollDelta == 0) {
                return false;
            }
            doScrollX(direction == View.FOCUS_RIGHT ? 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 booleancanScroll()

return
Returns true this HorizontalScrollView can be scrolled

        View child = getChildAt(0);
        if (child != null) {
            int childWidth = child.getWidth();
            return getWidth() < childWidth + mPaddingLeft + mPaddingRight ;
        }
        return false;
    
private static intclamp(int n, int my, int child)

        if (my >= child || n < 0) {
            return 0;
        }
        if ((my + n) > child) {
            return child - my;
        }
        return n;
    
protected intcomputeHorizontalScrollOffset()

        return Math.max(0, super.computeHorizontalScrollOffset());
    
protected intcomputeHorizontalScrollRange()

The scroll range of a scroll view is the overall width of all of its children.

        final int count = getChildCount();
        final int contentWidth = getWidth() - mPaddingLeft - mPaddingRight;
        if (count == 0) {
            return contentWidth;
        }

        int scrollRange = getChildAt(0).getRight();
        final int scrollX = mScrollX;
        final int overscrollRight = Math.max(0, scrollRange - contentWidth);
        if (scrollX < 0) {
            scrollRange -= scrollX;
        } else if (scrollX > overscrollRight) {
            scrollRange += scrollX - overscrollRight;
        }

        return scrollRange;
    
public voidcomputeScroll()

        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, range, 0,
                        mOverflingDistance, 0, false);
                onScrollChanged(mScrollX, mScrollY, oldX, oldY);

                if (canOverscroll) {
                    if (x < 0 && oldX >= 0) {
                        mEdgeGlowLeft.onAbsorb((int) mScroller.getCurrVelocity());
                    } else if (x > range && oldX <= range) {
                        mEdgeGlowRight.onAbsorb((int) mScroller.getCurrVelocity());
                    }
                }
            }

            if (!awakenScrollBars()) {
                postInvalidateOnAnimation();
            }
        }
    
protected intcomputeScrollDeltaToGetChildRectOnScreen(android.graphics.Rect rect)
Compute the amount to scroll in the X 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).

param
rect The rect.
return
The scroll delta.

        if (getChildCount() == 0) return 0;

        int width = getWidth();
        int screenLeft = getScrollX();
        int screenRight = screenLeft + width;

        int fadingEdge = getHorizontalFadingEdgeLength();

        // leave room for left fading edge as long as rect isn't at very left
        if (rect.left > 0) {
            screenLeft += fadingEdge;
        }

        // leave room for right fading edge as long as rect isn't at very right
        if (rect.right < getChildAt(0).getWidth()) {
            screenRight -= fadingEdge;
        }

        int scrollXDelta = 0;

        if (rect.right > screenRight && rect.left > screenLeft) {
            // need to move right to get it in view: move right just enough so
            // that the entire rectangle is in view (or at least the first
            // screen size chunk).

            if (rect.width() > width) {
                // just enough to get screen size chunk on
                scrollXDelta += (rect.left - screenLeft);
            } else {
                // get entire rect at right of screen
                scrollXDelta += (rect.right - screenRight);
            }

            // make sure we aren't scrolling beyond the end of our content
            int right = getChildAt(0).getRight();
            int distanceToRight = right - screenRight;
            scrollXDelta = Math.min(scrollXDelta, distanceToRight);

        } else if (rect.left < screenLeft && rect.right < screenRight) {
            // need to move right to get it in view: move right just enough so that
            // entire rectangle is in view (or at least the first screen
            // size chunk of it).

            if (rect.width() > width) {
                // screen size chunk
                scrollXDelta -= (screenRight - rect.right);
            } else {
                // entire rect at left
                scrollXDelta -= (screenLeft - rect.left);
            }

            // make sure we aren't scrolling any further than the left our content
            scrollXDelta = Math.max(scrollXDelta, -getScrollX());
        }
        return scrollXDelta;
    
public booleandispatchKeyEvent(android.view.KeyEvent event)

        // Let the focused view and/or our descendants get the key first
        return super.dispatchKeyEvent(event) || executeKeyEvent(event);
    
private voiddoScrollX(int delta)
Smooth scroll by a X delta

param
delta the number of pixels to scroll by on the X axis

        if (delta != 0) {
            if (mSmoothScrollingEnabled) {
                smoothScrollBy(delta, 0);
            } else {
                scrollBy(delta, 0);
            }
        }
    
public voiddraw(android.graphics.Canvas canvas)

        super.draw(canvas);
        if (mEdgeGlowLeft != null) {
            final int scrollX = mScrollX;
            if (!mEdgeGlowLeft.isFinished()) {
                final int restoreCount = canvas.save();
                final int height = getHeight() - mPaddingTop - mPaddingBottom;

                canvas.rotate(270);
                canvas.translate(-height + mPaddingTop, Math.min(0, scrollX));
                mEdgeGlowLeft.setSize(height, getWidth());
                if (mEdgeGlowLeft.draw(canvas)) {
                    postInvalidateOnAnimation();
                }
                canvas.restoreToCount(restoreCount);
            }
            if (!mEdgeGlowRight.isFinished()) {
                final int restoreCount = canvas.save();
                final int width = getWidth();
                final int height = getHeight() - mPaddingTop - mPaddingBottom;

                canvas.rotate(90);
                canvas.translate(-mPaddingTop,
                        -(Math.max(getScrollRange(), scrollX) + width));
                mEdgeGlowRight.setSize(height, width);
                if (mEdgeGlowRight.draw(canvas)) {
                    postInvalidateOnAnimation();
                }
                canvas.restoreToCount(restoreCount);
            }
        }
    
public booleanexecuteKeyEvent(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.

param
event The key event to execute.
return
Return true if the event was handled, else false.

        mTempRect.setEmpty();

        if (!canScroll()) {
            if (isFocused()) {
                View currentFocused = findFocus();
                if (currentFocused == this) currentFocused = null;
                View nextFocused = FocusFinder.getInstance().findNextFocus(this,
                        currentFocused, View.FOCUS_RIGHT);
                return nextFocused != null && nextFocused != this &&
                        nextFocused.requestFocus(View.FOCUS_RIGHT);
            }
            return false;
        }

        boolean handled = false;
        if (event.getAction() == KeyEvent.ACTION_DOWN) {
            switch (event.getKeyCode()) {
                case KeyEvent.KEYCODE_DPAD_LEFT:
                    if (!event.isAltPressed()) {
                        handled = arrowScroll(View.FOCUS_LEFT);
                    } else {
                        handled = fullScroll(View.FOCUS_LEFT);
                    }
                    break;
                case KeyEvent.KEYCODE_DPAD_RIGHT:
                    if (!event.isAltPressed()) {
                        handled = arrowScroll(View.FOCUS_RIGHT);
                    } else {
                        handled = fullScroll(View.FOCUS_RIGHT);
                    }
                    break;
                case KeyEvent.KEYCODE_SPACE:
                    pageScroll(event.isShiftPressed() ? View.FOCUS_LEFT : View.FOCUS_RIGHT);
                    break;
            }
        }

        return handled;
    
private android.view.ViewfindFocusableViewInBounds(boolean leftFocus, int left, int right)

Finds the next focusable component that fits in the specified bounds.

param
leftFocus look for a candidate is the one at the left of the bounds if leftFocus is true, or at the right of the bounds if leftFocus is false
param
left the left offset of the bounds in which a focusable must be found
param
right the right offset of the bounds in which a focusable must be found
return
the next focusable component in the bounds or null if none can be found


        List<View> focusables = getFocusables(View.FOCUS_FORWARD);
        View focusCandidate = null;

        /*
         * A fully contained focusable is one where its left is below the bound's
         * left, and its right is above the bound's right. 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 viewLeft = view.getLeft();
            int viewRight = view.getRight();

            if (left < viewRight && viewLeft < right) {
                /*
                 * the focusable is in the target area, it is a candidate for
                 * focusing
                 */

                final boolean viewIsFullyContained = (left < viewLeft) &&
                        (viewRight < right);

                if (focusCandidate == null) {
                    /* No candidate, take this one */
                    focusCandidate = view;
                    foundFullyContainedFocusable = viewIsFullyContained;
                } else {
                    final boolean viewIsCloserToBoundary =
                            (leftFocus && viewLeft < focusCandidate.getLeft()) ||
                                    (!leftFocus && viewRight > focusCandidate.getRight());

                    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;
    
private android.view.ViewfindFocusableViewInMyBounds(boolean leftFocus, int left, android.view.View preferredFocusable)

Finds the next focusable component that fits in this View's bounds (excluding fading edges) pretending that this View's left is located at the parameter left.

param
leftFocus look for a candidate is the one at the left of the bounds if leftFocus is true, or at the right of the bounds if leftFocus is false
param
left the left offset of the bounds in which a focusable must be found (the fading edge is assumed to start at this position)
param
preferredFocusable the View that has highest priority and will be returned if it is within my bounds (null is valid)
return
the next focusable component in the bounds or null if none can be found

        /*
         * The fading edge's transparent side should be considered for focus
         * since it's mostly visible, so we divide the actual fading edge length
         * by 2.
         */
        final int fadingEdgeLength = getHorizontalFadingEdgeLength() / 2;
        final int leftWithoutFadingEdge = left + fadingEdgeLength;
        final int rightWithoutFadingEdge = left + getWidth() - fadingEdgeLength;

        if ((preferredFocusable != null)
                && (preferredFocusable.getLeft() < rightWithoutFadingEdge)
                && (preferredFocusable.getRight() > leftWithoutFadingEdge)) {
            return preferredFocusable;
        }

        return findFocusableViewInBounds(leftFocus, leftWithoutFadingEdge,
                rightWithoutFadingEdge);
    
public voidfling(int velocityX)
Fling the scroll view

param
velocityX The initial velocity in the X direction. Positive numbers mean that the finger/cursor is moving down the screen, which means we want to scroll towards the left.

        if (getChildCount() > 0) {
            int width = getWidth() - mPaddingRight - mPaddingLeft;
            int right = getChildAt(0).getWidth();

            mScroller.fling(mScrollX, mScrollY, velocityX, 0, 0,
                    Math.max(0, right - width), 0, 0, width/2, 0);

            final boolean movingRight = velocityX > 0;

            View currentFocused = findFocus();
            View newFocused = findFocusableViewInMyBounds(movingRight,
                    mScroller.getFinalX(), currentFocused);

            if (newFocused == null) {
                newFocused = this;
            }

            if (newFocused != currentFocused) {
                newFocused.requestFocus(movingRight ? View.FOCUS_RIGHT : View.FOCUS_LEFT);
            }

            postInvalidateOnAnimation();
        }
    
public booleanfullScroll(int direction)

Handles scrolling in response to a "home/end" shortcut press. This method will scroll the view to the left or right and give the focus to the leftmost/rightmost component in the new visible area. If no component is a good candidate for focus, this scrollview reclaims the focus.

param
direction the scroll direction: {@link android.view.View#FOCUS_LEFT} to go the left of the view or {@link android.view.View#FOCUS_RIGHT} to go the right
return
true if the key event is consumed by this method, false otherwise

        boolean right = direction == View.FOCUS_RIGHT;
        int width = getWidth();

        mTempRect.left = 0;
        mTempRect.right = width;

        if (right) {
            int count = getChildCount();
            if (count > 0) {
                View view = getChildAt(0);
                mTempRect.right = view.getRight();
                mTempRect.left = mTempRect.right - width;
            }
        }

        return scrollAndFocus(direction, mTempRect.left, mTempRect.right);
    
protected floatgetLeftFadingEdgeStrength()

        if (getChildCount() == 0) {
            return 0.0f;
        }

        final int length = getHorizontalFadingEdgeLength();
        if (mScrollX < length) {
            return mScrollX / (float) length;
        }

        return 1.0f;
    
public intgetMaxScrollAmount()

return
The maximum amount this scroll view will scroll in response to an arrow event.

        return (int) (MAX_SCROLL_FACTOR * (mRight - mLeft));
    
protected floatgetRightFadingEdgeStrength()

        if (getChildCount() == 0) {
            return 0.0f;
        }

        final int length = getHorizontalFadingEdgeLength();
        final int rightEdge = getWidth() - mPaddingRight;
        final int span = getChildAt(0).getRight() - mScrollX - rightEdge;
        if (span < length) {
            return span / (float) length;
        }

        return 1.0f;
    
private intgetScrollRange()

        int scrollRange = 0;
        if (getChildCount() > 0) {
            View child = getChildAt(0);
            scrollRange = Math.max(0,
                    child.getWidth() - (getWidth() - mPaddingLeft - mPaddingRight));
        }
        return scrollRange;
    
private booleaninChild(int x, int y)

        if (getChildCount() > 0) {
            final int scrollX = mScrollX;
            final View child = getChildAt(0);
            return !(y < child.getTop()
                    || y >= child.getBottom()
                    || x < child.getLeft() - scrollX
                    || x >= child.getRight() - scrollX);
        }
        return false;
    
private voidinitOrResetVelocityTracker()

        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        } else {
            mVelocityTracker.clear();
        }
    
private voidinitScrollView()

        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 voidinitVelocityTrackerIfNotExists()

        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
    
public booleanisFillViewport()
Indicates whether this HorizontalScrollView's content is stretched to fill the viewport.

return
True if the content fills the viewport, false otherwise.
attr
ref android.R.styleable#HorizontalScrollView_fillViewport

        return mFillViewport;
    
private booleanisOffScreen(android.view.View descendant)

return
whether the descendant of this scroll view is scrolled off screen.

        return !isWithinDeltaOfScreen(descendant, 0);
    
public booleanisSmoothScrollingEnabled()

return
Whether arrow scrolling will animate its transition.

        return mSmoothScrollingEnabled;
    
private static booleanisViewDescendantOf(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 booleanisWithinDeltaOfScreen(android.view.View descendant, int delta)

return
whether the descendant of this scroll view is within delta pixels of being on the screen.

        descendant.getDrawingRect(mTempRect);
        offsetDescendantRectToMyCoords(descendant, mTempRect);

        return (mTempRect.right + delta) >= getScrollX()
                && (mTempRect.left - delta) <= (getScrollX() + getWidth());
    
protected voidmeasureChild(android.view.View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec)

        ViewGroup.LayoutParams lp = child.getLayoutParams();

        int childWidthMeasureSpec;
        int childHeightMeasureSpec;

        childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop
                + mPaddingBottom, lp.height);

        childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    
protected voidmeasureChildWithMargins(android.view.View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed)

        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);
        final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                lp.leftMargin + lp.rightMargin, MeasureSpec.UNSPECIFIED);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    
public booleanonGenericMotionEvent(android.view.MotionEvent event)

        if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_SCROLL: {
                    if (!mIsBeingDragged) {
                        final float hscroll;
                        if ((event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0) {
                            hscroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL);
                        } else {
                            hscroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL);
                        }
                        if (hscroll != 0) {
                            final int delta = (int) (hscroll * getHorizontalScrollFactor());
                            final int range = getScrollRange();
                            int oldScrollX = mScrollX;
                            int newScrollX = oldScrollX + delta;
                            if (newScrollX < 0) {
                                newScrollX = 0;
                            } else if (newScrollX > range) {
                                newScrollX = range;
                            }
                            if (newScrollX != oldScrollX) {
                                super.scrollTo(newScrollX, mScrollY);
                                return true;
                            }
                        }
                    }
                }
            }
        }
        return super.onGenericMotionEvent(event);
    
public voidonInitializeAccessibilityEvent(android.view.accessibility.AccessibilityEvent event)

        super.onInitializeAccessibilityEvent(event);
        event.setClassName(HorizontalScrollView.class.getName());
        event.setScrollable(getScrollRange() > 0);
        event.setScrollX(mScrollX);
        event.setScrollY(mScrollY);
        event.setMaxScrollX(getScrollRange());
        event.setMaxScrollY(mScrollY);
    
public voidonInitializeAccessibilityNodeInfo(android.view.accessibility.AccessibilityNodeInfo info)

        super.onInitializeAccessibilityNodeInfo(info);
        info.setClassName(HorizontalScrollView.class.getName());
        final int scrollRange = getScrollRange();
        if (scrollRange > 0) {
            info.setScrollable(true);
            if (isEnabled() && mScrollX > 0) {
                info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
            }
            if (isEnabled() && mScrollX < scrollRange) {
                info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
            }
        }
    
public booleanonInterceptTouchEvent(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;
        }

        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. mLastMotionX is set to the x 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 x = (int) ev.getX(pointerIndex);
                final int xDiff = (int) Math.abs(x - mLastMotionX);
                if (xDiff > mTouchSlop) {
                    mIsBeingDragged = true;
                    mLastMotionX = x;
                    initVelocityTrackerIfNotExists();
                    mVelocityTracker.addMovement(ev);
                    if (mParent != null) mParent.requestDisallowInterceptTouchEvent(true);
                }
                break;
            }

            case MotionEvent.ACTION_DOWN: {
                final int x = (int) ev.getX();
                if (!inChild((int) x, (int) ev.getY())) {
                    mIsBeingDragged = false;
                    recycleVelocityTracker();
                    break;
                }

                /*
                 * Remember location of down touch.
                 * ACTION_DOWN always refers to pointer index 0.
                 */
                mLastMotionX = x;
                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();
                break;
            }

            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                /* Release the drag */
                mIsBeingDragged = false;
                mActivePointerId = INVALID_POINTER;
                if (mScroller.springBack(mScrollX, mScrollY, 0, getScrollRange(), 0, 0)) {
                    postInvalidateOnAnimation();
                }
                break;
            case MotionEvent.ACTION_POINTER_DOWN: {
                final int index = ev.getActionIndex();
                mLastMotionX = (int) ev.getX(index);
                mActivePointerId = ev.getPointerId(index);
                break;
            }
            case MotionEvent.ACTION_POINTER_UP:
                onSecondaryPointerUp(ev);
                mLastMotionX = (int) ev.getX(ev.findPointerIndex(mActivePointerId));
                break;
        }

        /*
        * The only time we want to intercept motion events is if we are in the
        * drag mode.
        */
        return mIsBeingDragged;
    
protected voidonLayout(boolean changed, int l, int t, int r, int b)

        int childWidth = 0;
        int childMargins = 0;

        if (getChildCount() > 0) {
            childWidth = getChildAt(0).getMeasuredWidth();
            LayoutParams childParams = (LayoutParams) getChildAt(0).getLayoutParams();
            childMargins = childParams.leftMargin + childParams.rightMargin;
        }

        final int available = r - l - getPaddingLeftWithForeground() -
                getPaddingRightWithForeground() - childMargins;

        final boolean forceLeftGravity = (childWidth > available);

        layoutChildren(l, t, r, b, forceLeftGravity);

        mIsLayoutDirty = false;
        // Give a child focus if it needs it
        if (mChildToScrollTo != null && isViewDescendantOf(mChildToScrollTo, this)) {
            scrollToChild(mChildToScrollTo);
        }
        mChildToScrollTo = null;

        if (!isLaidOut()) {
            final int scrollRange = Math.max(0,
                    childWidth - (r - l - mPaddingLeft - mPaddingRight));
            if (mSavedState != null) {
                if (isLayoutRtl() == mSavedState.isLayoutRtl) {
                    mScrollX = mSavedState.scrollPosition;
                } else {
                    mScrollX = scrollRange - mSavedState.scrollPosition;
                }
                mSavedState = null;
            } else {
                if (isLayoutRtl()) {
                    mScrollX = scrollRange - mScrollX;
                } // mScrollX default value is "0" for LTR
            }
            // Don't forget to clamp
            if (mScrollX > scrollRange) {
                mScrollX = scrollRange;
            } else if (mScrollX < 0) {
                mScrollX = 0;
            }
        }

        // Calling this with the present values causes it to re-claim them
        scrollTo(mScrollX, mScrollY);
    
protected voidonMeasure(int widthMeasureSpec, int heightMeasureSpec)

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        if (!mFillViewport) {
            return;
        }

        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        if (widthMode == MeasureSpec.UNSPECIFIED) {
            return;
        }

        if (getChildCount() > 0) {
            final View child = getChildAt(0);
            int width = getMeasuredWidth();
            if (child.getMeasuredWidth() < width) {
                final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();

                int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, mPaddingTop
                        + mPaddingBottom, lp.height);
                width -= mPaddingLeft;
                width -= mPaddingRight;
                int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);

                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            }
        }
    
protected voidonOverScrolled(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 (clampedX) {
                mScroller.springBack(mScrollX, mScrollY, 0, getScrollRange(), 0, 0);
            }
        } else {
            super.scrollTo(scrollX, scrollY);
        }

        awakenScrollBars();
    
protected booleanonRequestFocusInDescendants(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_RIGHT;
        } else if (direction == View.FOCUS_BACKWARD) {
            direction = View.FOCUS_LEFT;
        }

        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 voidonRestoreInstanceState(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.ParcelableonSaveInstanceState()

        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 = mScrollX;
        ss.isLayoutRtl = isLayoutRtl();
        return ss;
    
private voidonSecondaryPointerUp(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;
            mLastMotionX = (int) ev.getX(newPointerIndex);
            mActivePointerId = ev.getPointerId(newPointerIndex);
            if (mVelocityTracker != null) {
                mVelocityTracker.clear();
            }
        }
    
protected voidonSizeChanged(int w, int h, int oldw, int oldh)

        super.onSizeChanged(w, h, oldw, oldh);

        View currentFocused = findFocus();
        if (null == currentFocused || this == currentFocused)
            return;

        final int maxJump = mRight - mLeft;

        if (isWithinDeltaOfScreen(currentFocused, maxJump)) {
            currentFocused.getDrawingRect(mTempRect);
            offsetDescendantRectToMyCoords(currentFocused, mTempRect);
            int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
            doScrollX(scrollDelta);
        }
    
public booleanonTouchEvent(android.view.MotionEvent ev)

        initVelocityTrackerIfNotExists();
        mVelocityTracker.addMovement(ev);

        final int action = ev.getAction();

        switch (action & MotionEvent.ACTION_MASK) {
            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();
                }

                // Remember where the motion event started
                mLastMotionX = (int) ev.getX();
                mActivePointerId = ev.getPointerId(0);
                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 x = (int) ev.getX(activePointerIndex);
                int deltaX = mLastMotionX - x;
                if (!mIsBeingDragged && Math.abs(deltaX) > mTouchSlop) {
                    final ViewParent parent = getParent();
                    if (parent != null) {
                        parent.requestDisallowInterceptTouchEvent(true);
                    }
                    mIsBeingDragged = true;
                    if (deltaX > 0) {
                        deltaX -= mTouchSlop;
                    } else {
                        deltaX += mTouchSlop;
                    }
                }
                if (mIsBeingDragged) {
                    // Scroll to follow the motion event
                    mLastMotionX = x;

                    final int oldX = mScrollX;
                    final int oldY = mScrollY;
                    final int range = getScrollRange();
                    final int overscrollMode = getOverScrollMode();
                    final 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(deltaX, 0, mScrollX, 0, range, 0,
                            mOverscrollDistance, 0, true)) {
                        // Break our velocity if we hit a scroll barrier.
                        mVelocityTracker.clear();
                    }

                    if (canOverscroll) {
                        final int pulledToX = oldX + deltaX;
                        if (pulledToX < 0) {
                            mEdgeGlowLeft.onPull((float) deltaX / getWidth(),
                                    1.f - ev.getY(activePointerIndex) / getHeight());
                            if (!mEdgeGlowRight.isFinished()) {
                                mEdgeGlowRight.onRelease();
                            }
                        } else if (pulledToX > range) {
                            mEdgeGlowRight.onPull((float) deltaX / getWidth(),
                                    ev.getY(activePointerIndex) / getHeight());
                            if (!mEdgeGlowLeft.isFinished()) {
                                mEdgeGlowLeft.onRelease();
                            }
                        }
                        if (mEdgeGlowLeft != null
                                && (!mEdgeGlowLeft.isFinished() || !mEdgeGlowRight.isFinished())) {
                            postInvalidateOnAnimation();
                        }
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                if (mIsBeingDragged) {
                    final VelocityTracker velocityTracker = mVelocityTracker;
                    velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                    int initialVelocity = (int) velocityTracker.getXVelocity(mActivePointerId);

                    if (getChildCount() > 0) {
                        if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
                            fling(-initialVelocity);
                        } else {
                            if (mScroller.springBack(mScrollX, mScrollY, 0,
                                    getScrollRange(), 0, 0)) {
                                postInvalidateOnAnimation();
                            }
                        }
                    }

                    mActivePointerId = INVALID_POINTER;
                    mIsBeingDragged = false;
                    recycleVelocityTracker();

                    if (mEdgeGlowLeft != null) {
                        mEdgeGlowLeft.onRelease();
                        mEdgeGlowRight.onRelease();
                    }
                }
                break;
            case MotionEvent.ACTION_CANCEL:
                if (mIsBeingDragged && getChildCount() > 0) {
                    if (mScroller.springBack(mScrollX, mScrollY, 0, getScrollRange(), 0, 0)) {
                        postInvalidateOnAnimation();
                    }
                    mActivePointerId = INVALID_POINTER;
                    mIsBeingDragged = false;
                    recycleVelocityTracker();

                    if (mEdgeGlowLeft != null) {
                        mEdgeGlowLeft.onRelease();
                        mEdgeGlowRight.onRelease();
                    }
                }
                break;
            case MotionEvent.ACTION_POINTER_UP:
                onSecondaryPointerUp(ev);
                break;
        }
        return true;
    
public booleanpageScroll(int direction)

Handles scrolling in response to a "page up/down" shortcut press. This method will scroll the view by one page left or right and give the focus to the leftmost/rightmost component in the new visible area. If no component is a good candidate for focus, this scrollview reclaims the focus.

param
direction the scroll direction: {@link android.view.View#FOCUS_LEFT} to go one page left or {@link android.view.View#FOCUS_RIGHT} to go one page right
return
true if the key event is consumed by this method, false otherwise

        boolean right = direction == View.FOCUS_RIGHT;
        int width = getWidth();

        if (right) {
            mTempRect.left = getScrollX() + width;
            int count = getChildCount();
            if (count > 0) {
                View view = getChildAt(0);
                if (mTempRect.left + width > view.getRight()) {
                    mTempRect.left = view.getRight() - width;
                }
            }
        } else {
            mTempRect.left = getScrollX() - width;
            if (mTempRect.left < 0) {
                mTempRect.left = 0;
            }
        }
        mTempRect.right = mTempRect.left + width;

        return scrollAndFocus(direction, mTempRect.left, mTempRect.right);
    
public booleanperformAccessibilityAction(int action, android.os.Bundle arguments)

        if (super.performAccessibilityAction(action, arguments)) {
            return true;
        }
        switch (action) {
            case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
                if (!isEnabled()) {
                    return false;
                }
                final int viewportWidth = getWidth() - mPaddingLeft - mPaddingRight;
                final int targetScrollX = Math.min(mScrollX + viewportWidth, getScrollRange());
                if (targetScrollX != mScrollX) {
                    smoothScrollTo(targetScrollX, 0);
                    return true;
                }
            } return false;
            case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
                if (!isEnabled()) {
                    return false;
                }
                final int viewportWidth = getWidth() - mPaddingLeft - mPaddingRight;
                final int targetScrollX = Math.max(0, mScrollX - viewportWidth);
                if (targetScrollX != mScrollX) {
                    smoothScrollTo(targetScrollX, 0);
                    return true;
                }
            } return false;
        }
        return false;
    
private voidrecycleVelocityTracker()

        if (mVelocityTracker != null) {
            mVelocityTracker.recycle();
            mVelocityTracker = null;
        }
    
public voidrequestChildFocus(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 booleanrequestChildRectangleOnScreen(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 voidrequestDisallowInterceptTouchEvent(boolean disallowIntercept)

        if (disallowIntercept) {
            recycleVelocityTracker();
        }
        super.requestDisallowInterceptTouchEvent(disallowIntercept);
    
public voidrequestLayout()

        mIsLayoutDirty = true;
        super.requestLayout();
    
private booleanscrollAndFocus(int direction, int left, int right)

Scrolls the view to make the area defined by left and right 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.

param
direction the scroll direction: {@link android.view.View#FOCUS_LEFT} to go left {@link android.view.View#FOCUS_RIGHT} to right
param
left the left offset of the new area to be made visible
param
right the right offset of the new area to be made visible
return
true if the key event is consumed by this method, false otherwise

        boolean handled = true;

        int width = getWidth();
        int containerLeft = getScrollX();
        int containerRight = containerLeft + width;
        boolean goLeft = direction == View.FOCUS_LEFT;

        View newFocused = findFocusableViewInBounds(goLeft, left, right);
        if (newFocused == null) {
            newFocused = this;
        }

        if (left >= containerLeft && right <= containerRight) {
            handled = false;
        } else {
            int delta = goLeft ? (left - containerLeft) : (right - containerRight);
            doScrollX(delta);
        }

        if (newFocused != findFocus()) newFocused.requestFocus(direction);

        return handled;
    
public voidscrollTo(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 voidscrollToChild(android.view.View child)
Scrolls the view to the given child.

param
child the View to scroll to

        child.getDrawingRect(mTempRect);

        /* Offset from child's local coordinates to ScrollView coordinates */
        offsetDescendantRectToMyCoords(child, mTempRect);

        int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);

        if (scrollDelta != 0) {
            scrollBy(scrollDelta, 0);
        }
    
private booleanscrollToChildRect(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.

param
rect The rectangle.
param
immediate True to scroll immediately without animation
return
true if scrolling was performed

        final int delta = computeScrollDeltaToGetChildRectOnScreen(rect);
        final boolean scroll = delta != 0;
        if (scroll) {
            if (immediate) {
                scrollBy(delta, 0);
            } else {
                smoothScrollBy(delta, 0);
            }
        }
        return scroll;
    
public voidsetFillViewport(boolean fillViewport)
Indicates this HorizontalScrollView whether it should stretch its content width to fill the viewport or not.

param
fillViewport True to stretch the content's width to the viewport's boundaries, false otherwise.
attr
ref android.R.styleable#HorizontalScrollView_fillViewport

        if (fillViewport != mFillViewport) {
            mFillViewport = fillViewport;
            requestLayout();
        }
    
public voidsetOverScrollMode(int mode)

        if (mode != OVER_SCROLL_NEVER) {
            if (mEdgeGlowLeft == null) {
                Context context = getContext();
                mEdgeGlowLeft = new EdgeEffect(context);
                mEdgeGlowRight = new EdgeEffect(context);
            }
        } else {
            mEdgeGlowLeft = null;
            mEdgeGlowRight = null;
        }
        super.setOverScrollMode(mode);
    
public voidsetSmoothScrollingEnabled(boolean smoothScrollingEnabled)
Set whether arrow scrolling will animate its transition.

param
smoothScrollingEnabled whether arrow scrolling will animate its transition

        mSmoothScrollingEnabled = smoothScrollingEnabled;
    
public booleanshouldDelayChildPressedState()

        return true;
    
public final voidsmoothScrollBy(int dx, int dy)
Like {@link View#scrollBy}, but scroll smoothly instead of immediately.

param
dx the number of pixels to scroll by on the X axis
param
dy the number of pixels to scroll by on the Y axis

        if (getChildCount() == 0) {
            // Nothing to do.
            return;
        }
        long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
        if (duration > ANIMATED_SCROLL_GAP) {
            final int width = getWidth() - mPaddingRight - mPaddingLeft;
            final int right = getChildAt(0).getWidth();
            final int maxX = Math.max(0, right - width);
            final int scrollX = mScrollX;
            dx = Math.max(0, Math.min(scrollX + dx, maxX)) - scrollX;

            mScroller.startScroll(scrollX, mScrollY, dx, 0);
            postInvalidateOnAnimation();
        } else {
            if (!mScroller.isFinished()) {
                mScroller.abortAnimation();
            }
            scrollBy(dx, dy);
        }
        mLastScroll = AnimationUtils.currentAnimationTimeMillis();
    
public final voidsmoothScrollTo(int x, int y)
Like {@link #scrollTo}, but scroll smoothly instead of immediately.

param
x the position where to scroll on the X axis
param
y the position where to scroll on the Y axis

        smoothScrollBy(x - mScrollX, y - mScrollY);