布局动画Android ScrollView HorizontalScrollView 实现全方向(上下左右)反弹效果

废话就不多说了,开始。。。

    自定义控件View :

    每日一道理
心的本色该是如此。成,如朗月照花,深潭微澜,不论顺逆,不论成败的超然,是扬鞭策马,登高临远的驿站;败,仍滴水穿石,汇流入海,有穷且益坚,不坠青云的傲岸,有“将相本无主,男儿当自强”的倔强。荣,江山依旧,风采犹然,恰沧海巫山,熟视岁月如流,浮华万千,不屑过眼烟云;辱,胯下韩信,雪底苍松,宛若羽化之仙,知退一步,海阔天空,不肯因噎废食。
/**

 * 类功能描述:

 * 拥有弹性效果的全方向ScrollView,参考ScrollView与HorizontalScrollView源码

 * 

 */

public class HorizontalScrollView 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 HorizontalScrollView(Context context) {

        this(context, null);

    }



    public HorizontalScrollView(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;

    }



    private boolean scrollableOutsideTouch = false;



    /**

     * 设置scroller是否可以滑动内容当触屏事件在chileview之外 default:false

     * @param b true-可以,false-不可

     */

    public void setScrollableOutsideChile(boolean b) {

        scrollableOutsideTouch = b;

    }



    private boolean flexible = true;



    /**

     * 设置是否可有弹性效果

     * @param b true-可以,false-不可

     */

    public void setFlexible(boolean b) {

        flexible = b;

    }



    private long lastEvenTime;



    @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)) && !scrollableOutsideTouch) {

                    return false;

                }

                // 阻止测试职员暴力测试

                if (System.currentTimeMillis() - lastEvenTime < 200) {

                    ev.setAction(MotionEvent.ACTION_CANCEL);

                }

                lastEvenTime = System.currentTimeMillis();

                /*

                 * 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 || scrollableOutsideTouch) {

                    // 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);

                    // 当滚动到边界时就不会再滚动,这时挪动布局

                    if (isNeedMove() && flexible) {

                        if (normal.isEmpty()) {

                            // 保存畸形的布局属性

                            normal.set(inner.getLeft(), inner.getTop(), inner.getRight(),

                                    inner.getBottom());

                        }

                        // 挪动布局

                        inner.layout(inner.getLeft() - deltaX / 2, inner.getTop() - deltaY / 2,

                                inner.getRight() - deltaX / 2, inner.getBottom() - deltaY / 2);

                    }

                }

                break;

            case MotionEvent.ACTION_UP:

                if (mIsBeingDragged || scrollableOutsideTouch) {

                    if (mFlingEnabled) {

                        final VelocityTracker velocityTracker = mVelocityTracker;

                        velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);

                        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);

                            }



                        }

                    }

                    if (isNeedAnimation()) {

                        animation();

                    }

                    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_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_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);

                // getHeight()- child.getHeight()=y ->底部 y=0 ->顶端

                // 惯性强度 mScroller.getDuration()

            }

            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).

        // 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;

    }



    /** scrollview内容与属性记录*/

    private View inner;

    private Rect normal = new Rect();



    @Override

    protected void onFinishInflate() {

        if (getChildCount() > 0) {

            inner = getChildAt(0);

        }

    }



    // 是否须要开启动画

    public boolean isNeedAnimation() {

        return !normal.isEmpty();

    }



    // 开启动画挪动

    public void animation() {

        // 开启挪动动画

        TranslateAnimation ta = new TranslateAnimation(0, -inner.getLeft(), 0, -inner.getTop());

        ta.setDuration(200);

        inner.startAnimation(ta);

        // 设置回到畸形的布局位置

        new Handler().postDelayed(new Runnable() {

            public void run() {

                inner.clearAnimation();

                inner.layout(normal.left, normal.top, normal.right, normal.bottom);

                normal.setEmpty();

            }

        }, 200);



    }



    // 是否须要挪动布局

    public boolean isNeedMove() {

        int offsetX = inner.getMeasuredWidth() - getWidth();

        int scrollX = getScrollX();

        if (scrollX == 0 || scrollX == offsetX) {

            return true;

        }



        int offsetY = inner.getMeasuredHeight() - getHeight();

        int scrollY = getScrollY();

        if (scrollY == 0 || scrollY == offsetY) {

            return true;

        }

        return false;

    }

}

    

    实现很简单,这里就不做太多解释!

文章结束给大家分享下程序员的一些笑话语录: 看新闻说中国输入法全球第一!领先了又如何?西方文字根本不需要输入法。一点可比性都没有。

你可能感兴趣的:(布局动画Android ScrollView HorizontalScrollView 实现全方向(上下左右)反弹效果)