Android双向滚动ScrollView

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


  1. /* 
  2.  * Integration of ScrollView and HorizontalScrollView 
  3.  *  
  4.  * For some unknown reason, the H/V scroll bar are missing. 
  5.  */  
  6.   
  7. package com.yfmandroid.widget;  
  8.   
  9. import java.util.List;  
  10.   
  11. import android.content.Context;  
  12. import android.graphics.Rect;  
  13. import android.util.AttributeSet;  
  14. import android.view.FocusFinder;  
  15. import android.view.KeyEvent;  
  16. import android.view.MotionEvent;  
  17. import android.view.VelocityTracker;  
  18. import android.view.View;  
  19. import android.view.ViewConfiguration;  
  20. import android.view.ViewGroup;  
  21. import android.view.ViewParent;  
  22. import android.view.animation.AnimationUtils;  
  23. import android.widget.FrameLayout;  
  24. import android.widget.Scroller;  
  25.   
  26. /** 
  27.  * Reference to ScrollView and HorizontalScrollView 
  28.  */  
  29. public class CustomScrollView extends FrameLayout {  
  30.     static final int ANIMATED_SCROLL_GAP = 250;  
  31.   
  32.     static final float MAX_SCROLL_FACTOR = 0.5f;  
  33.   
  34.   
  35.     private long mLastScroll;  
  36.   
  37.     private final Rect mTempRect = new Rect();  
  38.     private Scroller mScroller;  
  39.   
  40.     /** 
  41.      * Flag to indicate that we are moving focus ourselves. This is so the 
  42.      * code that watches for focus changes initiated outside this ScrollView 
  43.      * knows that it does not have to do anything. 
  44.      */  
  45.     private boolean mScrollViewMovedFocus;  
  46.   
  47.     /** 
  48.      * Position of the last motion event. 
  49.      */  
  50.     private float mLastMotionY;  
  51.     private float mLastMotionX;  
  52.   
  53.     /** 
  54.      * True when the layout has changed but the traversal has not come through yet. 
  55.      * Ideally the view hierarchy would keep track of this for us. 
  56.      */  
  57.     private boolean mIsLayoutDirty = true;  
  58.   
  59.     /** 
  60.      * The child to give focus to in the event that a child has requested focus while the 
  61.      * layout is dirty. This prevents the scroll from being wrong if the child has not been 
  62.      * laid out before requesting focus. 
  63.      */  
  64.     private View mChildToScrollTo = null;  
  65.   
  66.     /** 
  67.      * True if the user is currently dragging this ScrollView around. This is 
  68.      * not the same as 'is being flinged', which can be checked by 
  69.      * mScroller.isFinished() (flinging begins when the user lifts his finger). 
  70.      */  
  71.     private boolean mIsBeingDragged = false;  
  72.   
  73.     /** 
  74.      * Determines speed during touch scrolling 
  75.      */  
  76.     private VelocityTracker mVelocityTracker;  
  77.   
  78.     /** 
  79.      * When set to true, the scroll view measure its child to make it fill the currently 
  80.      * visible area. 
  81.      */  
  82.     private boolean mFillViewport;  
  83.   
  84.     /** 
  85.      * Whether arrow scrolling is animated. 
  86.      */  
  87.     private boolean mSmoothScrollingEnabled = true;  
  88.   
  89.     private int mTouchSlop;  
  90.     private int mMinimumVelocity;  
  91.     private int mMaximumVelocity;  
  92.       
  93.     /** 
  94.      * ID of the active pointer. This is used to retain consistency during 
  95.      * drags/flings if multiple pointers are used. 
  96.      */  
  97.     private int mActivePointerId = INVALID_POINTER;  
  98.       
  99.     /** 
  100.      * Sentinel value for no current active pointer. 
  101.      * Used by {@link #mActivePointerId}. 
  102.      */  
  103.     private static final int INVALID_POINTER = -1;  
  104.       
  105.     private boolean mFlingEnabled = true;  
  106.   
  107.     public CustomScrollView(Context context) {  
  108.         this(context, null);  
  109.     }  
  110.   
  111.     public CustomScrollView(Context context, AttributeSet attrs) {  
  112.         super(context, attrs);  
  113.         initScrollView();  
  114.     }  
  115.   
  116.     @Override  
  117.     protected float getTopFadingEdgeStrength() {  
  118.         if (getChildCount() == 0) {  
  119.             return 0.0f;  
  120.         }  
  121.   
  122.         final int length = getVerticalFadingEdgeLength();  
  123.         if (getScrollY() < length) {  
  124.             return getScrollY() / (float) length;  
  125.         }  
  126.   
  127.         return 1.0f;  
  128.     }  
  129.       
  130.     @Override  
  131.     protected float getLeftFadingEdgeStrength() {  
  132.         if (getChildCount() == 0) {  
  133.             return 0.0f;  
  134.         }  
  135.   
  136.         final int length = getHorizontalFadingEdgeLength();  
  137.         if (getScrollX() < length) {  
  138.             return getScrollX() / (float) length;  
  139.         }  
  140.   
  141.         return 1.0f;  
  142.     }  
  143.       
  144.     @Override  
  145.     protected float getRightFadingEdgeStrength() {  
  146.         if (getChildCount() == 0) {  
  147.             return 0.0f;  
  148.         }  
  149.   
  150.         final int length = getHorizontalFadingEdgeLength();  
  151.         final int rightEdge = getWidth() - getPaddingRight();  
  152.         final int span = getChildAt(0).getRight() - getScrollX() - rightEdge;  
  153.         if (span < length) {  
  154.             return span / (float) length;  
  155.         }  
  156.   
  157.         return 1.0f;  
  158.     }  
  159.   
  160.     @Override  
  161.     protected float getBottomFadingEdgeStrength() {  
  162.         if (getChildCount() == 0) {  
  163.             return 0.0f;  
  164.         }  
  165.   
  166.         final int length = getVerticalFadingEdgeLength();  
  167.         final int bottomEdge = getHeight() - getPaddingBottom();  
  168.         final int span = getChildAt(0).getBottom() - getScrollY() - bottomEdge;  
  169.         if (span < length) {  
  170.             return span / (float) length;  
  171.         }  
  172.   
  173.         return 1.0f;  
  174.     }  
  175.   
  176.     /** 
  177.      * @return The maximum amount this scroll view will scroll in response to 
  178.      *   an arrow event. 
  179.      */  
  180.     public int getMaxScrollAmountV() {  
  181.         return (int) (MAX_SCROLL_FACTOR * (getBottom() - getTop()));  
  182.     }  
  183.       
  184.     public int getMaxScrollAmountH() {  
  185.         return (int) (MAX_SCROLL_FACTOR * (getRight() - getLeft()));  
  186.     }  
  187.   
  188.   
  189.     private void initScrollView() {  
  190.         mScroller = new Scroller(getContext());  
  191.         setFocusable(true);  
  192.         setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);  
  193.         setWillNotDraw(false);  
  194.         final ViewConfiguration configuration = ViewConfiguration.get(getContext());  
  195.         mTouchSlop = configuration.getScaledTouchSlop();  
  196.         mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();  
  197.         mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();  
  198.     }  
  199.   
  200.     @Override  
  201.     public void addView(View child) {  
  202.         if (getChildCount() > 0) {  
  203.             throw new IllegalStateException("ScrollView can host only one direct child");  
  204.         }  
  205.   
  206.         super.addView(child);  
  207.     }  
  208.   
  209.     @Override  
  210.     public void addView(View child, int index) {  
  211.         if (getChildCount() > 0) {  
  212.             throw new IllegalStateException("ScrollView can host only one direct child");  
  213.         }  
  214.   
  215.         super.addView(child, index);  
  216.     }  
  217.   
  218.     @Override  
  219.     public void addView(View child, ViewGroup.LayoutParams params) {  
  220.         if (getChildCount() > 0) {  
  221.             throw new IllegalStateException("ScrollView can host only one direct child");  
  222.         }  
  223.   
  224.         super.addView(child, params);  
  225.     }  
  226.   
  227.     @Override  
  228.     public void addView(View child, int index, ViewGroup.LayoutParams params) {  
  229.         if (getChildCount() > 0) {  
  230.             throw new IllegalStateException("ScrollView can host only one direct child");  
  231.         }  
  232.   
  233.         super.addView(child, index, params);  
  234.     }  
  235.   
  236.     /** 
  237.      * @return Returns true this ScrollView can be scrolled 
  238.      */  
  239.     private boolean canScrollV() {  
  240.         View child = getChildAt(0);  
  241.         if (child != null) {  
  242.             int childHeight = child.getHeight();  
  243.             return getHeight() < childHeight + getPaddingTop() + getPaddingBottom();  
  244.         }  
  245.         return false;  
  246.     }  
  247.       
  248.     private boolean canScrollH() {  
  249.         View child = getChildAt(0);  
  250.         if (child != null) {  
  251.             int childWidth = child.getWidth();  
  252.             return getWidth() < childWidth + getPaddingLeft() + getPaddingRight() ;  
  253.         }  
  254.         return false;  
  255.     }  
  256.   
  257.     /** 
  258.      * Indicates whether this ScrollView's content is stretched to fill the viewport. 
  259.      * 
  260.      * @return True if the content fills the viewport, false otherwise. 
  261.      */  
  262.     public boolean isFillViewport() {  
  263.         return mFillViewport;  
  264.     }  
  265.   
  266.     /** 
  267.      * Indicates this ScrollView whether it should stretch its content height to fill 
  268.      * the viewport or not. 
  269.      * 
  270.      * @param fillViewport True to stretch the content's height to the viewport's 
  271.      *        boundaries, false otherwise. 
  272.      */  
  273.     public void setFillViewport(boolean fillViewport) {  
  274.         if (fillViewport != mFillViewport) {  
  275.             mFillViewport = fillViewport;  
  276.             requestLayout();  
  277.         }  
  278.     }  
  279.   
  280.     /** 
  281.      * @return Whether arrow scrolling will animate its transition. 
  282.      */  
  283.     public boolean isSmoothScrollingEnabled() {  
  284.         return mSmoothScrollingEnabled;  
  285.     }  
  286.   
  287.     /** 
  288.      * Set whether arrow scrolling will animate its transition. 
  289.      * @param smoothScrollingEnabled whether arrow scrolling will animate its transition 
  290.      */  
  291.     public void setSmoothScrollingEnabled(boolean smoothScrollingEnabled) {  
  292.         mSmoothScrollingEnabled = smoothScrollingEnabled;  
  293.     }  
  294.   
  295.     @Override  
  296.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  297.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  298.   
  299.         if (!mFillViewport) {  
  300.             return;  
  301.         }  
  302.   
  303.         final int heightMode = MeasureSpec.getMode(heightMeasureSpec);  
  304.         final int widthMode = MeasureSpec.getMode(widthMeasureSpec);  
  305.         if (heightMode == MeasureSpec.UNSPECIFIED && widthMode == MeasureSpec.UNSPECIFIED) {  
  306.             return;  
  307.         }  
  308.   
  309.         if (getChildCount() > 0) {  
  310.             final View child = getChildAt(0);  
  311.             int height = getMeasuredHeight();  
  312.             int width = getMeasuredWidth();  
  313.             if (child.getMeasuredHeight() < height || child.getMeasuredWidth() < width) {  
  314.                 width -= getPaddingLeft();  
  315.                 width -= getPaddingRight();  
  316.                 int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);  
  317.                   
  318.                 height -= getPaddingTop();  
  319.                 height -= getPaddingBottom();  
  320.                 int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);  
  321.       
  322.                 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);  
  323.             }  
  324.         }  
  325.     }  

