content: package com.lvrenyang.labelview;
  
  /*
   * Integration of ScrollView and HorizontalScrollView
   * 
   * For some unknown reason, the H/V scroll bar are missing.
   */
  
  import java.util.List;
  
  import android.annotation.SuppressLint;
  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;
  
  	private OnScrollChangedListener mScrollChanged = null;
  
  	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 performClick() {
  		return super.performClick();
  	}
  
  	@SuppressLint("ClickableViewAccessibility")
  	@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;
  	}
  
  	@SuppressWarnings("deprecation")
  	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);
  			}
  		}
  	}
  
  	public interface OnScrollChangedListener {
  		void onScrollChanged(int l, int t, int oldl, int oldt);
  	}
  
  	public void setOnScrollChangedListener(
  			OnScrollChangedListener onScrollChangedListener) {
  		mScrollChanged = onScrollChangedListener;
  	}
  
  	/**
  	 * 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());
  			int oldx = getScrollX();
  			int oldy = getScrollY();
  			if (x != oldx || y != oldy) {
  				super.scrollTo(x, y);
  				if (null != mScrollChanged)
  					mScrollChanged.onScrollChanged(x, y, oldx, oldy);
  			}
  		}
  	}
  
  	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;
  	}
  }
  