60  static final int ANIMATED_SCROLL_GAP = 250;
 
   61  static final float MAX_SCROLL_FACTOR = 0.5f;
 
   62  private final Rect mTempRect = 
new Rect();
 
   64  private long mLastScroll;
 
   65  private Scroller mScroller;
 
   66  private boolean scrollEnabled = 
true;
 
   72  private boolean mTwoDScrollViewMovedFocus;
 
   76  private float mLastMotionY;
 
   77  private float mLastMotionX;
 
   82  private boolean mIsLayoutDirty = 
true;
 
   88  private View mChildToScrollTo = 
null;
 
   94  private boolean mIsBeingDragged = 
false;
 
   98  private VelocityTracker mVelocityTracker;
 
  102  private int mTouchSlop;
 
  103  private int mMinimumVelocity;
 
  104  private int mMaximumVelocity;
 
  108    initTwoDScrollView();
 
  111  public ScrollView2D(Context context, AttributeSet attrs)
 
  113    super(context, attrs);
 
  114    initTwoDScrollView();
 
  117  public ScrollView2D(Context context, AttributeSet attrs, 
int defStyle)
 
  119    super(context, attrs, defStyle);
 
  120    initTwoDScrollView();
 
  123  @Override 
protected float getTopFadingEdgeStrength()
 
  125    if (getChildCount() == 0)
 
  129    final int length = getVerticalFadingEdgeLength();
 
  130    if (getScrollY() < length)
 
  132      return getScrollY() / (float)length;
 
  137  @Override 
protected float getBottomFadingEdgeStrength()
 
  139    if (getChildCount() == 0)
 
  143    final int length = getVerticalFadingEdgeLength();
 
  144    final int bottomEdge = getHeight() - getPaddingBottom();
 
  145    final int span = getChildAt(0).getBottom() - getScrollY() - bottomEdge;
 
  148      return span / (float)length;
 
  153  @Override 
protected float getLeftFadingEdgeStrength()
 
  155    if (getChildCount() == 0)
 
  159    final int length = getHorizontalFadingEdgeLength();
 
  160    if (getScrollX() < length)
 
  162      return getScrollX() / (float)length;
 
  167  @Override 
protected float getRightFadingEdgeStrength()
 
  169    if (getChildCount() == 0)
 
  173    final int length = getHorizontalFadingEdgeLength();
 
  174    final int rightEdge = getWidth() - getPaddingRight();
 
  175    final int span = getChildAt(0).getRight() - getScrollX() - rightEdge;
 
  178      return span / (float)length;
 
  188    scrollEnabled = enable;
 
 
  197    return (
int)(MAX_SCROLL_FACTOR * getHeight());
 
 
  200  public int getMaxScrollAmountHorizontal()
 
  202    return (
int)(MAX_SCROLL_FACTOR * getWidth());
 
  205  private void initTwoDScrollView()
 
  207    mScroller = 
new Scroller(getContext());
 
  209    setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
 
  210    setWillNotDraw(
false);
 
  211    final ViewConfiguration configuration = ViewConfiguration.get(getContext());
 
  212    mTouchSlop = configuration.getScaledTouchSlop();
 
  213    mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
 
  214    mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
 
  217  @Override 
public void addView(View child)
 
  219    if (getChildCount() > 0)
 
  221      throw new IllegalStateException(
"TwoDScrollView can host only one direct child");
 
  223    super.addView(child);
 
  226  @Override 
public void addView(View child, 
int index)
 
  228    if (getChildCount() > 0)
 
  230      throw new IllegalStateException(
"TwoDScrollView can host only one direct child");
 
  232    super.addView(child, index);
 
  235  @Override 
public void addView(View child, ViewGroup.LayoutParams params)
 
  237    if (getChildCount() > 0)
 
  239      throw new IllegalStateException(
"TwoDScrollView can host only one direct child");
 
  241    super.addView(child, params);
 
  244  @Override 
public void addView(View child, 
int index, ViewGroup.LayoutParams params)
 
  246    if (getChildCount() > 0)
 
  248      throw new IllegalStateException(
"TwoDScrollView can host only one direct child");
 
  250    super.addView(child, index, params);
 
  256  private boolean canScroll()
 
  260    View child = getChildAt(0);
 
  263      int childHeight = child.getHeight();
 
  264      int childWidth = child.getWidth();
 
  265      return (getHeight() < childHeight + getPaddingTop() + getPaddingBottom()) ||
 
  266          (getWidth() < childWidth + getPaddingLeft() + getPaddingRight());
 
  271  @Override 