[java]  view plain copy
  1. @Override  
  2. public boolean dispatchKeyEvent(KeyEvent event) {  
  3.     // Let the focused view and/or our descendants get the key first  
  4.     return super.dispatchKeyEvent(event) || executeKeyEvent(event);  
  5. }  
  6.   
  7. /** 
  8.  * You can call this function yourself to have the scroll view perform 
  9.  * scrolling from a key event, just as if the event had been dispatched to 
  10.  * it by the view hierarchy. 
  11.  * 
  12.  * @param event The key event to execute. 
  13.  * @return Return true if the event was handled, else false. 
  14.  */  
  15. public boolean executeKeyEvent(KeyEvent event) {  
  16.     mTempRect.setEmpty();  
  17.       
  18.     boolean handled = false;  
  19.       
  20.     if (event.getAction() == KeyEvent.ACTION_DOWN) {  
  21.         switch (event.getKeyCode()) {  
  22.             case KeyEvent.KEYCODE_DPAD_LEFT:  
  23.                 if(canScrollH()){  
  24.                     if (!event.isAltPressed()) {  
  25.                         handled = arrowScrollH(View.FOCUS_LEFT);  
  26.                     } else {  
  27.                         handled = fullScrollH(View.FOCUS_LEFT);  
  28.                     }  
  29.                 }  
  30.                 break;  
  31.             case KeyEvent.KEYCODE_DPAD_RIGHT:  
  32.                 if(canScrollH()){  
  33.                     if (!event.isAltPressed()) {  
  34.                         handled = arrowScrollH(View.FOCUS_RIGHT);  
  35.                     } else {  
  36.                         handled = fullScrollH(View.FOCUS_RIGHT);  
  37.                     }  
  38.                 }  
  39.                 break;  
  40.             case KeyEvent.KEYCODE_DPAD_UP:  
  41.                 if(canScrollV()){  
  42.                     if (!event.isAltPressed()) {  
  43.                         handled = arrowScrollV(View.FOCUS_UP);  
  44.                     } else {  
  45.                         handled = fullScrollV(View.FOCUS_UP);  
  46.                     }  
  47.                 }  
  48.                 break;  
  49.             case KeyEvent.KEYCODE_DPAD_DOWN:  
  50.                 if(canScrollV()){  
  51.                     if (!event.isAltPressed()) {  
  52.                         handled = arrowScrollV(View.FOCUS_DOWN);  
  53.                     } else {  
  54.                         handled = fullScrollV(View.FOCUS_DOWN);  
  55.                     }  
  56.                 }  
  57.                 break;  
  58.         }  
  59.     }  
  60.     return handled;  
  61. }  
  62.   
  63. private boolean inChild(int x, int y) {  
  64.     if (getChildCount() > 0) {  
  65.         final int scrollX = getScrollX();  
  66.         final int scrollY = getScrollY();  
  67.         final View child = getChildAt(0);  
  68.         return !(y < child.getTop() - scrollY  
  69.                 || y >= child.getBottom() - scrollY  
  70.                 || x < child.getLeft() - scrollX  
  71.                 || x >= child.getRight() - scrollX);  
  72.     }  
  73.     return false;  
  74. }  
  75.   
  76. @Override  
  77. public boolean onInterceptTouchEvent(MotionEvent ev) {  
  78.     /* 
  79.      * This method JUST determines whether we want to intercept the motion. 
  80.      * If we return true, onMotionEvent will be called and we do the actual 
  81.      * scrolling there. 
  82.      */  
  83.   
  84.     /* 
  85.     * Shortcut the most recurring case: the user is in the dragging 
  86.     * state and he is moving his finger.  We want to intercept this 
  87.     * motion. 
  88.     */  
  89.     final int action = ev.getAction();  
  90.     if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {  
  91.         return true;  
  92.     }  
  93.   
  94.     switch (action & MotionEvent.ACTION_MASK) {  
  95.         case MotionEvent.ACTION_MOVE: {  
  96.             /* 
  97.              * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check 
  98.              * whether the user has moved far enough from his original down touch. 
  99.              */  
  100.   
  101.             /* 
  102.             * Locally do absolute value. mLastMotionY is set to the y value 
  103.             * of the down event. 
  104.             */  
  105.             final int activePointerId = mActivePointerId;  
  106.             if (activePointerId == INVALID_POINTER) {  
  107.                 // If we don't have a valid id, the touch down wasn't on content.  
  108.                 break;  
  109.             }  
  110.   
  111.             final int pointerIndex = ev.findPointerIndex(activePointerId);  
  112.             final float y = ev.getY(pointerIndex);  
  113.             final int yDiff = (int) Math.abs(y - mLastMotionY);  
  114.             if (yDiff > mTouchSlop) {  
  115.                 mIsBeingDragged = true;  
  116.                 mLastMotionY = y;  
  117.             }  
  118.             final float x = ev.getX(pointerIndex);  
  119.             final int xDiff = (int) Math.abs(x - mLastMotionX);  
  120.             if (xDiff > mTouchSlop) {  
  121.                 mIsBeingDragged = true;  
  122.                 mLastMotionX = x;  
  123.             }  
  124.             break;  
  125.         }  
  126.   
  127.         case MotionEvent.ACTION_DOWN: {  
  128.             final float x = ev.getX();  
  129.             final float y = ev.getY();  
  130.             if (!inChild((int)x, (int) y)) {  
  131.                 mIsBeingDragged = false;  
  132.                 break;  
  133.             }  
  134.   
  135.             /* 
  136.              * Remember location of down touch. 
  137.              * ACTION_DOWN always refers to pointer index 0. 
  138.              */  
  139.             mLastMotionY = y;  
  140.             mLastMotionX = x;  
  141.             mActivePointerId = ev.getPointerId(0);  
  142.   
  143.             /* 
  144.             * If being flinged and user touches the screen, initiate drag; 
  145.             * otherwise don't.  mScroller.isFinished should be false when 
  146.             * being flinged. 
  147.             */  
  148.             mIsBeingDragged = !mScroller.isFinished();  
  149.             break;  
  150.         }  
  151.   
  152.         case MotionEvent.ACTION_CANCEL:  
  153.         case MotionEvent.ACTION_UP:  
  154.             /* Release the drag */  
  155.             mIsBeingDragged = false;  
  156.             mActivePointerId = INVALID_POINTER;  
  157.             break;  
  158.         case MotionEvent.ACTION_POINTER_UP:  
  159.             onSecondaryPointerUp(ev);  
  160.             break;  
  161.     }  
  162.   
  163.     /* 
  164.     * The only time we want to intercept motion events is if we are in the 
  165.     * drag mode. 
  166.     */  
  167.     return mIsBeingDragged;  
  168. }  
  169.   
  170. @Override  
  171. public boolean onTouchEvent(MotionEvent ev) {  
  172.   
  173.     if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {  
  174.         // Don't handle edge touches immediately -- they may actually belong to one of our  
  175.         // descendants.  
  176.         return false;  
  177.     }  
  178.   
  179.     if (mVelocityTracker == null) {  
  180.         mVelocityTracker = VelocityTracker.obtain();  
  181.     }  
  182.     mVelocityTracker.addMovement(ev);  
  183.   
  184.     final int action = ev.getAction();  
  185.   
  186.     switch (action & MotionEvent.ACTION_MASK) {  
  187.         case MotionEvent.ACTION_DOWN: {  
  188.             final float x = ev.getX();  
  189.             final float y = ev.getY();  
  190.             if (!(mIsBeingDragged = inChild((int) x, (int) y))) {  
  191.                 return false;  
  192.             }  
  193.               
  194.             /* 
  195.              * If being flinged and user touches, stop the fling. isFinished 
  196.              * will be false if being flinged. 
  197.              */  
  198.             if (!mScroller.isFinished()) {  
  199.                 mScroller.abortAnimation();  
  200.             }  
  201.   
  202.             // Remember where the motion event started  
  203.             mLastMotionY = y;  
  204.             mLastMotionX = x;  
  205.             mActivePointerId = ev.getPointerId(0);  
  206.             break;  
  207.         }  
  208.         case MotionEvent.ACTION_MOVE:  
  209.             if (mIsBeingDragged) {  
  210.                 // Scroll to follow the motion event  
  211.                 final int activePointerIndex = ev.findPointerIndex(mActivePointerId);  
  212.                 final float y = ev.getY(activePointerIndex);  
  213.                 final int deltaY = (int) (mLastMotionY - y);  
  214.                 mLastMotionY = y;  
  215.                   
  216.                 final float x = ev.getX(activePointerIndex);  
  217.                 final int deltaX = (int) (mLastMotionX - x);  
  218.                 mLastMotionX = x;  
  219.   
  220.                 scrollBy(deltaX, deltaY);  
  221.             }  
  222.             break;  
  223.         case MotionEvent.ACTION_UP:   
  224.             if (mIsBeingDragged) {  
  225.                 if(mFlingEnabled){  
  226.                     final VelocityTracker velocityTracker = mVelocityTracker;  
  227.                     velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);  
  228.                     int initialVelocitx = (int) velocityTracker.getXVelocity(mActivePointerId);  
  229.                     int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);  
  230.                       
  231.                     if (getChildCount() > 0) {  
  232.                         if(Math.abs(initialVelocitx) > initialVelocitx || Math.abs(initialVelocity) > mMinimumVelocity) {  
  233.                             fling(-initialVelocitx, -initialVelocity);  
  234.                         }  
  235.                           
  236.                     }  
  237.                 }  
  238.   
  239.                 mActivePointerId = INVALID_POINTER;  
  240.                 mIsBeingDragged = false;  
  241.   
  242.                 if (mVelocityTracker != null) {  
  243.                     mVelocityTracker.recycle();  
  244.                     mVelocityTracker = null;  
  245.                 }  
  246.             }  
  247.             break;  
  248.         case MotionEvent.ACTION_CANCEL:  
  249.             if (mIsBeingDragged && getChildCount() > 0) {  
  250.                 mActivePointerId = INVALID_POINTER;  
  251.                 mIsBeingDragged = false;  
  252.                 if (mVelocityTracker != null) {  
  253.                     mVelocityTracker.recycle();  
  254.                     mVelocityTracker = null;  
  255.                 }  
  256.             }  
  257.             break;  
  258.         case MotionEvent.ACTION_POINTER_UP:  
  259.             onSecondaryPointerUp(ev);  
  260.             break;  
  261.     }  
  262.     return true;  
  263. }  
  264.   
  265. private void onSecondaryPointerUp(MotionEvent ev) {  
  266.     final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>  
  267.             MotionEvent.ACTION_POINTER_INDEX_SHIFT;  
  268.     final int pointerId = ev.getPointerId(pointerIndex);  
  269.     if (pointerId == mActivePointerId) {  
  270.         // This was our active pointer going up. Choose a new  
  271.         // active pointer and adjust accordingly.  
  272.         final int newPointerIndex = pointerIndex == 0 ? 1 : 0;  
  273.         mLastMotionX = ev.getX(newPointerIndex);  
  274.         mLastMotionY = ev.getY(newPointerIndex);  
  275.         mActivePointerId = ev.getPointerId(newPointerIndex);  
  276.         if (mVelocityTracker != null) {  
  277.             mVelocityTracker.clear();  
  278.         }  
  279.     }  
  280. }  

