Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2006 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.widget;
     18 
     19 import android.annotation.NonNull;
     20 import android.os.Build;
     21 import android.os.Build.VERSION_CODES;
     22 import android.os.Parcel;
     23 import android.os.Parcelable;
     24 import com.android.internal.R;
     25 
     26 import android.content.Context;
     27 import android.content.res.TypedArray;
     28 import android.graphics.Canvas;
     29 import android.graphics.Rect;
     30 import android.os.Bundle;
     31 import android.os.StrictMode;
     32 import android.util.AttributeSet;
     33 import android.util.Log;
     34 import android.view.FocusFinder;
     35 import android.view.InputDevice;
     36 import android.view.KeyEvent;
     37 import android.view.MotionEvent;
     38 import android.view.VelocityTracker;
     39 import android.view.View;
     40 import android.view.ViewConfiguration;
     41 import android.view.ViewDebug;
     42 import android.view.ViewGroup;
     43 import android.view.ViewHierarchyEncoder;
     44 import android.view.ViewParent;
     45 import android.view.accessibility.AccessibilityEvent;
     46 import android.view.accessibility.AccessibilityNodeInfo;
     47 import android.view.animation.AnimationUtils;
     48 
     49 import java.util.List;
     50 
     51 /**
     52  * Layout container for a view hierarchy that can be scrolled by the user,
     53  * allowing it to be larger than the physical display.  A ScrollView
     54  * is a {@link FrameLayout}, meaning you should place one child in it
     55  * containing the entire contents to scroll; this child may itself be a layout
     56  * manager with a complex hierarchy of objects.  A child that is often used
     57  * is a {@link LinearLayout} in a vertical orientation, presenting a vertical
     58  * array of top-level items that the user can scroll through.
     59  * <p>You should never use a ScrollView with a {@link ListView}, because
     60  * ListView takes care of its own vertical scrolling.  Most importantly, doing this
     61  * defeats all of the important optimizations in ListView for dealing with
     62  * large lists, since it effectively forces the ListView to display its entire
     63  * list of items to fill up the infinite container supplied by ScrollView.
     64  * <p>The {@link TextView} class also
     65  * takes care of its own scrolling, so does not require a ScrollView, but
     66  * using the two together is possible to achieve the effect of a text view
     67  * within a larger container.
     68  *
     69  * <p>ScrollView only supports vertical scrolling. For horizontal scrolling,
     70  * use {@link HorizontalScrollView}.
     71  *
     72  * @attr ref android.R.styleable#ScrollView_fillViewport
     73  */
     74 public class ScrollView extends FrameLayout {
     75     static final int ANIMATED_SCROLL_GAP = 250;
     76 
     77     static final float MAX_SCROLL_FACTOR = 0.5f;
     78 
     79     private static final String TAG = "ScrollView";
     80 
     81     private long mLastScroll;
     82 
     83     private final Rect mTempRect = new Rect();
     84     private OverScroller mScroller;
     85     private EdgeEffect mEdgeGlowTop;
     86     private EdgeEffect mEdgeGlowBottom;
     87 
     88     /**
     89      * Position of the last motion event.
     90      */
     91     private int mLastMotionY;
     92 
     93     /**
     94      * True when the layout has changed but the traversal has not come through yet.
     95      * Ideally the view hierarchy would keep track of this for us.
     96      */
     97     private boolean mIsLayoutDirty = true;
     98 
     99     /**
    100      * The child to give focus to in the event that a child has requested focus while the
    101      * layout is dirty. This prevents the scroll from being wrong if the child has not been
    102      * laid out before requesting focus.
    103      */
    104     private View mChildToScrollTo = null;
    105 
    106     /**
    107      * True if the user is currently dragging this ScrollView around. This is
    108      * not the same as 'is being flinged', which can be checked by
    109      * mScroller.isFinished() (flinging begins when the user lifts his finger).
    110      */
    111     private boolean mIsBeingDragged = false;
    112 
    113     /**
    114      * Determines speed during touch scrolling
    115      */
    116     private VelocityTracker mVelocityTracker;
    117 
    118     /**
    119      * When set to true, the scroll view measure its child to make it fill the currently
    120      * visible area.
    121      */
    122     @ViewDebug.ExportedProperty(category = "layout")
    123     private boolean mFillViewport;
    124 
    125     /**
    126      * Whether arrow scrolling is animated.
    127      */
    128     private boolean mSmoothScrollingEnabled = true;
    129 
    130     private int mTouchSlop;
    131     private int mMinimumVelocity;
    132     private int mMaximumVelocity;
    133 
    134     private int mOverscrollDistance;
    135     private int mOverflingDistance;
    136 
    137     /**
    138      * ID of the active pointer. This is used to retain consistency during
    139      * drags/flings if multiple pointers are used.
    140      */
    141     private int mActivePointerId = INVALID_POINTER;
    142 
    143     /**
    144      * Used during scrolling to retrieve the new offset within the window.
    145      */
    146     private final int[] mScrollOffset = new int[2];
    147     private final int[] mScrollConsumed = new int[2];
    148     private int mNestedYOffset;
    149 
    150     /**
    151      * The StrictMode "critical time span" objects to catch animation
    152      * stutters.  Non-null when a time-sensitive animation is
    153      * in-flight.  Must call finish() on them when done animating.
    154      * These are no-ops on user builds.
    155      */
    156     private StrictMode.Span mScrollStrictSpan = null;  // aka "drag"
    157     private StrictMode.Span mFlingStrictSpan = null;
    158 
    159     /**
    160      * Sentinel value for no current active pointer.
    161      * Used by {@link #mActivePointerId}.
    162      */
    163     private static final int INVALID_POINTER = -1;
    164 
    165     private SavedState mSavedState;
    166 
    167     public ScrollView(Context context) {
    168         this(context, null);
    169     }
    170 
    171     public ScrollView(Context context, AttributeSet attrs) {
    172         this(context, attrs, com.android.internal.R.attr.scrollViewStyle);
    173     }
    174 
    175     public ScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
    176         this(context, attrs, defStyleAttr, 0);
    177     }
    178 
    179     public ScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    180         super(context, attrs, defStyleAttr, defStyleRes);
    181         initScrollView();
    182 
    183         final TypedArray a = context.obtainStyledAttributes(
    184                 attrs, com.android.internal.R.styleable.ScrollView, defStyleAttr, defStyleRes);
    185 
    186         setFillViewport(a.getBoolean(R.styleable.ScrollView_fillViewport, false));
    187 
    188         a.recycle();
    189     }
    190 
    191     @Override
    192     public boolean shouldDelayChildPressedState() {
    193         return true;
    194     }
    195 
    196     @Override
    197     protected float getTopFadingEdgeStrength() {
    198         if (getChildCount() == 0) {
    199             return 0.0f;
    200         }
    201 
    202         final int length = getVerticalFadingEdgeLength();
    203         if (mScrollY < length) {
    204             return mScrollY / (float) length;
    205         }
    206 
    207         return 1.0f;
    208     }
    209 
    210     @Override
    211     protected float getBottomFadingEdgeStrength() {
    212         if (getChildCount() == 0) {
    213             return 0.0f;
    214         }
    215 
    216         final int length = getVerticalFadingEdgeLength();
    217         final int bottomEdge = getHeight() - mPaddingBottom;
    218         final int span = getChildAt(0).getBottom() - mScrollY - bottomEdge;
    219         if (span < length) {
    220             return span / (float) length;
    221         }
    222 
    223         return 1.0f;
    224     }
    225 
    226     /**
    227      * @return The maximum amount this scroll view will scroll in response to
    228      *   an arrow event.
    229      */
    230     public int getMaxScrollAmount() {
    231         return (int) (MAX_SCROLL_FACTOR * (mBottom - mTop));
    232     }
    233 
    234 
    235     private void initScrollView() {
    236         mScroller = new OverScroller(getContext());
    237         setFocusable(true);
    238         setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
    239         setWillNotDraw(false);
    240         final ViewConfiguration configuration = ViewConfiguration.get(mContext);
    241         mTouchSlop = configuration.getScaledTouchSlop();
    242         mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
    243         mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
    244         mOverscrollDistance = configuration.getScaledOverscrollDistance();
    245         mOverflingDistance = configuration.getScaledOverflingDistance();
    246     }
    247 
    248     @Override
    249     public void addView(View child) {
    250         if (getChildCount() > 0) {
    251             throw new IllegalStateException("ScrollView can host only one direct child");
    252         }
    253 
    254         super.addView(child);
    255     }
    256 
    257     @Override
    258     public void addView(View child, int index) {
    259         if (getChildCount() > 0) {
    260             throw new IllegalStateException("ScrollView can host only one direct child");
    261         }
    262 
    263         super.addView(child, index);
    264     }
    265 
    266     @Override
    267     public void addView(View child, ViewGroup.LayoutParams params) {
    268         if (getChildCount() > 0) {
    269             throw new IllegalStateException("ScrollView can host only one direct child");
    270         }
    271 
    272         super.addView(child, params);
    273     }
    274 
    275     @Override
    276     public void addView(View child, int index, ViewGroup.LayoutParams params) {
    277         if (getChildCount() > 0) {
    278             throw new IllegalStateException("ScrollView can host only one direct child");
    279         }
    280 
    281         super.addView(child, index, params);
    282     }
    283 
    284     /**
    285      * @return Returns true this ScrollView can be scrolled
    286      */
    287     private boolean canScroll() {
    288         View child = getChildAt(0);
    289         if (child != null) {
    290             int childHeight = child.getHeight();
    291             return getHeight() < childHeight + mPaddingTop + mPaddingBottom;
    292         }
    293         return false;
    294     }
    295 
    296     /**
    297      * Indicates whether this ScrollView's content is stretched to fill the viewport.
    298      *
    299      * @return True if the content fills the viewport, false otherwise.
    300      *
    301      * @attr ref android.R.styleable#ScrollView_fillViewport
    302      */
    303     public boolean isFillViewport() {
    304         return mFillViewport;
    305     }
    306 
    307     /**
    308      * Indicates this ScrollView whether it should stretch its content height to fill
    309      * the viewport or not.
    310      *
    311      * @param fillViewport True to stretch the content's height to the viewport's
    312      *        boundaries, false otherwise.
    313      *
    314      * @attr ref android.R.styleable#ScrollView_fillViewport
    315      */
    316     public void setFillViewport(boolean fillViewport) {
    317         if (fillViewport != mFillViewport) {
    318             mFillViewport = fillViewport;
    319             requestLayout();
    320         }
    321     }
    322 
    323     /**
    324      * @return Whether arrow scrolling will animate its transition.
    325      */
    326     public boolean isSmoothScrollingEnabled() {
    327         return mSmoothScrollingEnabled;
    328     }
    329 
    330     /**
    331      * Set whether arrow scrolling will animate its transition.
    332      * @param smoothScrollingEnabled whether arrow scrolling will animate its transition
    333      */
    334     public void setSmoothScrollingEnabled(boolean smoothScrollingEnabled) {
    335         mSmoothScrollingEnabled = smoothScrollingEnabled;
    336     }
    337 
    338     @Override
    339     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    340         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    341 
    342         if (!mFillViewport) {
    343             return;
    344         }
    345 
    346         final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    347         if (heightMode == MeasureSpec.UNSPECIFIED) {
    348             return;
    349         }
    350 
    351         if (getChildCount() > 0) {
    352             final View child = getChildAt(0);
    353             final int widthPadding;
    354             final int heightPadding;
    355             final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
    356             final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
    357             if (targetSdkVersion >= VERSION_CODES.M) {
    358                 widthPadding = mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin;
    359                 heightPadding = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin;
    360             } else {
    361                 widthPadding = mPaddingLeft + mPaddingRight;
    362                 heightPadding = mPaddingTop + mPaddingBottom;
    363             }
    364 
    365             final int desiredHeight = getMeasuredHeight() - heightPadding;
    366             if (child.getMeasuredHeight() < desiredHeight) {
    367                 final int childWidthMeasureSpec = getChildMeasureSpec(
    368                         widthMeasureSpec, widthPadding, lp.width);
    369                 final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
    370                         desiredHeight, MeasureSpec.EXACTLY);
    371                 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    372             }
    373         }
    374     }
    375 
    376     @Override
    377     public boolean dispatchKeyEvent(KeyEvent event) {
    378         // Let the focused view and/or our descendants get the key first
    379         return super.dispatchKeyEvent(event) || executeKeyEvent(event);
    380     }
    381 
    382     /**
    383      * You can call this function yourself to have the scroll view perform
    384      * scrolling from a key event, just as if the event had been dispatched to
    385      * it by the view hierarchy.
    386      *
    387      * @param event The key event to execute.
    388      * @return Return true if the event was handled, else false.
    389      */
    390     public boolean executeKeyEvent(KeyEvent event) {
    391         mTempRect.setEmpty();
    392 
    393         if (!canScroll()) {
    394             if (isFocused() && event.getKeyCode() != KeyEvent.KEYCODE_BACK) {
    395                 View currentFocused = findFocus();
    396                 if (currentFocused == this) currentFocused = null;
    397                 View nextFocused = FocusFinder.getInstance().findNextFocus(this,
    398                         currentFocused, View.FOCUS_DOWN);
    399                 return nextFocused != null
    400                         && nextFocused != this
    401                         && nextFocused.requestFocus(View.FOCUS_DOWN);
    402             }
    403             return false;
    404         }
    405 
    406         boolean handled = false;
    407         if (event.getAction() == KeyEvent.ACTION_DOWN) {
    408             switch (event.getKeyCode()) {
    409                 case KeyEvent.KEYCODE_DPAD_UP:
    410                     if (!event.isAltPressed()) {
    411                         handled = arrowScroll(View.FOCUS_UP);
    412                     } else {
    413                         handled = fullScroll(View.FOCUS_UP);
    414                     }
    415                     break;
    416                 case KeyEvent.KEYCODE_DPAD_DOWN:
    417                     if (!event.isAltPressed()) {
    418                         handled = arrowScroll(View.FOCUS_DOWN);
    419                     } else {
    420                         handled = fullScroll(View.FOCUS_DOWN);
    421                     }
    422                     break;
    423                 case KeyEvent.KEYCODE_SPACE:
    424                     pageScroll(event.isShiftPressed() ? View.FOCUS_UP : View.FOCUS_DOWN);
    425                     break;
    426             }
    427         }
    428 
    429         return handled;
    430     }
    431 
    432     private boolean inChild(int x, int y) {
    433         if (getChildCount() > 0) {
    434             final int scrollY = mScrollY;
    435             final View child = getChildAt(0);
    436             return !(y < child.getTop() - scrollY
    437                     || y >= child.getBottom() - scrollY
    438                     || x < child.getLeft()
    439                     || x >= child.getRight());
    440         }
    441         return false;
    442     }
    443 
    444     private void initOrResetVelocityTracker() {
    445         if (mVelocityTracker == null) {
    446             mVelocityTracker = VelocityTracker.obtain();
    447         } else {
    448             mVelocityTracker.clear();
    449         }
    450     }
    451 
    452     private void initVelocityTrackerIfNotExists() {
    453         if (mVelocityTracker == null) {
    454             mVelocityTracker = VelocityTracker.obtain();
    455         }
    456     }
    457 
    458     private void recycleVelocityTracker() {
    459         if (mVelocityTracker != null) {
    460             mVelocityTracker.recycle();
    461             mVelocityTracker = null;
    462         }
    463     }
    464 
    465     @Override
    466     public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
    467         if (disallowIntercept) {
    468             recycleVelocityTracker();
    469         }
    470         super.requestDisallowInterceptTouchEvent(disallowIntercept);
    471     }
    472 
    473 
    474     @Override
    475     public boolean onInterceptTouchEvent(MotionEvent ev) {
    476         /*
    477          * This method JUST determines whether we want to intercept the motion.
    478          * If we return true, onMotionEvent will be called and we do the actual
    479          * scrolling there.
    480          */
    481 
    482         /*
    483         * Shortcut the most recurring case: the user is in the dragging
    484         * state and he is moving his finger.  We want to intercept this
    485         * motion.
    486         */
    487         final int action = ev.getAction();
    488         if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
    489             return true;
    490         }
    491 
    492         if (super.onInterceptTouchEvent(ev)) {
    493             return true;
    494         }
    495 
    496         /*
    497          * Don't try to intercept touch if we can't scroll anyway.
    498          */
    499         if (getScrollY() == 0 && !canScrollVertically(1)) {
    500             return false;
    501         }
    502 
    503         switch (action & MotionEvent.ACTION_MASK) {
    504             case MotionEvent.ACTION_MOVE: {
    505                 /*
    506                  * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
    507                  * whether the user has moved far enough from his original down touch.
    508                  */
    509 
    510                 /*
    511                 * Locally do absolute value. mLastMotionY is set to the y value
    512                 * of the down event.
    513                 */
    514                 final int activePointerId = mActivePointerId;
    515                 if (activePointerId == INVALID_POINTER) {
    516                     // If we don't have a valid id, the touch down wasn't on content.
    517                     break;
    518                 }
    519 
    520                 final int pointerIndex = ev.findPointerIndex(activePointerId);
    521                 if (pointerIndex == -1) {
    522                     Log.e(TAG, "Invalid pointerId=" + activePointerId
    523                             + " in onInterceptTouchEvent");
    524                     break;
    525                 }
    526 
    527                 final int y = (int) ev.getY(pointerIndex);
    528                 final int yDiff = Math.abs(y - mLastMotionY);
    529                 if (yDiff > mTouchSlop && (getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) {
    530                     mIsBeingDragged = true;
    531                     mLastMotionY = y;
    532                     initVelocityTrackerIfNotExists();
    533                     mVelocityTracker.addMovement(ev);
    534                     mNestedYOffset = 0;
    535                     if (mScrollStrictSpan == null) {
    536                         mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll");
    537                     }
    538                     final ViewParent parent = getParent();
    539                     if (parent != null) {
    540                         parent.requestDisallowInterceptTouchEvent(true);
    541                     }
    542                 }
    543                 break;
    544             }
    545 
    546             case MotionEvent.ACTION_DOWN: {
    547                 final int y = (int) ev.getY();
    548                 if (!inChild((int) ev.getX(), (int) y)) {
    549                     mIsBeingDragged = false;
    550                     recycleVelocityTracker();
    551                     break;
    552                 }
    553 
    554                 /*
    555                  * Remember location of down touch.
    556                  * ACTION_DOWN always refers to pointer index 0.
    557                  */
    558                 mLastMotionY = y;
    559                 mActivePointerId = ev.getPointerId(0);
    560 
    561                 initOrResetVelocityTracker();
    562                 mVelocityTracker.addMovement(ev);
    563                 /*
    564                  * If being flinged and user touches the screen, initiate drag;
    565                  * otherwise don't. mScroller.isFinished should be false when
    566                  * being flinged. We need to call computeScrollOffset() first so that
    567                  * isFinished() is correct.
    568                 */
    569                 mScroller.computeScrollOffset();
    570                 mIsBeingDragged = !mScroller.isFinished();
    571                 if (mIsBeingDragged && mScrollStrictSpan == null) {
    572                     mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll");
    573                 }
    574                 startNestedScroll(SCROLL_AXIS_VERTICAL);
    575                 break;
    576             }
    577 
    578             case MotionEvent.ACTION_CANCEL:
    579             case MotionEvent.ACTION_UP:
    580                 /* Release the drag */
    581                 mIsBeingDragged = false;
    582                 mActivePointerId = INVALID_POINTER;
    583                 recycleVelocityTracker();
    584                 if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) {
    585                     postInvalidateOnAnimation();
    586                 }
    587                 stopNestedScroll();
    588                 break;
    589             case MotionEvent.ACTION_POINTER_UP:
    590                 onSecondaryPointerUp(ev);
    591                 break;
    592         }
    593 
    594         /*
    595         * The only time we want to intercept motion events is if we are in the
    596         * drag mode.
    597         */
    598         return mIsBeingDragged;
    599     }
    600 
    601     @Override
    602     public boolean onTouchEvent(MotionEvent ev) {
    603         initVelocityTrackerIfNotExists();
    604 
    605         MotionEvent vtev = MotionEvent.obtain(ev);
    606 
    607         final int actionMasked = ev.getActionMasked();
    608 
    609         if (actionMasked == MotionEvent.ACTION_DOWN) {
    610             mNestedYOffset = 0;
    611         }
    612         vtev.offsetLocation(0, mNestedYOffset);
    613 
    614         switch (actionMasked) {
    615             case MotionEvent.ACTION_DOWN: {
    616                 if (getChildCount() == 0) {
    617                     return false;
    618                 }
    619                 if ((mIsBeingDragged = !mScroller.isFinished())) {
    620                     final ViewParent parent = getParent();
    621                     if (parent != null) {
    622                         parent.requestDisallowInterceptTouchEvent(true);
    623                     }
    624                 }
    625 
    626                 /*
    627                  * If being flinged and user touches, stop the fling. isFinished
    628                  * will be false if being flinged.
    629                  */
    630                 if (!mScroller.isFinished()) {
    631                     mScroller.abortAnimation();
    632                     if (mFlingStrictSpan != null) {
    633                         mFlingStrictSpan.finish();
    634                         mFlingStrictSpan = null;
    635                     }
    636                 }
    637 
    638                 // Remember where the motion event started
    639                 mLastMotionY = (int) ev.getY();
    640                 mActivePointerId = ev.getPointerId(0);
    641                 startNestedScroll(SCROLL_AXIS_VERTICAL);
    642                 break;
    643             }
    644             case MotionEvent.ACTION_MOVE:
    645                 final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
    646                 if (activePointerIndex == -1) {
    647                     Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
    648                     break;
    649                 }
    650 
    651                 final int y = (int) ev.getY(activePointerIndex);
    652                 int deltaY = mLastMotionY - y;
    653                 if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset)) {
    654                     deltaY -= mScrollConsumed[1];
    655                     vtev.offsetLocation(0, mScrollOffset[1]);
    656                     mNestedYOffset += mScrollOffset[1];
    657                 }
    658                 if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) {
    659                     final ViewParent parent = getParent();
    660                     if (parent != null) {
    661                         parent.requestDisallowInterceptTouchEvent(true);
    662                     }
    663                     mIsBeingDragged = true;
    664                     if (deltaY > 0) {
    665                         deltaY -= mTouchSlop;
    666                     } else {
    667                         deltaY += mTouchSlop;
    668                     }
    669                 }
    670                 if (mIsBeingDragged) {
    671                     // Scroll to follow the motion event
    672                     mLastMotionY = y - mScrollOffset[1];
    673 
    674                     final int oldY = mScrollY;
    675                     final int range = getScrollRange();
    676                     final int overscrollMode = getOverScrollMode();
    677                     boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
    678                             (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
    679 
    680                     // Calling overScrollBy will call onOverScrolled, which
    681                     // calls onScrollChanged if applicable.
    682                     if (overScrollBy(0, deltaY, 0, mScrollY, 0, range, 0, mOverscrollDistance, true)
    683                             && !hasNestedScrollingParent()) {
    684                         // Break our velocity if we hit a scroll barrier.
    685                         mVelocityTracker.clear();
    686                     }
    687 
    688                     final int scrolledDeltaY = mScrollY - oldY;
    689                     final int unconsumedY = deltaY - scrolledDeltaY;
    690                     if (dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, mScrollOffset)) {
    691                         mLastMotionY -= mScrollOffset[1];
    692                         vtev.offsetLocation(0, mScrollOffset[1]);
    693                         mNestedYOffset += mScrollOffset[1];
    694                     } else if (canOverscroll) {
    695                         final int pulledToY = oldY + deltaY;
    696                         if (pulledToY < 0) {
    697                             mEdgeGlowTop.onPull((float) deltaY / getHeight(),
    698                                     ev.getX(activePointerIndex) / getWidth());
    699                             if (!mEdgeGlowBottom.isFinished()) {
    700                                 mEdgeGlowBottom.onRelease();
    701                             }
    702                         } else if (pulledToY > range) {
    703                             mEdgeGlowBottom.onPull((float) deltaY / getHeight(),
    704                                     1.f - ev.getX(activePointerIndex) / getWidth());
    705                             if (!mEdgeGlowTop.isFinished()) {
    706                                 mEdgeGlowTop.onRelease();
    707                             }
    708                         }
    709                         if (mEdgeGlowTop != null
    710                                 && (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished())) {
    711                             postInvalidateOnAnimation();
    712                         }
    713                     }
    714                 }
    715                 break;
    716             case MotionEvent.ACTION_UP:
    717                 if (mIsBeingDragged) {
    718                     final VelocityTracker velocityTracker = mVelocityTracker;
    719                     velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
    720                     int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
    721 
    722                     if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
    723                         flingWithNestedDispatch(-initialVelocity);
    724                     } else if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0,
    725                             getScrollRange())) {
    726                         postInvalidateOnAnimation();
    727                     }
    728 
    729                     mActivePointerId = INVALID_POINTER;
    730                     endDrag();
    731                 }
    732                 break;
    733             case MotionEvent.ACTION_CANCEL:
    734                 if (mIsBeingDragged && getChildCount() > 0) {
    735                     if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) {
    736                         postInvalidateOnAnimation();
    737                     }
    738                     mActivePointerId = INVALID_POINTER;
    739                     endDrag();
    740                 }
    741                 break;
    742             case MotionEvent.ACTION_POINTER_DOWN: {
    743                 final int index = ev.getActionIndex();
    744                 mLastMotionY = (int) ev.getY(index);
    745                 mActivePointerId = ev.getPointerId(index);
    746                 break;
    747             }
    748             case MotionEvent.ACTION_POINTER_UP:
    749                 onSecondaryPointerUp(ev);
    750                 mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
    751                 break;
    752         }
    753 
    754         if (mVelocityTracker != null) {
    755             mVelocityTracker.addMovement(vtev);
    756         }
    757         vtev.recycle();
    758         return true;
    759     }
    760 
    761     private void onSecondaryPointerUp(MotionEvent ev) {
    762         final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
    763                 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
    764         final int pointerId = ev.getPointerId(pointerIndex);
    765         if (pointerId == mActivePointerId) {
    766             // This was our active pointer going up. Choose a new
    767             // active pointer and adjust accordingly.
    768             // TODO: Make this decision more intelligent.
    769             final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
    770             mLastMotionY = (int) ev.getY(newPointerIndex);
    771             mActivePointerId = ev.getPointerId(newPointerIndex);
    772             if (mVelocityTracker != null) {
    773                 mVelocityTracker.clear();
    774             }
    775         }
    776     }
    777 
    778     @Override
    779     public boolean onGenericMotionEvent(MotionEvent event) {
    780         if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
    781             switch (event.getAction()) {
    782                 case MotionEvent.ACTION_SCROLL: {
    783                     if (!mIsBeingDragged) {
    784                         final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
    785                         if (vscroll != 0) {
    786                             final int delta = (int) (vscroll * getVerticalScrollFactor());
    787                             final int range = getScrollRange();
    788                             int oldScrollY = mScrollY;
    789                             int newScrollY = oldScrollY - delta;
    790                             if (newScrollY < 0) {
    791                                 newScrollY = 0;
    792                             } else if (newScrollY > range) {
    793                                 newScrollY = range;
    794                             }
    795                             if (newScrollY != oldScrollY) {
    796                                 super.scrollTo(mScrollX, newScrollY);
    797                                 return true;
    798                             }
    799                         }
    800                     }
    801                 }
    802             }
    803         }
    804         return super.onGenericMotionEvent(event);
    805     }
    806 
    807     @Override
    808     protected void onOverScrolled(int scrollX, int scrollY,
    809             boolean clampedX, boolean clampedY) {
    810         // Treat animating scrolls differently; see #computeScroll() for why.
    811         if (!mScroller.isFinished()) {
    812             final int oldX = mScrollX;
    813             final int oldY = mScrollY;
    814             mScrollX = scrollX;
    815             mScrollY = scrollY;
    816             invalidateParentIfNeeded();
    817             onScrollChanged(mScrollX, mScrollY, oldX, oldY);
    818             if (clampedY) {
    819                 mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange());
    820             }
    821         } else {
    822             super.scrollTo(scrollX, scrollY);
    823         }
    824 
    825         awakenScrollBars();
    826     }
    827 
    828     /** @hide */
    829     @Override
    830     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
    831         if (super.performAccessibilityActionInternal(action, arguments)) {
    832             return true;
    833         }
    834         if (!isEnabled()) {
    835             return false;
    836         }
    837         switch (action) {
    838             case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
    839             case R.id.accessibilityActionScrollDown: {
    840                 final int viewportHeight = getHeight() - mPaddingBottom - mPaddingTop;
    841                 final int targetScrollY = Math.min(mScrollY + viewportHeight, getScrollRange());
    842                 if (targetScrollY != mScrollY) {
    843                     smoothScrollTo(0, targetScrollY);
    844                     return true;
    845                 }
    846             } return false;
    847             case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
    848             case R.id.accessibilityActionScrollUp: {
    849                 final int viewportHeight = getHeight() - mPaddingBottom - mPaddingTop;
    850                 final int targetScrollY = Math.max(mScrollY - viewportHeight, 0);
    851                 if (targetScrollY != mScrollY) {
    852                     smoothScrollTo(0, targetScrollY);
    853                     return true;
    854                 }
    855             } return false;
    856         }
    857         return false;
    858     }
    859 
    860     @Override
    861     public CharSequence getAccessibilityClassName() {
    862         return ScrollView.class.getName();
    863     }
    864 
    865     /** @hide */
    866     @Override
    867     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
    868         super.onInitializeAccessibilityNodeInfoInternal(info);
    869         if (isEnabled()) {
    870             final int scrollRange = getScrollRange();
    871             if (scrollRange > 0) {
    872                 info.setScrollable(true);
    873                 if (mScrollY > 0) {
    874                     info.addAction(
    875                             AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD);
    876                     info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP);
    877                 }
    878                 if (mScrollY < scrollRange) {
    879                     info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
    880                     info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_DOWN);
    881                 }
    882             }
    883         }
    884     }
    885 
    886     /** @hide */
    887     @Override
    888     public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
    889         super.onInitializeAccessibilityEventInternal(event);
    890         final boolean scrollable = getScrollRange() > 0;
    891         event.setScrollable(scrollable);
    892         event.setScrollX(mScrollX);
    893         event.setScrollY(mScrollY);
    894         event.setMaxScrollX(mScrollX);
    895         event.setMaxScrollY(getScrollRange());
    896     }
    897 
    898     private int getScrollRange() {
    899         int scrollRange = 0;
    900         if (getChildCount() > 0) {
    901             View child = getChildAt(0);
    902             scrollRange = Math.max(0,
    903                     child.getHeight() - (getHeight() - mPaddingBottom - mPaddingTop));
    904         }
    905         return scrollRange;
    906     }
    907 
    908     /**
    909      * <p>
    910      * Finds the next focusable component that fits in the specified bounds.
    911      * </p>
    912      *
    913      * @param topFocus look for a candidate is the one at the top of the bounds
    914      *                 if topFocus is true, or at the bottom of the bounds if topFocus is
    915      *                 false
    916      * @param top      the top offset of the bounds in which a focusable must be
    917      *                 found
    918      * @param bottom   the bottom offset of the bounds in which a focusable must
    919      *                 be found
    920      * @return the next focusable component in the bounds or null if none can
    921      *         be found
    922      */
    923     private View findFocusableViewInBounds(boolean topFocus, int top, int bottom) {
    924 
    925         List<View> focusables = getFocusables(View.FOCUS_FORWARD);
    926         View focusCandidate = null;
    927 
    928         /*
    929          * A fully contained focusable is one where its top is below the bound's
    930          * top, and its bottom is above the bound's bottom. A partially
    931          * contained focusable is one where some part of it is within the
    932          * bounds, but it also has some part that is not within bounds.  A fully contained
    933          * focusable is preferred to a partially contained focusable.
    934          */
    935         boolean foundFullyContainedFocusable = false;
    936 
    937         int count = focusables.size();
    938         for (int i = 0; i < count; i++) {
    939             View view = focusables.get(i);
    940             int viewTop = view.getTop();
    941             int viewBottom = view.getBottom();
    942 
    943             if (top < viewBottom && viewTop < bottom) {
    944                 /*
    945                  * the focusable is in the target area, it is a candidate for
    946                  * focusing
    947                  */
    948 
    949                 final boolean viewIsFullyContained = (top < viewTop) &&
    950                         (viewBottom < bottom);
    951 
    952                 if (focusCandidate == null) {
    953                     /* No candidate, take this one */
    954                     focusCandidate = view;
    955                     foundFullyContainedFocusable = viewIsFullyContained;
    956                 } else {
    957                     final boolean viewIsCloserToBoundary =
    958                             (topFocus && viewTop < focusCandidate.getTop()) ||
    959                                     (!topFocus && viewBottom > focusCandidate
    960                                             .getBottom());
    961 
    962                     if (foundFullyContainedFocusable) {
    963                         if (viewIsFullyContained && viewIsCloserToBoundary) {
    964                             /*
    965                              * We're dealing with only fully contained views, so
    966                              * it has to be closer to the boundary to beat our
    967                              * candidate
    968                              */
    969                             focusCandidate = view;
    970                         }
    971                     } else {
    972                         if (viewIsFullyContained) {
    973                             /* Any fully contained view beats a partially contained view */
    974                             focusCandidate = view;
    975                             foundFullyContainedFocusable = true;
    976                         } else if (viewIsCloserToBoundary) {
    977                             /*
    978                              * Partially contained view beats another partially
    979                              * contained view if it's closer
    980                              */
    981                             focusCandidate = view;
    982                         }
    983                     }
    984                 }
    985             }
    986         }
    987 
    988         return focusCandidate;
    989     }
    990 
    991     /**
    992      * <p>Handles scrolling in response to a "page up/down" shortcut press. This
    993      * method will scroll the view by one page up or down and give the focus
    994      * to the topmost/bottommost component in the new visible area. If no
    995      * component is a good candidate for focus, this scrollview reclaims the
    996      * focus.</p>
    997      *
    998      * @param direction the scroll direction: {@link android.view.View#FOCUS_UP}
    999      *                  to go one page up or
   1000      *                  {@link android.view.View#FOCUS_DOWN} to go one page down
   1001      * @return true if the key event is consumed by this method, false otherwise
   1002      */
   1003     public boolean pageScroll(int direction) {
   1004         boolean down = direction == View.FOCUS_DOWN;
   1005         int height = getHeight();
   1006 
   1007         if (down) {
   1008             mTempRect.top = getScrollY() + height;
   1009             int count = getChildCount();
   1010             if (count > 0) {
   1011                 View view = getChildAt(count - 1);
   1012                 if (mTempRect.top + height > view.getBottom()) {
   1013                     mTempRect.top = view.getBottom() - height;
   1014                 }
   1015             }
   1016         } else {
   1017             mTempRect.top = getScrollY() - height;
   1018             if (mTempRect.top < 0) {
   1019                 mTempRect.top = 0;
   1020             }
   1021         }
   1022         mTempRect.bottom = mTempRect.top + height;
   1023 
   1024         return scrollAndFocus(direction, mTempRect.top, mTempRect.bottom);
   1025     }
   1026 
   1027     /**
   1028      * <p>Handles scrolling in response to a "home/end" shortcut press. This
   1029      * method will scroll the view to the top or bottom and give the focus
   1030      * to the topmost/bottommost component in the new visible area. If no
   1031      * component is a good candidate for focus, this scrollview reclaims the
   1032      * focus.</p>
   1033      *
   1034      * @param direction the scroll direction: {@link android.view.View#FOCUS_UP}
   1035      *                  to go the top of the view or
   1036      *                  {@link android.view.View#FOCUS_DOWN} to go the bottom
   1037      * @return true if the key event is consumed by this method, false otherwise
   1038      */
   1039     public boolean fullScroll(int direction) {
   1040         boolean down = direction == View.FOCUS_DOWN;
   1041         int height = getHeight();
   1042 
   1043         mTempRect.top = 0;
   1044         mTempRect.bottom = height;
   1045 
   1046         if (down) {
   1047             int count = getChildCount();
   1048             if (count > 0) {
   1049                 View view = getChildAt(count - 1);
   1050                 mTempRect.bottom = view.getBottom() + mPaddingBottom;
   1051                 mTempRect.top = mTempRect.bottom - height;
   1052             }
   1053         }
   1054 
   1055         return scrollAndFocus(direction, mTempRect.top, mTempRect.bottom);
   1056     }
   1057 
   1058     /**
   1059      * <p>Scrolls the view to make the area defined by <code>top</code> and
   1060      * <code>bottom</code> visible. This method attempts to give the focus
   1061      * to a component visible in this area. If no component can be focused in
   1062      * the new visible area, the focus is reclaimed by this ScrollView.</p>
   1063      *
   1064      * @param direction the scroll direction: {@link android.view.View#FOCUS_UP}
   1065      *                  to go upward, {@link android.view.View#FOCUS_DOWN} to downward
   1066      * @param top       the top offset of the new area to be made visible
   1067      * @param bottom    the bottom offset of the new area to be made visible
   1068      * @return true if the key event is consumed by this method, false otherwise
   1069      */
   1070     private boolean scrollAndFocus(int direction, int top, int bottom) {
   1071         boolean handled = true;
   1072 
   1073         int height = getHeight();
   1074         int containerTop = getScrollY();
   1075         int containerBottom = containerTop + height;
   1076         boolean up = direction == View.FOCUS_UP;
   1077 
   1078         View newFocused = findFocusableViewInBounds(up, top, bottom);
   1079         if (newFocused == null) {
   1080             newFocused = this;
   1081         }
   1082 
   1083         if (top >= containerTop && bottom <= containerBottom) {
   1084             handled = false;
   1085         } else {
   1086             int delta = up ? (top - containerTop) : (bottom - containerBottom);
   1087             doScrollY(delta);
   1088         }
   1089 
   1090         if (newFocused != findFocus()) newFocused.requestFocus(direction);
   1091 
   1092         return handled;
   1093     }
   1094 
   1095     /**
   1096      * Handle scrolling in response to an up or down arrow click.
   1097      *
   1098      * @param direction The direction corresponding to the arrow key that was
   1099      *                  pressed
   1100      * @return True if we consumed the event, false otherwise
   1101      */
   1102     public boolean arrowScroll(int direction) {
   1103 
   1104         View currentFocused = findFocus();
   1105         if (currentFocused == this) currentFocused = null;
   1106 
   1107         View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, direction);
   1108 
   1109         final int maxJump = getMaxScrollAmount();
   1110 
   1111         if (nextFocused != null && isWithinDeltaOfScreen(nextFocused, maxJump, getHeight())) {
   1112             nextFocused.getDrawingRect(mTempRect);
   1113             offsetDescendantRectToMyCoords(nextFocused, mTempRect);
   1114             int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
   1115             doScrollY(scrollDelta);
   1116             nextFocused.requestFocus(direction);
   1117         } else {
   1118             // no new focus
   1119             int scrollDelta = maxJump;
   1120 
   1121             if (direction == View.FOCUS_UP && getScrollY() < scrollDelta) {
   1122                 scrollDelta = getScrollY();
   1123             } else if (direction == View.FOCUS_DOWN) {
   1124                 if (getChildCount() > 0) {
   1125                     int daBottom = getChildAt(0).getBottom();
   1126                     int screenBottom = getScrollY() + getHeight() - mPaddingBottom;
   1127                     if (daBottom - screenBottom < maxJump) {
   1128                         scrollDelta = daBottom - screenBottom;
   1129                     }
   1130                 }
   1131             }
   1132             if (scrollDelta == 0) {
   1133                 return false;
   1134             }
   1135             doScrollY(direction == View.FOCUS_DOWN ? scrollDelta : -scrollDelta);
   1136         }
   1137 
   1138         if (currentFocused != null && currentFocused.isFocused()
   1139                 && isOffScreen(currentFocused)) {
   1140             // previously focused item still has focus and is off screen, give
   1141             // it up (take it back to ourselves)
   1142             // (also, need to temporarily force FOCUS_BEFORE_DESCENDANTS so we are
   1143             // sure to
   1144             // get it)
   1145             final int descendantFocusability = getDescendantFocusability();  // save
   1146             setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
   1147             requestFocus();
   1148             setDescendantFocusability(descendantFocusability);  // restore
   1149         }
   1150         return true;
   1151     }
   1152 
   1153     /**
   1154      * @return whether the descendant of this scroll view is scrolled off
   1155      *  screen.
   1156      */
   1157     private boolean isOffScreen(View descendant) {
   1158         return !isWithinDeltaOfScreen(descendant, 0, getHeight());
   1159     }
   1160 
   1161     /**
   1162      * @return whether the descendant of this scroll view is within delta
   1163      *  pixels of being on the screen.
   1164      */
   1165     private boolean isWithinDeltaOfScreen(View descendant, int delta, int height) {
   1166         descendant.getDrawingRect(mTempRect);
   1167         offsetDescendantRectToMyCoords(descendant, mTempRect);
   1168 
   1169         return (mTempRect.bottom + delta) >= getScrollY()
   1170                 && (mTempRect.top - delta) <= (getScrollY() + height);
   1171     }
   1172 
   1173     /**
   1174      * Smooth scroll by a Y delta
   1175      *
   1176      * @param delta the number of pixels to scroll by on the Y axis
   1177      */
   1178     private void doScrollY(int delta) {
   1179         if (delta != 0) {
   1180             if (mSmoothScrollingEnabled) {
   1181                 smoothScrollBy(0, delta);
   1182             } else {
   1183                 scrollBy(0, delta);
   1184             }
   1185         }
   1186     }
   1187 
   1188     /**
   1189      * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
   1190      *
   1191      * @param dx the number of pixels to scroll by on the X axis
   1192      * @param dy the number of pixels to scroll by on the Y axis
   1193      */
   1194     public final void smoothScrollBy(int dx, int dy) {
   1195         if (getChildCount() == 0) {
   1196             // Nothing to do.
   1197             return;
   1198         }
   1199         long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
   1200         if (duration > ANIMATED_SCROLL_GAP) {
   1201             final int height = getHeight() - mPaddingBottom - mPaddingTop;
   1202             final int bottom = getChildAt(0).getHeight();
   1203             final int maxY = Math.max(0, bottom - height);
   1204             final int scrollY = mScrollY;
   1205             dy = Math.max(0, Math.min(scrollY + dy, maxY)) - scrollY;
   1206 
   1207             mScroller.startScroll(mScrollX, scrollY, 0, dy);
   1208             postInvalidateOnAnimation();
   1209         } else {
   1210             if (!mScroller.isFinished()) {
   1211                 mScroller.abortAnimation();
   1212                 if (mFlingStrictSpan != null) {
   1213                     mFlingStrictSpan.finish();
   1214                     mFlingStrictSpan = null;
   1215                 }
   1216             }
   1217             scrollBy(dx, dy);
   1218         }
   1219         mLastScroll = AnimationUtils.currentAnimationTimeMillis();
   1220     }
   1221 
   1222     /**
   1223      * Like {@link #scrollTo}, but scroll smoothly instead of immediately.
   1224      *
   1225      * @param x the position where to scroll on the X axis
   1226      * @param y the position where to scroll on the Y axis
   1227      */
   1228     public final void smoothScrollTo(int x, int y) {
   1229         smoothScrollBy(x - mScrollX, y - mScrollY);
   1230     }
   1231 
   1232     /**
   1233      * <p>The scroll range of a scroll view is the overall height of all of its
   1234      * children.</p>
   1235      */
   1236     @Override
   1237     protected int computeVerticalScrollRange() {
   1238         final int count = getChildCount();
   1239         final int contentHeight = getHeight() - mPaddingBottom - mPaddingTop;
   1240         if (count == 0) {
   1241             return contentHeight;
   1242         }
   1243 
   1244         int scrollRange = getChildAt(0).getBottom();
   1245         final int scrollY = mScrollY;
   1246         final int overscrollBottom = Math.max(0, scrollRange - contentHeight);
   1247         if (scrollY < 0) {
   1248             scrollRange -= scrollY;
   1249         } else if (scrollY > overscrollBottom) {
   1250             scrollRange += scrollY - overscrollBottom;
   1251         }
   1252 
   1253         return scrollRange;
   1254     }
   1255 
   1256     @Override
   1257     protected int computeVerticalScrollOffset() {
   1258         return Math.max(0, super.computeVerticalScrollOffset());
   1259     }
   1260 
   1261     @Override
   1262     protected void measureChild(View child, int parentWidthMeasureSpec,
   1263             int parentHeightMeasureSpec) {
   1264         ViewGroup.LayoutParams lp = child.getLayoutParams();
   1265 
   1266         int childWidthMeasureSpec;
   1267         int childHeightMeasureSpec;
   1268 
   1269         childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft
   1270                 + mPaddingRight, lp.width);
   1271         final int verticalPadding = mPaddingTop + mPaddingBottom;
   1272         childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
   1273                 Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - verticalPadding),
   1274                 MeasureSpec.UNSPECIFIED);
   1275 
   1276         child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
   1277     }
   1278 
   1279     @Override
   1280     protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
   1281             int parentHeightMeasureSpec, int heightUsed) {
   1282         final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
   1283 
   1284         final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
   1285                 mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
   1286                         + widthUsed, lp.width);
   1287         final int usedTotal = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin +
   1288                 heightUsed;
   1289         final int childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
   1290                 Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - usedTotal),
   1291                 MeasureSpec.UNSPECIFIED);
   1292 
   1293         child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
   1294     }
   1295 
   1296     @Override
   1297     public void computeScroll() {
   1298         if (mScroller.computeScrollOffset()) {
   1299             // This is called at drawing time by ViewGroup.  We don't want to
   1300             // re-show the scrollbars at this point, which scrollTo will do,
   1301             // so we replicate most of scrollTo here.
   1302             //
   1303             //         It's a little odd to call onScrollChanged from inside the drawing.
   1304             //
   1305             //         It is, except when you remember that computeScroll() is used to
   1306             //         animate scrolling. So unless we want to defer the onScrollChanged()
   1307             //         until the end of the animated scrolling, we don't really have a
   1308             //         choice here.
   1309             //
   1310             //         I agree.  The alternative, which I think would be worse, is to post
   1311             //         something and tell the subclasses later.  This is bad because there
   1312             //         will be a window where mScrollX/Y is different from what the app
   1313             //         thinks it is.
   1314             //
   1315             int oldX = mScrollX;
   1316             int oldY = mScrollY;
   1317             int x = mScroller.getCurrX();
   1318             int y = mScroller.getCurrY();
   1319 
   1320             if (oldX != x || oldY != y) {
   1321                 final int range = getScrollRange();
   1322                 final int overscrollMode = getOverScrollMode();
   1323                 final boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
   1324                         (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
   1325 
   1326                 overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range,
   1327                         0, mOverflingDistance, false);
   1328                 onScrollChanged(mScrollX, mScrollY, oldX, oldY);
   1329 
   1330                 if (canOverscroll) {
   1331                     if (y < 0 && oldY >= 0) {
   1332                         mEdgeGlowTop.onAbsorb((int) mScroller.getCurrVelocity());
   1333                     } else if (y > range && oldY <= range) {
   1334                         mEdgeGlowBottom.onAbsorb((int) mScroller.getCurrVelocity());
   1335                     }
   1336                 }
   1337             }
   1338 
   1339             if (!awakenScrollBars()) {
   1340                 // Keep on drawing until the animation has finished.
   1341                 postInvalidateOnAnimation();
   1342             }
   1343         } else {
   1344             if (mFlingStrictSpan != null) {
   1345                 mFlingStrictSpan.finish();
   1346                 mFlingStrictSpan = null;
   1347             }
   1348         }
   1349     }
   1350 
   1351     /**
   1352      * Scrolls the view to the given child.
   1353      *
   1354      * @param child the View to scroll to
   1355      */
   1356     private void scrollToChild(View child) {
   1357         child.getDrawingRect(mTempRect);
   1358 
   1359         /* Offset from child's local coordinates to ScrollView coordinates */
   1360         offsetDescendantRectToMyCoords(child, mTempRect);
   1361 
   1362         int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
   1363 
   1364         if (scrollDelta != 0) {
   1365             scrollBy(0, scrollDelta);
   1366         }
   1367     }
   1368 
   1369     /**
   1370      * If rect is off screen, scroll just enough to get it (or at least the
   1371      * first screen size chunk of it) on screen.
   1372      *
   1373      * @param rect      The rectangle.
   1374      * @param immediate True to scroll immediately without animation
   1375      * @return true if scrolling was performed
   1376      */
   1377     private boolean scrollToChildRect(Rect rect, boolean immediate) {
   1378         final int delta = computeScrollDeltaToGetChildRectOnScreen(rect);
   1379         final boolean scroll = delta != 0;
   1380         if (scroll) {
   1381             if (immediate) {
   1382                 scrollBy(0, delta);
   1383             } else {
   1384                 smoothScrollBy(0, delta);
   1385             }
   1386         }
   1387         return scroll;
   1388     }
   1389 
   1390     /**
   1391      * Compute the amount to scroll in the Y direction in order to get
   1392      * a rectangle completely on the screen (or, if taller than the screen,
   1393      * at least the first screen size chunk of it).
   1394      *
   1395      * @param rect The rect.
   1396      * @return The scroll delta.
   1397      */
   1398     protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) {
   1399         if (getChildCount() == 0) return 0;
   1400 
   1401         int height = getHeight();
   1402         int screenTop = getScrollY();
   1403         int screenBottom = screenTop + height;
   1404 
   1405         int fadingEdge = getVerticalFadingEdgeLength();
   1406 
   1407         // leave room for top fading edge as long as rect isn't at very top
   1408         if (rect.top > 0) {
   1409             screenTop += fadingEdge;
   1410         }
   1411 
   1412         // leave room for bottom fading edge as long as rect isn't at very bottom
   1413         if (rect.bottom < getChildAt(0).getHeight()) {
   1414             screenBottom -= fadingEdge;
   1415         }
   1416 
   1417         int scrollYDelta = 0;
   1418 
   1419         if (rect.bottom > screenBottom && rect.top > screenTop) {
   1420             // need to move down to get it in view: move down just enough so
   1421             // that the entire rectangle is in view (or at least the first
   1422             // screen size chunk).
   1423 
   1424             if (rect.height() > height) {
   1425                 // just enough to get screen size chunk on
   1426                 scrollYDelta += (rect.top - screenTop);
   1427             } else {
   1428                 // get entire rect at bottom of screen
   1429                 scrollYDelta += (rect.bottom - screenBottom);
   1430             }
   1431 
   1432             // make sure we aren't scrolling beyond the end of our content
   1433             int bottom = getChildAt(0).getBottom();
   1434             int distanceToBottom = bottom - screenBottom;
   1435             scrollYDelta = Math.min(scrollYDelta, distanceToBottom);
   1436 
   1437         } else if (rect.top < screenTop && rect.bottom < screenBottom) {
   1438             // need to move up to get it in view: move up just enough so that
   1439             // entire rectangle is in view (or at least the first screen
   1440             // size chunk of it).
   1441 
   1442             if (rect.height() > height) {
   1443                 // screen size chunk
   1444                 scrollYDelta -= (screenBottom - rect.bottom);
   1445             } else {
   1446                 // entire rect at top
   1447                 scrollYDelta -= (screenTop - rect.top);
   1448             }
   1449 
   1450             // make sure we aren't scrolling any further than the top our content
   1451             scrollYDelta = Math.max(scrollYDelta, -getScrollY());
   1452         }
   1453         return scrollYDelta;
   1454     }
   1455 
   1456     @Override
   1457     public void requestChildFocus(View child, View focused) {
   1458         if (!mIsLayoutDirty) {
   1459             scrollToChild(focused);
   1460         } else {
   1461             // The child may not be laid out yet, we can't compute the scroll yet
   1462             mChildToScrollTo = focused;
   1463         }
   1464         super.requestChildFocus(child, focused);
   1465     }
   1466 
   1467 
   1468     /**
   1469      * When looking for focus in children of a scroll view, need to be a little
   1470      * more careful not to give focus to something that is scrolled off screen.
   1471      *
   1472      * This is more expensive than the default {@link android.view.ViewGroup}
   1473      * implementation, otherwise this behavior might have been made the default.
   1474      */
   1475     @Override
   1476     protected boolean onRequestFocusInDescendants(int direction,
   1477             Rect previouslyFocusedRect) {
   1478 
   1479         // convert from forward / backward notation to up / down / left / right
   1480         // (ugh).
   1481         if (direction == View.FOCUS_FORWARD) {
   1482             direction = View.FOCUS_DOWN;
   1483         } else if (direction == View.FOCUS_BACKWARD) {
   1484             direction = View.FOCUS_UP;
   1485         }
   1486 
   1487         final View nextFocus = previouslyFocusedRect == null ?
   1488                 FocusFinder.getInstance().findNextFocus(this, null, direction) :
   1489                 FocusFinder.getInstance().findNextFocusFromRect(this,
   1490                         previouslyFocusedRect, direction);
   1491 
   1492         if (nextFocus == null) {
   1493             return false;
   1494         }
   1495 
   1496         if (isOffScreen(nextFocus)) {
   1497             return false;
   1498         }
   1499 
   1500         return nextFocus.requestFocus(direction, previouslyFocusedRect);
   1501     }
   1502 
   1503     @Override
   1504     public boolean requestChildRectangleOnScreen(View child, Rect rectangle,
   1505             boolean immediate) {
   1506         // offset into coordinate space of this scroll view
   1507         rectangle.offset(child.getLeft() - child.getScrollX(),
   1508                 child.getTop() - child.getScrollY());
   1509 
   1510         return scrollToChildRect(rectangle, immediate);
   1511     }
   1512 
   1513     @Override
   1514     public void requestLayout() {
   1515         mIsLayoutDirty = true;
   1516         super.requestLayout();
   1517     }
   1518 
   1519     @Override
   1520     protected void onDetachedFromWindow() {
   1521         super.onDetachedFromWindow();
   1522 
   1523         if (mScrollStrictSpan != null) {
   1524             mScrollStrictSpan.finish();
   1525             mScrollStrictSpan = null;
   1526         }
   1527         if (mFlingStrictSpan != null) {
   1528             mFlingStrictSpan.finish();
   1529             mFlingStrictSpan = null;
   1530         }
   1531     }
   1532 
   1533     @Override
   1534     protected void onLayout(boolean changed, int l, int t, int r, int b) {
   1535         super.onLayout(changed, l, t, r, b);
   1536         mIsLayoutDirty = false;
   1537         // Give a child focus if it needs it
   1538         if (mChildToScrollTo != null && isViewDescendantOf(mChildToScrollTo, this)) {
   1539             scrollToChild(mChildToScrollTo);
   1540         }
   1541         mChildToScrollTo = null;
   1542 
   1543         if (!isLaidOut()) {
   1544             if (mSavedState != null) {
   1545                 mScrollY = mSavedState.scrollPosition;
   1546                 mSavedState = null;
   1547             } // mScrollY default value is "0"
   1548 
   1549             final int childHeight = (getChildCount() > 0) ? getChildAt(0).getMeasuredHeight() : 0;
   1550             final int scrollRange = Math.max(0,
   1551                     childHeight - (b - t - mPaddingBottom - mPaddingTop));
   1552 
   1553             // Don't forget to clamp
   1554             if (mScrollY > scrollRange) {
   1555                 mScrollY = scrollRange;
   1556             } else if (mScrollY < 0) {
   1557                 mScrollY = 0;
   1558             }
   1559         }
   1560 
   1561         // Calling this with the present values causes it to re-claim them
   1562         scrollTo(mScrollX, mScrollY);
   1563     }
   1564 
   1565     @Override
   1566     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
   1567         super.onSizeChanged(w, h, oldw, oldh);
   1568 
   1569         View currentFocused = findFocus();
   1570         if (null == currentFocused || this == currentFocused)
   1571             return;
   1572 
   1573         // If the currently-focused view was visible on the screen when the
   1574         // screen was at the old height, then scroll the screen to make that
   1575         // view visible with the new screen height.
   1576         if (isWithinDeltaOfScreen(currentFocused, 0, oldh)) {
   1577             currentFocused.getDrawingRect(mTempRect);
   1578             offsetDescendantRectToMyCoords(currentFocused, mTempRect);
   1579             int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
   1580             doScrollY(scrollDelta);
   1581         }
   1582     }
   1583 
   1584     /**
   1585      * Return true if child is a descendant of parent, (or equal to the parent).
   1586      */
   1587     private static boolean isViewDescendantOf(View child, View parent) {
   1588         if (child == parent) {
   1589             return true;
   1590         }
   1591 
   1592         final ViewParent theParent = child.getParent();
   1593         return (theParent instanceof ViewGroup) && isViewDescendantOf((View) theParent, parent);
   1594     }
   1595 
   1596     /**
   1597      * Fling the scroll view
   1598      *
   1599      * @param velocityY The initial velocity in the Y direction. Positive
   1600      *                  numbers mean that the finger/cursor is moving down the screen,
   1601      *                  which means we want to scroll towards the top.
   1602      */
   1603     public void fling(int velocityY) {
   1604         if (getChildCount() > 0) {
   1605             int height = getHeight() - mPaddingBottom - mPaddingTop;
   1606             int bottom = getChildAt(0).getHeight();
   1607 
   1608             mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0,
   1609                     Math.max(0, bottom - height), 0, height/2);
   1610 
   1611             if (mFlingStrictSpan == null) {
   1612                 mFlingStrictSpan = StrictMode.enterCriticalSpan("ScrollView-fling");
   1613             }
   1614 
   1615             postInvalidateOnAnimation();
   1616         }
   1617     }
   1618 
   1619     private void flingWithNestedDispatch(int velocityY) {
   1620         final boolean canFling = (mScrollY > 0 || velocityY > 0) &&
   1621                 (mScrollY < getScrollRange() || velocityY < 0);
   1622         if (!dispatchNestedPreFling(0, velocityY)) {
   1623             dispatchNestedFling(0, velocityY, canFling);
   1624             if (canFling) {
   1625                 fling(velocityY);
   1626             }
   1627         }
   1628     }
   1629 
   1630     private void endDrag() {
   1631         mIsBeingDragged = false;
   1632 
   1633         recycleVelocityTracker();
   1634 
   1635         if (mEdgeGlowTop != null) {
   1636             mEdgeGlowTop.onRelease();
   1637             mEdgeGlowBottom.onRelease();
   1638         }
   1639 
   1640         if (mScrollStrictSpan != null) {
   1641             mScrollStrictSpan.finish();
   1642             mScrollStrictSpan = null;
   1643         }
   1644     }
   1645 
   1646     /**
   1647      * {@inheritDoc}
   1648      *
   1649      * <p>This version also clamps the scrolling to the bounds of our child.
   1650      */
   1651     @Override
   1652     public void scrollTo(int x, int y) {
   1653         // we rely on the fact the View.scrollBy calls scrollTo.
   1654         if (getChildCount() > 0) {
   1655             View child = getChildAt(0);
   1656             x = clamp(x, getWidth() - mPaddingRight - mPaddingLeft, child.getWidth());
   1657             y = clamp(y, getHeight() - mPaddingBottom - mPaddingTop, child.getHeight());
   1658             if (x != mScrollX || y != mScrollY) {
   1659                 super.scrollTo(x, y);
   1660             }
   1661         }
   1662     }
   1663 
   1664     @Override
   1665     public void setOverScrollMode(int mode) {
   1666         if (mode != OVER_SCROLL_NEVER) {
   1667             if (mEdgeGlowTop == null) {
   1668                 Context context = getContext();
   1669                 mEdgeGlowTop = new EdgeEffect(context);
   1670                 mEdgeGlowBottom = new EdgeEffect(context);
   1671             }
   1672         } else {
   1673             mEdgeGlowTop = null;
   1674             mEdgeGlowBottom = null;
   1675         }
   1676         super.setOverScrollMode(mode);
   1677     }
   1678 
   1679     @Override
   1680     public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
   1681         return (nestedScrollAxes & SCROLL_AXIS_VERTICAL) != 0;
   1682     }
   1683 
   1684     @Override
   1685     public void onNestedScrollAccepted(View child, View target, int axes) {
   1686         super.onNestedScrollAccepted(child, target, axes);
   1687         startNestedScroll(SCROLL_AXIS_VERTICAL);
   1688     }
   1689 
   1690     /**
   1691      * @inheritDoc
   1692      */
   1693     @Override
   1694     public void onStopNestedScroll(View target) {
   1695         super.onStopNestedScroll(target);
   1696     }
   1697 
   1698     @Override
   1699     public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
   1700             int dxUnconsumed, int dyUnconsumed) {
   1701         final int oldScrollY = mScrollY;
   1702         scrollBy(0, dyUnconsumed);
   1703         final int myConsumed = mScrollY - oldScrollY;
   1704         final int myUnconsumed = dyUnconsumed - myConsumed;
   1705         dispatchNestedScroll(0, myConsumed, 0, myUnconsumed, null);
   1706     }
   1707 
   1708     /**
   1709      * @inheritDoc
   1710      */
   1711     @Override
   1712     public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
   1713         if (!consumed) {
   1714             flingWithNestedDispatch((int) velocityY);
   1715             return true;
   1716         }
   1717         return false;
   1718     }
   1719 
   1720     @Override
   1721     public void draw(Canvas canvas) {
   1722         super.draw(canvas);
   1723         if (mEdgeGlowTop != null) {
   1724             final int scrollY = mScrollY;
   1725             final boolean clipToPadding = getClipToPadding();
   1726             if (!mEdgeGlowTop.isFinished()) {
   1727                 final int restoreCount = canvas.save();
   1728                 final int width;
   1729                 final int height;
   1730                 final float translateX;
   1731                 final float translateY;
   1732                 if (clipToPadding) {
   1733                     width = getWidth() - mPaddingLeft - mPaddingRight;
   1734                     height = getHeight() - mPaddingTop - mPaddingBottom;
   1735                     translateX = mPaddingLeft;
   1736                     translateY = mPaddingTop;
   1737                 } else {
   1738                     width = getWidth();
   1739                     height = getHeight();
   1740                     translateX = 0;
   1741                     translateY = 0;
   1742                 }
   1743                 canvas.translate(translateX, Math.min(0, scrollY) + translateY);
   1744                 mEdgeGlowTop.setSize(width, height);
   1745                 if (mEdgeGlowTop.draw(canvas)) {
   1746                     postInvalidateOnAnimation();
   1747                 }
   1748                 canvas.restoreToCount(restoreCount);
   1749             }
   1750             if (!mEdgeGlowBottom.isFinished()) {
   1751                 final int restoreCount = canvas.save();
   1752                 final int width;
   1753                 final int height;
   1754                 final float translateX;
   1755                 final float translateY;
   1756                 if (clipToPadding) {
   1757                     width = getWidth() - mPaddingLeft - mPaddingRight;
   1758                     height = getHeight() - mPaddingTop - mPaddingBottom;
   1759                     translateX = mPaddingLeft;
   1760                     translateY = mPaddingTop;
   1761                 } else {
   1762                     width = getWidth();
   1763                     height = getHeight();
   1764                     translateX = 0;
   1765                     translateY = 0;
   1766                 }
   1767                 canvas.translate(-width + translateX,
   1768                             Math.max(getScrollRange(), scrollY) + height + translateY);
   1769                 canvas.rotate(180, width, 0);
   1770                 mEdgeGlowBottom.setSize(width, height);
   1771                 if (mEdgeGlowBottom.draw(canvas)) {
   1772                     postInvalidateOnAnimation();
   1773                 }
   1774                 canvas.restoreToCount(restoreCount);
   1775             }
   1776         }
   1777     }
   1778 
   1779     private static int clamp(int n, int my, int child) {
   1780         if (my >= child || n < 0) {
   1781             /* my >= child is this case:
   1782              *                    |--------------- me ---------------|
   1783              *     |------ child ------|
   1784              * or
   1785              *     |--------------- me ---------------|
   1786              *            |------ child ------|
   1787              * or
   1788              *     |--------------- me ---------------|
   1789              *                                  |------ child ------|
   1790              *
   1791              * n < 0 is this case:
   1792              *     |------ me ------|
   1793              *                    |-------- child --------|
   1794              *     |-- mScrollX --|
   1795              */
   1796             return 0;
   1797         }
   1798         if ((my+n) > child) {
   1799             /* this case:
   1800              *                    |------ me ------|
   1801              *     |------ child ------|
   1802              *     |-- mScrollX --|
   1803              */
   1804             return child-my;
   1805         }
   1806         return n;
   1807     }
   1808 
   1809     @Override
   1810     protected void onRestoreInstanceState(Parcelable state) {
   1811         if (mContext.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR2) {
   1812             // Some old apps reused IDs in ways they shouldn't have.
   1813             // Don't break them, but they don't get scroll state restoration.
   1814             super.onRestoreInstanceState(state);
   1815             return;
   1816         }
   1817         SavedState ss = (SavedState) state;
   1818         super.onRestoreInstanceState(ss.getSuperState());
   1819         mSavedState = ss;
   1820         requestLayout();
   1821     }
   1822 
   1823     @Override
   1824     protected Parcelable onSaveInstanceState() {
   1825         if (mContext.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR2) {
   1826             // Some old apps reused IDs in ways they shouldn't have.
   1827             // Don't break them, but they don't get scroll state restoration.
   1828             return super.onSaveInstanceState();
   1829         }
   1830         Parcelable superState = super.onSaveInstanceState();
   1831         SavedState ss = new SavedState(superState);
   1832         ss.scrollPosition = mScrollY;
   1833         return ss;
   1834     }
   1835 
   1836     /** @hide */
   1837     @Override
   1838     protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
   1839         super.encodeProperties(encoder);
   1840         encoder.addProperty("fillViewport", mFillViewport);
   1841     }
   1842 
   1843     static class SavedState extends BaseSavedState {
   1844         public int scrollPosition;
   1845 
   1846         SavedState(Parcelable superState) {
   1847             super(superState);
   1848         }
   1849 
   1850         public SavedState(Parcel source) {
   1851             super(source);
   1852             scrollPosition = source.readInt();
   1853         }
   1854 
   1855         @Override
   1856         public void writeToParcel(Parcel dest, int flags) {
   1857             super.writeToParcel(dest, flags);
   1858             dest.writeInt(scrollPosition);
   1859         }
   1860 
   1861         @Override
   1862         public String toString() {
   1863             return "HorizontalScrollView.SavedState{"
   1864                     + Integer.toHexString(System.identityHashCode(this))
   1865                     + " scrollPosition=" + scrollPosition + "}";
   1866         }
   1867 
   1868         public static final Parcelable.Creator<SavedState> CREATOR
   1869                 = new Parcelable.Creator<SavedState>() {
   1870             public SavedState createFromParcel(Parcel in) {
   1871                 return new SavedState(in);
   1872             }
   1873 
   1874             public SavedState[] newArray(int size) {
   1875                 return new SavedState[size];
   1876             }
   1877         };
   1878     }
   1879 
   1880 }
   1881