• android:滚动布局探讨


      谈到滚动布局,android有两个基本的布局:HorizontalScrollView和ScrollView。从类的视图结构上说,他们都是FrameLayout,所以说只能包含一个子View,所以说视图如果是一个复杂的布局的话,那么你就得使用一个layout了(通常是LinearLayout)。需要说明一下:

      ScrollView:只支持垂直滚动。对于TextView控件有它自己的scroll,所以你没有必要因为这去实现,但是它们两个组合可以实现一个更大的容器内的一个文本视图的效果。

      HorizontalScrollView:只支持水平滚动。对于TextView控件有它自己的scroll,所以你没有必要因为这去实现,但是它们两个组合可以实现一个更大的容器内的一个文本视图的效果;对于ListView,你不可以把HorizontalScrollView与ListView结合使用,因为ListView本身也有它自己的Scroll,结合会出现混乱(可以这个我倒是没有试过)。

      简单的事例就不举了,这里就说说可以垂直公动的ScrollView.我网上下了一个,发现就是把ScrollView添加了点东西。另外从帮助文档上看ScrollView就是比FrameLayout多了一个属性:android:fillViewport。所以来说这个类不用定义什么属性,就可以直接使用到我们的项目里。我用对比工具meld跟系统ScrollView比较了下,可能Meld还不够智能吧,对比的十分混乱,完全看不懂,有高手的话,还望指教。下面就是这个类:

      

    package com.android.slider;
    
    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;
    	}
    }
    

        

  • 相关阅读:
    ViewData,ViewBag,TempData
    http和https
    Array与ArrayList
    程序员与书和视频
    技术学习的方法研究
    文章发布声明
    面向对象JAVA多态性
    嵌入式开发总结
    CSDN博客代码显示乱码的原因
    将Windows的桌面目录设置到D盘
  • 原文地址:https://www.cnblogs.com/slider/p/2311640.html
Copyright © 2020-2023  润新知