public boolean dispatchKeyEvent(KeyEvent event)
 
  274    boolean handled = super.dispatchKeyEvent(event);
 
  292    mTempRect.setEmpty();
 
  297        View currentFocused = findFocus();
 
  298        if (currentFocused == 
this)
 
  299          currentFocused = 
null;
 
  301            FocusFinder.getInstance().findNextFocus(
this, currentFocused, View.FOCUS_DOWN);
 
  302        return nextFocused != 
null && nextFocused != 
this &&
 
  303            nextFocused.requestFocus(View.FOCUS_DOWN);
 
  307    boolean handled = 
false;
 
  308    if (event.getAction() == KeyEvent.ACTION_DOWN)
 
  310      switch (event.getKeyCode())
 
  312        case KeyEvent.KEYCODE_DPAD_UP:
 
  313          if (!event.isAltPressed())
 
  322        case KeyEvent.KEYCODE_DPAD_DOWN:
 
  323          if (!event.isAltPressed())
 
  332        case KeyEvent.KEYCODE_DPAD_LEFT:
 
  333          if (!event.isAltPressed())
 
  342        case KeyEvent.KEYCODE_DPAD_RIGHT:
 
  343          if (!event.isAltPressed())
 
 
  357  @Override 
public boolean onInterceptTouchEvent(MotionEvent ev)
 
  368    final int action = ev.getAction();
 
  369    if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged))
 
  375      mIsBeingDragged = 
false;
 
  378    final float y = ev.getY();
 
  379    final float x = ev.getX();
 
  382      case MotionEvent.ACTION_MOVE:
 
  391        final int yDiff = (int)Math.abs(y - mLastMotionY);
 
  392        final int xDiff = (int)Math.abs(x - mLastMotionX);
 
  393        if (yDiff > mTouchSlop || xDiff > mTouchSlop)
 
  395          mIsBeingDragged = 
true;
 
  399      case MotionEvent.ACTION_DOWN:
 
  409        mIsBeingDragged = !mScroller.isFinished();
 
  412      case MotionEvent.ACTION_CANCEL:
 
  413      case MotionEvent.ACTION_UP:
 
  415        mIsBeingDragged = 
false;
 
  423    return mIsBeingDragged;
 
  426  @Override 