[java]  view plain copy
  1. /** 
  2.  * <p> 
  3.  * Finds the next focusable component that fits in the specified bounds. 
  4.  * </p> 
  5.  * 
  6.  * @param topFocus look for a candidate is the one at the top of the bounds 
  7.  *                 if topFocus is true, or at the bottom of the bounds if topFocus is 
  8.  *                 false 
  9.  * @param top      the top offset of the bounds in which a focusable must be 
  10.  *                 found 
  11.  * @param bottom   the bottom offset of the bounds in which a focusable must 
  12.  *                 be found 
  13.  * @return the next focusable component in the bounds or null if none can 
  14.  *         be found 
  15.  */  
  16. private View findFocusableViewInBoundsV(boolean topFocus, int top, int bottom) {  
  17.   
  18.     List<View> focusables = getFocusables(View.FOCUS_FORWARD);  
  19.     View focusCandidate = null;  
  20.   
  21.     /* 
  22.      * A fully contained focusable is one where its top is below the bound's 
  23.      * top, and its bottom is above the bound's bottom. A partially 
  24.      * contained focusable is one where some part of it is within the 
  25.      * bounds, but it also has some part that is not within bounds.  A fully contained 
  26.      * focusable is preferred to a partially contained focusable. 
  27.      */  
  28.     boolean foundFullyContainedFocusable = false;  
  29.   
  30.     int count = focusables.size();  
  31.     for (int i = 0; i < count; i++) {  
  32.         View view = focusables.get(i);  
  33.         int viewTop = view.getTop();  
  34.         int viewBottom = view.getBottom();  
  35.   
  36.         if (top < viewBottom && viewTop < bottom) {  
  37.             /* 
  38.              * the focusable is in the target area, it is a candidate for 
  39.              * focusing 
  40.              */  
  41.   
  42.             final boolean viewIsFullyContained = (top < viewTop) &&  
  43.                     (viewBottom < bottom);  
  44.   
  45.             if (focusCandidate == null) {  
  46.                 /* No candidate, take this one */  
  47.                 focusCandidate = view;  
  48.                 foundFullyContainedFocusable = viewIsFullyContained;  
  49.             } else {  
  50.                 final boolean viewIsCloserToBoundary =  
  51.                         (topFocus && viewTop < focusCandidate.getTop()) ||  
  52.                                 (!topFocus && viewBottom > focusCandidate  
  53.                                         .getBottom());  
  54.   
  55.                 if (foundFullyContainedFocusable) {  
  56.                     if (viewIsFullyContained && viewIsCloserToBoundary) {  
  57.                         /* 
  58.                          * We're dealing with only fully contained views, so 
  59.                          * it has to be closer to the boundary to beat our 
  60.                          * candidate 
  61.                          */  
  62.                         focusCandidate = view;  
  63.                     }  
  64.                 } else {  
  65.                     if (viewIsFullyContained) {  
  66.                         /* Any fully contained view beats a partially contained view */  
  67.                         focusCandidate = view;  
  68.                         foundFullyContainedFocusable = true;  
  69.                     } else if (viewIsCloserToBoundary) {  
  70.                         /* 
  71.                          * Partially contained view beats another partially 
  72.                          * contained view if it's closer 
  73.                          */  
  74.                         focusCandidate = view;  
  75.                     }  
  76.                 }  
  77.             }  
  78.         }  
  79.     }  
  80.   
  81.     return focusCandidate;  
  82. }  
  83.   
  84. private View findFocusableViewInBoundsH(boolean leftFocus, int left, int right) {  
  85.   
  86.     List<View> focusables = getFocusables(View.FOCUS_FORWARD);  
  87.     View focusCandidate = null;  
  88.   
  89.     /* 
  90.      * A fully contained focusable is one where its left is below the bound's 
  91.      * left, and its right is above the bound's right. A partially 
  92.      * contained focusable is one where some part of it is within the 
  93.      * bounds, but it also has some part that is not within bounds.  A fully contained 
  94.      * focusable is preferred to a partially contained focusable. 
  95.      */  
  96.     boolean foundFullyContainedFocusable = false;  
  97.   
  98.     int count = focusables.size();  
  99.     for (int i = 0; i < count; i++) {  
  100.         View view = focusables.get(i);  
  101.         int viewLeft = view.getLeft();  
  102.         int viewRight = view.getRight();  
  103.   
  104.         if (left < viewRight && viewLeft < right) {  
  105.             /* 
  106.              * the focusable is in the target area, it is a candidate for 
  107.              * focusing 
  108.              */  
  109.   
  110.             final boolean viewIsFullyContained = (left < viewLeft) &&  
  111.                     (viewRight < right);  
  112.   
  113.             if (focusCandidate == null) {  
  114.                 /* No candidate, take this one */  
  115.                 focusCandidate = view;  
  116.                 foundFullyContainedFocusable = viewIsFullyContained;  
  117.             } else {  
  118.                 final boolean viewIsCloserToBoundary =  
  119.                         (leftFocus && viewLeft < focusCandidate.getLeft()) ||  
  120.                                 (!leftFocus && viewRight > focusCandidate.getRight());  
  121.   
  122.                 if (foundFullyContainedFocusable) {  
  123.                     if (viewIsFullyContained && viewIsCloserToBoundary) {  
  124.                         /* 
  125.                          * We're dealing with only fully contained views, so 
  126.                          * it has to be closer to the boundary to beat our 
  127.                          * candidate 
  128.                          */  
  129.                         focusCandidate = view;  
  130.                     }  
  131.                 } else {  
  132.                     if (viewIsFullyContained) {  
  133.                         /* Any fully contained view beats a partially contained view */  
  134.                         focusCandidate = view;  
  135.                         foundFullyContainedFocusable = true;  
  136.                     } else if (viewIsCloserToBoundary) {  
  137.                         /* 
  138.                          * Partially contained view beats another partially 
  139.                          * contained view if it's closer 
  140.                          */  
  141.                         focusCandidate = view;  
  142.                     }  
  143.                 }  
  144.             }  
  145.         }  
  146.     }  
  147.   
  148.     return focusCandidate;  
  149. }  
  150.   
  151. /** 
  152.  * <p>Handles scrolling in response to a "home/end" shortcut press. This 
  153.  * method will scroll the view to the top or bottom and give the focus 
  154.  * to the topmost/bottommost component in the new visible area. If no 
  155.  * component is a good candidate for focus, this scrollview reclaims the 
  156.  * focus.</p> 
  157.  * 
  158.  * @param direction the scroll direction: {@link android.view.View#FOCUS_UP} 
  159.  *                  to go the top of the view or 
  160.  *                  {@link android.view.View#FOCUS_DOWN} to go the bottom 
  161.  * @return true if the key event is consumed by this method, false otherwise 
  162.  */  
  163. public boolean fullScrollV(int direction) {  
  164.     boolean down = direction == View.FOCUS_DOWN;  
  165.     int height = getHeight();  
  166.   
  167.     mTempRect.top = 0;  
  168.     mTempRect.bottom = height;  
  169.   
  170.     if (down) {  
  171.         int count = getChildCount();  
  172.         if (count > 0) {  
  173.             View view = getChildAt(count - 1);  
  174.             mTempRect.bottom = view.getBottom();  
  175.             mTempRect.top = mTempRect.bottom - height;  
  176.         }  
  177.     }  
  178.   
  179.     return scrollAndFocusV(direction, mTempRect.top, mTempRect.bottom);  
  180. }  
  181.   
  182. public boolean fullScrollH(int direction) {  
  183.     boolean right = direction == View.FOCUS_RIGHT;  
  184.     int width = getWidth();  
  185.   
  186.     mTempRect.left = 0;  
  187.     mTempRect.right = width;  
  188.   
  189.     if (right) {  
  190.         int count = getChildCount();  
  191.         if (count > 0) {  
  192.             View view = getChildAt(0);  
  193.             mTempRect.right = view.getRight();  
  194.             mTempRect.left = mTempRect.right - width;  
  195.         }  
  196.     }  
  197.   
  198.     return scrollAndFocusH(direction, mTempRect.left, mTempRect.right);  
  199. }  
  200.   
  201. /** 
  202.  * <p>Scrolls the view to make the area defined by <code>top</code> and 
  203.  * <code>bottom</code> visible. This method attempts to give the focus 
  204.  * to a component visible in this area. If no component can be focused in 
  205.  * the new visible area, the focus is reclaimed by this scrollview.</p> 
  206.  * 
  207.  * @param direction the scroll direction: {@link android.view.View#FOCUS_UP} 
  208.  *                  to go upward 
  209.  *                  {@link android.view.View#FOCUS_DOWN} to downward 
  210.  * @param top       the top offset of the new area to be made visible 
  211.  * @param bottom    the bottom offset of the new area to be made visible 
  212.  * @return true if the key event is consumed by this method, false otherwise 
  213.  */  
  214. private boolean scrollAndFocusV(int direction, int top, int bottom) {  
  215.     boolean handled = true;  
  216.   
  217.     int height = getHeight();  
  218.     int containerTop = getScrollY();  
  219.     int containerBottom = containerTop + height;  
  220.     boolean up = direction == View.FOCUS_UP;  
  221.   
  222.     View newFocused = findFocusableViewInBoundsV(up, top, bottom);  
  223.     if (newFocused == null) {  
  224.         newFocused = this;  
  225.     }  
  226.   
  227.     if (top >= containerTop && bottom <= containerBottom) {  
  228.         handled = false;  
  229.     } else {  
  230.         int delta = up ? (top - containerTop) : (bottom - containerBottom);  
  231.         doScrollY(delta);  
  232.     }  
  233.   
  234.     if (newFocused != findFocus() && newFocused.requestFocus(direction)) {  
  235.         mScrollViewMovedFocus = true;  
  236.         mScrollViewMovedFocus = false;  
  237.     }  
  238.   
  239.     return handled;  
  240. }  
  241.   
  242. private boolean scrollAndFocusH(int direction, int left, int right) {  
  243.     boolean handled = true;  
  244.   
  245.     int width = getWidth();  
  246.     int containerLeft = getScrollX();  
  247.     int containerRight = containerLeft + width;  
  248.     boolean goLeft = direction == View.FOCUS_LEFT;  
  249.   
  250.     View newFocused = findFocusableViewInBoundsH(goLeft, left, right);  
  251.     if (newFocused == null) {  
  252.         newFocused = this;  
  253.     }  
  254.   
  255.     if (left >= containerLeft && right <= containerRight) {  
  256.         handled = false;  
  257.     } else {  
  258.         int delta = goLeft ? (left - containerLeft) : (right - containerRight);  
  259.         doScrollX(delta);  
  260.     }  
  261.   
  262.     if (newFocused != findFocus() && newFocused.requestFocus(direction)) {  
  263.         mScrollViewMovedFocus = true;  
  264.         mScrollViewMovedFocus = false;  
  265.     }  
  266.   
  267.     return handled;  
  268. }  
  269.   
  270. /** 
  271.  * Handle scrolling in response to an up or down arrow click. 
  272.  * 
  273.  * @param direction The direction corresponding to the arrow key that was 
  274.  *                  pressed 
  275.  * @return True if we consumed the event, false otherwise 
  276.  */  
  277. public boolean arrowScrollV(int direction) {  
  278.   
  279.     View currentFocused = findFocus();  
  280.     if (currentFocused == this) currentFocused = null;  
  281.   
  282.     View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, direction);  
  283.   
  284.     final int maxJump = getMaxScrollAmountV();  
  285.       
  286.     if (nextFocused != null && isWithinDeltaOfScreenV(nextFocused, maxJump, getHeight())) {  
  287.         nextFocused.getDrawingRect(mTempRect);  
  288.         offsetDescendantRectToMyCoords(nextFocused, mTempRect);  
  289.         int scrollDelta = computeScrollDeltaToGetChildRectOnScreenV(mTempRect);  
  290.         doScrollY(scrollDelta);  
  291.         nextFocused.requestFocus(direction);  
  292.     } else {  
  293.         // no new focus  
  294.         int scrollDelta = maxJump;  
  295.   
  296.         if (direction == View.FOCUS_UP && getScrollY() < scrollDelta) {  
  297.             scrollDelta = getScrollY();  
  298.         } else if (direction == View.FOCUS_DOWN) {  
  299.             if (getChildCount() > 0) {  
  300.                 int daBottom = getChildAt(0).getBottom();  
  301.   
  302.                 int screenBottom = getScrollY() + getHeight();  
  303.   
  304.                 if (daBottom - screenBottom < maxJump) {  
  305.                     scrollDelta = daBottom - screenBottom;  
  306.                 }  
  307.             }  
  308.         }  
  309.         if (scrollDelta == 0) {  
  310.             return false;  
  311.         }  
  312.         doScrollY(direction == View.FOCUS_DOWN ? scrollDelta : -scrollDelta);  
  313.     }  
  314.   
  315.     if (currentFocused != null && currentFocused.isFocused()  
  316.             && isOffScreenV(currentFocused)) {  
  317.         // previously focused item still has focus and is off screen, give  
  318.         // it up (take it back to ourselves)  
  319.         // (also, need to temporarily force FOCUS_BEFORE_DESCENDANTS so we are  
  320.         // sure to  
  321.         // get it)  
  322.         final int descendantFocusability = getDescendantFocusability();  // save  
  323.         setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);  
  324.         requestFocus();  
  325.         setDescendantFocusability(descendantFocusability);  // restore  
  326.     }  
  327.     return true;  
  328. }  
  329.   
  330. public boolean arrowScrollH(int direction) {  
  331.   
  332.     View currentFocused = findFocus();  
  333.     if (currentFocused == this) currentFocused = null;  
  334.   
  335.     View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, direction);  
  336.   
  337.     final int maxJump = getMaxScrollAmountH();  
  338.   
  339.     if (nextFocused != null && isWithinDeltaOfScreenH(nextFocused, maxJump)) {  
  340.         nextFocused.getDrawingRect(mTempRect);  
  341.         offsetDescendantRectToMyCoords(nextFocused, mTempRect);  
  342.         int scrollDelta = computeScrollDeltaToGetChildRectOnScreenH(mTempRect);  
  343.         doScrollX(scrollDelta);  
  344.         nextFocused.requestFocus(direction);  
  345.     } else {  
  346.         // no new focus  
  347.         int scrollDelta = maxJump;  
  348.   
  349.         if (direction == View.FOCUS_LEFT && getScrollX() < scrollDelta) {  
  350.             scrollDelta = getScrollX();  
  351.         } else if (direction == View.FOCUS_RIGHT && getChildCount() > 0) {  
  352.               
  353.             int daRight = getChildAt(0).getRight();  
  354.   
  355.             int screenRight = getScrollX() + getWidth();  
  356.   
  357.             if (daRight - screenRight < maxJump) {  
  358.                 scrollDelta = daRight - screenRight;  
  359.             }  
  360.         }  
  361.         if (scrollDelta == 0) {  
  362.             return false;  
  363.         }  
  364.         doScrollX(direction == View.FOCUS_RIGHT ? scrollDelta : -scrollDelta);  
  365.     }  
  366.   
  367.     if (currentFocused != null && currentFocused.isFocused()  
  368.             && isOffScreenH(currentFocused)) {  
  369.         // previously focused item still has focus and is off screen, give  
  370.         // it up (take it back to ourselves)  
  371.         // (also, need to temporarily force FOCUS_BEFORE_DESCENDANTS so we are  
  372.         // sure to  
  373.         // get it)  
  374.         final int descendantFocusability = getDescendantFocusability();  // save  
  375.         setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);  
  376.         requestFocus();  
  377.         setDescendantFocusability(descendantFocusability);  // restore  
  378.     }  
  379.     return true;  
  380. }  

[java]  view plain copy
  1. /** 
  2.  * @return whether the descendant of this scroll view is scrolled off 
  3.  *  screen. 
  4.  */  
  5. private boolean isOffScreenV(View descendant) {  
  6.     return !isWithinDeltaOfScreenV(descendant, 0, getHeight());  
  7. }  
  8.   
  9. private boolean isOffScreenH(View descendant) {  
  10.     return !isWithinDeltaOfScreenH(descendant, 0);  
  11. }  
  12.   
  13. /** 
  14.  * @return whether the descendant of this scroll view is within delta 
  15.  *  pixels of being on the screen. 
  16.  */  
  17. private boolean isWithinDeltaOfScreenV(View descendant, int delta, int height) {  
  18.     descendant.getDrawingRect(mTempRect);  
  19.     offsetDescendantRectToMyCoords(descendant, mTempRect);  
  20.   
  21.     return (mTempRect.bottom + delta) >= getScrollY()  
  22.             && (mTempRect.top - delta) <= (getScrollY() + height);  
  23. }  
  24.   
  25. private boolean isWithinDeltaOfScreenH(View descendant, int delta) {  
  26.     descendant.getDrawingRect(mTempRect);  
  27.     offsetDescendantRectToMyCoords(descendant, mTempRect);  
  28.   
  29.     return (mTempRect.right + delta) >= getScrollX()  
  30.             && (mTempRect.left - delta) <= (getScrollX() + getWidth());  
  31. }  
  32.   
  33. /** 
  34.  * Smooth scroll by a Y delta 
  35.  * 
  36.  * @param delta the number of pixels to scroll by on the Y axis 
  37.  */  
  38. private void doScrollY(int delta) {  
  39.     if (delta != 0) {  
  40.         if (mSmoothScrollingEnabled) {  
  41.             smoothScrollBy(0, delta);  
  42.         } else {  
  43.             scrollBy(0, delta);  
  44.         }  
  45.     }  
  46. }  
  47.   
  48. private void doScrollX(int delta) {  
  49.     if (delta != 0) {  
  50.         if (mSmoothScrollingEnabled) {  
  51.             smoothScrollBy(delta, 0);  
  52.         } else {  
  53.             scrollBy(delta, 0);  
  54.         }  
  55.     }  
  56. }  
  57.   
  58. /** 
  59.  * Like {@link View#scrollBy}, but scroll smoothly instead of immediately. 
  60.  * 
  61.  * @param dx the number of pixels to scroll by on the X axis 
  62.  * @param dy the number of pixels to scroll by on the Y axis 
  63.  */  
  64. public void smoothScrollBy(int dx, int dy) {  
  65.     if (getChildCount() == 0) {  
  66.         // Nothing to do.  
  67.         return;  
  68.     }  
  69.     long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;  
  70.     if (duration > ANIMATED_SCROLL_GAP) {  
  71.         final int height = getHeight() - getPaddingBottom() - getPaddingTop();  
  72.         final int bottom = getChildAt(0).getHeight();  
  73.         final int maxY = Math.max(0, bottom - height);  
  74.         final int scrollY = getScrollY();  
  75.         dy = Math.max(0, Math.min(scrollY + dy, maxY)) - scrollY;  
  76.           
  77.         final int width = getWidth() - getPaddingRight() - getPaddingLeft();  
  78.         final int right = getChildAt(0).getWidth();  
  79.         final int maxX = Math.max(0, right - width);  
  80.         final int scrollX = getScrollX();  
  81.         dx = Math.max(0, Math.min(scrollX + dx, maxX)) - scrollX;  
  82.           
  83.         mScroller.startScroll(scrollX, scrollY, dx, dy);  
  84.         invalidate();  
  85.     } else {  
  86.         if (!mScroller.isFinished()) {  
  87.             mScroller.abortAnimation();  
  88.         }  
  89.         scrollBy(dx, dy);  
  90.     }  
  91.     mLastScroll = AnimationUtils.currentAnimationTimeMillis();  
  92. }  
  93.   
  94. /** 
  95.  * Like {@link #scrollTo}, but scroll smoothly instead of immediately. 
  96.  * 
  97.  * @param x the position where to scroll on the X axis 
  98.  * @param y the position where to scroll on the Y axis 
  99.  */  
  100. public final void smoothScrollTo(int x, int y) {  
  101.     smoothScrollBy(x - getScrollX(), y - getScrollY());  
  102. }  
  103.   
  104. /** 
  105.  * <p>The scroll range of a scroll view is the overall height of all of its 
  106.  * children.</p> 
  107.  */  
  108. @Override  
  109. protected int computeVerticalScrollRange() {  
  110.     final int count = getChildCount();  
  111.     final int contentHeight = getHeight() - getPaddingBottom() - getPaddingTop();  
  112.     if (count == 0) {  
  113.         return contentHeight;  
  114.     }  
  115.       
  116.     return getChildAt(0).getBottom();  
  117. }  
  118.   
  119. @Override  
  120. protected int computeHorizontalScrollRange() {  
  121.     final int count = getChildCount();  
  122.     final int contentWidth = getWidth() - getPaddingLeft() - getPaddingRight();  
  123.     if (count == 0) {  
  124.         return contentWidth;  
  125.     }  
  126.       
  127.     return getChildAt(0).getRight();  
  128. }  
  129.   
  130. @Override  
  131. protected int computeVerticalScrollOffset() {  
  132.     return Math.max(0super.computeVerticalScrollOffset());  
  133. }  
  134.   
  135. @Override  
  136. protected int computeHorizontalScrollOffset() {  
  137.     return Math.max(0super.computeHorizontalScrollOffset());  
  138. }  
  139.   
  140. @Override  
  141. protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {  
  142.     int childWidthMeasureSpec;  
  143.     int childHeightMeasureSpec;  
  144.   
  145.     childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);  
  146.   
  147.     childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);  
  148.   
  149.     child.measure(childWidthMeasureSpec, childHeightMeasureSpec);  
  150. }  
  151.   
  152. @Override  
  153. protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,  
  154.         int parentHeightMeasureSpec, int heightUsed) {  
  155.     final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();  
  156.   
  157.     final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(  
  158.             lp.leftMargin + lp.rightMargin, MeasureSpec.UNSPECIFIED);  
  159.     final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(  
  160.             lp.topMargin + lp.bottomMargin, MeasureSpec.UNSPECIFIED);  
  161.   
  162.     child.measure(childWidthMeasureSpec, childHeightMeasureSpec);  
  163. }  
  164.   
  165. @Override  
  166. public void computeScroll() {  
  167.     if (mScroller.computeScrollOffset()) {  
  168.         // This is called at drawing time by ViewGroup.  We don't want to  
  169.         // re-show the scrollbars at this point, which scrollTo will do,  
  170.         // so we replicate most of scrollTo here.  
  171.         //  
  172.         //         It's a little odd to call onScrollChanged from inside the drawing.  
  173.         //  
  174.         //         It is, except when you remember that computeScroll() is used to  
  175.         //         animate scrolling. So unless we want to defer the onScrollChanged()  
  176.         //         until the end of the animated scrolling, we don't really have a  
  177.         //         choice here.  
  178.         //  
  179.         //         I agree.  The alternative, which I think would be worse, is to post  
  180.         //         something and tell the subclasses later.  This is bad because there  
  181.         //         will be a window where mScrollX/Y is different from what the app  
  182.         //         thinks it is.  
  183.         //  
  184.         int x = mScroller.getCurrX();  
  185.         int y = mScroller.getCurrY();  
  186.   
  187.         if (getChildCount() > 0) {  
  188.             View child = getChildAt(0);  
  189.             x = clamp(x, getWidth() - getPaddingRight() - getPaddingLeft(), child.getWidth());  
  190.             y = clamp(y, getHeight() - getPaddingBottom() - getPaddingTop(), child.getHeight());  
  191.             super.scrollTo(x, y);  
  192.         }  
  193.         awakenScrollBars();  
  194.   
  195.         // Keep on drawing until the animation has finished.  
  196.         postInvalidate();  
  197.     }  
  198. }  
  199.   
  200. /** 
  201.  * Scrolls the view to the given child. 
  202.  * 
  203.  * @param child the View to scroll to 
  204.  */  
  205. private void scrollToChild(View child) {  
  206.     child.getDrawingRect(mTempRect);  
  207.   
  208.     /* Offset from child's local coordinates to ScrollView coordinates */  
  209.     offsetDescendantRectToMyCoords(child, mTempRect);  
  210.   
  211.     int scrollDeltaV = computeScrollDeltaToGetChildRectOnScreenV(mTempRect);  
  212.     int scrollDeltaH = computeScrollDeltaToGetChildRectOnScreenH(mTempRect);  
  213.   
  214.     if (scrollDeltaH != 0 || scrollDeltaV != 0) {  
  215.         scrollBy(scrollDeltaH, scrollDeltaV);  
  216.     }  
  217. }  
  218.   
  219. /** 
  220.  * If rect is off screen, scroll just enough to get it (or at least the 
  221.  * first screen size chunk of it) on screen. 
  222.  * 
  223.  * @param rect      The rectangle. 
  224.  * @param immediate True to scroll immediately without animation 
  225.  * @return true if scrolling was performed 
  226.  */  
  227. private boolean scrollToChildRect(Rect rect, boolean immediate) {  
  228.     final int deltaV = computeScrollDeltaToGetChildRectOnScreenV(rect);  
  229.     final int deltaH = computeScrollDeltaToGetChildRectOnScreenH(rect);  
  230.     final boolean scroll = deltaH != 0 || deltaV != 0;  
  231.     if (scroll) {  
  232.         if (immediate) {  
  233.             scrollBy(deltaH, deltaV);  
  234.         } else {  
  235.             smoothScrollBy(deltaH, deltaV);  
  236.         }  
  237.     }  
  238.     return scroll;  
  239. }  

