[轉]Android中實現上下左右都可滑動的ScrollView


本文轉自:

http://blog.csdn.net/zyongsheng83/article/details/6228246

 

 

從網上down下來的,以免忘記特此標記:

Java代碼 復制代碼
  1. import java.util.List;   
  2.  
  3. import android.content.Context;   
  4. import android.graphics.Rect;   
  5. import android.util.AttributeSet;   
  6. import android.view.FocusFinder;   
  7. import android.view.KeyEvent;   
  8. import android.view.MotionEvent;   
  9. import android.view.VelocityTracker;   
  10. import android.view.View;   
  11. import android.view.ViewConfiguration;   
  12. import android.view.ViewGroup;   
  13. import android.view.ViewParent;   
  14. import android.view.animation.AnimationUtils;   
  15. import android.widget.FrameLayout;   
  16. import android.widget.Scroller;   
  17.  
  18. /**
  19. * Reference to ScrollView and HorizontalScrollView
  20. */   
  21. public class HVScrollView extends FrameLayout {   
  22.     static final int ANIMATED_SCROLL_GAP = 250;   
  23.  
  24.     static final float MAX_SCROLL_FACTOR = 0.5f;   
  25.  
  26.  
  27.     private long mLastScroll;   
  28.  
  29.     private final Rect mTempRect = new Rect();   
  30.     private Scroller mScroller;   
  31.  
  32.     /**
  33.      * Flag to indicate that we are moving focus ourselves. This is so the
  34.      * code that watches for focus changes initiated outside this ScrollView
  35.      * knows that it does not have to do anything.
  36.      */   
  37.     private boolean mScrollViewMovedFocus;   
  38.  
  39.     /**
  40.      * Position of the last motion event.
  41.      */   
  42.     private float mLastMotionY;   
  43.     private float mLastMotionX;   
  44.  
  45.     /**
  46.      * True when the layout has changed but the traversal has not come through yet.
  47.      * Ideally the view hierarchy would keep track of this for us.
  48.      */   
  49.     private boolean mIsLayoutDirty = true;   
  50.  
  51.     /**
  52.      * The child to give focus to in the event that a child has requested focus while the
  53.      * layout is dirty. This prevents the scroll from being wrong if the child has not been
  54.      * laid out before requesting focus.
  55.      */   
  56.     private View mChildToScrollTo = null;   
  57.  
  58.     /**
  59.      * True if the user is currently dragging this ScrollView around. This is
  60.      * not the same as 'is being flinged', which can be checked by
  61.      * mScroller.isFinished() (flinging begins when the user lifts his finger).
  62.      */   
  63.     private boolean mIsBeingDragged = false;   
  64.  
  65.     /**
  66.      * Determines speed during touch scrolling
  67.      */   
  68.     private VelocityTracker mVelocityTracker;   
  69.  
  70.     /**
  71.      * When set to true, the scroll view measure its child to make it fill the currently
  72.      * visible area.
  73.      */   
  74.     private boolean mFillViewport;   
  75.  
  76.     /**
  77.      * Whether arrow scrolling is animated.
  78.      */   
  79.     private boolean mSmoothScrollingEnabled = true;   
  80.  
  81.     private int mTouchSlop;   
  82.     private int mMinimumVelocity;   
  83.     private int mMaximumVelocity;   
  84.  
  85.     /**
  86.      * ID of the active pointer. This is used to retain consistency during
  87.      * drags/flings if multiple pointers are used.
  88.      */   
  89.     private int mActivePointerId = INVALID_POINTER;   
  90.  
  91.     /**
  92.      * Sentinel value for no current active pointer.
  93.      * Used by {@link #mActivePointerId}.
  94.      */   
  95.     private static final int INVALID_POINTER = -1;   
  96.  
  97.     private boolean mFlingEnabled = true;   
  98.  
  99.     public HVScrollView(Context context) {   
  100.         this(context, null);   
  101.     }   
  102.  
  103.     public HVScrollView(Context context, AttributeSet attrs) {   
  104.         super(context, attrs);   
  105.         initScrollView();   
  106.     }   
  107.  
  108.     @Override   
  109.     protected float getTopFadingEdgeStrength() {   
  110.         if (getChildCount() == 0) {   
  111.             return 0.0f;   
  112.         }   
  113.  
  114.         final int length = getVerticalFadingEdgeLength();   
  115.         if (getScrollY() < length) {   
  116.             return getScrollY() / (float) length;   
  117.         }   
  118.  
  119.         return 1.0f;   
  120.     }   
  121.  
  122.     @Override   
  123.     protected float getLeftFadingEdgeStrength() {   
  124.         if (getChildCount() == 0) {   
  125.             return 0.0f;   
  126.         }   
  127.  
  128.         final int length = getHorizontalFadingEdgeLength();   
  129.         if (getScrollX() < length) {   
  130.             return getScrollX() / (float) length;   
  131.         }   
  132.  
  133.         return 1.0f;   
  134.     }   
  135.  
  136.     @Override   
  137.     protected float getRightFadingEdgeStrength() {   
  138.         if (getChildCount() == 0) {   
  139.             return 0.0f;   
  140.         }   
  141.  
  142.         final int length = getHorizontalFadingEdgeLength();   
  143.         final int rightEdge = getWidth() - getPaddingRight();   
  144.         final int span = getChildAt(0).getRight() - getScrollX() - rightEdge;   
  145.         if (span < length) {   
  146.             return span / (float) length;   
  147.         }   
  148.  
  149.         return 1.0f;   
  150.     }   
  151.  
  152.     @Override   
  153.     protected float getBottomFadingEdgeStrength() {   
  154.         if (getChildCount() == 0) {   
  155.             return 0.0f;   
  156.         }   
  157.  
  158.         final int length = getVerticalFadingEdgeLength();   
  159.         final int bottomEdge = getHeight() - getPaddingBottom();   
  160.         final int span = getChildAt(0).getBottom() - getScrollY() - bottomEdge;   
  161.         if (span < length) {   
  162.             return span / (float) length;   
  163.         }   
  164.  
  165.         return 1.0f;   
  166.     }   
  167.  
  168.     /**
  169.      * @return The maximum amount this scroll view will scroll in response to
  170.      *   an arrow event.
  171.      */   
  172.      public int getMaxScrollAmountV() {   
  173.         return (int) (MAX_SCROLL_FACTOR * (getBottom() - getTop()));   
  174.     }   
  175.  
  176.     public int getMaxScrollAmountH() {   
  177.         return (int) (MAX_SCROLL_FACTOR * (getRight() - getLeft()));   
  178.     }   
  179.  
  180.  
  181.     private void initScrollView() {   
  182.         mScroller = new Scroller(getContext());   
  183.         setFocusable(true);   
  184.         setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);   
  185.         setWillNotDraw(false);   
  186.         final ViewConfiguration configuration = ViewConfiguration.get(getContext());   
  187.         mTouchSlop = configuration.getScaledTouchSlop();   
  188.         mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();   
  189.         mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();   
  190.     }   
  191.  
  192.     @Override   
  193.     public void addView(View child) {   
  194.         if (getChildCount() > 0) {   
  195.             throw new IllegalStateException("ScrollView can host only one direct child");   
  196.         }   
  197.  
  198.         super.addView(child);   
  199.     }   
  200.  
  201.     @Override   
  202.     public void addView(View child, int index) {   
  203.         if (getChildCount() > 0) {   
  204.             throw new IllegalStateException("ScrollView can host only one direct child");   
  205.         }   
  206.  
  207.         super.addView(child, index);   
  208.     }   
  209.  
  210.     @Override   
  211.     public void addView(View child, ViewGroup.LayoutParams params) {   
  212.         if (getChildCount() > 0) {   
  213.             throw new IllegalStateException("ScrollView can host only one direct child");   
  214.         }   
  215.  
  216.         super.addView(child, params);   
  217.     }   
  218.  
  219.     @Override   
  220.     public void addView(View child, int index, ViewGroup.LayoutParams params) {   
  221.         if (getChildCount() > 0) {   
  222.             throw new IllegalStateException("ScrollView can host only one direct child");   
  223.         }   
  224.  
  225.         super.addView(child, index, params);   
  226.     }   
  227.  
  228.     /**
  229.      * @return Returns true this ScrollView can be scrolled
  230.      */   
  231.     private boolean canScrollV() {   
  232.         View child = getChildAt(0);   
  233.         if (child != null) {   
  234.             int childHeight = child.getHeight();   
  235.             return getHeight() < childHeight + getPaddingTop() + getPaddingBottom();   
  236.         }   
  237.         return false;   
  238.     }   
  239.  
  240.     private boolean canScrollH() {   
  241.         View child = getChildAt(0);   
  242.         if (child != null) {   
  243.             int childWidth = child.getWidth();   
  244.             return getWidth() < childWidth + getPaddingLeft() + getPaddingRight() ;   
  245.         }   
  246.         return false;   
  247.     }   
  248.  
  249.     /**
  250.      * Indicates whether this ScrollView's content is stretched to fill the viewport.
  251.      *
  252.      * @return True if the content fills the viewport, false otherwise.
  253.      */   
  254.     public boolean isFillViewport() {   
  255.         return mFillViewport;   
  256.     }   
  257.  
  258.     /**
  259.      * Indicates this ScrollView whether it should stretch its content height to fill
  260.      * the viewport or not.
  261.      *
  262.      * @param fillViewport True to stretch the content's height to the viewport's
  263.      *        boundaries, false otherwise.
  264.      */   
  265.     public void setFillViewport(boolean fillViewport) {   
  266.         if (fillViewport != mFillViewport) {   
  267.             mFillViewport = fillViewport;   
  268.             requestLayout();   
  269.         }   
  270.     }   
  271.  
  272.     /**
  273.      * @return Whether arrow scrolling will animate its transition.
  274.      */   
  275.     public boolean isSmoothScrollingEnabled() {   
  276.         return mSmoothScrollingEnabled;   
  277.     }   
  278.  
  279.     /**
  280.      * Set whether arrow scrolling will animate its transition.
  281.      * @param smoothScrollingEnabled whether arrow scrolling will animate its transition
  282.      */   
  283.     public void setSmoothScrollingEnabled(boolean smoothScrollingEnabled) {   
  284.         mSmoothScrollingEnabled = smoothScrollingEnabled;   
  285.     }   
  286.  
  287.     @Override   
  288.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {   
  289.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);   
  290.  
  291.         if (!mFillViewport) {   
  292.             return;   
  293.         }   
  294.  
  295.         final int heightMode = MeasureSpec.getMode(heightMeasureSpec);   
  296.         final int widthMode = MeasureSpec.getMode(widthMeasureSpec);   
  297.         if (heightMode == MeasureSpec.UNSPECIFIED && widthMode == MeasureSpec.UNSPECIFIED) {   
  298.             return;   
  299.         }   
  300.  
  301.         if (getChildCount() > 0) {   
  302.             final View child = getChildAt(0);   
  303.             int height = getMeasuredHeight();   
  304.             int width = getMeasuredWidth();   
  305.             if (child.getMeasuredHeight() < height || child.getMeasuredWidth() < width) {   
  306.                 width -= getPaddingLeft();   
  307.                 width -= getPaddingRight();   
  308.                 int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);   
  309.  
  310.                 height -= getPaddingTop();   
  311.                 height -= getPaddingBottom();   
  312.                 int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);   
  313.  
  314.                 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);   
  315.             }   
  316.         }   
  317.     }   
  318.     @Override   
  319.     public boolean dispatchKeyEvent(KeyEvent event) {   
  320.         // Let the focused view and/or our descendants get the key first   
  321.         return super.dispatchKeyEvent(event) || executeKeyEvent(event);   
  322.     }   
  323.  
  324.     /**
  325.      * You can call this function yourself to have the scroll view perform
  326.      * scrolling from a key event, just as if the event had been dispatched to
  327.      * it by the view hierarchy.
  328.      *
  329.      * @param event The key event to execute.
  330.      * @return Return true if the event was handled, else false.
  331.      */   
  332.     public boolean executeKeyEvent(KeyEvent event) {   
  333.         mTempRect.setEmpty();   
  334.  
  335.         boolean handled = false;   
  336.  
  337.         if (event.getAction() == KeyEvent.ACTION_DOWN) {   
  338.             switch (event.getKeyCode()) {   
  339.             case KeyEvent.KEYCODE_DPAD_LEFT:   
  340.                 if(canScrollH()){   
  341.                     if (!event.isAltPressed()) {   
  342.                         handled = arrowScrollH(View.FOCUS_LEFT);   
  343.                     } else {   
  344.                         handled = fullScrollH(View.FOCUS_LEFT);   
  345.                     }   
  346.                 }   
  347.                 break;   
  348.             case KeyEvent.KEYCODE_DPAD_RIGHT:   
  349.                 if(canScrollH()){   
  350.                     if (!event.isAltPressed()) {   
  351.                         handled = arrowScrollH(View.FOCUS_RIGHT);   
  352.                     } else {   
  353.                         handled = fullScrollH(View.FOCUS_RIGHT);   
  354.                     }   
  355.                 }   
  356.                 break;   
  357.             case KeyEvent.KEYCODE_DPAD_UP:   
  358.                 if(canScrollV()){   
  359.                     if (!event.isAltPressed()) {   
  360.                         handled = arrowScrollV(View.FOCUS_UP);   
  361.                     } else {   
  362.                         handled = fullScrollV(View.FOCUS_UP);   
  363.                     }   
  364.                 }   
  365.                 break;   
  366.             case KeyEvent.KEYCODE_DPAD_DOWN:   
  367.                 if(canScrollV()){   
  368.                     if (!event.isAltPressed()) {   
  369.                         handled = arrowScrollV(View.FOCUS_DOWN);   
  370.                     } else {   
  371.                         handled = fullScrollV(View.FOCUS_DOWN);   
  372.                     }   
  373.                 }   
  374.                 break;   
  375.             }   
  376.         }   
  377.         return handled;   
  378.     }   
  379.  
  380.     private boolean inChild(int x, int y) {   
  381.         if (getChildCount() > 0) {   
  382.             final int scrollX = getScrollX();   
  383.             final int scrollY = getScrollY();   
  384.             final View child = getChildAt(0);   
  385.             return !(y < child.getTop() - scrollY   
  386.                     || y >= child.getBottom() - scrollY   
  387.                     || x < child.getLeft() - scrollX   
  388.                     || x >= child.getRight() - scrollX);   
  389.         }   
  390.         return false;   
  391.     }   
  392.  
  393.     @Override   
  394.     public boolean onInterceptTouchEvent(MotionEvent ev) {   
  395.         /*
  396.          * This method JUST determines whether we want to intercept the motion.
  397.          * If we return true, onMotionEvent will be called and we do the actual
  398.          * scrolling there.
  399.          */   
  400.  
  401.         /*
  402.          * Shortcut the most recurring case: the user is in the dragging
  403.          * state and he is moving his finger.  We want to intercept this
  404.          * motion.
  405.          */   
  406.         final int action = ev.getAction();   
  407.         if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {   
  408.             return true;   
  409.         }   
  410.  
  411.         switch (action & MotionEvent.ACTION_MASK) {   
  412.         case MotionEvent.ACTION_MOVE: {   
  413.             /*
  414.              * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
  415.              * whether the user has moved far enough from his original down touch.
  416.              */   
  417.  
  418.             /*
  419.              * Locally do absolute value. mLastMotionY is set to the y value
  420.              * of the down event.
  421.              */   
  422.             final int activePointerId = mActivePointerId;   
  423.             if (activePointerId == INVALID_POINTER) {   
  424.                 // If we don't have a valid id, the touch down wasn't on content.   
  425.                 break;   
  426.             }   
  427.  
  428.             final int pointerIndex = ev.findPointerIndex(activePointerId);   
  429.             final float y = ev.getY(pointerIndex);   
  430.             final int yDiff = (int) Math.abs(y - mLastMotionY);   
  431.             if (yDiff > mTouchSlop) {   
  432.                 mIsBeingDragged = true;   
  433.                 mLastMotionY = y;   
  434.             }   
  435.             final float x = ev.getX(pointerIndex);   
  436.             final int xDiff = (int) Math.abs(x - mLastMotionX);   
  437.             if (xDiff > mTouchSlop) {   
  438.                 mIsBeingDragged = true;   
  439.                 mLastMotionX = x;   
  440.             }   
  441.             break;   
  442.         }   
  443.  
  444.         case MotionEvent.ACTION_DOWN: {   
  445.             final float x = ev.getX();   
  446.             final float y = ev.getY();   
  447.             if (!inChild((int)x, (int) y)) {   
  448.                 mIsBeingDragged = false;   
  449.                 break;   
  450.             }   
  451.  
  452.             /*
  453.              * Remember location of down touch.
  454.              * ACTION_DOWN always refers to pointer index 0.
  455.              */   
  456.             mLastMotionY = y;   
  457.             mLastMotionX = x;   
  458.             mActivePointerId = ev.getPointerId(0);   
  459.  
  460.             /*
  461.              * If being flinged and user touches the screen, initiate drag;
  462.              * otherwise don't.  mScroller.isFinished should be false when
  463.              * being flinged.
  464.              */   
  465.             mIsBeingDragged = !mScroller.isFinished();   
  466.             break;   
  467.         }   
  468.  
  469.         case MotionEvent.ACTION_CANCEL:   
  470.         case MotionEvent.ACTION_UP:   
  471.             /* Release the drag */   
  472.             mIsBeingDragged = false;   
  473.             mActivePointerId = INVALID_POINTER;   
  474.             break;   
  475.         case MotionEvent.ACTION_POINTER_UP:   
  476.             onSecondaryPointerUp(ev);   
  477.             break;   
  478.         }   
  479.  
  480.         /*
  481.          * The only time we want to intercept motion events is if we are in the
  482.          * drag mode.
  483.          */   
  484.         return mIsBeingDragged;   
  485.     }   
  486.  
  487.     @Override   
  488.     public boolean onTouchEvent(MotionEvent ev) {   
  489.  
  490.         if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {   
  491.             // Don't handle edge touches immediately -- they may actually belong to one of our   
  492.             // descendants.   
  493.             return false;   
  494.         }   
  495.  
  496.         if (mVelocityTracker == null) {   
  497.             mVelocityTracker = VelocityTracker.obtain();   
  498.         }   
  499.         mVelocityTracker.addMovement(ev);   
  500.  
  501.         final int action = ev.getAction();   
  502.  
  503.         switch (action & MotionEvent.ACTION_MASK) {   
  504.         case MotionEvent.ACTION_DOWN: {   
  505.             final float x = ev.getX();   
  506.             final float y = ev.getY();   
  507.             if (!(mIsBeingDragged = inChild((int) x, (int) y))) {   
  508.                 return false;   
  509.             }   
  510.  
  511.             /*
  512.              * If being flinged and user touches, stop the fling. isFinished
  513.              * will be false if being flinged.
  514.              */   
  515.             if (!mScroller.isFinished()) {   
  516.                 mScroller.abortAnimation();   
  517.             }   
  518.  
  519.             // Remember where the motion event started   
  520.             mLastMotionY = y;   
  521.             mLastMotionX = x;   
  522.             mActivePointerId = ev.getPointerId(0);   
  523.             break;   
  524.         }   
  525.         case MotionEvent.ACTION_MOVE:   
  526.             if (mIsBeingDragged) {   
  527.                 // Scroll to follow the motion event   
  528.                 final int activePointerIndex = ev.findPointerIndex(mActivePointerId);   
  529.                 final float y = ev.getY(activePointerIndex);   
  530.                 final int deltaY = (int) (mLastMotionY - y);   
  531.                 mLastMotionY = y;   
  532.  
  533.                 final float x = ev.getX(activePointerIndex);   
  534.                 final int deltaX = (int) (mLastMotionX - x);   
  535.                 mLastMotionX = x;   
  536.  
  537.                 scrollBy(deltaX, deltaY);   
  538.             }   
  539.             break;   
  540.         case MotionEvent.ACTION_UP:    
  541.             if (mIsBeingDragged) {   
  542.                 if(mFlingEnabled){   
  543.                     final VelocityTracker velocityTracker = mVelocityTracker;   
  544.                     velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);   
  545.                     int initialVelocitx = (int) velocityTracker.getXVelocity();   
  546.                     int initialVelocity = (int) velocityTracker.getYVelocity();  
  547. //                  int initialVelocitx = (int) velocityTracker.getXVelocity(mActivePointerId);   
  548. //                  int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);   
  549.  
  550.                     if (getChildCount() > 0) {   
  551.                         if(Math.abs(initialVelocitx) > initialVelocitx || Math.abs(initialVelocity) > mMinimumVelocity) {   
  552.                             fling(-initialVelocitx, -initialVelocity);   
  553.                         }   
  554.  
  555.                     }   
  556.                 }   
  557.  
  558.                 mActivePointerId = INVALID_POINTER;   
  559.                 mIsBeingDragged = false;   
  560.  
  561.                 if (mVelocityTracker != null) {   
  562.                     mVelocityTracker.recycle();   
  563.                     mVelocityTracker = null;   
  564.                 }   
  565.             }   
  566.             break;   
  567.         case MotionEvent.ACTION_CANCEL:   
  568.             if (mIsBeingDragged && getChildCount() > 0) {   
  569.                 mActivePointerId = INVALID_POINTER;   
  570.                 mIsBeingDragged = false;   
  571.                 if (mVelocityTracker != null) {   
  572.                     mVelocityTracker.recycle();   
  573.                     mVelocityTracker = null;   
  574.                 }   
  575.             }   
  576.             break;   
  577.         case MotionEvent.ACTION_POINTER_UP:   
  578.             onSecondaryPointerUp(ev);   
  579.             break;   
  580.         }   
  581.         return true;   
  582.     }   
  583.  
  584.     private void onSecondaryPointerUp(MotionEvent ev) {   
  585.         final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_ID_MASK) >>   
  586.         MotionEvent.ACTION_POINTER_ID_SHIFT;   
  587.         final int pointerId = ev.getPointerId(pointerIndex);   
  588.         if (pointerId == mActivePointerId) {   
  589.             // This was our active pointer going up. Choose a new   
  590.             // active pointer and adjust accordingly.   
  591.             final int newPointerIndex = pointerIndex == 0 ? 1 : 0;   
  592.             mLastMotionX = ev.getX(newPointerIndex);   
  593.             mLastMotionY = ev.getY(newPointerIndex);   
  594.             mActivePointerId = ev.getPointerId(newPointerIndex);   
  595.             if (mVelocityTracker != null) {   
  596.                 mVelocityTracker.clear();   
  597.             }   
  598.         }   
  599.     }  
  600.     /**
  601.      * <p>
  602.      * Finds the next focusable component that fits in the specified bounds.
  603.      * </p>
  604.      *
  605.      * @param topFocus look for a candidate is the one at the top of the bounds
  606.      *                 if topFocus is true, or at the bottom of the bounds if topFocus is
  607.      *                 false
  608.      * @param top      the top offset of the bounds in which a focusable must be
  609.      *                 found
  610.      * @param bottom   the bottom offset of the bounds in which a focusable must
  611.      *                 be found
  612.      * @return the next focusable component in the bounds or null if none can
  613.      *         be found
  614.      */   
  615.     private View findFocusableViewInBoundsV(boolean topFocus, int top, int bottom) {   
  616.  
  617.         List<View> focusables = getFocusables(View.FOCUS_FORWARD);   
  618.         View focusCandidate = null;   
  619.  
  620.         /*
  621.          * A fully contained focusable is one where its top is below the bound's
  622.          * top, and its bottom is above the bound's bottom. A partially
  623.          * contained focusable is one where some part of it is within the
  624.          * bounds, but it also has some part that is not within bounds.  A fully contained
  625.          * focusable is preferred to a partially contained focusable.
  626.          */   
  627.         boolean foundFullyContainedFocusable = false;   
  628.  
  629.         int count = focusables.size();   
  630.         for (int i = 0; i < count; i++) {   
  631.             View view = focusables.get(i);   
  632.             int viewTop = view.getTop();   
  633.             int viewBottom = view.getBottom();   
  634.  
  635.             if (top < viewBottom && viewTop < bottom) {   
  636.                 /*
  637.                  * the focusable is in the target area, it is a candidate for
  638.                  * focusing
  639.                  */   
  640.  
  641.                 final boolean viewIsFullyContained = (top < viewTop) &&   
  642.                 (viewBottom < bottom);   
  643.  
  644.                 if (focusCandidate == null) {   
  645.                     /* No candidate, take this one */   
  646.                     focusCandidate = view;   
  647.                     foundFullyContainedFocusable = viewIsFullyContained;   
  648.                 } else {   
  649.                     final boolean viewIsCloserToBoundary =   
  650.                         (topFocus && viewTop < focusCandidate.getTop()) ||   
  651.                         (!topFocus && viewBottom > focusCandidate   
  652.                                 .getBottom());   
  653.  
  654.                     if (foundFullyContainedFocusable) {   
  655.                         if (viewIsFullyContained && viewIsCloserToBoundary) {   
  656.                             /*
  657.                              * We're dealing with only fully contained views, so
  658.                              * it has to be closer to the boundary to beat our
  659.                              * candidate
  660.                              */   
  661.                             focusCandidate = view;   
  662.                         }   
  663.                     } else {   
  664.                         if (viewIsFullyContained) {   
  665.                             /* Any fully contained view beats a partially contained view */   
  666.                             focusCandidate = view;   
  667.                             foundFullyContainedFocusable = true;   
  668.                         } else if (viewIsCloserToBoundary) {   
  669.                             /*
  670.                              * Partially contained view beats another partially
  671.                              * contained view if it's closer
  672.                              */   
  673.                             focusCandidate = view;   
  674.                         }   
  675.                     }   
  676.                 }   
  677.             }   
  678.         }   
  679.  
  680.         return focusCandidate;   
  681.     }   
  682.  
  683.     private View findFocusableViewInBoundsH(boolean leftFocus, int left, int right) {   
  684.  
  685.         List<View> focusables = getFocusables(View.FOCUS_FORWARD);   
  686.         View focusCandidate = null;   
  687.  
  688.         /*
  689.          * A fully contained focusable is one where its left is below the bound's
  690.          * left, and its right is above the bound's right. A partially
  691.          * contained focusable is one where some part of it is within the
  692.          * bounds, but it also has some part that is not within bounds.  A fully contained
  693.          * focusable is preferred to a partially contained focusable.
  694.          */   
  695.         boolean foundFullyContainedFocusable = false;   
  696.  
  697.         int count = focusables.size();   
  698.         for (int i = 0; i < count; i++) {   
  699.             View view = focusables.get(i);   
  700.             int viewLeft = view.getLeft();   
  701.             int viewRight = view.getRight();   
  702.  
  703.             if (left < viewRight && viewLeft < right) {   
  704.                 /*
  705.                  * the focusable is in the target area, it is a candidate for
  706.                  * focusing
  707.                  */   
  708.  
  709.                 final boolean viewIsFullyContained = (left < viewLeft) &&   
  710.                 (viewRight < right);   
  711.  
  712.                 if (focusCandidate == null) {   
  713.                     /* No candidate, take this one */   
  714.                     focusCandidate = view;   
  715.                     foundFullyContainedFocusable = viewIsFullyContained;   
  716.                 } else {   
  717.                     final boolean viewIsCloserToBoundary =   
  718.                         (leftFocus && viewLeft < focusCandidate.getLeft()) ||   
  719.                         (!leftFocus && viewRight > focusCandidate.getRight());   
  720.  
  721.                     if (foundFullyContainedFocusable) {   
  722.                         if (viewIsFullyContained && viewIsCloserToBoundary) {   
  723.                             /*
  724.                              * We're dealing with only fully contained views, so
  725.                              * it has to be closer to the boundary to beat our
  726.                              * candidate
  727.                              */   
  728.                             focusCandidate = view;   
  729.                         }   
  730.                     } else {   
  731.                         if (viewIsFullyContained) {   
  732.                             /* Any fully contained view beats a partially contained view */   
  733.                             focusCandidate = view;   
  734.                             foundFullyContainedFocusable = true;   
  735.                         } else if (viewIsCloserToBoundary) {   
  736.                             /*
  737.                              * Partially contained view beats another partially
  738.                              * contained view if it's closer
  739.                              */   
  740.                             focusCandidate = view;   
  741.                         }   
  742.                     }   
  743.                 }   
  744.             }   
  745.         }   
  746.  
  747.         return focusCandidate;   
  748.     }   
  749.  
  750.     /**
  751.      * <p>Handles scrolling in response to a "home/end" shortcut press. This
  752.      * method will scroll the view to the top or bottom and give the focus
  753.      * to the topmost/bottommost component in the new visible area. If no
  754.      * component is a good candidate for focus, this scrollview reclaims the
  755.      * focus.</p>
  756.      *
  757.      * @param direction the scroll direction: {@link android.view.View#FOCUS_UP}
  758.      *                  to go the top of the view or
  759.      *                  {@link android.view.View#FOCUS_DOWN} to go the bottom
  760.      * @return true if the key event is consumed by this method, false otherwise
  761.      */   
  762.     public boolean fullScrollV(int direction) {   
  763.         boolean down = direction == View.FOCUS_DOWN;   
  764.         int height = getHeight();   
  765.  
  766.         mTempRect.top = 0;   
  767.         mTempRect.bottom = height;   
  768.  
  769.         if (down) {   
  770.             int count = getChildCount();   
  771.             if (count > 0) {   
  772.                 View view = getChildAt(count - 1);   
  773.                 mTempRect.bottom = view.getBottom();   
  774.                 mTempRect.top = mTempRect.bottom - height;   
  775.             }   
  776.         }   
  777.  
  778.         return scrollAndFocusV(direction, mTempRect.top, mTempRect.bottom);   
  779.     }   
  780.  
  781.     public boolean fullScrollH(int direction) {   
  782.         boolean right = direction == View.FOCUS_RIGHT;   
  783.         int width = getWidth();   
  784.  
  785.         mTempRect.left = 0;   
  786.         mTempRect.right = width;   
  787.  
  788.         if (right) {   
  789.             int count = getChildCount();   
  790.             if (count > 0) {   
  791.                 View view = getChildAt(0);   
  792.                 mTempRect.right = view.getRight();   
  793.                 mTempRect.left = mTempRect.right - width;   
  794.             }   
  795.         }   
  796.  
  797.         return scrollAndFocusH(direction, mTempRect.left, mTempRect.right);   
  798.     }   
  799.  
  800.     /**
  801.      * <p>Scrolls the view to make the area defined by <code>top</code> and
  802.      * <code>bottom</code> visible. This method attempts to give the focus
  803.      * to a component visible in this area. If no component can be focused in
  804.      * the new visible area, the focus is reclaimed by this scrollview.</p>
  805.      *
  806.      * @param direction the scroll direction: {@link android.view.View#FOCUS_UP}
  807.      *                  to go upward
  808.      *                  {@link android.view.View#FOCUS_DOWN} to downward
  809.      * @param top       the top offset of the new area to be made visible
  810.      * @param bottom    the bottom offset of the new area to be made visible
  811.      * @return true if the key event is consumed by this method, false otherwise
  812.      */   
  813.     private boolean scrollAndFocusV(int direction, int top, int bottom) {   
  814.         boolean handled = true;   
  815.  
  816.         int height = getHeight();   
  817.         int containerTop = getScrollY();   
  818.         int containerBottom = containerTop + height;   
  819.         boolean up = direction == View.FOCUS_UP;   
  820.  
  821.         View newFocused = findFocusableViewInBoundsV(up, top, bottom);   
  822.         if (newFocused == null) {   
  823.             newFocused = this;   
  824.         }   
  825.  
  826.         if (top >= containerTop && bottom <= containerBottom) {   
  827.             handled = false;   
  828.         } else {   
  829.             int delta = up ? (top - containerTop) : (bottom - containerBottom);   
  830.             doScrollY(delta);   
  831.         }   
  832.  
  833.         if (newFocused != findFocus() && newFocused.requestFocus(direction)) {   
  834.             mScrollViewMovedFocus = true;   
  835.             mScrollViewMovedFocus = false;   
  836.         }   
  837.  
  838.         return handled;   
  839.     }   
  840.  
  841.     private boolean scrollAndFocusH(int direction, int left, int right) {   
  842.         boolean handled = true;   
  843.  
  844.         int width = getWidth();   
  845.         int containerLeft = getScrollX();   
  846.         int containerRight = containerLeft + width;   
  847.         boolean goLeft = direction == View.FOCUS_LEFT;   
  848.  
  849.         View newFocused = findFocusableViewInBoundsH(goLeft, left, right);   
  850.         if (newFocused == null) {   
  851.             newFocused = this;   
  852.         }   
  853.  
  854.         if (left >= containerLeft && right <= containerRight) {   
  855.             handled = false;   
  856.         } else {   
  857.             int delta = goLeft ? (left - containerLeft) : (right - containerRight);   
  858.             doScrollX(delta);   
  859.         }   
  860.  
  861.         if (newFocused != findFocus() && newFocused.requestFocus(direction)) {   
  862.             mScrollViewMovedFocus = true;   
  863.             mScrollViewMovedFocus = false;   
  864.         }   
  865.  
  866.         return handled;   
  867.     }   
  868.  
  869.     /**
  870.      * Handle scrolling in response to an up or down arrow click.
  871.      *
  872.      * @param direction The direction corresponding to the arrow key that was
  873.      *                  pressed
  874.      * @return True if we consumed the event, false otherwise
  875.      */   
  876.     public boolean arrowScrollV(int direction) {   
  877.  
  878.         View currentFocused = findFocus();   
  879.         if (currentFocused == this) currentFocused = null;   
  880.  
  881.         View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, direction);   
  882.  
  883.         final int maxJump = getMaxScrollAmountV();   
  884.  
  885.         if (nextFocused != null && isWithinDeltaOfScreenV(nextFocused, maxJump, getHeight())) {   
  886.             nextFocused.getDrawingRect(mTempRect);   
  887.             offsetDescendantRectToMyCoords(nextFocused, mTempRect);   
  888.             int scrollDelta = computeScrollDeltaToGetChildRectOnScreenV(mTempRect);   
  889.             doScrollY(scrollDelta);   
  890.             nextFocused.requestFocus(direction);   
  891.         } else {   
  892.             // no new focus   
  893.             int scrollDelta = maxJump;   
  894.  
  895.             if (direction == View.FOCUS_UP && getScrollY() < scrollDelta) {   
  896.                 scrollDelta = getScrollY();   
  897.             } else if (direction == View.FOCUS_DOWN) {   
  898.                 if (getChildCount() > 0) {   
  899.                     int daBottom = getChildAt(0).getBottom();   
  900.  
  901.                     int screenBottom = getScrollY() + getHeight();   
  902.  
  903.                     if (daBottom - screenBottom < maxJump) {   
  904.                         scrollDelta = daBottom - screenBottom;   
  905.                     }   
  906.                 }   
  907.             }   
  908.             if (scrollDelta == 0) {   
  909.                 return false;   
  910.             }   
  911.             doScrollY(direction == View.FOCUS_DOWN ? scrollDelta : -scrollDelta);   
  912.         }   
  913.  
  914.         if (currentFocused != null && currentFocused.isFocused()   
  915.                 && isOffScreenV(currentFocused)) {   
  916.             // previously focused item still has focus and is off screen, give   
  917.             // it up (take it back to ourselves)   
  918.             // (also, need to temporarily force FOCUS_BEFORE_DESCENDANTS so we are   
  919.             // sure to   
  920.             // get it)   
  921.             final int descendantFocusability = getDescendantFocusability();  // save   
  922.             setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);   
  923.             requestFocus();   
  924.             setDescendantFocusability(descendantFocusability);  // restore   
  925.         }   
  926.         return true;   
  927.     }   
  928.  
  929.     public boolean arrowScrollH(int direction) {   
  930.  
  931.         View currentFocused = findFocus();   
  932.         if (currentFocused == this) currentFocused = null;   
  933.  
  934.         View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, direction);   
  935.  
  936.         final int maxJump = getMaxScrollAmountH();   
  937.  
  938.         if (nextFocused != null && isWithinDeltaOfScreenH(nextFocused, maxJump)) {   
  939.             nextFocused.getDrawingRect(mTempRect);   
  940.             offsetDescendantRectToMyCoords(nextFocused, mTempRect);   
  941.             int scrollDelta = computeScrollDeltaToGetChildRectOnScreenH(mTempRect);   
  942.             doScrollX(scrollDelta);   
  943.             nextFocused.requestFocus(direction);   
  944.         } else {   
  945.             // no new focus   
  946.             int scrollDelta = maxJump;   
  947.  
  948.             if (direction == View.FOCUS_LEFT && getScrollX() < scrollDelta) {   
  949.                 scrollDelta = getScrollX();   
  950.             } else if (direction == View.FOCUS_RIGHT && getChildCount() > 0) {   
  951.  
  952.                 int daRight = getChildAt(0).getRight();   
  953.  
  954.                 int screenRight = getScrollX() + getWidth();   
  955.  
  956.                 if (daRight - screenRight < maxJump) {   
  957.                     scrollDelta = daRight - screenRight;   
  958.                 }   
  959.             }   
  960.             if (scrollDelta == 0) {   
  961.                 return false;   
  962.             }   
  963.             doScrollX(direction == View.FOCUS_RIGHT ? scrollDelta : -scrollDelta);   
  964.         }   
  965.  
  966.         if (currentFocused != null && currentFocused.isFocused()   
  967.                 && isOffScreenH(currentFocused)) {   
  968.             // previously focused item still has focus and is off screen, give   
  969.             // it up (take it back to ourselves)   
  970.             // (also, need to temporarily force FOCUS_BEFORE_DESCENDANTS so we are   
  971.             // sure to   
  972.             // get it)   
  973.             final int descendantFocusability = getDescendantFocusability();  // save   
  974.             setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);   
  975.             requestFocus();   
  976.             setDescendantFocusability(descendantFocusability);  // restore   
  977.         }   
  978.         return true;   
  979.     } 
  980.     /**
  981.      * @return whether the descendant of this scroll view is scrolled off
  982.      *  screen.
  983.      */   
  984.     private boolean isOffScreenV(View descendant) {   
  985.         return !isWithinDeltaOfScreenV(descendant, 0, getHeight());   
  986.     }   
  987.  
  988.     private boolean isOffScreenH(View descendant) {   
  989.         return !isWithinDeltaOfScreenH(descendant, 0);   
  990.     }   
  991.  
  992.     /**
  993.      * @return whether the descendant of this scroll view is within delta
  994.      *  pixels of being on the screen.
  995.      */   
  996.     private boolean isWithinDeltaOfScreenV(View descendant, int delta, int height) {   
  997.         descendant.getDrawingRect(mTempRect);   
  998.         offsetDescendantRectToMyCoords(descendant, mTempRect);   
  999.  
  1000.         return (mTempRect.bottom + delta) >= getScrollY()   
  1001.         && (mTempRect.top - delta) <= (getScrollY() + height);   
  1002.     }   
  1003.  
  1004.     private boolean isWithinDeltaOfScreenH(View descendant, int delta) {   
  1005.         descendant.getDrawingRect(mTempRect);   
  1006.         offsetDescendantRectToMyCoords(descendant, mTempRect);   
  1007.  
  1008.         return (mTempRect.right + delta) >= getScrollX()   
  1009.         && (mTempRect.left - delta) <= (getScrollX() + getWidth());   
  1010.     }   
  1011.  
  1012.     /**
  1013.      * Smooth scroll by a Y delta
  1014.      *
  1015.      * @param delta the number of pixels to scroll by on the Y axis
  1016.      */   
  1017.     private void doScrollY(int delta) {   
  1018.         if (delta != 0) {   
  1019.             if (mSmoothScrollingEnabled) {   
  1020.                 smoothScrollBy(0, delta);   
  1021.             } else {   
  1022.                 scrollBy(0, delta);   
  1023.             }   
  1024.         }   
  1025.     }   
  1026.  
  1027.     private void doScrollX(int delta) {   
  1028.         if (delta != 0) {   
  1029.             if (mSmoothScrollingEnabled) {   
  1030.                 smoothScrollBy(delta, 0);   
  1031.             } else {   
  1032.                 scrollBy(delta, 0);   
  1033.             }   
  1034.         }   
  1035.     }   
  1036.  
  1037.     /**
  1038.      * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
  1039.      *
  1040.      * @param dx the number of pixels to scroll by on the X axis
  1041.      * @param dy the number of pixels to scroll by on the Y axis
  1042.      */   
  1043.     public void smoothScrollBy(int dx, int dy) {   
  1044.         if (getChildCount() == 0) {   
  1045.             // Nothing to do.   
  1046.             return;   
  1047.         }   
  1048.         long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;   
  1049.         if (duration > ANIMATED_SCROLL_GAP) {   
  1050.             final int height = getHeight() - getPaddingBottom() - getPaddingTop();   
  1051.             final int bottom = getChildAt(0).getHeight();   
  1052.             final int maxY = Math.max(0, bottom - height);   
  1053.             final int scrollY = getScrollY();   
  1054.             dy = Math.max(0, Math.min(scrollY + dy, maxY)) - scrollY;   
  1055.  
  1056.             final int width = getWidth() - getPaddingRight() - getPaddingLeft();   
  1057.             final int right = getChildAt(0).getWidth();   
  1058.             final int maxX = Math.max(0, right - width);   
  1059.             final int scrollX = getScrollX();   
  1060.             dx = Math.max(0, Math.min(scrollX + dx, maxX)) - scrollX;   
  1061.  
  1062.             mScroller.startScroll(scrollX, scrollY, dx, dy);   
  1063.             invalidate();   
  1064.         } else {   
  1065.             if (!mScroller.isFinished()) {   
  1066.                 mScroller.abortAnimation();   
  1067.             }   
  1068.             scrollBy(dx, dy);   
  1069.         }   
  1070.         mLastScroll = AnimationUtils.currentAnimationTimeMillis();   
  1071.     }   
  1072.  
  1073.     /**
  1074.      * Like {@link #scrollTo}, but scroll smoothly instead of immediately.
  1075.      *
  1076.      * @param x the position where to scroll on the X axis
  1077.      * @param y the position where to scroll on the Y axis
  1078.      */   
  1079.     public final void smoothScrollTo(int x, int y) {   
  1080.         smoothScrollBy(x - getScrollX(), y - getScrollY());   
  1081.     }   
  1082.  
  1083.     /**
  1084.      * <p>The scroll range of a scroll view is the overall height of all of its
  1085.      * children.</p>
  1086.      */   
  1087.     @Override   
  1088.     protected int computeVerticalScrollRange() {   
  1089.         final int count = getChildCount();   
  1090.         final int contentHeight = getHeight() - getPaddingBottom() - getPaddingTop();   
  1091.         if (count == 0) {   
  1092.             return contentHeight;   
  1093.         }   
  1094.  
  1095.         return getChildAt(0).getBottom();   
  1096.     }   
  1097.  
  1098.     @Override   
  1099.     protected int computeHorizontalScrollRange() {   
  1100.         final int count = getChildCount();   
  1101.         final int contentWidth = getWidth() - getPaddingLeft() - getPaddingRight();   
  1102.         if (count == 0) {   
  1103.             return contentWidth;   
  1104.         }   
  1105.  
  1106.         return getChildAt(0).getRight();   
  1107.     }   
  1108.  
  1109.     @Override   
  1110.     protected int computeVerticalScrollOffset() {   
  1111.         return Math.max(0, super.computeVerticalScrollOffset());   
  1112.     }   
  1113.  
  1114.     @Override   
  1115.     protected int computeHorizontalScrollOffset() {   
  1116.         return Math.max(0, super.computeHorizontalScrollOffset());   
  1117.     }   
  1118.  
  1119.     @Override   
  1120.     protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {   
  1121.         int childWidthMeasureSpec;   
  1122.         int childHeightMeasureSpec;   
  1123.  
  1124.         childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);   
  1125.  
  1126.         childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);   
  1127.  
  1128.         child.measure(childWidthMeasureSpec, childHeightMeasureSpec);   
  1129.     }   
  1130.  
  1131.     @Override   
  1132.     protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,   
  1133.             int parentHeightMeasureSpec, int heightUsed) {   
  1134.         final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();   
  1135.  
  1136.         final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(   
  1137.                 lp.leftMargin + lp.rightMargin, MeasureSpec.UNSPECIFIED);   
  1138.         final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(   
  1139.                 lp.topMargin + lp.bottomMargin, MeasureSpec.UNSPECIFIED);   
  1140.  
  1141.         child.measure(childWidthMeasureSpec, childHeightMeasureSpec);   
  1142.     }   
  1143.  
  1144.     @Override   
  1145.     public void computeScroll() {   
  1146.         if (mScroller.computeScrollOffset()) {   
  1147.             // This is called at drawing time by ViewGroup.  We don't want to   
  1148.             // re-show the scrollbars at this point, which scrollTo will do,   
  1149.             // so we replicate most of scrollTo here.   
  1150.             //   
  1151.             //         It's a little odd to call onScrollChanged from inside the drawing.   
  1152.             //   
  1153.             //         It is, except when you remember that computeScroll() is used to   
  1154.             //         animate scrolling. So unless we want to defer the onScrollChanged()   
  1155.             //         until the end of the animated scrolling, we don't really have a   
  1156.             //         choice here.   
  1157.             //   
  1158.             //         I agree.  The alternative, which I think would be worse, is to post   
  1159.             //         something and tell the subclasses later.  This is bad because there   
  1160.             //         will be a window where mScrollX/Y is different from what the app   
  1161.             //         thinks it is.   
  1162.             //   
  1163.             int x = mScroller.getCurrX();   
  1164.             int y = mScroller.getCurrY();   
  1165.  
  1166.             if (getChildCount() > 0) {   
  1167.                 View child = getChildAt(0);   
  1168.                 x = clamp(x, getWidth() - getPaddingRight() - getPaddingLeft(), child.getWidth());   
  1169.                 y = clamp(y, getHeight() - getPaddingBottom() - getPaddingTop(), child.getHeight());   
  1170.                 super.scrollTo(x, y);   
  1171.             }   
  1172.             awakenScrollBars();   
  1173.  
  1174.             // Keep on drawing until the animation has finished.   
  1175.             postInvalidate();   
  1176.         }   
  1177.     }   
  1178.  
  1179.     /**
  1180.      * Scrolls the view to the given child.
  1181.      *
  1182.      * @param child the View to scroll to
  1183.      */   
  1184.     private void scrollToChild(View child) {   
  1185.         child.getDrawingRect(mTempRect);   
  1186.  
  1187.         /* Offset from child's local coordinates to ScrollView coordinates */   
  1188.         offsetDescendantRectToMyCoords(child, mTempRect);   
  1189.  
  1190.         int scrollDeltaV = computeScrollDeltaToGetChildRectOnScreenV(mTempRect);   
  1191.         int scrollDeltaH = computeScrollDeltaToGetChildRectOnScreenH(mTempRect);   
  1192.  
  1193.         if (scrollDeltaH != 0 || scrollDeltaV != 0) {   
  1194.             scrollBy(scrollDeltaH, scrollDeltaV);   
  1195.         }   
  1196.     }   
  1197.  
  1198.     /**
  1199.      * If rect is off screen, scroll just enough to get it (or at least the
  1200.      * first screen size chunk of it) on screen.
  1201.      *
  1202.      * @param rect      The rectangle.
  1203.      * @param immediate True to scroll immediately without animation
  1204.      * @return true if scrolling was performed
  1205.      */   
  1206.     private boolean scrollToChildRect(Rect rect, boolean immediate) {   
  1207.         final int deltaV = computeScrollDeltaToGetChildRectOnScreenV(rect);   
  1208.         final int deltaH = computeScrollDeltaToGetChildRectOnScreenH(rect);   
  1209.         final boolean scroll = deltaH != 0 || deltaV != 0;   
  1210.         if (scroll) {   
  1211.             if (immediate) {   
  1212.                 scrollBy(deltaH, deltaV);   
  1213.             } else {   
  1214.                 smoothScrollBy(deltaH, deltaV);   
  1215.             }   
  1216.         }   
  1217.         return scroll;   
  1218.     } 
  1219.     /**
  1220.      * Compute the amount to scroll in the Y direction in order to get
  1221.      * a rectangle completely on the screen (or, if taller than the screen,
  1222.      * at least the first screen size chunk of it).
  1223.      *
  1224.      * @param rect The rect.
  1225.      * @return The scroll delta.
  1226.      */   
  1227.     protected int computeScrollDeltaToGetChildRectOnScreenV(Rect rect) {   
  1228.         if (getChildCount() == 0) return 0;   
  1229.  
  1230.         int height = getHeight();   
  1231.         int screenTop = getScrollY();   
  1232.         int screenBottom = screenTop + height;   
  1233.  
  1234.         int fadingEdge = getVerticalFadingEdgeLength();   
  1235.  
  1236.         // leave room for top fading edge as long as rect isn't at very top   
  1237.         if (rect.top > 0) {   
  1238.             screenTop += fadingEdge;   
  1239.         }   
  1240.  
  1241.         // leave room for bottom fading edge as long as rect isn't at very bottom   
  1242.         if (rect.bottom < getChildAt(0).getHeight()) {   
  1243.             screenBottom -= fadingEdge;   
  1244.         }   
  1245.  
  1246.         int scrollYDelta = 0;   
  1247.  
  1248.         if (rect.bottom > screenBottom && rect.top > screenTop) {   
  1249.             // need to move down to get it in view: move down just enough so   
  1250.             // that the entire rectangle is in view (or at least the first   
  1251.             // screen size chunk).   
  1252.  
  1253.             if (rect.height() > height) {   
  1254.                 // just enough to get screen size chunk on   
  1255.                 scrollYDelta += (rect.top - screenTop);   
  1256.             } else {   
  1257.                 // get entire rect at bottom of screen   
  1258.                 scrollYDelta += (rect.bottom - screenBottom);   
  1259.             }   
  1260.  
  1261.             // make sure we aren't scrolling beyond the end of our content   
  1262.             int bottom = getChildAt(0).getBottom();   
  1263.             int distanceToBottom = bottom - screenBottom;   
  1264.             scrollYDelta = Math.min(scrollYDelta, distanceToBottom);   
  1265.  
  1266.         } else if (rect.top < screenTop && rect.bottom < screenBottom) {   
  1267.             // need to move up to get it in view: move up just enough so that   
  1268.             // entire rectangle is in view (or at least the first screen   
  1269.             // size chunk of it).   
  1270.  
  1271.             if (rect.height() > height) {   
  1272.                 // screen size chunk   
  1273.                 scrollYDelta -= (screenBottom - rect.bottom);   
  1274.             } else {   
  1275.                 // entire rect at top   
  1276.                 scrollYDelta -= (screenTop - rect.top);   
  1277.             }   
  1278.  
  1279.             // make sure we aren't scrolling any further than the top our content   
  1280.             scrollYDelta = Math.max(scrollYDelta, -getScrollY());   
  1281.         }   
  1282.         return scrollYDelta;   
  1283.     }   
  1284.  
  1285.     protected int computeScrollDeltaToGetChildRectOnScreenH(Rect rect) {   
  1286.         if (getChildCount() == 0) return 0;   
  1287.  
  1288.         int width = getWidth();   
  1289.         int screenLeft = getScrollX();   
  1290.         int screenRight = screenLeft + width;   
  1291.  
  1292.         int fadingEdge = getHorizontalFadingEdgeLength();   
  1293.  
  1294.         // leave room for left fading edge as long as rect isn't at very left   
  1295.         if (rect.left > 0) {   
  1296.             screenLeft += fadingEdge;   
  1297.         }   
  1298.  
  1299.         // leave room for right fading edge as long as rect isn't at very right   
  1300.         if (rect.right < getChildAt(0).getWidth()) {   
  1301.             screenRight -= fadingEdge;   
  1302.         }   
  1303.  
  1304.         int scrollXDelta = 0;   
  1305.  
  1306.         if (rect.right > screenRight && rect.left > screenLeft) {   
  1307.             // need to move right to get it in view: move right just enough so   
  1308.             // that the entire rectangle is in view (or at least the first   
  1309.             // screen size chunk).   
  1310.  
  1311.             if (rect.width() > width) {   
  1312.                 // just enough to get screen size chunk on   
  1313.                 scrollXDelta += (rect.left - screenLeft);   
  1314.             } else {   
  1315.                 // get entire rect at right of screen   
  1316.                 scrollXDelta += (rect.right - screenRight);   
  1317.             }   
  1318.  
  1319.             // make sure we aren't scrolling beyond the end of our content   
  1320.             int right = getChildAt(0).getRight();   
  1321.             int distanceToRight = right - screenRight;   
  1322.             scrollXDelta = Math.min(scrollXDelta, distanceToRight);   
  1323.  
  1324.         } else if (rect.left < screenLeft && rect.right < screenRight) {   
  1325.             // need to move right to get it in view: move right just enough so that   
  1326.             // entire rectangle is in view (or at least the first screen   
  1327.             // size chunk of it).   
  1328.  
  1329.             if (rect.width() > width) {   
  1330.                 // screen size chunk   
  1331.                 scrollXDelta -= (screenRight - rect.right);   
  1332.             } else {   
  1333.                 // entire rect at left   
  1334.                 scrollXDelta -= (screenLeft - rect.left);   
  1335.             }   
  1336.  
  1337.             // make sure we aren't scrolling any further than the left our content   
  1338.             scrollXDelta = Math.max(scrollXDelta, -getScrollX());   
  1339.         }   
  1340.         return scrollXDelta;   
  1341.     }   
  1342.  
  1343.     @Override   
  1344.     public void requestChildFocus(View child, View focused) {   
  1345.         if (!mScrollViewMovedFocus) {   
  1346.             if (!mIsLayoutDirty) {   
  1347.                 scrollToChild(focused);   
  1348.             } else {   
  1349.                 // The child may not be laid out yet, we can't compute the scroll yet   
  1350.                 mChildToScrollTo = focused;   
  1351.             }   
  1352.         }   
  1353.         super.requestChildFocus(child, focused);   
  1354.     }   
  1355.  
  1356.  
  1357.     /**
  1358.      * When looking for focus in children of a scroll view, need to be a little
  1359.      * more careful not to give focus to something that is scrolled off screen.
  1360.      *
  1361.      * This is more expensive than the default {@link android.view.ViewGroup}
  1362.      * implementation, otherwise this behavior might have been made the default.
  1363.      */   
  1364.     @Override   
  1365.     protected boolean onRequestFocusInDescendants(int direction,   
  1366.             Rect previouslyFocusedRect) {   
  1367.  
  1368.         // convert from forward / backward notation to up / down / left / right   
  1369.         // (ugh).   
  1370.         // TODO: FUCK   
  1371.         //        if (direction == View.FOCUS_FORWARD) {   
  1372.         //            direction = View.FOCUS_RIGHT;   
  1373.         //        } else if (direction == View.FOCUS_BACKWARD) {   
  1374.         //            direction = View.FOCUS_LEFT;   
  1375.         //        }   
  1376.  
  1377.         final View nextFocus = previouslyFocusedRect == null ?   
  1378.                 FocusFinder.getInstance().findNextFocus(this, null, direction) :   
  1379.                     FocusFinder.getInstance().findNextFocusFromRect(this,   
  1380.                             previouslyFocusedRect, direction);   
  1381.  
  1382.                 if (nextFocus == null) {   
  1383.                     return false;   
  1384.                 }   
  1385.  
  1386.                 //        if (isOffScreenH(nextFocus)) {   
  1387.                     //            return false;   
  1388.                 //        }   
  1389.  
  1390.                 return nextFocus.requestFocus(direction, previouslyFocusedRect);   
  1391.     }     
  1392.  
  1393.     @Override   
  1394.     public boolean requestChildRectangleOnScreen(View child, Rect rectangle,   
  1395.             boolean immediate) {   
  1396.         // offset into coordinate space of this scroll view   
  1397.         rectangle.offset(child.getLeft() - child.getScrollX(),   
  1398.                 child.getTop() - child.getScrollY());   
  1399.  
  1400.         return scrollToChildRect(rectangle, immediate);   
  1401.     }   
  1402.  
  1403.     @Override   
  1404.     public void requestLayout() {   
  1405.         mIsLayoutDirty = true;   
  1406.         super.requestLayout();   
  1407.     }   
  1408.  
  1409.     @Override   
  1410.     protected void onLayout(boolean changed, int l, int t, int r, int b) {   
  1411.         super.onLayout(changed, l, t, r, b);   
  1412.         mIsLayoutDirty = false;   
  1413.         // Give a child focus if it needs it    
  1414.         if (mChildToScrollTo != null && isViewDescendantOf(mChildToScrollTo, this)) {   
  1415.             scrollToChild(mChildToScrollTo);   
  1416.         }   
  1417.         mChildToScrollTo = null;   
  1418.  
  1419.         // Calling this with the present values causes it to re-clam them   
  1420.         scrollTo(getScrollX(), getScrollY());   
  1421.     }   
  1422.  
  1423.     @Override   
  1424.     protected void onSizeChanged(int w, int h, int oldw, int oldh) {   
  1425.         super.onSizeChanged(w, h, oldw, oldh);   
  1426.  
  1427.         View currentFocused = findFocus();   
  1428.         if (null == currentFocused || this == currentFocused)   
  1429.             return;   
  1430.  
  1431.         // If the currently-focused view was visible on the screen when the   
  1432.         // screen was at the old height, then scroll the screen to make that   
  1433.         // view visible with the new screen height.   
  1434.         if (isWithinDeltaOfScreenV(currentFocused, 0, oldh)) {   
  1435.             currentFocused.getDrawingRect(mTempRect);   
  1436.             offsetDescendantRectToMyCoords(currentFocused, mTempRect);   
  1437.             int scrollDelta = computeScrollDeltaToGetChildRectOnScreenV(mTempRect);   
  1438.             doScrollY(scrollDelta);   
  1439.         }   
  1440.  
  1441.         final int maxJump = getRight() - getLeft();   
  1442.         if (isWithinDeltaOfScreenH(currentFocused, maxJump)) {   
  1443.             currentFocused.getDrawingRect(mTempRect);   
  1444.             offsetDescendantRectToMyCoords(currentFocused, mTempRect);   
  1445.             int scrollDelta = computeScrollDeltaToGetChildRectOnScreenH(mTempRect);   
  1446.             doScrollX(scrollDelta);   
  1447.         }   
  1448.     }       
  1449.  
  1450.     /**
  1451.      * Return true if child is an descendant of parent, (or equal to the parent).
  1452.      */   
  1453.     private boolean isViewDescendantOf(View child, View parent) {   
  1454.         if (child == parent) {   
  1455.             return true;   
  1456.         }   
  1457.  
  1458.         final ViewParent theParent = child.getParent();   
  1459.         return (theParent instanceof ViewGroup) && isViewDescendantOf((View) theParent, parent);   
  1460.     }       
  1461.  
  1462.     /**
  1463.      * Fling the scroll view
  1464.      *
  1465.      * @param velocityY The initial velocity in the Y direction. Positive
  1466.      *                  numbers mean that the finger/cursor is moving down the screen,
  1467.      *                  which means we want to scroll towards the top.
  1468.      */   
  1469.     public void fling(int velocityX, int velocityY) {   
  1470.         if (getChildCount() > 0) {   
  1471.             int width = getWidth() - getPaddingRight() - getPaddingLeft();   
  1472.             int right = getChildAt(0).getWidth();   
  1473.  
  1474.             int height = getHeight() - getPaddingBottom() - getPaddingTop();   
  1475.             int bottom = getChildAt(0).getHeight();   
  1476.  
  1477.             mScroller.fling(getScrollX(), getScrollY(), velocityX, velocityY,    
  1478.                     0, Math.max(0, right - width),    
  1479.                     0, Math.max(0, bottom - height));   
  1480.  
  1481.             //            final boolean movingDown = velocityX > 0 || velocityY > 0;   
  1482.             //       
  1483.             //            View newFocused =   
  1484.             //                    findFocusableViewInMyBoundsV(movingDown, mScroller.getFinalY(), findFocus());   
  1485.             //            if (newFocused == null) {   
  1486.             //                newFocused = this;   
  1487.             //            }   
  1488.             //       
  1489.             //            if (newFocused != findFocus()   
  1490.             //                    && newFocused.requestFocus(movingDown ? View.FOCUS_DOWN : View.FOCUS_UP)) {   
  1491.             //                mScrollViewMovedFocus = true;   
  1492.             //                mScrollViewMovedFocus = false;   
  1493.             //            }   
  1494.  
  1495.             invalidate();   
  1496.         }   
  1497.     }   
  1498.  
  1499.     /**
  1500.      * {@inheritDoc}
  1501.      *
  1502.      * <p>This version also clamps the scrolling to the bounds of our child.
  1503.      */   
  1504.     @Override   
  1505.     public void scrollTo(int x, int y) {   
  1506.         // we rely on the fact the View.scrollBy calls scrollTo.   
  1507.         if (getChildCount() > 0) {   
  1508.             View child = getChildAt(0);   
  1509.             x = clamp(x, getWidth() - getPaddingRight() - getPaddingLeft(), child.getWidth());   
  1510.             y = clamp(y, getHeight() - getPaddingBottom() - getPaddingTop(), child.getHeight());   
  1511.             if (x != getScrollX() || y != getScrollY()) {   
  1512.                 super.scrollTo(x, y);   
  1513.             }   
  1514.         }   
  1515.     }   
  1516.  
  1517.     private int clamp(int n, int my, int child) {   
  1518.         if (my >= child || n < 0) {   
  1519.             /* my >= child is this case:
  1520.              *                    |--------------- me ---------------|
  1521.              *     |------ child ------|
  1522.              * or
  1523.              *     |--------------- me ---------------|
  1524.              *            |------ child ------|
  1525.              * or
  1526.              *     |--------------- me ---------------|
  1527.              *                                  |------ child ------|
  1528.              *
  1529.              * n < 0 is this case:
  1530.              *     |------ me ------|
  1531.              *                    |-------- child --------|
  1532.              *     |-- mScrollX --|
  1533.              */   
  1534.             return 0;   
  1535.         }   
  1536.         if ((my+n) > child) {   
  1537.             /* this case:
  1538.              *                    |------ me ------|
  1539.              *     |------ child ------|
  1540.              *     |-- mScrollX --|
  1541.              */   
  1542.             return child-my;   
  1543.         }   
  1544.         return n;   
  1545.     }   
  1546.  
  1547.     public boolean isFlingEnabled() {   
  1548.         return mFlingEnabled;   
  1549.     }   
  1550.  
  1551.     public void setFlingEnabled(boolean flingEnabled) {   
  1552.         this.mFlingEnabled = flingEnabled;   
  1553.     }   
  1554. }  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM