Android中实现上下左右都可滑动的ScrollView

来源:互联网 发布:excel vba sql查询 编辑:程序博客网 时间:2024/06/05 11:29
  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(0super.computeVerticalScrollOffset());     
  1112.     }     
  1113.    
  1114.     @Override     
  1115.     protected int computeHorizontalScrollOffset() {     
  1116.         return Math.max(0super.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() == 0return 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() == 0return 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(thisnull, 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. }    
0 0