Android中实现上下左右都可滑动的ScrollView(带有反弹效果)

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值