[java]  view plain copy
  1.     /** 
  2.      * Compute the amount to scroll in the Y direction in order to get 
  3.      * a rectangle completely on the screen (or, if taller than the screen, 
  4.      * at least the first screen size chunk of it). 
  5.      * 
  6.      * @param rect The rect. 
  7.      * @return The scroll delta. 
  8.      */  
  9.     protected int computeScrollDeltaToGetChildRectOnScreenV(Rect rect) {  
  10.         if (getChildCount() == 0return 0;  
  11.   
  12.         int height = getHeight();  
  13.         int screenTop = getScrollY();  
  14.         int screenBottom = screenTop + height;  
  15.   
  16.         int fadingEdge = getVerticalFadingEdgeLength();  
  17.   
  18.         // leave room for top fading edge as long as rect isn't at very top  
  19.         if (rect.top > 0) {  
  20.             screenTop += fadingEdge;  
  21.         }  
  22.   
  23.         // leave room for bottom fading edge as long as rect isn't at very bottom  
  24.         if (rect.bottom < getChildAt(0).getHeight()) {  
  25.             screenBottom -= fadingEdge;  
  26.         }  
  27.   
  28.         int scrollYDelta = 0;  
  29.   
  30.         if (rect.bottom > screenBottom && rect.top > screenTop) {  
  31.             // need to move down to get it in view: move down just enough so  
  32.             // that the entire rectangle is in view (or at least the first  
  33.             // screen size chunk).  
  34.   
  35.             if (rect.height() > height) {  
  36.                 // just enough to get screen size chunk on  
  37.                 scrollYDelta += (rect.top - screenTop);  
  38.             } else {  
  39.                 // get entire rect at bottom of screen  
  40.                 scrollYDelta += (rect.bottom - screenBottom);  
  41.             }  
  42.   
  43.             // make sure we aren't scrolling beyond the end of our content  
  44.             int bottom = getChildAt(0).getBottom();  
  45.             int distanceToBottom = bottom - screenBottom;  
  46.             scrollYDelta = Math.min(scrollYDelta, distanceToBottom);  
  47.   
  48.         } else if (rect.top < screenTop && rect.bottom < screenBottom) {  
  49.             // need to move up to get it in view: move up just enough so that  
  50.             // entire rectangle is in view (or at least the first screen  
  51.             // size chunk of it).  
  52.   
  53.             if (rect.height() > height) {  
  54.                 // screen size chunk  
  55.                 scrollYDelta -= (screenBottom - rect.bottom);  
  56.             } else {  
  57.                 // entire rect at top  
  58.                 scrollYDelta -= (screenTop - rect.top);  
  59.             }  
  60.   
  61.             // make sure we aren't scrolling any further than the top our content  
  62.             scrollYDelta = Math.max(scrollYDelta, -getScrollY());  
  63.         }  
  64.         return scrollYDelta;  
  65.     }  
  66.       
  67.     protected int computeScrollDeltaToGetChildRectOnScreenH(Rect rect) {  
  68.         if (getChildCount() == 0return 0;  
  69.   
  70.         int width = getWidth();  
  71.         int screenLeft = getScrollX();  
  72.         int screenRight = screenLeft + width;  
  73.   
  74.         int fadingEdge = getHorizontalFadingEdgeLength();  
  75.   
  76.         // leave room for left fading edge as long as rect isn't at very left  
  77.         if (rect.left > 0) {  
  78.             screenLeft += fadingEdge;  
  79.         }  
  80.   
  81.         // leave room for right fading edge as long as rect isn't at very right  
  82.         if (rect.right < getChildAt(0).getWidth()) {  
  83.             screenRight -= fadingEdge;  
  84.         }  
  85.   
  86.         int scrollXDelta = 0;  
  87.   
  88.         if (rect.right > screenRight && rect.left > screenLeft) {  
  89.             // need to move right to get it in view: move right just enough so  
  90.             // that the entire rectangle is in view (or at least the first  
  91.             // screen size chunk).  
  92.   
  93.             if (rect.width() > width) {  
  94.                 // just enough to get screen size chunk on  
  95.                 scrollXDelta += (rect.left - screenLeft);  
  96.             } else {  
  97.                 // get entire rect at right of screen  
  98.                 scrollXDelta += (rect.right - screenRight);  
  99.             }  
  100.   
  101.             // make sure we aren't scrolling beyond the end of our content  
  102.             int right = getChildAt(0).getRight();  
  103.             int distanceToRight = right - screenRight;  
  104.             scrollXDelta = Math.min(scrollXDelta, distanceToRight);  
  105.   
  106.         } else if (rect.left < screenLeft && rect.right < screenRight) {  
  107.             // need to move right to get it in view: move right just enough so that  
  108.             // entire rectangle is in view (or at least the first screen  
  109.             // size chunk of it).  
  110.   
  111.             if (rect.width() > width) {  
  112.                 // screen size chunk  
  113.                 scrollXDelta -= (screenRight - rect.right);  
  114.             } else {  
  115.                 // entire rect at left  
  116.                 scrollXDelta -= (screenLeft - rect.left);  
  117.             }  
  118.   
  119.             // make sure we aren't scrolling any further than the left our content  
  120.             scrollXDelta = Math.max(scrollXDelta, -getScrollX());  
  121.         }  
  122.         return scrollXDelta;  
  123.     }  
  124.   
  125.     @Override  
  126.     public void requestChildFocus(View child, View focused) {  
  127.         if (!mScrollViewMovedFocus) {  
  128.             if (!mIsLayoutDirty) {  
  129.                 scrollToChild(focused);  
  130.             } else {  
  131.                 // The child may not be laid out yet, we can't compute the scroll yet  
  132.                 mChildToScrollTo = focused;  
  133.             }  
  134.         }  
  135.         super.requestChildFocus(child, focused);  
  136.     }  
  137.   
  138.   
  139.     /** 
  140.      * When looking for focus in children of a scroll view, need to be a little 
  141.      * more careful not to give focus to something that is scrolled off screen. 
  142.      * 
  143.      * This is more expensive than the default {@link android.view.ViewGroup} 
  144.      * implementation, otherwise this behavior might have been made the default. 
  145.      */  
  146.     @Override  
  147.     protected boolean onRequestFocusInDescendants(int direction,  
  148.             Rect previouslyFocusedRect) {  
  149.   
  150.         // convert from forward / backward notation to up / down / left / right  
  151.         // (ugh).  
  152.         // TODO: FUCK  
  153. //        if (direction == View.FOCUS_FORWARD) {  
  154. //            direction = View.FOCUS_RIGHT;  
  155. //        } else if (direction == View.FOCUS_BACKWARD) {  
  156. //            direction = View.FOCUS_LEFT;  
  157. //        }  
  158.   
  159.         final View nextFocus = previouslyFocusedRect == null ?  
  160.                 FocusFinder.getInstance().findNextFocus(thisnull, direction) :  
  161.                 FocusFinder.getInstance().findNextFocusFromRect(this,  
  162.                         previouslyFocusedRect, direction);  
  163.   
  164.         if (nextFocus == null) {  
  165.             return false;  
  166.         }  
  167.   
  168. //        if (isOffScreenH(nextFocus)) {  
  169. //            return false;  
  170. //        }  
  171.   
  172.         return nextFocus.requestFocus(direction, previouslyFocusedRect);  
  173.     }    
  174.   
  175.     @Override  
  176.     public boolean requestChildRectangleOnScreen(View child, Rect rectangle,  
  177.             boolean immediate) {  
  178.         // offset into coordinate space of this scroll view  
  179.         rectangle.offset(child.getLeft() - child.getScrollX(),  
  180.                 child.getTop() - child.getScrollY());  
  181.   
  182.         return scrollToChildRect(rectangle, immediate);  
  183.     }  
  184.   
  185.     @Override  
  186.     public void requestLayout() {  
  187.         mIsLayoutDirty = true;  
  188.         super.requestLayout();  
  189.     }  
  190.   
  191.     @Override  
  192.     protected void onLayout(boolean changed, int l, int t, int r, int b) {  
  193.         super.onLayout(changed, l, t, r, b);  
  194.         mIsLayoutDirty = false;  
  195.         // Give a child focus if it needs it   
  196.         if (mChildToScrollTo != null && isViewDescendantOf(mChildToScrollTo, this)) {  
  197.                 scrollToChild(mChildToScrollTo);  
  198.         }  
  199.         mChildToScrollTo = null;  
  200.   
  201.         // Calling this with the present values causes it to re-clam them  
  202.         scrollTo(getScrollX(), getScrollY());  
  203.     }  
  204.   
  205.     @Override  
  206.     protected void onSizeChanged(int w, int h, int oldw, int oldh) {  
  207.         super.onSizeChanged(w, h, oldw, oldh);  
  208.   
  209.         View currentFocused = findFocus();  
  210.         if (null == currentFocused || this == currentFocused)  
  211.             return;  
  212.   
  213.         // If the currently-focused view was visible on the screen when the  
  214.         // screen was at the old height, then scroll the screen to make that  
  215.         // view visible with the new screen height.  
  216.         if (isWithinDeltaOfScreenV(currentFocused, 0, oldh)) {  
  217.             currentFocused.getDrawingRect(mTempRect);  
  218.             offsetDescendantRectToMyCoords(currentFocused, mTempRect);  
  219.             int scrollDelta = computeScrollDeltaToGetChildRectOnScreenV(mTempRect);  
  220.             doScrollY(scrollDelta);  
  221.         }  
  222.           
  223.         final int maxJump = getRight() - getLeft();  
  224.         if (isWithinDeltaOfScreenH(currentFocused, maxJump)) {  
  225.             currentFocused.getDrawingRect(mTempRect);  
  226.             offsetDescendantRectToMyCoords(currentFocused, mTempRect);  
  227.             int scrollDelta = computeScrollDeltaToGetChildRectOnScreenH(mTempRect);  
  228.             doScrollX(scrollDelta);  
  229.         }  
  230.     }      
  231.   
  232.     /** 
  233.      * Return true if child is an descendant of parent, (or equal to the parent). 
  234.      */  
  235.     private boolean isViewDescendantOf(View child, View parent) {  
  236.         if (child == parent) {  
  237.             return true;  
  238.         }  
  239.   
  240.         final ViewParent theParent = child.getParent();  
  241.         return (theParent instanceof ViewGroup) && isViewDescendantOf((View) theParent, parent);  
  242.     }      
  243.   
  244.     /** 
  245.      * Fling the scroll view 
  246.      * 
  247.      * @param velocityY The initial velocity in the Y direction. Positive 
  248.      *                  numbers mean that the finger/cursor is moving down the screen, 
  249.      *                  which means we want to scroll towards the top. 
  250.      */  
  251.     public void fling(int velocityX, int velocityY) {  
  252.         if (getChildCount() > 0) {  
  253.             int width = getWidth() - getPaddingRight() - getPaddingLeft();  
  254.             int right = getChildAt(0).getWidth();  
  255.               
  256.             int height = getHeight() - getPaddingBottom() - getPaddingTop();  
  257.             int bottom = getChildAt(0).getHeight();  
  258.       
  259.             mScroller.fling(getScrollX(), getScrollY(), velocityX, velocityY,   
  260.                     0, Math.max(0, right - width),   
  261.                     0, Math.max(0, bottom - height));  
  262.       
  263. //            final boolean movingDown = velocityX > 0 || velocityY > 0;  
  264. //      
  265. //            View newFocused =  
  266. //                    findFocusableViewInMyBoundsV(movingDown, mScroller.getFinalY(), findFocus());  
  267. //            if (newFocused == null) {  
  268. //                newFocused = this;  
  269. //            }  
  270. //      
  271. //            if (newFocused != findFocus()  
  272. //                    && newFocused.requestFocus(movingDown ? View.FOCUS_DOWN : View.FOCUS_UP)) {  
  273. //                mScrollViewMovedFocus = true;  
  274. //                mScrollViewMovedFocus = false;  
  275. //            }  
  276.       
  277.             invalidate();  
  278.         }  
  279.     }  
  280.   
  281.     /** 
  282.      * {@inheritDoc} 
  283.      * 
  284.      * <p>This version also clamps the scrolling to the bounds of our child. 
  285.      */  
  286.     @Override  
  287.     public void scrollTo(int x, int y) {  
  288.         // we rely on the fact the View.scrollBy calls scrollTo.  
  289.         if (getChildCount() > 0) {  
  290.             View child = getChildAt(0);  
  291.             x = clamp(x, getWidth() - getPaddingRight() - getPaddingLeft(), child.getWidth());  
  292.             y = clamp(y, getHeight() - getPaddingBottom() - getPaddingTop(), child.getHeight());  
  293.             if (x != getScrollX() || y != getScrollY()) {  
  294.                 super.scrollTo(x, y);  
  295.             }  
  296.         }  
  297.     }  
  298.   
  299.     private int clamp(int n, int my, int child) {  
  300.         if (my >= child || n < 0) {  
  301.             /* my >= child is this case: 
  302.              *                    |--------------- me ---------------| 
  303.              *     |------ child ------| 
  304.              * or 
  305.              *     |--------------- me ---------------| 
  306.              *            |------ child ------| 
  307.              * or 
  308.              *     |--------------- me ---------------| 
  309.              *                                  |------ child ------| 
  310.              * 
  311.              * n < 0 is this case: 
  312.              *     |------ me ------| 
  313.              *                    |-------- child --------| 
  314.              *     |-- mScrollX --| 
  315.              */  
  316.             return 0;  
  317.         }  
  318.         if ((my+n) > child) {  
  319.             /* this case: 
  320.              *                    |------ me ------| 
  321.              *     |------ child ------| 
  322.              *     |-- mScrollX --| 
  323.              */  
  324.             return child-my;  
  325.         }  
  326.         return n;  
  327.     }  
  328.   
  329.     public boolean isFlingEnabled() {  
  330.         return mFlingEnabled;  
  331.     }  
  332.   
  333.     public void setFlingEnabled(boolean flingEnabled) {  
  334.         this.mFlingEnabled = flingEnabled;  
  335.     }  
  336. }  


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值