zoukankan      html  css  js  c++  java
  • CustomScrollVeiw(双向滑动)

    /*
     * Integration of ScrollView and HorizontalScrollView
     * 
     * For some unknown reason, the H/V scroll bar are missing.
     */
    
    package com.k1.graphcode.ui.views;
    
    import java.util.List;
    
    import android.content.Context;
    import android.graphics.Rect;
    import android.util.AttributeSet;
    import android.view.FocusFinder;
    import android.view.KeyEvent;
    import android.view.MotionEvent;
    import android.view.VelocityTracker;
    import android.view.View;
    import android.view.ViewConfiguration;
    import android.view.ViewGroup;
    import android.view.ViewParent;
    import android.view.animation.AnimationUtils;
    import android.widget.FrameLayout;
    import android.widget.Scroller;
    
    /**
     * Reference to ScrollView and HorizontalScrollView
     */
    public class CustomScrollView extends FrameLayout {
     static final int ANIMATED_SCROLL_GAP = 250;
    
     static final float MAX_SCROLL_FACTOR = 0.5f;
    
     private long mLastScroll;
    
     private final Rect mTempRect = new Rect();
     private Scroller mScroller;
    
     /**
      * Flag to indicate that we are moving focus ourselves. This is so the code
      * that watches for focus changes initiated outside this ScrollView knows
      * that it does not have to do anything.
      */
     private boolean mScrollViewMovedFocus;
    
     /**
      * Position of the last motion event.
      */
     private float mLastMotionY;
     private float mLastMotionX;
    
     /**
      * True when the layout has changed but the traversal has not come through
      * yet. Ideally the view hierarchy would keep track of this for us.
      */
     private boolean mIsLayoutDirty = true;
    
     /**
      * The child to give focus to in the event that a child has requested focus
      * while the layout is dirty. This prevents the scroll from being wrong if
      * the child has not been laid out before requesting focus.
      */
     private View mChildToScrollTo = null;
    
     /**
      * True if the user is currently dragging this ScrollView around. This is
      * not the same as 'is being flinged', which can be checked by
      * mScroller.isFinished() (flinging begins when the user lifts his finger).
      */
     private boolean mIsBeingDragged = false;
    
     /**
      * Determines speed during touch scrolling
      */
     private VelocityTracker mVelocityTracker;
    
     /**
      * When set to true, the scroll view measure its child to make it fill the
      * currently visible area.
      */
     private boolean mFillViewport;
    
     /**
      * Whether arrow scrolling is animated.
      */
     private boolean mSmoothScrollingEnabled = true;
    
     private int mTouchSlop;
     private int mMinimumVelocity;
     private int mMaximumVelocity;
    
     /**
      * ID of the active pointer. This is used to retain consistency during
      * drags/flings if multiple pointers are used.
      */
     private int mActivePointerId = INVALID_POINTER;
    
     /**
      * Sentinel value for no current active pointer. Used by
      * {@link #mActivePointerId}.
      */
     private static final int INVALID_POINTER = -1;
    
     private boolean mFlingEnabled = true;
    
     public CustomScrollView(Context context) {
      this(context, null);
     }
    
     public CustomScrollView(Context context, AttributeSet attrs) {
      super(context, attrs);
      initScrollView();
     }
    
     @Override
     protected float getTopFadingEdgeStrength() {
      if (getChildCount() == 0) {
       return 0.0f;
      }
    
      final int length = getVerticalFadingEdgeLength();
      if (getScrollY() < length) {
       return getScrollY() / (float) length;
      }
    
      return 1.0f;
     }
    
     @Override
     protected float getLeftFadingEdgeStrength() {
      if (getChildCount() == 0) {
       return 0.0f;
      }
    
      final int length = getHorizontalFadingEdgeLength();
      if (getScrollX() < length) {
       return getScrollX() / (float) length;
      }
    
      return 1.0f;
     }
    
     @Override
     protected float getRightFadingEdgeStrength() {
      if (getChildCount() == 0) {
       return 0.0f;
      }
    
      final int length = getHorizontalFadingEdgeLength();
      final int rightEdge = getWidth() - getPaddingRight();
      final int span = getChildAt(0).getRight() - getScrollX() - rightEdge;
      if (span < length) {
       return span / (float) length;
      }
    
      return 1.0f;
     }
    
     @Override
     protected float getBottomFadingEdgeStrength() {
      if (getChildCount() == 0) {
       return 0.0f;
      }
    
      final int length = getVerticalFadingEdgeLength();
      final int bottomEdge = getHeight() - getPaddingBottom();
      final int span = getChildAt(0).getBottom() - getScrollY() - bottomEdge;
      if (span < length) {
       return span / (float) length;
      }
    
      return 1.0f;
     }
    
     /**
      * @return The maximum amount this scroll view will scroll in response to an
      *         arrow event.
      */
     public int getMaxScrollAmountV() {
      return (int) (MAX_SCROLL_FACTOR * (getBottom() - getTop()));
     }
    
     public int getMaxScrollAmountH() {
      return (int) (MAX_SCROLL_FACTOR * (getRight() - getLeft()));
     }
    
     private void initScrollView() {
      mScroller = new Scroller(getContext());
      setFocusable(true);
      setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
      setWillNotDraw(false);
      final ViewConfiguration configuration = ViewConfiguration
        .get(getContext());
      mTouchSlop = configuration.getScaledTouchSlop();
      mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
      mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
     }
    
     @Override
     public void addView(View child) {
      if (getChildCount() > 0) {
       throw new IllegalStateException(
         "ScrollView can host only one direct child");
      }
    
      super.addView(child);
     }
    
     @Override
     public void addView(View child, int index) {
      if (getChildCount() > 0) {
       throw new IllegalStateException(
         "ScrollView can host only one direct child");
      }
    
      super.addView(child, index);
     }
    
     @Override
     public void addView(View child, ViewGroup.LayoutParams params) {
      if (getChildCount() > 0) {
       throw new IllegalStateException(
         "ScrollView can host only one direct child");
      }
    
      super.addView(child, params);
     }
    
     @Override
     public void addView(View child, int index, ViewGroup.LayoutParams params) {
      if (getChildCount() > 0) {
       throw new IllegalStateException(
         "ScrollView can host only one direct child");
      }
    
      super.addView(child, index, params);
     }
    
     /**
      * @return Returns true this ScrollView can be scrolled
      */
     private boolean canScrollV() {
      View child = getChildAt(0);
      if (child != null) {
       int childHeight = child.getHeight();
       return getHeight() < childHeight + getPaddingTop()
         + getPaddingBottom();
      }
      return false;
     }
    
     private boolean canScrollH() {
      View child = getChildAt(0);
      if (child != null) {
       int childWidth = child.getWidth();
       return getWidth() < childWidth + getPaddingLeft()
         + getPaddingRight();
      }
      return false;
     }
    
     /**
      * Indicates whether this ScrollView's content is stretched to fill the
      * viewport.
      * 
      * @return True if the content fills the viewport, false otherwise.
      */
     public boolean isFillViewport() {
      return mFillViewport;
     }
    
     /**
      * Indicates this ScrollView whether it should stretch its content height to
      * fill the viewport or not.
      * 
      * @param fillViewport
      *            True to stretch the content's height to the viewport's
      *            boundaries, false otherwise.
      */
     public void setFillViewport(boolean fillViewport) {
      if (fillViewport != mFillViewport) {
       mFillViewport = fillViewport;
       requestLayout();
      }
     }
    
     /**
      * @return Whether arrow scrolling will animate its transition.
      */
     public boolean isSmoothScrollingEnabled() {
      return mSmoothScrollingEnabled;
     }
    
     /**
      * Set whether arrow scrolling will animate its transition.
      * 
      * @param smoothScrollingEnabled
      *            whether arrow scrolling will animate its transition
      */
     public void setSmoothScrollingEnabled(boolean smoothScrollingEnabled) {
      mSmoothScrollingEnabled = smoothScrollingEnabled;
     }
    
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
      super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    
      if (!mFillViewport) {
       return;
      }
    
      final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
      final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
      if (heightMode == MeasureSpec.UNSPECIFIED
        && widthMode == MeasureSpec.UNSPECIFIED) {
       return;
      }
    
      if (getChildCount() > 0) {
       final View child = getChildAt(0);
       int height = getMeasuredHeight();
       int width = getMeasuredWidth();
       if (child.getMeasuredHeight() < height
         || child.getMeasuredWidth() < width) {
        width -= getPaddingLeft();
        width -= getPaddingRight();
        int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
          MeasureSpec.EXACTLY);
    
        height -= getPaddingTop();
        height -= getPaddingBottom();
        int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
          height, MeasureSpec.EXACTLY);
    
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
       }
      }
     }
    
     @Override
     public boolean dispatchKeyEvent(KeyEvent event) {
      // Let the focused view and/or our descendants get the key first
      return super.dispatchKeyEvent(event) || executeKeyEvent(event);
     }
    
     /**
      * You can call this function yourself to have the scroll view perform
      * scrolling from a key event, just as if the event had been dispatched to
      * it by the view hierarchy.
      * 
      * @param event
      *            The key event to execute.
      * @return Return true if the event was handled, else false.
      */
     public boolean executeKeyEvent(KeyEvent event) {
      mTempRect.setEmpty();
    
      boolean handled = false;
    
      if (event.getAction() == KeyEvent.ACTION_DOWN) {
       switch (event.getKeyCode()) {
       case KeyEvent.KEYCODE_DPAD_LEFT:
        if (canScrollH()) {
         if (!event.isAltPressed()) {
          handled = arrowScrollH(View.FOCUS_LEFT);
         } else {
          handled = fullScrollH(View.FOCUS_LEFT);
         }
        }
        break;
       case KeyEvent.KEYCODE_DPAD_RIGHT:
        if (canScrollH()) {
         if (!event.isAltPressed()) {
          handled = arrowScrollH(View.FOCUS_RIGHT);
         } else {
          handled = fullScrollH(View.FOCUS_RIGHT);
         }
        }
        break;
       case KeyEvent.KEYCODE_DPAD_UP:
        if (canScrollV()) {
         if (!event.isAltPressed()) {
          handled = arrowScrollV(View.FOCUS_UP);
         } else {
          handled = fullScrollV(View.FOCUS_UP);
         }
        }
        break;
       case KeyEvent.KEYCODE_DPAD_DOWN:
        if (canScrollV()) {
         if (!event.isAltPressed()) {
          handled = arrowScrollV(View.FOCUS_DOWN);
         } else {
          handled = fullScrollV(View.FOCUS_DOWN);
         }
        }
        break;
       }
      }
      return handled;
     }
    
     private boolean inChild(int x, int y) {
      if (getChildCount() > 0) {
       final int scrollX = getScrollX();
       final int scrollY = getScrollY();
       final View child = getChildAt(0);
       return !(y < child.getTop() - scrollY
         || y >= child.getBottom() - scrollY
         || x < child.getLeft() - scrollX || x >= child.getRight()
         - scrollX);
      }
      return false;
     }
    
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
      /*
       * This method JUST determines whether we want to intercept the motion.
       * If we return true, onMotionEvent will be called and we do the actual
       * scrolling there.
       */
    
      /*
       * Shortcut the most recurring case: the user is in the dragging state
       * and he is moving his finger. We want to intercept this motion.
       */
      final int action = ev.getAction();
      if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
       return true;
      }
    
      switch (action & MotionEvent.ACTION_MASK) {
      case MotionEvent.ACTION_MOVE: {
       /*
        * mIsBeingDragged == false, otherwise the shortcut would have
        * caught it. Check whether the user has moved far enough from his
        * original down touch.
        */
    
       /*
        * Locally do absolute value. mLastMotionY is set to the y value of
        * the down event.
        */
       final int activePointerId = mActivePointerId;
       if (activePointerId == INVALID_POINTER) {
        // If we don't have a valid id, the touch down wasn't on
        // content.
        break;
       }
    
       final int pointerIndex = ev.findPointerIndex(activePointerId);
       final float y = ev.getY(pointerIndex);
       final int yDiff = (int) Math.abs(y - mLastMotionY);
       if (yDiff > mTouchSlop) {
        mIsBeingDragged = true;
        mLastMotionY = y;
       }
       final float x = ev.getX(pointerIndex);
       final int xDiff = (int) Math.abs(x - mLastMotionX);
       if (xDiff > mTouchSlop) {
        mIsBeingDragged = true;
        mLastMotionX = x;
       }
       break;
      }
    
      case MotionEvent.ACTION_DOWN: {
       final float x = ev.getX();
       final float y = ev.getY();
       if (!inChild((int) x, (int) y)) {
        mIsBeingDragged = false;
        break;
       }
    
       /*
        * Remember location of down touch. ACTION_DOWN always refers to
        * pointer index 0.
        */
       mLastMotionY = y;
       mLastMotionX = x;
       mActivePointerId = ev.getPointerId(0);
    
       /*
        * If being flinged and user touches the screen, initiate drag;
        * otherwise don't. mScroller.isFinished should be false when being
        * flinged.
        */
       mIsBeingDragged = !mScroller.isFinished();
       break;
      }
    
      case MotionEvent.ACTION_CANCEL:
      case MotionEvent.ACTION_UP:
       /* Release the drag */
       mIsBeingDragged = false;
       mActivePointerId = INVALID_POINTER;
       break;
      case MotionEvent.ACTION_POINTER_UP:
       onSecondaryPointerUp(ev);
       break;
      }
    
      /*
       * The only time we want to intercept motion events is if we are in the
       * drag mode.
       */
      return mIsBeingDragged;
     }
    
     @Override
     public boolean onTouchEvent(MotionEvent ev) {
    
      if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
       // Don't handle edge touches immediately -- they may actually belong
       // to one of our
       // descendants.
       return false;
      }
    
      if (mVelocityTracker == null) {
       mVelocityTracker = VelocityTracker.obtain();
      }
      mVelocityTracker.addMovement(ev);
    
      final int action = ev.getAction();
    
      switch (action & MotionEvent.ACTION_MASK) {
      case MotionEvent.ACTION_DOWN: {
       final float x = ev.getX();
       final float y = ev.getY();
       if (!(mIsBeingDragged = inChild((int) x, (int) y))) {
        return false;
       }
    
       /*
        * If being flinged and user touches, stop the fling. isFinished
        * will be false if being flinged.
        */
       if (!mScroller.isFinished()) {
        mScroller.abortAnimation();
       }
    
       // Remember where the motion event started
       mLastMotionY = y;
       mLastMotionX = x;
       mActivePointerId = ev.getPointerId(0);
       break;
      }
      case MotionEvent.ACTION_MOVE:
       if (mIsBeingDragged) {
        // Scroll to follow the motion event
        final int activePointerIndex = ev
          .findPointerIndex(mActivePointerId);
        final float y = ev.getY(activePointerIndex);
        final int deltaY = (int) (mLastMotionY - y);
        mLastMotionY = y;
    
        final float x = ev.getX(activePointerIndex);
        final int deltaX = (int) (mLastMotionX - x);
        mLastMotionX = x;
    
        scrollBy(deltaX, deltaY);
       }
       break;
      case MotionEvent.ACTION_UP:
       if (mIsBeingDragged) {
        if (mFlingEnabled) {
         final VelocityTracker velocityTracker = mVelocityTracker;
         velocityTracker.computeCurrentVelocity(1000,
           mMaximumVelocity);
         int initialVelocitx = (int) velocityTracker
           .getXVelocity(mActivePointerId);
         int initialVelocity = (int) velocityTracker
           .getYVelocity(mActivePointerId);
    
         if (getChildCount() > 0) {
          if (Math.abs(initialVelocitx) > initialVelocitx
            || Math.abs(initialVelocity) > mMinimumVelocity) {
           fling(-initialVelocitx, -initialVelocity);
          }
    
         }
        }
    
        mActivePointerId = INVALID_POINTER;
        mIsBeingDragged = false;
    
        if (mVelocityTracker != null) {
         mVelocityTracker.recycle();
         mVelocityTracker = null;
        }
       }
       break;
      case MotionEvent.ACTION_CANCEL:
       if (mIsBeingDragged && getChildCount() > 0) {
        mActivePointerId = INVALID_POINTER;
        mIsBeingDragged = false;
        if (mVelocityTracker != null) {
         mVelocityTracker.recycle();
         mVelocityTracker = null;
        }
       }
       break;
      case MotionEvent.ACTION_POINTER_UP:
       onSecondaryPointerUp(ev);
       break;
      }
      return true;
     }
    
     private void onSecondaryPointerUp(MotionEvent ev) {
      final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
      final int pointerId = ev.getPointerId(pointerIndex);
      if (pointerId == mActivePointerId) {
       // This was our active pointer going up. Choose a new
       // active pointer and adjust accordingly.
       final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
       mLastMotionX = ev.getX(newPointerIndex);
       mLastMotionY = ev.getY(newPointerIndex);
       mActivePointerId = ev.getPointerId(newPointerIndex);
       if (mVelocityTracker != null) {
        mVelocityTracker.clear();
       }
      }
     }
    
     /**
      * <p>
      * Finds the next focusable component that fits in the specified bounds.
      * </p>
      * 
      * @param topFocus
      *            look for a candidate is the one at the top of the bounds if
      *            topFocus is true, or at the bottom of the bounds if topFocus
      *            is false
      * @param top
      *            the top offset of the bounds in which a focusable must be
      *            found
      * @param bottom
      *            the bottom offset of the bounds in which a focusable must be
      *            found
      * @return the next focusable component in the bounds or null if none can be
      *         found
      */
     private View findFocusableViewInBoundsV(boolean topFocus, int top,
       int bottom) {
    
      List<View> focusables = getFocusables(View.FOCUS_FORWARD);
      View focusCandidate = null;
    
      /*
       * A fully contained focusable is one where its top is below the bound's
       * top, and its bottom is above the bound's bottom. A partially
       * contained focusable is one where some part of it is within the
       * bounds, but it also has some part that is not within bounds. A fully
       * contained focusable is preferred to a partially contained focusable.
       */
      boolean foundFullyContainedFocusable = false;
    
      int count = focusables.size();
      for (int i = 0; i < count; i++) {
       View view = focusables.get(i);
       int viewTop = view.getTop();
       int viewBottom = view.getBottom();
    
       if (top < viewBottom && viewTop < bottom) {
        /*
         * the focusable is in the target area, it is a candidate for
         * focusing
         */
    
        final boolean viewIsFullyContained = (top < viewTop)
          && (viewBottom < bottom);
    
        if (focusCandidate == null) {
         /* No candidate, take this one */
         focusCandidate = view;
         foundFullyContainedFocusable = viewIsFullyContained;
        } else {
         final boolean viewIsCloserToBoundary = (topFocus && viewTop < focusCandidate
           .getTop())
           || (!topFocus && viewBottom > focusCandidate
             .getBottom());
    
         if (foundFullyContainedFocusable) {
          if (viewIsFullyContained && viewIsCloserToBoundary) {
           /*
            * We're dealing with only fully contained views, so
            * it has to be closer to the boundary to beat our
            * candidate
            */
           focusCandidate = view;
          }
         } else {
          if (viewIsFullyContained) {
           /*
            * Any fully contained view beats a partially
            * contained view
            */
           focusCandidate = view;
           foundFullyContainedFocusable = true;
          } else if (viewIsCloserToBoundary) {
           /*
            * Partially contained view beats another partially
            * contained view if it's closer
            */
           focusCandidate = view;
          }
         }
        }
       }
      }
    
      return focusCandidate;
     }
    
     private View findFocusableViewInBoundsH(boolean leftFocus, int left,
       int right) {
    
      List<View> focusables = getFocusables(View.FOCUS_FORWARD);
      View focusCandidate = null;
    
      /*
       * A fully contained focusable is one where its left is below the
       * bound's left, and its right is above the bound's right. A partially
       * contained focusable is one where some part of it is within the
       * bounds, but it also has some part that is not within bounds. A fully
       * contained focusable is preferred to a partially contained focusable.
       */
      boolean foundFullyContainedFocusable = false;
    
      int count = focusables.size();
      for (int i = 0; i < count; i++) {
       View view = focusables.get(i);
       int viewLeft = view.getLeft();
       int viewRight = view.getRight();
    
       if (left < viewRight && viewLeft < right) {
        /*
         * the focusable is in the target area, it is a candidate for
         * focusing
         */
    
        final boolean viewIsFullyContained = (left < viewLeft)
          && (viewRight < right);
    
        if (focusCandidate == null) {
         /* No candidate, take this one */
         focusCandidate = view;
         foundFullyContainedFocusable = viewIsFullyContained;
        } else {
         final boolean viewIsCloserToBoundary = (leftFocus && viewLeft < focusCandidate
           .getLeft())
           || (!leftFocus && viewRight > focusCandidate
             .getRight());
    
         if (foundFullyContainedFocusable) {
          if (viewIsFullyContained && viewIsCloserToBoundary) {
           /*
            * We're dealing with only fully contained views, so
            * it has to be closer to the boundary to beat our
            * candidate
            */
           focusCandidate = view;
          }
         } else {
          if (viewIsFullyContained) {
           /*
            * Any fully contained view beats a partially
            * contained view
            */
           focusCandidate = view;
           foundFullyContainedFocusable = true;
          } else if (viewIsCloserToBoundary) {
           /*
            * Partially contained view beats another partially
            * contained view if it's closer
            */
           focusCandidate = view;
          }
         }
        }
       }
      }
    
      return focusCandidate;
     }
    
     /**
      * <p>
      * Handles scrolling in response to a "home/end" shortcut press. This method
      * will scroll the view to the top or bottom and give the focus to the
      * topmost/bottommost component in the new visible area. If no component is
      * a good candidate for focus, this scrollview reclaims the focus.
      * </p>
      * 
      * @param direction
      *            the scroll direction: {@link android.view.View#FOCUS_UP} to go
      *            the top of the view or {@link android.view.View#FOCUS_DOWN} to
      *            go the bottom
      * @return true if the key event is consumed by this method, false otherwise
      */
     public boolean fullScrollV(int direction) {
      boolean down = direction == View.FOCUS_DOWN;
      int height = getHeight();
    
      mTempRect.top = 0;
      mTempRect.bottom = height;
    
      if (down) {
       int count = getChildCount();
       if (count > 0) {
        View view = getChildAt(count - 1);
        mTempRect.bottom = view.getBottom();
        mTempRect.top = mTempRect.bottom - height;
       }
      }
    
      return scrollAndFocusV(direction, mTempRect.top, mTempRect.bottom);
     }
    
     public boolean fullScrollH(int direction) {
      boolean right = direction == View.FOCUS_RIGHT;
      int width = getWidth();
    
      mTempRect.left = 0;
      mTempRect.right = width;
    
      if (right) {
       int count = getChildCount();
       if (count > 0) {
        View view = getChildAt(0);
        mTempRect.right = view.getRight();
        mTempRect.left = mTempRect.right - width;
       }
      }
    
      return scrollAndFocusH(direction, mTempRect.left, mTempRect.right);
     }
    
     /**
      * <p>
      * Scrolls the view to make the area defined by <code>top</code> and
      * <code>bottom</code> visible. This method attempts to give the focus to a
      * component visible in this area. If no component can be focused in the new
      * visible area, the focus is reclaimed by this scrollview.
      * </p>
      * 
      * @param direction
      *            the scroll direction: {@link android.view.View#FOCUS_UP} to go
      *            upward {@link android.view.View#FOCUS_DOWN} to downward
      * @param top
      *            the top offset of the new area to be made visible
      * @param bottom
      *            the bottom offset of the new area to be made visible
      * @return true if the key event is consumed by this method, false otherwise
      */
     private boolean scrollAndFocusV(int direction, int top, int bottom) {
      boolean handled = true;
    
      int height = getHeight();
      int containerTop = getScrollY();
      int containerBottom = containerTop + height;
      boolean up = direction == View.FOCUS_UP;
    
      View newFocused = findFocusableViewInBoundsV(up, top, bottom);
      if (newFocused == null) {
       newFocused = this;
      }
    
      if (top >= containerTop && bottom <= containerBottom) {
       handled = false;
      } else {
       int delta = up ? (top - containerTop) : (bottom - containerBottom);
       doScrollY(delta);
      }
    
      if (newFocused != findFocus() && newFocused.requestFocus(direction)) {
       mScrollViewMovedFocus = true;
       mScrollViewMovedFocus = false;
      }
    
      return handled;
     }
    
     private boolean scrollAndFocusH(int direction, int left, int right) {
      boolean handled = true;
    
      int width = getWidth();
      int containerLeft = getScrollX();
      int containerRight = containerLeft + width;
      boolean goLeft = direction == View.FOCUS_LEFT;
    
      View newFocused = findFocusableViewInBoundsH(goLeft, left, right);
      if (newFocused == null) {
       newFocused = this;
      }
    
      if (left >= containerLeft && right <= containerRight) {
       handled = false;
      } else {
       int delta = goLeft ? (left - containerLeft)
         : (right - containerRight);
       doScrollX(delta);
      }
    
      if (newFocused != findFocus() && newFocused.requestFocus(direction)) {
       mScrollViewMovedFocus = true;
       mScrollViewMovedFocus = false;
      }
    
      return handled;
     }
    
     /**
      * Handle scrolling in response to an up or down arrow click.
      * 
      * @param direction
      *            The direction corresponding to the arrow key that was pressed
      * @return True if we consumed the event, false otherwise
      */
     public boolean arrowScrollV(int direction) {
    
      View currentFocused = findFocus();
      if (currentFocused == this)
       currentFocused = null;
    
      View nextFocused = FocusFinder.getInstance().findNextFocus(this,
        currentFocused, direction);
    
      final int maxJump = getMaxScrollAmountV();
    
      if (nextFocused != null
        && isWithinDeltaOfScreenV(nextFocused, maxJump, getHeight())) {
       nextFocused.getDrawingRect(mTempRect);
       offsetDescendantRectToMyCoords(nextFocused, mTempRect);
       int scrollDelta = computeScrollDeltaToGetChildRectOnScreenV(mTempRect);
       doScrollY(scrollDelta);
       nextFocused.requestFocus(direction);
      } else {
       // no new focus
       int scrollDelta = maxJump;
    
       if (direction == View.FOCUS_UP && getScrollY() < scrollDelta) {
        scrollDelta = getScrollY();
       } else if (direction == View.FOCUS_DOWN) {
        if (getChildCount() > 0) {
         int daBottom = getChildAt(0).getBottom();
    
         int screenBottom = getScrollY() + getHeight();
    
         if (daBottom - screenBottom < maxJump) {
          scrollDelta = daBottom - screenBottom;
         }
        }
       }
       if (scrollDelta == 0) {
        return false;
       }
       doScrollY(direction == View.FOCUS_DOWN ? scrollDelta : -scrollDelta);
      }
    
      if (currentFocused != null && currentFocused.isFocused()
        && isOffScreenV(currentFocused)) {
       // previously focused item still has focus and is off screen, give
       // it up (take it back to ourselves)
       // (also, need to temporarily force FOCUS_BEFORE_DESCENDANTS so we
       // are
       // sure to
       // get it)
       final int descendantFocusability = getDescendantFocusability(); // save
       setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
       requestFocus();
       setDescendantFocusability(descendantFocusability); // restore
      }
      return true;
     }
    
     public boolean arrowScrollH(int direction) {
    
      View currentFocused = findFocus();
      if (currentFocused == this)
       currentFocused = null;
    
      View nextFocused = FocusFinder.getInstance().findNextFocus(this,
        currentFocused, direction);
    
      final int maxJump = getMaxScrollAmountH();
    
      if (nextFocused != null && isWithinDeltaOfScreenH(nextFocused, maxJump)) {
       nextFocused.getDrawingRect(mTempRect);
       offsetDescendantRectToMyCoords(nextFocused, mTempRect);
       int scrollDelta = computeScrollDeltaToGetChildRectOnScreenH(mTempRect);
       doScrollX(scrollDelta);
       nextFocused.requestFocus(direction);
      } else {
       // no new focus
       int scrollDelta = maxJump;
    
       if (direction == View.FOCUS_LEFT && getScrollX() < scrollDelta) {
        scrollDelta = getScrollX();
       } else if (direction == View.FOCUS_RIGHT && getChildCount() > 0) {
    
        int daRight = getChildAt(0).getRight();
    
        int screenRight = getScrollX() + getWidth();
    
        if (daRight - screenRight < maxJump) {
         scrollDelta = daRight - screenRight;
        }
       }
       if (scrollDelta == 0) {
        return false;
       }
       doScrollX(direction == View.FOCUS_RIGHT ? scrollDelta
         : -scrollDelta);
      }
    
      if (currentFocused != null && currentFocused.isFocused()
        && isOffScreenH(currentFocused)) {
       // previously focused item still has focus and is off screen, give
       // it up (take it back to ourselves)
       // (also, need to temporarily force FOCUS_BEFORE_DESCENDANTS so we
       // are
       // sure to
       // get it)
       final int descendantFocusability = getDescendantFocusability(); // save
       setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
       requestFocus();
       setDescendantFocusability(descendantFocusability); // restore
      }
      return true;
     }
    
     /**
      * @return whether the descendant of this scroll view is scrolled off
      *         screen.
      */
     private boolean isOffScreenV(View descendant) {
      return !isWithinDeltaOfScreenV(descendant, 0, getHeight());
     }
    
     private boolean isOffScreenH(View descendant) {
      return !isWithinDeltaOfScreenH(descendant, 0);
     }
    
     /**
      * @return whether the descendant of this scroll view is within delta pixels
      *         of being on the screen.
      */
     private boolean isWithinDeltaOfScreenV(View descendant, int delta,
       int height) {
      descendant.getDrawingRect(mTempRect);
      offsetDescendantRectToMyCoords(descendant, mTempRect);
    
      return (mTempRect.bottom + delta) >= getScrollY()
        && (mTempRect.top - delta) <= (getScrollY() + height);
     }
    
     private boolean isWithinDeltaOfScreenH(View descendant, int delta) {
      descendant.getDrawingRect(mTempRect);
      offsetDescendantRectToMyCoords(descendant, mTempRect);
    
      return (mTempRect.right + delta) >= getScrollX()
        && (mTempRect.left - delta) <= (getScrollX() + getWidth());
     }
    
     /**
      * Smooth scroll by a Y delta
      * 
      * @param delta
      *            the number of pixels to scroll by on the Y axis
      */
     private void doScrollY(int delta) {
      if (delta != 0) {
       if (mSmoothScrollingEnabled) {
        smoothScrollBy(0, delta);
       } else {
        scrollBy(0, delta);
       }
      }
     }
    
     private void doScrollX(int delta) {
      if (delta != 0) {
       if (mSmoothScrollingEnabled) {
        smoothScrollBy(delta, 0);
       } else {
        scrollBy(delta, 0);
       }
      }
     }
    
     /**
      * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
      * 
      * @param dx
      *            the number of pixels to scroll by on the X axis
      * @param dy
      *            the number of pixels to scroll by on the Y axis
      */
     public void smoothScrollBy(int dx, int dy) {
      if (getChildCount() == 0) {
       // Nothing to do.
       return;
      }
      long duration = AnimationUtils.currentAnimationTimeMillis()
        - mLastScroll;
      if (duration > ANIMATED_SCROLL_GAP) {
       final int height = getHeight() - getPaddingBottom()
         - getPaddingTop();
       final int bottom = getChildAt(0).getHeight();
       final int maxY = Math.max(0, bottom - height);
       final int scrollY = getScrollY();
       dy = Math.max(0, Math.min(scrollY + dy, maxY)) - scrollY;
    
       final int width = getWidth() - getPaddingRight() - getPaddingLeft();
       final int right = getChildAt(0).getWidth();
       final int maxX = Math.max(0, right - width);
       final int scrollX = getScrollX();
       dx = Math.max(0, Math.min(scrollX + dx, maxX)) - scrollX;
    
       mScroller.startScroll(scrollX, scrollY, dx, dy);
       invalidate();
      } else {
       if (!mScroller.isFinished()) {
        mScroller.abortAnimation();
       }
       scrollBy(dx, dy);
      }
      mLastScroll = AnimationUtils.currentAnimationTimeMillis();
     }
    
     /**
      * Like {@link #scrollTo}, but scroll smoothly instead of immediately.
      * 
      * @param x
      *            the position where to scroll on the X axis
      * @param y
      *            the position where to scroll on the Y axis
      */
     public final void smoothScrollTo(int x, int y) {
      smoothScrollBy(x - getScrollX(), y - getScrollY());
     }
    
     /**
      * <p>
      * The scroll range of a scroll view is the overall height of all of its
      * children.
      * </p>
      */
     @Override
     protected int computeVerticalScrollRange() {
      final int count = getChildCount();
      final int contentHeight = getHeight() - getPaddingBottom()
        - getPaddingTop();
      if (count == 0) {
       return contentHeight;
      }
    
      return getChildAt(0).getBottom();
     }
    
     @Override
     protected int computeHorizontalScrollRange() {
      final int count = getChildCount();
      final int contentWidth = getWidth() - getPaddingLeft()
        - getPaddingRight();
      if (count == 0) {
       return contentWidth;
      }
    
      return getChildAt(0).getRight();
     }
    
     @Override
     protected int computeVerticalScrollOffset() {
      return Math.max(0, super.computeVerticalScrollOffset());
     }
    
     @Override
     protected int computeHorizontalScrollOffset() {
      return Math.max(0, super.computeHorizontalScrollOffset());
     }
    
     @Override
     protected void measureChild(View child, int parentWidthMeasureSpec,
       int parentHeightMeasureSpec) {
      int childWidthMeasureSpec;
      int childHeightMeasureSpec;
    
      childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(0,
        MeasureSpec.UNSPECIFIED);
    
      childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0,
        MeasureSpec.UNSPECIFIED);
    
      child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
     }
    
     @Override
     protected void measureChildWithMargins(View child,
       int parentWidthMeasureSpec, int widthUsed,
       int parentHeightMeasureSpec, int heightUsed) {
      final MarginLayoutParams lp = (MarginLayoutParams) child
        .getLayoutParams();
    
      final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
        lp.leftMargin + lp.rightMargin, MeasureSpec.UNSPECIFIED);
      final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
        lp.topMargin + lp.bottomMargin, MeasureSpec.UNSPECIFIED);
    
      child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
     }
    
     @Override
     public void computeScroll() {
      if (mScroller.computeScrollOffset()) {
       // This is called at drawing time by ViewGroup. We don't want to
       // re-show the scrollbars at this point, which scrollTo will do,
       // so we replicate most of scrollTo here.
       //
       // It's a little odd to call onScrollChanged from inside the
       // drawing.
       //
       // It is, except when you remember that computeScroll() is used to
       // animate scrolling. So unless we want to defer the
       // onScrollChanged()
       // until the end of the animated scrolling, we don't really have a
       // choice here.
       //
       // I agree. The alternative, which I think would be worse, is to
       // post
       // something and tell the subclasses later. This is bad because
       // there
       // will be a window where mScrollX/Y is different from what the app
       // thinks it is.
       //
       int x = mScroller.getCurrX();
       int y = mScroller.getCurrY();
    
       if (getChildCount() > 0) {
        View child = getChildAt(0);
        x = clamp(x, getWidth() - getPaddingRight() - getPaddingLeft(),
          child.getWidth());
        y = clamp(y,
          getHeight() - getPaddingBottom() - getPaddingTop(),
          child.getHeight());
        super.scrollTo(x, y);
       }
       awakenScrollBars();
    
       // Keep on drawing until the animation has finished.
       postInvalidate();
      }
     }
    
     /**
      * Scrolls the view to the given child.
      * 
      * @param child
      *            the View to scroll to
      */
     private void scrollToChild(View child) {
      child.getDrawingRect(mTempRect);
    
      /* Offset from child's local coordinates to ScrollView coordinates */
      offsetDescendantRectToMyCoords(child, mTempRect);
    
      int scrollDeltaV = computeScrollDeltaToGetChildRectOnScreenV(mTempRect);
      int scrollDeltaH = computeScrollDeltaToGetChildRectOnScreenH(mTempRect);
    
      if (scrollDeltaH != 0 || scrollDeltaV != 0) {
       scrollBy(scrollDeltaH, scrollDeltaV);
      }
     }
    
     /**
      * If rect is off screen, scroll just enough to get it (or at least the
      * first screen size chunk of it) on screen.
      * 
      * @param rect
      *            The rectangle.
      * @param immediate
      *            True to scroll immediately without animation
      * @return true if scrolling was performed
      */
     private boolean scrollToChildRect(Rect rect, boolean immediate) {
      final int deltaV = computeScrollDeltaToGetChildRectOnScreenV(rect);
      final int deltaH = computeScrollDeltaToGetChildRectOnScreenH(rect);
      final boolean scroll = deltaH != 0 || deltaV != 0;
      if (scroll) {
       if (immediate) {
        scrollBy(deltaH, deltaV);
       } else {
        smoothScrollBy(deltaH, deltaV);
       }
      }
      return scroll;
     }
    
     /**
      * Compute the amount to scroll in the Y direction in order to get a
      * rectangle completely on the screen (or, if taller than the screen, at
      * least the first screen size chunk of it).
      * 
      * @param rect
      *            The rect.
      * @return The scroll delta.
      */
     protected int computeScrollDeltaToGetChildRectOnScreenV(Rect rect) {
      if (getChildCount() == 0)
       return 0;
    
      int height = getHeight();
      int screenTop = getScrollY();
      int screenBottom = screenTop + height;
    
      int fadingEdge = getVerticalFadingEdgeLength();
    
      // leave room for top fading edge as long as rect isn't at very top
      if (rect.top > 0) {
       screenTop += fadingEdge;
      }
    
      // leave room for bottom fading edge as long as rect isn't at very
      // bottom
      if (rect.bottom < getChildAt(0).getHeight()) {
       screenBottom -= fadingEdge;
      }
    
      int scrollYDelta = 0;
    
      if (rect.bottom > screenBottom && rect.top > screenTop) {
       // need to move down to get it in view: move down just enough so
       // that the entire rectangle is in view (or at least the first
       // screen size chunk).
    
       if (rect.height() > height) {
        // just enough to get screen size chunk on
        scrollYDelta += (rect.top - screenTop);
       } else {
        // get entire rect at bottom of screen
        scrollYDelta += (rect.bottom - screenBottom);
       }
    
       // make sure we aren't scrolling beyond the end of our content
       int bottom = getChildAt(0).getBottom();
       int distanceToBottom = bottom - screenBottom;
       scrollYDelta = Math.min(scrollYDelta, distanceToBottom);
    
      } else if (rect.top < screenTop && rect.bottom < screenBottom) {
       // need to move up to get it in view: move up just enough so that
       // entire rectangle is in view (or at least the first screen
       // size chunk of it).
    
       if (rect.height() > height) {
        // screen size chunk
        scrollYDelta -= (screenBottom - rect.bottom);
       } else {
        // entire rect at top
        scrollYDelta -= (screenTop - rect.top);
       }
    
       // make sure we aren't scrolling any further than the top our
       // content
       scrollYDelta = Math.max(scrollYDelta, -getScrollY());
      }
      return scrollYDelta;
     }
    
     protected int computeScrollDeltaToGetChildRectOnScreenH(Rect rect) {
      if (getChildCount() == 0)
       return 0;
    
      int width = getWidth();
      int screenLeft = getScrollX();
      int screenRight = screenLeft + width;
    
      int fadingEdge = getHorizontalFadingEdgeLength();
    
      // leave room for left fading edge as long as rect isn't at very left
      if (rect.left > 0) {
       screenLeft += fadingEdge;
      }
    
      // leave room for right fading edge as long as rect isn't at very right
      if (rect.right < getChildAt(0).getWidth()) {
       screenRight -= fadingEdge;
      }
    
      int scrollXDelta = 0;
    
      if (rect.right > screenRight && rect.left > screenLeft) {
       // need to move right to get it in view: move right just enough so
       // that the entire rectangle is in view (or at least the first
       // screen size chunk).
    
       if (rect.width() > width) {
        // just enough to get screen size chunk on
        scrollXDelta += (rect.left - screenLeft);
       } else {
        // get entire rect at right of screen
        scrollXDelta += (rect.right - screenRight);
       }
    
       // make sure we aren't scrolling beyond the end of our content
       int right = getChildAt(0).getRight();
       int distanceToRight = right - screenRight;
       scrollXDelta = Math.min(scrollXDelta, distanceToRight);
    
      } else if (rect.left < screenLeft && rect.right < screenRight) {
       // need to move right to get it in view: move right just enough so
       // that
       // entire rectangle is in view (or at least the first screen
       // size chunk of it).
    
       if (rect.width() > width) {
        // screen size chunk
        scrollXDelta -= (screenRight - rect.right);
       } else {
        // entire rect at left
        scrollXDelta -= (screenLeft - rect.left);
       }
    
       // make sure we aren't scrolling any further than the left our
       // content
       scrollXDelta = Math.max(scrollXDelta, -getScrollX());
      }
      return scrollXDelta;
     }
    
     @Override
     public void requestChildFocus(View child, View focused) {
      if (!mScrollViewMovedFocus) {
       if (!mIsLayoutDirty) {
        scrollToChild(focused);
       } else {
        // The child may not be laid out yet, we can't compute the
        // scroll yet
        mChildToScrollTo = focused;
       }
      }
      super.requestChildFocus(child, focused);
     }
    
     /**
      * When looking for focus in children of a scroll view, need to be a little
      * more careful not to give focus to something that is scrolled off screen.
      * 
      * This is more expensive than the default {@link android.view.ViewGroup}
      * implementation, otherwise this behavior might have been made the default.
      */
     @Override
     protected boolean onRequestFocusInDescendants(int direction,
       Rect previouslyFocusedRect) {
    
      // convert from forward / backward notation to up / down / left / right
      // (ugh).
      // TODO: FUCK
      // if (direction == View.FOCUS_FORWARD) {
      // direction = View.FOCUS_RIGHT;
      // } else if (direction == View.FOCUS_BACKWARD) {
      // direction = View.FOCUS_LEFT;
      // }
    
      final View nextFocus = previouslyFocusedRect == null ? FocusFinder
        .getInstance().findNextFocus(this, null, direction)
        : FocusFinder.getInstance().findNextFocusFromRect(this,
          previouslyFocusedRect, direction);
    
      if (nextFocus == null) {
       return false;
      }
    
      // if (isOffScreenH(nextFocus)) {
      // return false;
      // }
    
      return nextFocus.requestFocus(direction, previouslyFocusedRect);
     }
    
     @Override
     public boolean requestChildRectangleOnScreen(View child, Rect rectangle,
       boolean immediate) {
      // offset into coordinate space of this scroll view
      rectangle.offset(child.getLeft() - child.getScrollX(), child.getTop()
        - child.getScrollY());
    
      return scrollToChildRect(rectangle, immediate);
     }
    
     @Override
     public void requestLayout() {
      mIsLayoutDirty = true;
      super.requestLayout();
     }
    
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
      super.onLayout(changed, l, t, r, b);
      mIsLayoutDirty = false;
      // Give a child focus if it needs it
      if (mChildToScrollTo != null
        && isViewDescendantOf(mChildToScrollTo, this)) {
       scrollToChild(mChildToScrollTo);
      }
      mChildToScrollTo = null;
    
      // Calling this with the present values causes it to re-clam them
      scrollTo(getScrollX(), getScrollY());
     }
    
     @Override
     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
      super.onSizeChanged(w, h, oldw, oldh);
    
      View currentFocused = findFocus();
      if (null == currentFocused || this == currentFocused)
       return;
    
      // If the currently-focused view was visible on the screen when the
      // screen was at the old height, then scroll the screen to make that
      // view visible with the new screen height.
      if (isWithinDeltaOfScreenV(currentFocused, 0, oldh)) {
       currentFocused.getDrawingRect(mTempRect);
       offsetDescendantRectToMyCoords(currentFocused, mTempRect);
       int scrollDelta = computeScrollDeltaToGetChildRectOnScreenV(mTempRect);
       doScrollY(scrollDelta);
      }
    
      final int maxJump = getRight() - getLeft();
      if (isWithinDeltaOfScreenH(currentFocused, maxJump)) {
       currentFocused.getDrawingRect(mTempRect);
       offsetDescendantRectToMyCoords(currentFocused, mTempRect);
       int scrollDelta = computeScrollDeltaToGetChildRectOnScreenH(mTempRect);
       doScrollX(scrollDelta);
      }
     }
    
     /**
      * Return true if child is an descendant of parent, (or equal to the
      * parent).
      */
     private boolean isViewDescendantOf(View child, View parent) {
      if (child == parent) {
       return true;
      }
    
      final ViewParent theParent = child.getParent();
      return (theParent instanceof ViewGroup)
        && isViewDescendantOf((View) theParent, parent);
     }
    
     /**
      * Fling the scroll view
      * 
      * @param velocityY
      *            The initial velocity in the Y direction. Positive numbers mean
      *            that the finger/cursor is moving down the screen, which means
      *            we want to scroll towards the top.
      */
     public void fling(int velocityX, int velocityY) {
      if (getChildCount() > 0) {
       int width = getWidth() - getPaddingRight() - getPaddingLeft();
       int right = getChildAt(0).getWidth();
    
       int height = getHeight() - getPaddingBottom() - getPaddingTop();
       int bottom = getChildAt(0).getHeight();
    
       mScroller.fling(getScrollX(), getScrollY(), velocityX, velocityY,
         0, Math.max(0, right - width), 0,
         Math.max(0, bottom - height));
    
       // final boolean movingDown = velocityX > 0 || velocityY > 0;
       //
       // View newFocused =
       // findFocusableViewInMyBoundsV(movingDown, mScroller.getFinalY(),
       // findFocus());
       // if (newFocused == null) {
       // newFocused = this;
       // }
       //
       // if (newFocused != findFocus()
       // && newFocused.requestFocus(movingDown ? View.FOCUS_DOWN :
       // View.FOCUS_UP)) {
       // mScrollViewMovedFocus = true;
       // mScrollViewMovedFocus = false;
       // }
    
       invalidate();
      }
     }
    
     /**
      * {@inheritDoc}
      * 
      * <p>
      * This version also clamps the scrolling to the bounds of our child.
      */
     @Override
     public void scrollTo(int x, int y) {
      // we rely on the fact the View.scrollBy calls scrollTo.
      if (getChildCount() > 0) {
       View child = getChildAt(0);
       x = clamp(x, getWidth() - getPaddingRight() - getPaddingLeft(),
         child.getWidth());
       y = clamp(y, getHeight() - getPaddingBottom() - getPaddingTop(),
         child.getHeight());
       if (x != getScrollX() || y != getScrollY()) {
        super.scrollTo(x, y);
       }
      }
     }
    
     @Override
     protected void onScrollChanged(int l, int t, int oldl, int oldt) {
      super.onScrollChanged(l, t, oldl, oldt);
      if (mScrollerListener != null) {
       mScrollerListener.onScroll(l, t);
      }
     }
    
     private int clamp(int n, int my, int child) {
      if (my >= child || n < 0) {
       /*
        * my >= child is this case: |--------------- me ---------------|
        * |------ child ------| or |--------------- me ---------------|
        * |------ child ------| or |--------------- me ---------------|
        * |------ child ------|
        * 
        * n < 0 is this case: |------ me ------| |-------- child --------|
        * |-- mScrollX --|
        */
       return 0;
      }
      if ((my + n) > child) {
       /*
        * this case: |------ me ------| |------ child ------| |-- mScrollX
        * --|
        */
       return child - my;
      }
      return n;
     }
    
     public boolean isFlingEnabled() {
      return mFlingEnabled;
     }
    
     public void setFlingEnabled(boolean flingEnabled) {
      this.mFlingEnabled = flingEnabled;
     }
    
     private onScrollerListener mScrollerListener;
     
     public void setOnScrollerLitstener(onScrollerListener l) {
      mScrollerListener = l;
     }
     
     public interface onScrollerListener {
      public void onScroll(int x, int y);
     }
    
    }
  • 相关阅读:
    vscode中配置git
    javaScript基础-03 javascript语句
    使用DOM4J解析XML文档
    使用JDOM解析XML
    使用SAXParser解析XML文档的实例
    Schema技术的使用小结.
    Java.util.Arrays 工具类的使用
    3种类内部的用法(成员,局部,匿名)
    File类的常用方法.
    Math类的常用方法和静态导入.
  • 原文地址:https://www.cnblogs.com/g-sheng/p/5405935.html
Copyright © 2011-2022 走看看