import java.util.List;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.FocusFinder;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.animation.AnimationUtils;
import android.widget.FrameLayout;
import android.widget.Scroller;     
/**
- 
Reference to ScrollView and HorizontalScrollView 
 */
 public class HVScrollView extends FrameLayout {
 static final int ANIMATED_SCROLL_GAP = 250;static final float MAX_SCROLL_FACTOR = 0.5f; private long mLastScroll; private final Rect mTempRect = new Rect(); 
 private Scroller mScroller;/** - Flag to indicate that we are moving focus ourselves. This is so the
- code that watches for focus changes initiated outside this ScrollView
- knows that it does not have to do anything.
 */
 private boolean mScrollViewMovedFocus;
 /** - Position of the last motion event.
 */
 private float mLastMotionY;
 private float mLastMotionX;
 /** - True when the layout has changed but the traversal has not come through yet.
- Ideally the view hierarchy would keep track of this for us.
 */
 private boolean mIsLayoutDirty = true;
 /** - The child to give focus to in the event that a child has requested focus while the
- layout is dirty. This prevents the scroll from being wrong if the child has not been
- laid out before requesting focus.
 */
 private View mChildToScrollTo = null;
 /** - True if the user is currently dragging this ScrollView around. This is
- not the same as 'is being flinged', which can be checked by
- mScroller.isFinished() (flinging begins when the user lifts his finger).
 */
 private boolean mIsBeingDragged = false;
 /** - Determines speed during touch scrolling
 */
 private VelocityTracker mVelocityTracker;
 /** - When set to true, the scroll view measure its child to make it fill the currently
- visible area.
 */
 private boolean mFillViewport;
 /** - Whether arrow scrolling is animated.
 */
 private boolean mSmoothScrollingEnabled = true;
 private int mTouchSlop; 
 private int mMinimumVelocity;
 private int mMaximumVelocity;/** - ID of the active pointer. This is used to retain consistency during
- drags/flings if multiple pointers are used.
 */
 private int mActivePointerId = INVALID_POINTER;
 /** - Sentinel value for no current active pointer.
- Used by {@link #mActivePointerId}.
 */
 private static final int INVALID_POINTER = -1;
 private boolean mFlingEnabled = true; public HVScrollView(Context context) { 
 this(context, null);
 }public HVScrollView(Context context, AttributeSet attrs) { 
 super(context, attrs);
 initScrollView();
 }@Override 
 protected float getTopFadingEdgeStrength() {
 if (getChildCount() == 0) {
 return 0.0f;
 }final int length = getVerticalFadingEdgeLength(); if (getScrollY() < length) { return getScrollY() / (float) length; } return 1.0f;} @Override 
 protected float getLeftFadingEdgeStrength() {
 if (getChildCount() == 0) {
 return 0.0f;
 }final int length = getHorizontalFadingEdgeLength(); if (getScrollX() < length) { return getScrollX() / (float) length; } return 1.0f;} @Override 
 protected float getRightFadingEdgeStrength() {
 if (getChildCount() == 0) {
 return 0.0f;
 }final int length = getHorizontalFadingEdgeLength(); final int rightEdge = getWidth() - getPaddingRight(); final int span = getChildAt(0).getRight() - getScrollX() - rightEdge; if (span < length) { return span / (float) length; } return 1.0f;} @Override 
 protected float getBottomFadingEdgeStrength() {
 if (getChildCount() == 0) {
 return 0.0f;
 }final int length = getVerticalFadingEdgeLength(); final int bottomEdge = getHeight() - getPaddingBottom(); final int span = getChildAt(0).getBottom() - getScrollY() - bottomEdge; if (span < length) { return span / (float) length; } return 1.0f;} /** - @return The maximum amount this scroll view will scroll in response to
- an arrow event.
 /
 public int getMaxScrollAmountV() {
 return (int) (MAX_SCROLL_FACTOR (getBottom() - getTop()));
 }
 public int getMaxScrollAmountH() { 
 return (int) (MAX_SCROLL_FACTOR * (getRight() - getLeft()));
 }private void initScrollView() { 
 mScroller = new Scroller(getContext());
 setFocusable(true);
 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
 setWillNotDraw(false);
 final ViewConfiguration configuration = ViewConfiguration.get(getContext());
 mTouchSlop = configuration.getScaledTouchSlop();
 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
 }@Override 
 public void addView(View child) {
 if (getChildCount() > 0) {
 throw new IllegalStateException("ScrollView can host only one direct child");
 }super.addView(child);} @Override 
 public void addView(View child, int index) {
 if (getChildCount() > 0) {
 throw new IllegalStateException("ScrollView can host only one direct child");
 }super.addView(child, index);} @Override 
 public void addView(View child, ViewGroup.LayoutParams params) {
 if (getChildCount() > 0) {
 throw new IllegalStateException("ScrollView can host only one direct child");
 }super.addView(child, params);} @Override 
 public void addView(View child, int index, ViewGroup.LayoutParams params) {
 if (getChildCount() > 0) {
 throw new IllegalStateException("ScrollView can host only one direct child");
 }super.addView(child, index, params);} /** - @return Returns true this ScrollView can be scrolled
 */
 private boolean canScrollV() {
 View child = getChildAt(0);
 if (child != null) {
 int childHeight = child.getHeight();
 return getHeight() < childHeight + getPaddingTop() + getPaddingBottom();
 }
 return false;
 }
 private boolean canScrollH() { 
 View child = getChildAt(0);
 if (child != null) {
 int childWidth = child.getWidth();
 return getWidth() < childWidth + getPaddingLeft() + getPaddingRight() ;
 }
 return false;
 }/** - Indicates whether this ScrollView's content is stretched to fill the viewport.
- @return True if the content fills the viewport, false otherwise.
 */
 public boolean isFillViewport() {
 return mFillViewport;
 }
 /** - Indicates this ScrollView whether it should stretch its content height to fill
- the viewport or not.
- @param fillViewport True to stretch the content's height to the viewport's
- boundaries, false otherwise.
 */
 public void setFillViewport(boolean fillViewport) {
 if (fillViewport != mFillViewport) {
 mFillViewport = fillViewport;
 requestLayout();
 }
 }
 /** - @return Whether arrow scrolling will animate its transition.
 */
 public boolean isSmoothScrollingEnabled() {
 return mSmoothScrollingEnabled;
 }
 /** - Set whether arrow scrolling will animate its transition.
- @param smoothScrollingEnabled whether arrow scrolling will animate its transition
 */
 public void setSmoothScrollingEnabled(boolean smoothScrollingEnabled) {
 mSmoothScrollingEnabled = smoothScrollingEnabled;
 }
 @Override 
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 super.onMeasure(widthMeasureSpec, heightMeasureSpec);if (!mFillViewport) { return; } final int heightMode = MeasureSpec.getMode(heightMeasureSpec); final int widthMode = MeasureSpec.getMode(widthMeasureSpec); if (heightMode == MeasureSpec.UNSPECIFIED && widthMode == MeasureSpec.UNSPECIFIED) { return; } if (getChildCount() > 0) { final View child = getChildAt(0); int height = getMeasuredHeight(); int width = getMeasuredWidth(); if (child.getMeasuredHeight() < height || child.getMeasuredWidth() < width) { width -= getPaddingLeft(); width -= getPaddingRight(); int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY); height -= getPaddingTop(); height -= getPaddingBottom(); int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } }} 
 @Override
 public boolean dispatchKeyEvent(KeyEvent event) {
 // Let the focused view and/or our descendants get the key first
 return super.dispatchKeyEvent(event) || executeKeyEvent(event);
 }/** - You can call this function yourself to have the scroll view perform
- scrolling from a key event, just as if the event had been dispatched to
- it by the view hierarchy.
- @param event The key event to execute.
- 
@return Return true if the event was handled, else false. 
 */
 public boolean executeKeyEvent(KeyEvent event) {
 mTempRect.setEmpty();boolean handled = false; if (event.getAction() == KeyEvent.ACTION_DOWN) { 
 switch (event.getKeyCode()) {
 case KeyEvent.KEYCODE_DPAD_LEFT:
 if(canScrollH()){
 if (!event.isAltPressed()) {
 handled = arrowScrollH(View.FOCUS_LEFT);
 } else {
 handled = fullScrollH(View.FOCUS_LEFT);
 }
 }
 break;
 case KeyEvent.KEYCODE_DPAD_RIGHT:
 if(canScrollH()){
 if (!event.isAltPressed()) {
 handled = arrowScrollH(View.FOCUS_RIGHT);
 } else {
 handled = fullScrollH(View.FOCUS_RIGHT);
 }
 }
 break;
 case KeyEvent.KEYCODE_DPAD_UP:
 if(canScrollV()){
 if (!event.isAltPressed()) {
 handled = arrowScrollV(View.FOCUS_UP);
 } else {
 handled = fullScrollV(View.FOCUS_UP);
 }
 }
 break;
 case KeyEvent.KEYCODE_DPAD_DOWN:
 if(canScrollV()){
 if (!event.isAltPressed()) {
 handled = arrowScrollV(View.FOCUS_DOWN);
 } else {
 handled = fullScrollV(View.FOCUS_DOWN);
 }
 }
 break;
 }
 }
 return handled;
 }
 private boolean inChild(int x, int y) { 
 if (getChildCount() > 0) {
 final int scrollX = getScrollX();
 final int scrollY = getScrollY();
 final View child = getChildAt(0);
 return !(y < child.getTop() - scrollY
 || y >= child.getBottom() - scrollY
 || x < child.getLeft() - scrollX
 || x >= child.getRight() - scrollX);
 }
 return false;
 }@Override 
 public boolean onInterceptTouchEvent(MotionEvent ev) {
 /*- This method JUST determines whether we want to intercept the motion.
- If we return true, onMotionEvent will be called and we do the actual
- 
scrolling there. 
 *//* 
- Shortcut the most recurring case: the user is in the dragging
- state and he is moving his finger. We want to intercept this
- 
motion. 
 */
 final int action = ev.getAction();
 if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
 return true;
 }switch (action & MotionEvent.ACTION_MASK) { 
 case MotionEvent.ACTION_MOVE: {
 /*- mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
- 
whether the user has moved far enough from his original down touch. 
 *//* 
- Locally do absolute value. mLastMotionY is set to the y value
- 
of the down event. 
 */
 final int activePointerId = mActivePointerId;
 if (activePointerId == INVALID_POINTER) {
 // If we don't have a valid id, the touch down wasn't on content.
 break;
 }final int pointerIndex = ev.findPointerIndex(activePointerId); 
 final float y = ev.getY(pointerIndex);
 final int yDiff = (int) Math.abs(y - mLastMotionY);
 if (yDiff > mTouchSlop) {
 mIsBeingDragged = true;
 mLastMotionY = y;
 }
 final float x = ev.getX(pointerIndex);
 final int xDiff = (int) Math.abs(x - mLastMotionX);
 if (xDiff > mTouchSlop) {
 mIsBeingDragged = true;
 mLastMotionX = x;
 }
 break;
 }
 case MotionEvent.ACTION_DOWN: { 
 final float x = ev.getX();
 final float y = ev.getY();
 if (!inChild((int)x, (int) y)) {
 mIsBeingDragged = false;
 break;
 }/* * Remember location of down touch. * ACTION_DOWN always refers to pointer index 0. */ mLastMotionY = y; mLastMotionX = x; mActivePointerId = ev.getPointerId(0); /* * If being flinged and user touches the screen, initiate drag; * otherwise don't. mScroller.isFinished should be false when * being flinged. */ mIsBeingDragged = !mScroller.isFinished(); break;} case MotionEvent.ACTION_CANCEL: 
 case MotionEvent.ACTION_UP:
 / Release the drag /
 mIsBeingDragged = false;
 mActivePointerId = INVALID_POINTER;
 break;
 case MotionEvent.ACTION_POINTER_UP:
 onSecondaryPointerUp(ev);
 break;
 }/* 
- The only time we want to intercept motion events is if we are in the
- drag mode.
 */
 return mIsBeingDragged;
 }
 @Override 
 public boolean onTouchEvent(MotionEvent ev) {if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) { // Don't handle edge touches immediately -- they may actually belong to one of our // descendants. return false; } if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(ev); final int action = ev.getAction(); switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: { final float x = ev.getX(); final float y = ev.getY(); if (!(mIsBeingDragged = inChild((int) x, (int) y))) { return false; } /* * If being flinged and user touches, stop the fling. isFinished * will be false if being flinged. */ if (!mScroller.isFinished()) { mScroller.abortAnimation(); } // Remember where the motion event started mLastMotionY = y; mLastMotionX = x; mActivePointerId = ev.getPointerId(0); break; } case MotionEvent.ACTION_MOVE: if (mIsBeingDragged) { // Scroll to follow the motion event final int activePointerIndex = ev.findPointerIndex(mActivePointerId); final float y = ev.getY(activePointerIndex); final int deltaY = (int) (mLastMotionY - y); mLastMotionY = y; final float x = ev.getX(activePointerIndex); final int deltaX = (int) (mLastMotionX - x); mLastMotionX = x; scrollBy(deltaX, deltaY); } break; case MotionEvent.ACTION_UP: if (mIsBeingDragged) { if(mFlingEnabled){ final VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); int initialVelocitx = (int) velocityTracker.getXVelocity(); int initialVelocity = (int) velocityTracker.getYVelocity();// int initialVelocitx = (int) velocityTracker.getXVelocity(mActivePointerId); 
 // int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);if (getChildCount() > 0) { if(Math.abs(initialVelocitx) > initialVelocitx || Math.abs(initialVelocity) > mMinimumVelocity) { fling(-initialVelocitx, -initialVelocity); } } } mActivePointerId = INVALID_POINTER; mIsBeingDragged = false; if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } } break; case MotionEvent.ACTION_CANCEL: if (mIsBeingDragged && getChildCount() > 0) { mActivePointerId = INVALID_POINTER; mIsBeingDragged = false; if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } } break; case MotionEvent.ACTION_POINTER_UP: onSecondaryPointerUp(ev); break; } return true;} private void onSecondaryPointerUp(MotionEvent ev) { 
 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_ID_MASK) >>
 MotionEvent.ACTION_POINTER_ID_SHIFT;
 final int pointerId = ev.getPointerId(pointerIndex);
 if (pointerId == mActivePointerId) {
 // This was our active pointer going up. Choose a new
 // active pointer and adjust accordingly.
 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
 mLastMotionX = ev.getX(newPointerIndex);
 mLastMotionY = ev.getY(newPointerIndex);
 mActivePointerId = ev.getPointerId(newPointerIndex);
 if (mVelocityTracker != null) {
 mVelocityTracker.clear();
 }
 }
 }
 /**- <p>
- Finds the next focusable component that fits in the specified bounds.
- </p>
- @param topFocus look for a candidate is the one at the top of the bounds
- if topFocus is true, or at the bottom of the bounds if topFocus is
- false
- @param top the top offset of the bounds in which a focusable must be
- found
- @param bottom the bottom offset of the bounds in which a focusable must
- be found
- @return the next focusable component in the bounds or null if none can
- 
be found 
 */
 private View findFocusableViewInBoundsV(boolean topFocus, int top, int bottom) {List<View> focusables = getFocusables(View.FOCUS_FORWARD); 
 View focusCandidate = null;/* - A fully contained focusable is one where its top is below the bound's
- top, and its bottom is above the bound's bottom. A partially
- contained focusable is one where some part of it is within the
- bounds, but it also has some part that is not within bounds. A fully contained
- focusable is preferred to a partially contained focusable.
 */
 boolean foundFullyContainedFocusable = false;
 int count = focusables.size(); 
 for (int i = 0; i < count; i++) {
 View view = focusables.get(i);
 int viewTop = view.getTop();
 int viewBottom = view.getBottom();if (top < viewBottom && viewTop < bottom) { /* * the focusable is in the target area, it is a candidate for * focusing */ final boolean viewIsFullyContained = (top < viewTop) && (viewBottom < bottom); if (focusCandidate == null) { /* No candidate, take this one */ focusCandidate = view; foundFullyContainedFocusable = viewIsFullyContained; } else { final boolean viewIsCloserToBoundary = (topFocus && viewTop < focusCandidate.getTop()) || (!topFocus && viewBottom > focusCandidate .getBottom()); if (foundFullyContainedFocusable) { if (viewIsFullyContained && viewIsCloserToBoundary) { /* * We're dealing with only fully contained views, so * it has to be closer to the boundary to beat our * candidate */ focusCandidate = view; } } else { if (viewIsFullyContained) { /* Any fully contained view beats a partially contained view */ focusCandidate = view; foundFullyContainedFocusable = true; } else if (viewIsCloserToBoundary) { /* * Partially contained view beats another partially * contained view if it's closer */ focusCandidate = view; } } } }} return focusCandidate; 
 }
 private View findFocusableViewInBoundsH(boolean leftFocus, int left, int right) { List<View> focusables = getFocusables(View.FOCUS_FORWARD); View focusCandidate = null; /* * A fully contained focusable is one where its left is below the bound's * left, and its right is above the bound's right. A partially * contained focusable is one where some part of it is within the * bounds, but it also has some part that is not within bounds. A fully contained * focusable is preferred to a partially contained focusable. */ boolean foundFullyContainedFocusable = false; int count = focusables.size(); for (int i = 0; i < count; i++) { View view = focusables.get(i); int viewLeft = view.getLeft(); int viewRight = view.getRight(); if (left < viewRight && viewLeft < right) { /* * the focusable is in the target area, it is a candidate for * focusing */ final boolean viewIsFullyContained = (left < viewLeft) && (viewRight < right); if (focusCandidate == null) { /* No candidate, take this one */ focusCandidate = view; foundFullyContainedFocusable = viewIsFullyContained; } else { final boolean viewIsCloserToBoundary = (leftFocus && viewLeft < focusCandidate.getLeft()) || (!leftFocus && viewRight > focusCandidate.getRight()); if (foundFullyContainedFocusable) { if (viewIsFullyContained && viewIsCloserToBoundary) { /* * We're dealing with only fully contained views, so * it has to be closer to the boundary to beat our * candidate */ focusCandidate = view; } } else { if (viewIsFullyContained) { /* Any fully contained view beats a partially contained view */ focusCandidate = view; foundFullyContainedFocusable = true; } else if (viewIsCloserToBoundary) { /* * Partially contained view beats another partially * contained view if it's closer */ focusCandidate = view; } } } } } return focusCandidate;} /** - <p>Handles scrolling in response to a "home/end" shortcut press. This
- method will scroll the view to the top or bottom and give the focus
- to the topmost/bottommost component in the new visible area. If no
- component is a good candidate for focus, this scrollview reclaims the
- focus.</p>
- @param direction the scroll direction: {@link android.view.View#FOCUS_UP}
- to go the top of the view or
- {@link android.view.View#FOCUS_DOWN} to go the bottom
- 
@return true if the key event is consumed by this method, false otherwise 
 */
 public boolean fullScrollV(int direction) {
 boolean down = direction == View.FOCUS_DOWN;
 int height = getHeight();mTempRect.top = 0; 
 mTempRect.bottom = height;if (down) { 
 int count = getChildCount();
 if (count > 0) {
 View view = getChildAt(count - 1);
 mTempRect.bottom = view.getBottom();
 mTempRect.top = mTempRect.bottom - height;
 }
 }return scrollAndFocusV(direction, mTempRect.top, mTempRect.bottom); 
 }
 public boolean fullScrollH(int direction) { 
 boolean right = direction == View.FOCUS_RIGHT;
 int width = getWidth();mTempRect.left = 0; mTempRect.right = width; if (right) { int count = getChildCount(); if (count > 0) { View view = getChildAt(0); mTempRect.right = view.getRight(); mTempRect.left = mTempRect.right - width; } } return scrollAndFocusH(direction, mTempRect.left, mTempRect.right);} /** - <p>Scrolls the view to make the area defined by <code>top</code> and
- <code>bottom</code> visible. This method attempts to give the focus
- to a component visible in this area. If no component can be focused in
- the new visible area, the focus is reclaimed by this scrollview.</p>
- @param direction the scroll direction: {@link android.view.View#FOCUS_UP}
- to go upward
- {@link android.view.View#FOCUS_DOWN} to downward
- @param top the top offset of the new area to be made visible
- @param bottom the bottom offset of the new area to be made visible
- 
@return true if the key event is consumed by this method, false otherwise 
 */
 private boolean scrollAndFocusV(int direction, int top, int bottom) {
 boolean handled = true;int height = getHeight(); 
 int containerTop = getScrollY();
 int containerBottom = containerTop + height;
 boolean up = direction == View.FOCUS_UP;View newFocused = findFocusableViewInBoundsV(up, top, bottom); 
 if (newFocused == null) {
 newFocused = this;
 }if (top >= containerTop && bottom <= containerBottom) { 
 handled = false;
 } else {
 int delta = up ? (top - containerTop) : (bottom - containerBottom);
 doScrollY(delta);
 }if (newFocused != findFocus() && newFocused.requestFocus(direction)) { 
 mScrollViewMovedFocus = true;
 mScrollViewMovedFocus = false;
 }return handled; 
 }
 private boolean scrollAndFocusH(int direction, int left, int right) { 
 boolean handled = true;int width = getWidth(); int containerLeft = getScrollX(); int containerRight = containerLeft + width; boolean goLeft = direction == View.FOCUS_LEFT; View newFocused = findFocusableViewInBoundsH(goLeft, left, right); if (newFocused == null) { newFocused = this; } if (left >= containerLeft && right <= containerRight) { handled = false; } else { int delta = goLeft ? (left - containerLeft) : (right - containerRight); doScrollX(delta); } if (newFocused != findFocus() && newFocused.requestFocus(direction)) { mScrollViewMovedFocus = true; mScrollViewMovedFocus = false; } return handled;} /** - Handle scrolling in response to an up or down arrow click.
- @param direction The direction corresponding to the arrow key that was
- pressed
- 
@return True if we consumed the event, false otherwise 
 */
 public boolean arrowScrollV(int direction) {View currentFocused = findFocus(); 
 if (currentFocused == this) currentFocused = null;View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, direction); final int maxJump = getMaxScrollAmountV(); if (nextFocused != null && isWithinDeltaOfScreenV(nextFocused, maxJump, getHeight())) { 
 nextFocused.getDrawingRect(mTempRect);
 offsetDescendantRectToMyCoords(nextFocused, mTempRect);
 int scrollDelta = computeScrollDeltaToGetChildRectOnScreenV(mTempRect);
 doScrollY(scrollDelta);
 nextFocused.requestFocus(direction);
 } else {
 // no new focus
 int scrollDelta = maxJump;if (direction == View.FOCUS_UP && getScrollY() < scrollDelta) { scrollDelta = getScrollY(); } else if (direction == View.FOCUS_DOWN) { if (getChildCount() > 0) { int daBottom = getChildAt(0).getBottom(); int screenBottom = getScrollY() + getHeight(); if (daBottom - screenBottom < maxJump) { scrollDelta = daBottom - screenBottom; } } } if (scrollDelta == 0) { return false; } doScrollY(direction == View.FOCUS_DOWN ? scrollDelta : -scrollDelta);} if (currentFocused != null && currentFocused.isFocused() 
 && isOffScreenV(currentFocused)) {
 // previously focused item still has focus and is off screen, give
 // it up (take it back to ourselves)
 // (also, need to temporarily force FOCUS_BEFORE_DESCENDANTS so we are
 // sure to
 // get it)
 final int descendantFocusability = getDescendantFocusability(); // save
 setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
 requestFocus();
 setDescendantFocusability(descendantFocusability); // restore
 }
 return true;
 }
 public boolean arrowScrollH(int direction) { View currentFocused = findFocus(); if (currentFocused == this) currentFocused = null; View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, direction); final int maxJump = getMaxScrollAmountH(); if (nextFocused != null && isWithinDeltaOfScreenH(nextFocused, maxJump)) { nextFocused.getDrawingRect(mTempRect); offsetDescendantRectToMyCoords(nextFocused, mTempRect); int scrollDelta = computeScrollDeltaToGetChildRectOnScreenH(mTempRect); doScrollX(scrollDelta); nextFocused.requestFocus(direction); } else { // no new focus int scrollDelta = maxJump; if (direction == View.FOCUS_LEFT && getScrollX() < scrollDelta) { scrollDelta = getScrollX(); } else if (direction == View.FOCUS_RIGHT && getChildCount() > 0) { int daRight = getChildAt(0).getRight(); int screenRight = getScrollX() + getWidth(); if (daRight - screenRight < maxJump) { scrollDelta = daRight - screenRight; } } if (scrollDelta == 0) { return false; } doScrollX(direction == View.FOCUS_RIGHT ? scrollDelta : -scrollDelta); } if (currentFocused != null && currentFocused.isFocused() && isOffScreenH(currentFocused)) { // previously focused item still has focus and is off screen, give // it up (take it back to ourselves) // (also, need to temporarily force FOCUS_BEFORE_DESCENDANTS so we are // sure to // get it) final int descendantFocusability = getDescendantFocusability(); // save setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); requestFocus(); setDescendantFocusability(descendantFocusability); // restore } return true;} 
 /**- @return whether the descendant of this scroll view is scrolled off
- screen.
 */
 private boolean isOffScreenV(View descendant) {
 return !isWithinDeltaOfScreenV(descendant, 0, getHeight());
 }
 private boolean isOffScreenH(View descendant) { 
 return !isWithinDeltaOfScreenH(descendant, 0);
 }/** - @return whether the descendant of this scroll view is within delta
- 
pixels of being on the screen. 
 */
 private boolean isWithinDeltaOfScreenV(View descendant, int delta, int height) {
 descendant.getDrawingRect(mTempRect);
 offsetDescendantRectToMyCoords(descendant, mTempRect);return (mTempRect.bottom + delta) >= getScrollY() 
 && (mTempRect.top - delta) <= (getScrollY() + height);
 }
 private boolean isWithinDeltaOfScreenH(View descendant, int delta) { 
 descendant.getDrawingRect(mTempRect);
 offsetDescendantRectToMyCoords(descendant, mTempRect);return (mTempRect.right + delta) >= getScrollX() && (mTempRect.left - delta) <= (getScrollX() + getWidth());} /** - Smooth scroll by a Y delta
- @param delta the number of pixels to scroll by on the Y axis
 */
 private void doScrollY(int delta) {
 if (delta != 0) {
 if (mSmoothScrollingEnabled) {
 smoothScrollBy(0, delta);
 } else {
 scrollBy(0, delta);
 }
 }
 }
 private void doScrollX(int delta) { 
 if (delta != 0) {
 if (mSmoothScrollingEnabled) {
 smoothScrollBy(delta, 0);
 } else {
 scrollBy(delta, 0);
 }
 }
 }/** - Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
- @param dx the number of pixels to scroll by on the X axis
- 
@param dy the number of pixels to scroll by on the Y axis 
 */
 public void smoothScrollBy(int dx, int dy) {
 if (getChildCount() == 0) {
 // Nothing to do.
 return;
 }
 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
 if (duration > ANIMATED_SCROLL_GAP) {
 final int height = getHeight() - getPaddingBottom() - getPaddingTop();
 final int bottom = getChildAt(0).getHeight();
 final int maxY = Math.max(0, bottom - height);
 final int scrollY = getScrollY();
 dy = Math.max(0, Math.min(scrollY + dy, maxY)) - scrollY;final int width = getWidth() - getPaddingRight() - getPaddingLeft(); final int right = getChildAt(0).getWidth(); final int maxX = Math.max(0, right - width); final int scrollX = getScrollX(); dx = Math.max(0, Math.min(scrollX + dx, maxX)) - scrollX; mScroller.startScroll(scrollX, scrollY, dx, dy); invalidate();} else { 
 if (!mScroller.isFinished()) {
 mScroller.abortAnimation();
 }
 scrollBy(dx, dy);
 }
 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
 }
 /** - Like {@link #scrollTo}, but scroll smoothly instead of immediately.
- @param x the position where to scroll on the X axis
- @param y the position where to scroll on the Y axis
 */
 public final void smoothScrollTo(int x, int y) {
 smoothScrollBy(x - getScrollX(), y - getScrollY());
 }
 /** - <p>The scroll range of a scroll view is the overall height of all of its
- 
children.</p> 
 */
 @Override
 protected int computeVerticalScrollRange() {
 final int count = getChildCount();
 final int contentHeight = getHeight() - getPaddingBottom() - getPaddingTop();
 if (count == 0) {
 return contentHeight;
 }return getChildAt(0).getBottom(); 
 }
 @Override 
 protected int computeHorizontalScrollRange() {
 final int count = getChildCount();
 final int contentWidth = getWidth() - getPaddingLeft() - getPaddingRight();
 if (count == 0) {
 return contentWidth;
 }return getChildAt(0).getRight();} @Override 
 protected int computeVerticalScrollOffset() {
 return Math.max(0, super.computeVerticalScrollOffset());
 }@Override 
 protected int computeHorizontalScrollOffset() {
 return Math.max(0, super.computeHorizontalScrollOffset());
 }@Override 
 protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
 int childWidthMeasureSpec;
 int childHeightMeasureSpec;childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); child.measure(childWidthMeasureSpec, childHeightMeasureSpec);} @Override 
 protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
 int parentHeightMeasureSpec, int heightUsed) {
 final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( lp.leftMargin + lp.rightMargin, MeasureSpec.UNSPECIFIED); final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( lp.topMargin + lp.bottomMargin, MeasureSpec.UNSPECIFIED); child.measure(childWidthMeasureSpec, childHeightMeasureSpec);} @Override 
 public void computeScroll() {
 if (mScroller.computeScrollOffset()) {
 // This is called at drawing time by ViewGroup. We don't want to
 // re-show the scrollbars at this point, which scrollTo will do,
 // so we replicate most of scrollTo here.
 //
 // It's a little odd to call onScrollChanged from inside the drawing.
 //
 // It is, except when you remember that computeScroll() is used to
 // animate scrolling. So unless we want to defer the onScrollChanged()
 // until the end of the animated scrolling, we don't really have a
 // choice here.
 //
 // I agree. The alternative, which I think would be worse, is to post
 // something and tell the subclasses later. This is bad because there
 // will be a window where mScrollX/Y is different from what the app
 // thinks it is.
 //
 int x = mScroller.getCurrX();
 int y = mScroller.getCurrY();if (getChildCount() > 0) { View child = getChildAt(0); x = clamp(x, getWidth() - getPaddingRight() - getPaddingLeft(), child.getWidth()); y = clamp(y, getHeight() - getPaddingBottom() - getPaddingTop(), child.getHeight()); super.scrollTo(x, y); } awakenScrollBars(); // Keep on drawing until the animation has finished. postInvalidate(); }} /** - Scrolls the view to the given child.
- 
@param child the View to scroll to 
 */
 private void scrollToChild(View child) {
 child.getDrawingRect(mTempRect);/ Offset from child's local coordinates to ScrollView coordinates / 
 offsetDescendantRectToMyCoords(child, mTempRect);int scrollDeltaV = computeScrollDeltaToGetChildRectOnScreenV(mTempRect); 
 int scrollDeltaH = computeScrollDeltaToGetChildRectOnScreenH(mTempRect);if (scrollDeltaH != 0 || scrollDeltaV != 0) { 
 scrollBy(scrollDeltaH, scrollDeltaV);
 }
 }
 /** - If rect is off screen, scroll just enough to get it (or at least the
- first screen size chunk of it) on screen.
- @param rect The rectangle.
- @param immediate True to scroll immediately without animation
- @return true if scrolling was performed
 */
 private boolean scrollToChildRect(Rect rect, boolean immediate) {
 final int deltaV = computeScrollDeltaToGetChildRectOnScreenV(rect);
 final int deltaH = computeScrollDeltaToGetChildRectOnScreenH(rect);
 final boolean scroll = deltaH != 0 || deltaV != 0;
 if (scroll) {
 if (immediate) {
 scrollBy(deltaH, deltaV);
 } else {
 smoothScrollBy(deltaH, deltaV);
 }
 }
 return scroll;
 }
 /**
- Compute the amount to scroll in the Y direction in order to get
- a rectangle completely on the screen (or, if taller than the screen,
- at least the first screen size chunk of it).
- @param rect The rect.
- 
@return The scroll delta. 
 */
 protected int computeScrollDeltaToGetChildRectOnScreenV(Rect rect) {
 if (getChildCount() == 0) return 0;int height = getHeight(); 
 int screenTop = getScrollY();
 int screenBottom = screenTop + height;int fadingEdge = getVerticalFadingEdgeLength(); // leave room for top fading edge as long as rect isn't at very top 
 if (rect.top > 0) {
 screenTop += fadingEdge;
 }// leave room for bottom fading edge as long as rect isn't at very bottom 
 if (rect.bottom < getChildAt(0).getHeight()) {
 screenBottom -= fadingEdge;
 }int scrollYDelta = 0; if (rect.bottom > screenBottom && rect.top > screenTop) { 
 // need to move down to get it in view: move down just enough so
 // that the entire rectangle is in view (or at least the first
 // screen size chunk).if (rect.height() > height) { // just enough to get screen size chunk on scrollYDelta += (rect.top - screenTop); } else { // get entire rect at bottom of screen scrollYDelta += (rect.bottom - screenBottom); } // make sure we aren't scrolling beyond the end of our content int bottom = getChildAt(0).getBottom(); int distanceToBottom = bottom - screenBottom; scrollYDelta = Math.min(scrollYDelta, distanceToBottom);} else if (rect.top < screenTop && rect.bottom < screenBottom) { 
 // need to move up to get it in view: move up just enough so that
 // entire rectangle is in view (or at least the first screen
 // size chunk of it).if (rect.height() > height) { // screen size chunk scrollYDelta -= (screenBottom - rect.bottom); } else { // entire rect at top scrollYDelta -= (screenTop - rect.top); } // make sure we aren't scrolling any further than the top our content scrollYDelta = Math.max(scrollYDelta, -getScrollY());} 
 return scrollYDelta;
 }
 protected int computeScrollDeltaToGetChildRectOnScreenH(Rect rect) { 
 if (getChildCount() == 0) return 0;int width = getWidth(); int screenLeft = getScrollX(); int screenRight = screenLeft + width; int fadingEdge = getHorizontalFadingEdgeLength(); // leave room for left fading edge as long as rect isn't at very left if (rect.left > 0) { screenLeft += fadingEdge; } // leave room for right fading edge as long as rect isn't at very right if (rect.right < getChildAt(0).getWidth()) { screenRight -= fadingEdge; } int scrollXDelta = 0; if (rect.right > screenRight && rect.left > screenLeft) { // need to move right to get it in view: move right just enough so // that the entire rectangle is in view (or at least the first // screen size chunk). if (rect.width() > width) { // just enough to get screen size chunk on scrollXDelta += (rect.left - screenLeft); } else { // get entire rect at right of screen scrollXDelta += (rect.right - screenRight); } // make sure we aren't scrolling beyond the end of our content int right = getChildAt(0).getRight(); int distanceToRight = right - screenRight; scrollXDelta = Math.min(scrollXDelta, distanceToRight); } else if (rect.left < screenLeft && rect.right < screenRight) { // need to move right to get it in view: move right just enough so that // entire rectangle is in view (or at least the first screen // size chunk of it). if (rect.width() > width) { // screen size chunk scrollXDelta -= (screenRight - rect.right); } else { // entire rect at left scrollXDelta -= (screenLeft - rect.left); } // make sure we aren't scrolling any further than the left our content scrollXDelta = Math.max(scrollXDelta, -getScrollX()); } return scrollXDelta;} @Override 
 public void requestChildFocus(View child, View focused) {
 if (!mScrollViewMovedFocus) {
 if (!mIsLayoutDirty) {
 scrollToChild(focused);
 } else {
 // The child may not be laid out yet, we can't compute the scroll yet
 mChildToScrollTo = focused;
 }
 }
 super.requestChildFocus(child, focused);
 }/** - When looking for focus in children of a scroll view, need to be a little
- more careful not to give focus to something that is scrolled off screen.
- This is more expensive than the default {@link android.view.ViewGroup}
- 
implementation, otherwise this behavior might have been made the default. 
 */
 @Override
 protected boolean onRequestFocusInDescendants(int direction,
 Rect previouslyFocusedRect) {// convert from forward / backward notation to up / down / left / right 
 // (ugh).
 // TODO: FUCK
 // if (direction == View.FOCUS_FORWARD) {
 // direction = View.FOCUS_RIGHT;
 // } else if (direction == View.FOCUS_BACKWARD) {
 // direction = View.FOCUS_LEFT;
 // }final View nextFocus = previouslyFocusedRect == null ? 
 FocusFinder.getInstance().findNextFocus(this, null, direction) :
 FocusFinder.getInstance().findNextFocusFromRect(this,
 previouslyFocusedRect, direction);if (nextFocus == null) { return false; } // if (isOffScreenH(nextFocus)) { // return false; // } return nextFocus.requestFocus(direction, previouslyFocusedRect);} 
 @Override 
 public boolean requestChildRectangleOnScreen(View child, Rect rectangle,
 boolean immediate) {
 // offset into coordinate space of this scroll view
 rectangle.offset(child.getLeft() - child.getScrollX(),
 child.getTop() - child.getScrollY());return scrollToChildRect(rectangle, immediate);} @Override 
 public void requestLayout() {
 mIsLayoutDirty = true;
 super.requestLayout();
 }@Override 
 protected void onLayout(boolean changed, int l, int t, int r, int b) {
 super.onLayout(changed, l, t, r, b);
 mIsLayoutDirty = false;
 // Give a child focus if it needs it
 if (mChildToScrollTo != null && isViewDescendantOf(mChildToScrollTo, this)) {
 scrollToChild(mChildToScrollTo);
 }
 mChildToScrollTo = null;// Calling this with the present values causes it to re-clam them scrollTo(getScrollX(), getScrollY());} @Override 
 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
 super.onSizeChanged(w, h, oldw, oldh);View currentFocused = findFocus(); if (null == currentFocused || this == currentFocused) return; // If the currently-focused view was visible on the screen when the // screen was at the old height, then scroll the screen to make that // view visible with the new screen height. if (isWithinDeltaOfScreenV(currentFocused, 0, oldh)) { currentFocused.getDrawingRect(mTempRect); offsetDescendantRectToMyCoords(currentFocused, mTempRect); int scrollDelta = computeScrollDeltaToGetChildRectOnScreenV(mTempRect); doScrollY(scrollDelta); } final int maxJump = getRight() - getLeft(); if (isWithinDeltaOfScreenH(currentFocused, maxJump)) { currentFocused.getDrawingRect(mTempRect); offsetDescendantRectToMyCoords(currentFocused, mTempRect); int scrollDelta = computeScrollDeltaToGetChildRectOnScreenH(mTempRect); doScrollX(scrollDelta); }} /** - 
Return true if child is an descendant of parent, (or equal to the parent). 
 */
 private boolean isViewDescendantOf(View child, View parent) {
 if (child == parent) {
 return true;
 }final ViewParent theParent = child.getParent(); 
 return (theParent instanceof ViewGroup) && isViewDescendantOf((View) theParent, parent);
 }
 /** - Fling the scroll view
- @param velocityY The initial velocity in the Y direction. Positive
- numbers mean that the finger/cursor is moving down the screen,
- 
which means we want to scroll towards the top. 
 */
 public void fling(int velocityX, int velocityY) {
 if (getChildCount() > 0) {
 int width = getWidth() - getPaddingRight() - getPaddingLeft();
 int right = getChildAt(0).getWidth();int height = getHeight() - getPaddingBottom() - getPaddingTop(); int bottom = getChildAt(0).getHeight(); mScroller.fling(getScrollX(), getScrollY(), velocityX, velocityY, 0, Math.max(0, right - width), 0, Math.max(0, bottom - height)); // final boolean movingDown = velocityX > 0 || velocityY > 0; // // View newFocused = // findFocusableViewInMyBoundsV(movingDown, mScroller.getFinalY(), findFocus()); // if (newFocused == null) { // newFocused = this; // } // // if (newFocused != findFocus() // && newFocused.requestFocus(movingDown ? View.FOCUS_DOWN : View.FOCUS_UP)) { // mScrollViewMovedFocus = true; // mScrollViewMovedFocus = false; // } invalidate();} 
 }
 /** - {@inheritDoc}
- <p>This version also clamps the scrolling to the bounds of our child.
 */
 @Override
 public void scrollTo(int x, int y) {
 // we rely on the fact the View.scrollBy calls scrollTo.
 if (getChildCount() > 0) {
 View child = getChildAt(0);
 x = clamp(x, getWidth() - getPaddingRight() - getPaddingLeft(), child.getWidth());
 y = clamp(y, getHeight() - getPaddingBottom() - getPaddingTop(), child.getHeight());
 if (x != getScrollX() || y != getScrollY()) {
 super.scrollTo(x, y);
 }
 }
 }
 private int clamp(int n, int my, int child) { 
 if (my >= child || n < 0) {
 /* my >= child is this case:- |--------------- me ---------------|
- |------ child ------|
- or
- |--------------- me ---------------|
- |------ child ------|
- or
- |--------------- me ---------------|
- |------ child ------|
- n < 0 is this case:
- |------ me ------|
- |-------- child --------|
- |-- mScrollX --|
 /
 return 0;
 }
 if ((my+n) > child) {
 / this case:
- |------ me ------|
- |------ child ------|
- |-- mScrollX --|
 */
 return child-my;
 }
 return n;
 }
 public boolean isFlingEnabled() { 
 return mFlingEnabled;
 }public void setFlingEnabled(boolean flingEnabled) { 
 this.mFlingEnabled = flingEnabled;
 }
 }