public boolean onTouchEvent(MotionEvent ev)
 
  429    if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0)
 
  441    if (mVelocityTracker == 
null)
 
  443      mVelocityTracker = VelocityTracker.obtain();
 
  445    mVelocityTracker.addMovement(ev);
 
  447    final int action = ev.getAction();
 
  448    final float y = ev.getY();
 
  449    final float x = ev.getX();
 
  453      case MotionEvent.ACTION_DOWN:
 
  458        if (!mScroller.isFinished())
 
  460          mScroller.abortAnimation();
 
  467      case MotionEvent.ACTION_MOVE:
 
  469        int deltaX = (int)(mLastMotionX - x);
 
  470        int deltaY = (int)(mLastMotionY - y);
 
  476          if (getScrollX() < 0)
 
  483          final int rightEdge = getWidth() - getPaddingRight();
 
  484          final int availableToScroll =
 
  485              getChildAt(0).getRight() - getScrollX() - rightEdge;
 
  486          if (availableToScroll > 0)
 
  488            deltaX = Math.min(availableToScroll, deltaX);
 
  497          if (getScrollY() < 0)
 
  504          final int bottomEdge = getHeight() - getPaddingBottom();
 
  505          final int availableToScroll =
 
  506              getChildAt(0).getBottom() - getScrollY() - bottomEdge;
 
  507          if (availableToScroll > 0)
 
  509            deltaY = Math.min(availableToScroll, deltaY);
 
  516        if (deltaY != 0 || deltaX != 0)
 
  517          scrollBy(deltaX, deltaY);
 
  519      case MotionEvent.ACTION_UP:
 
  520        final VelocityTracker velocityTracker = mVelocityTracker;
 
  521        velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
 
  522        int initialXVelocity = (int)velocityTracker.getXVelocity();
 
  523        int initialYVelocity = (int)velocityTracker.getYVelocity();
 
  524        if ((Math.abs(initialXVelocity) + Math.abs(initialYVelocity) > mMinimumVelocity) &&
 
  527          fling(-initialXVelocity, -initialYVelocity);
 
  529        if (mVelocityTracker != 
null)
 
  531          mVelocityTracker.recycle();
 
  532          mVelocityTracker = 
null;
 
  553  private View findFocusableViewInMyBounds(
final boolean topFocus, 
final int top,
 
  554                                           final boolean leftFocus, 
final int left,
 
  555                                           View preferredFocusable)
 
  562    final int verticalFadingEdgeLength = getVerticalFadingEdgeLength() / 2;
 
  563    final int topWithoutFadingEdge = top + verticalFadingEdgeLength;
 
  564    final int bottomWithoutFadingEdge = top + getHeight() - verticalFadingEdgeLength;
 
  565    final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength() / 2;
 
  566    final int leftWithoutFadingEdge = left + horizontalFadingEdgeLength;
 
  567    final int rightWithoutFadingEdge = left + getWidth() - horizontalFadingEdgeLength;
 
  569    if ((preferredFocusable != 
null) &&
 
  570        (preferredFocusable.getTop() < bottomWithoutFadingEdge) &&
 
  571        (preferredFocusable.getBottom() > topWithoutFadingEdge) &&
 
  572        (preferredFocusable.getLeft() < rightWithoutFadingEdge) &&
 
  573        (preferredFocusable.getRight() > leftWithoutFadingEdge))
 
  575      return preferredFocusable;
 
  577    return findFocusableViewInBounds(topFocus, topWithoutFadingEdge, bottomWithoutFadingEdge,
 
  578                                     leftFocus, leftWithoutFadingEdge, rightWithoutFadingEdge);
 
  595  private View findFocusableViewInBounds(
boolean topFocus, 
int top, 
int bottom, 
boolean leftFocus,
 
  598    List<View> focusables = getFocusables(View.FOCUS_FORWARD);
 
  599    View focusCandidate = 
null;
 
  608    boolean foundFullyContainedFocusable = 
false;
 
  610    int count = focusables.size();
 
  611    for (
int i = 0; i < count; i++)
 
  613      View view = focusables.get(i);
 
  614      int viewTop = view.getTop();
 
  615      int viewBottom = view.getBottom();
 
  616      int viewLeft = view.getLeft();
 
  617      int viewRight = view.getRight();
 
  619      if (top < viewBottom && viewTop < bottom && left < viewRight && viewLeft < right)
 
  625        final boolean viewIsFullyContained = (top < viewTop) && (viewBottom < bottom) &&
 
  626                                             (left < viewLeft) && (viewRight < right);
 
  627        if (focusCandidate == 
null)
 
  630          focusCandidate = view;
 
  631          foundFullyContainedFocusable = viewIsFullyContained;
 
  635          final boolean viewIsCloserToVerticalBoundary =
 
  636              (topFocus && viewTop < focusCandidate.getTop()) ||
 
  637              (!topFocus && viewBottom > focusCandidate.getBottom());
 
  638          final boolean viewIsCloserToHorizontalBoundary =
 
  639              (leftFocus && viewLeft < focusCandidate.getLeft()) ||
 
  640              (!leftFocus && viewRight > focusCandidate.getRight());
 
  641          if (foundFullyContainedFocusable)
 
  643            if (viewIsFullyContained && viewIsCloserToVerticalBoundary &&
 
  644                viewIsCloserToHorizontalBoundary)
 
  651              focusCandidate = view;
 
  656            if (viewIsFullyContained)
 
  659              focusCandidate = view;
 
  660              foundFullyContainedFocusable = 
true;
 
  662            else if (viewIsCloserToVerticalBoundary && viewIsCloserToHorizontalBoundary)
 
  668              focusCandidate = view;
 
  674    return focusCandidate;
 
  693      boolean down = direction == View.FOCUS_DOWN;
 
  694      int height = getHeight();
 
  696      mTempRect.bottom = height;
 
  699        int count = getChildCount();
 
  702          View view = getChildAt(count - 1);
 
  703          mTempRect.bottom = view.getBottom();
 
  704          mTempRect.top = mTempRect.bottom - height;
 
  707      return scrollAndFocus(direction, mTempRect.top, mTempRect.bottom, 0, 0, 0);
 
  711      boolean right = direction == View.FOCUS_DOWN;
 
  712      int width = getWidth();
 
  714      mTempRect.right = width;
 
  717        int count = getChildCount();
 
  720          View view = getChildAt(count - 1);
 
  721          mTempRect.right = view.getBottom();
 
  722          mTempRect.left = mTempRect.right - width;
 
  725      return scrollAndFocus(0, 0, 0, direction, mTempRect.top, mTempRect.bottom);
 
 
  742  private boolean scrollAndFocus(
int directionY, 
int top, 
int bottom, 
int directionX, 
int left,
 
  745    boolean handled = 
true;
 
  746    int height = getHeight();
 
  747    int containerTop = getScrollY();
 
  748    int containerBottom = containerTop + height;
 
  749    boolean up = directionY == View.FOCUS_UP;
 
  750    int width = getWidth();
 
  751    int containerLeft = getScrollX();
 
  752    int containerRight = containerLeft + width;
 
  753    boolean leftwards = directionX == View.FOCUS_UP;
 
  754    View newFocused = findFocusableViewInBounds(up, top, bottom, leftwards, left, right);
 
  755    if (newFocused == 
null)
 
  759    if ((top >= containerTop && bottom <= containerBottom) ||
 
  760        (left >= containerLeft && right <= containerRight))
 
  766      int deltaY = up ? (top - containerTop) : (bottom - containerBottom);
 
  767      int deltaX = leftwards ? (left - containerLeft) : (right - containerRight);
 
  768      doScroll(deltaX, deltaY);
 
  770    if (newFocused != findFocus() && newFocused.requestFocus(directionY))
 
  772      mTwoDScrollViewMovedFocus = 
true;
 
  773      mTwoDScrollViewMovedFocus = 
false;
 
  787    View currentFocused = findFocus();
 
  788    if (currentFocused == 
this)
 
  789      currentFocused = 
null;
 
  790    View nextFocused = FocusFinder.getInstance().findNextFocus(
this, currentFocused, direction);
 
  796      if (nextFocused != 
null)
 
  798        nextFocused.getDrawingRect(mTempRect);
 
  799        offsetDescendantRectToMyCoords(nextFocused, mTempRect);
 
  801        doScroll(0, scrollDelta);
 
  802        nextFocused.requestFocus(direction);
 
  807        int scrollDelta = maxJump;
 
  808        if (direction == View.FOCUS_UP && getScrollY() < scrollDelta)
 
  810          scrollDelta = getScrollY();
 
  812        else if (direction == View.FOCUS_DOWN)
 
  814          if (getChildCount() > 0)
 
  816            int daBottom = getChildAt(0).getBottom();
 
  817            int screenBottom = getScrollY() + getHeight();
 
  818            if (daBottom - screenBottom < maxJump)
 
  820              scrollDelta = daBottom - screenBottom;
 
  824        if (scrollDelta == 0)
 
  828        doScroll(0, direction == View.FOCUS_DOWN ? scrollDelta : -scrollDelta);
 
  833      if (nextFocused != 
null)
 
  835        nextFocused.getDrawingRect(mTempRect);
 
  836        offsetDescendantRectToMyCoords(nextFocused, mTempRect);
 
  838        doScroll(scrollDelta, 0);
 
  839        nextFocused.requestFocus(direction);
 
  844        int scrollDelta = maxJump;
 
  845        if (direction == View.FOCUS_UP && getScrollY() < scrollDelta)
 
  847          scrollDelta = getScrollY();
 
  849        else if (direction == View.FOCUS_DOWN)
 
  851          if (getChildCount() > 0)
 
  853            int daBottom = getChildAt(0).getBottom();
 
  854            int screenBottom = getScrollY() + getHeight();
 
  855            if (daBottom - screenBottom < maxJump)
 
  857              scrollDelta = daBottom - screenBottom;
 
  861        if (scrollDelta == 0)
 
  865        doScroll(direction == View.FOCUS_DOWN ? scrollDelta : -scrollDelta, 0);
 
 
  876  private void doScroll(
int deltaX, 
int deltaY)
 
  878    if (deltaX != 0 || deltaY != 0)
 
  892    long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
 
  893    if (duration > ANIMATED_SCROLL_GAP)
 
  895      mScroller.startScroll(getScrollX(), getScrollY(), dx, dy);
 
  896      awakenScrollBars(mScroller.getDuration());
 
  901      if (!mScroller.isFinished())
 
  903        mScroller.abortAnimation();
 
  907    mLastScroll = AnimationUtils.currentAnimationTimeMillis();
 
 
  927    int count = getChildCount();
 
  928    return count == 0 ? getHeight() : (getChildAt(0)).getBottom();
 
 
  931  @Override 
protected int computeHorizontalScrollRange()
 
  933    int count = getChildCount();
 
  934    return count == 0 ? getWidth() : (getChildAt(0)).getRight();
 
  938  protected void measureChild(View child, 
int parentWidthMeasureSpec, 
int parentHeightMeasureSpec)
 
  940    ViewGroup.LayoutParams lp = child.getLayoutParams();
 
  941    int childWidthMeasureSpec;
 
  942    int childHeightMeasureSpec;
 
  944    childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
 
  945                                                getPaddingLeft() + getPaddingRight(), lp.width);
 
  946    childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
 
  948    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
 
  952  protected void measureChildWithMargins(View child, 
int parentWidthMeasureSpec, 
int widthUsed,
 
  953                                         int parentHeightMeasureSpec, 
int heightUsed)
 
  955    final MarginLayoutParams lp = (MarginLayoutParams)child.getLayoutParams();
 
  956    final int childWidthMeasureSpec =
 
  957        MeasureSpec.makeMeasureSpec(lp.leftMargin + lp.rightMargin, MeasureSpec.UNSPECIFIED);
 
  958    final int childHeightMeasureSpec =
 
  959        MeasureSpec.makeMeasureSpec(lp.topMargin + lp.bottomMargin, MeasureSpec.UNSPECIFIED);
 
  961    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
 
  964  @Override 
public void computeScroll()
 
  966    if (mScroller.computeScrollOffset())
 
  984      int oldX = getScrollX();
 
  985      int oldY = getScrollY();
 
  986      int x = mScroller.getCurrX();
 
  987      int y = mScroller.getCurrY();
 
  988      if (getChildCount() > 0)
 
  990        View child = getChildAt(0);
 
  992            clamp(x, getWidth() - getPaddingRight() - getPaddingLeft(), child.getWidth()),
 
  993            clamp(y, getHeight() - getPaddingBottom() - getPaddingTop(),
 
 1000      if (oldX != getScrollX() || oldY != getScrollY())
 
 1002        onScrollChanged(getScrollX(), getScrollY(), oldX, oldY);
 
 1015  private void scrollToChild(View child)
 
 1017    child.getDrawingRect(mTempRect);
 
 1019    offsetDescendantRectToMyCoords(child, mTempRect);
 
 1021    if (scrollDelta != 0)
 
 1023      scrollBy(0, scrollDelta);
 
 1035  private boolean scrollToChildRect(Rect rect, 
boolean immediate)
 
 1038    final boolean scroll = delta != 0;
 
 1063    if (getChildCount() == 0)
 
 1065    int height = getHeight();
 
 1066    int screenTop = getScrollY();
 
 1067    int screenBottom = screenTop + height;
 
 1068    int fadingEdge = getVerticalFadingEdgeLength();
 
 1072      screenTop += fadingEdge;
 
 1076    if (rect.bottom < getChildAt(0).getHeight())
 
 1078      screenBottom -= fadingEdge;
 
 1080    int scrollYDelta = 0;
 
 1081    if (rect.bottom > screenBottom && rect.top > screenTop)
 
 1086      if (rect.height() > height)
 
 1089        scrollYDelta += (rect.top - screenTop);
 
 1094        scrollYDelta += (rect.bottom - screenBottom);
 
 1098      int bottom = getChildAt(0).getBottom();
 
 1099      int distanceToBottom = bottom - screenBottom;
 
 1100      scrollYDelta = Math.min(scrollYDelta, distanceToBottom);
 
 1102    else if (rect.top < screenTop && rect.bottom < screenBottom)
 
 1108      if (rect.height() > height)
 
 1111        scrollYDelta -= (screenBottom - rect.bottom);
 
 1116        scrollYDelta -= (screenTop - rect.top);
 
 1120      scrollYDelta = Math.max(scrollYDelta, -getScrollY());
 
 1122    return scrollYDelta;
 
 
 1125  @Override 
public void requestChildFocus(View child, View focused)
 
 1127    if (!mTwoDScrollViewMovedFocus)
 
 1129      if (!mIsLayoutDirty)
 
 1131        scrollToChild(focused);
 
 1136        mChildToScrollTo = focused;
 
 1139    super.requestChildFocus(child, focused);
 
 1154    if (direction == View.FOCUS_FORWARD)
 
 1156      direction = View.FOCUS_DOWN;
 
 1158    else if (direction == View.FOCUS_BACKWARD)
 
 1160      direction = View.FOCUS_UP;
 
 1163    final View nextFocus = previouslyFocusedRect == 
null 
 1164                               ? FocusFinder.getInstance().findNextFocus(
this, 
null, direction)
 
 1165                               : FocusFinder.getInstance().findNextFocusFromRect(
 
 1166                                     this, previouslyFocusedRect, direction);
 
 1168    if (nextFocus == 
null)
 
 1173    return nextFocus.requestFocus(direction, previouslyFocusedRect);
 
 
 1177  public boolean requestChildRectangleOnScreen(View child, Rect rectangle, 
boolean immediate)
 
 1180    rectangle.offset(child.getLeft() - child.getScrollX(), child.getTop() - child.getScrollY());
 
 1181    return scrollToChildRect(rectangle, immediate);
 
 1184  @Override 
public void requestLayout()
 
 1186    mIsLayoutDirty = 
true;
 
 1187    super.requestLayout();
 
 1190  @Override 
protected void onLayout(
boolean changed, 
int l, 
int t, 
int r, 
int b)
 
 1192    super.onLayout(changed, l, t, r, b);
 
 1193    mIsLayoutDirty = 
false;
 
 1195    if (mChildToScrollTo != 
null && isViewDescendantOf(mChildToScrollTo, 
this))
 
 1197      scrollToChild(mChildToScrollTo);
 
 1199    mChildToScrollTo = 
null;
 
 1202    scrollTo(getScrollX(), getScrollY());
 
 1205  @Override 
protected void onSizeChanged(
int w, 
int h, 
int oldw, 
int oldh)
 
 1207    super.onSizeChanged(w, h, oldw, oldh);
 
 1209    View currentFocused = findFocus();
 
 1210    if (
null == currentFocused || 
this == currentFocused)
 
 1216    currentFocused.getDrawingRect(mTempRect);
 
 1217    offsetDescendantRectToMyCoords(currentFocused, mTempRect);
 
 1220    doScroll(scrollDeltaX, scrollDeltaY);
 
 1226  private boolean isViewDescendantOf(View child, View parent)
 
 1228    if (child == parent)
 
 1233    final ViewParent theParent = child.getParent();
 
 1234    return (theParent instanceof ViewGroup) && isViewDescendantOf((View)theParent, parent);
 
 1244  public void fling(
int velocityX, 
int velocityY)
 
 1246    if (getChildCount() > 0)
 
 1248      int height = getHeight() - getPaddingBottom() - getPaddingTop();
 
 1249      int bottom = getChildAt(0).getHeight();
 
 1250      int width = getWidth() - getPaddingRight() - getPaddingLeft();
 
 1251      int right = getChildAt(0).getWidth();
 
 1253      mScroller.fling(getScrollX(), getScrollY(), velocityX, velocityY, 0, right - width, 0,
 
 1256      final boolean movingDown = velocityY > 0;
 
 1257      final boolean movingRight = velocityX > 0;
 
 1259      View newFocused = findFocusableViewInMyBounds(
 
 1260          movingRight, mScroller.getFinalX(), movingDown, mScroller.getFinalY(), findFocus());
 
 1261      if (newFocused == 
null)
 
 1266      if (newFocused != findFocus() &&
 
 1267          newFocused.requestFocus(movingDown ? View.FOCUS_DOWN : View.FOCUS_UP))
 
 1269        mTwoDScrollViewMovedFocus = 
true;
 
 1270        mTwoDScrollViewMovedFocus = 
false;
 
 1273      awakenScrollBars(mScroller.getDuration());
 
 
 1286    if (getChildCount() > 0)
 
 1288      View child = getChildAt(0);
 
 1289      x = clamp(x, getWidth() - getPaddingRight() - getPaddingLeft(), child.getWidth());
 
 1290      y = clamp(y, getHeight() - getPaddingBottom() - getPaddingTop(), child.getHeight());
 
 1291      if (x != getScrollX() || y != getScrollY())
 
 1293        super.scrollTo(x, y);
 
 
 1298  private int clamp(
int n, 
int my, 
int child)
 
 1300    if (my >= child || n < 0)
 
 1319    if ((my + n) > child)
 
 1331  public void setScrollViewListener(ScrollView2DListener scrollViewListener)
 
 1333    this.scrollView2DListener = scrollViewListener;
 
 1336  @Override 
protected void onScrollChanged(
int x, 
int y, 
int oldx, 
int oldy)
 
 1338    super.onScrollChanged(x, y, oldx, oldy);
 
 1339    if (scrollView2DListener != 
null)
 
 1341      scrollView2DListener.onScrollChanged(
this, x, y, oldx, oldy);
 
 1347    void onScrollChanged(
ScrollView2D scrollView, 
int x, 
int y, 
int oldx, 
int oldy);