为了更加明了的理解。还是去看一下源代码。在View类中。scrollTo的代码例如以下:
  /**
     * Set the scrolled position of your view. This will cause a call to
     * {@link #onScrollChanged(int, int, int, int)} and the view will be
     * invalidated.
     * @param x the x position to scroll to
     * @param y the y position to scroll to
     */
    public void scrollTo(int x, int y) {
        if (mScrollX != x || mScrollY != y) {
            int oldX = mScrollX;
            int oldY = mScrollY;
            mScrollX = x;
            mScrollY = y;
            invalidateParentCaches();
            onScrollChanged(mScrollX, mScrollY, oldX, oldY);
            if (!awakenScrollBars()) {
                postInvalidateOnAnimation();
            }
        }
    }那它是怎样让视图滚动的呢?首先注意到在这种方法中有两个变量:mScrollX、mScrollY。这两个变量是在View类中定义的,
/**
     * The offset, in pixels, by which the content of this view is scrolled
     * horizontally.
     * {@hide}
     */
    @ViewDebug.ExportedProperty(category = "scrolling")
    protected int mScrollX;
    /**
     * The offset, in pixels, by which the content of this view is scrolled
     * vertically.
     * {@hide}
     */
    @ViewDebug.ExportedProperty(category = "scrolling")
    protected int mScrollY;)
    /**
     * Move the scrolled position of your view. This will cause a call to
     * {@link #onScrollChanged(int, int, int, int)} and the view will be
     * invalidated.
     * @param x the amount of pixels to scroll by horizontally
     * @param y the amount of pixels to scroll by vertically
     */
    public void scrollBy(int x, int y) {
        scrollTo(mScrollX + x, mScrollY + y);
    }  非常easy。就是直接调用了scrollTo方法,可是从这种方法的实现机制能够看出,它是一个累加减的过程,不断的将当前视图内容继续偏移(x , y)个单位。比方第一次 scrollBy(10,10)。第二次 scrollBy(10,10),那么最后的结果就相当于scrollTo(20,20)。
之后这种方法会通知View进行重绘。所以就去看一下draw()方法的源代码,由于这种方法比較长,基于篇幅就不所有列出,直说重点。先列出方法的前几行。
  public void draw(Canvas canvas) {
        if (mClipBounds != null) {
            canvas.clipRect(mClipBounds);
        }
        final int privateFlags = mPrivateFlags;
        final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
        mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background
         *      2. If necessary, save the canvas‘ layers to prepare for fading
         *      3. Draw view‘s content
         *      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         */
        // Step 1, draw the background, if needed
        int saveCount;在方法的最后,看到了
// Step 6, draw decorations (scrollbars)
        onDrawScrollBars(canvas);  然后看一下onDrawScrollBars(canvas)方法, protected final void onDrawScrollBars(Canvas canvas) {
        // scrollbars are drawn only when the animation is running
        final ScrollabilityCache cache = mScrollCache;
        if (cache != null) {
            int state = cache.state;
            if (state == ScrollabilityCache.OFF) {
                return;
            }
            boolean invalidate = false;
            if (state == ScrollabilityCache.FADING) {
                // We‘re fading -- get our fade interpolation
                if (cache.interpolatorValues == null) {
                    cache.interpolatorValues = new float[1];
                }
                float[] values = cache.interpolatorValues;
                // Stops the animation if we‘re done
                if (cache.scrollBarInterpolator.timeToValues(values) ==
                        Interpolator.Result.FREEZE_END) {
                    cache.state = ScrollabilityCache.OFF;
                } else {
                    cache.scrollBar.setAlpha(Math.round(values[0]));
                }
                // This will make the scroll bars inval themselves after
                // drawing. We only want this when we‘re fading so that
                // we prevent excessive redraws
                invalidate = true;
            } else {
                // We‘re just on -- but we may have been fading before so
                // reset alpha
                cache.scrollBar.setAlpha(255);
            }
            final int viewFlags = mViewFlags;
            final boolean drawHorizontalScrollBar =
                (viewFlags & SCROLLBARS_HORIZONTAL) == SCROLLBARS_HORIZONTAL;
            final boolean drawVerticalScrollBar =
                (viewFlags & SCROLLBARS_VERTICAL) == SCROLLBARS_VERTICAL
                && !isVerticalScrollBarHidden();
            if (drawVerticalScrollBar || drawHorizontalScrollBar) {
                final int width = mRight - mLeft;
                final int height = mBottom - mTop;
                final ScrollBarDrawable scrollBar = cache.scrollBar;
                final int scrollX = mScrollX;
                final int scrollY = mScrollY;
                final int inside = (viewFlags & SCROLLBARS_OUTSIDE_MASK) == 0 ? ~0 : 0;
                int left;
                int top;
                int right;
                int bottom;
                if (drawHorizontalScrollBar) {
                    int size = scrollBar.getSize(false);
                    if (size <= 0) {
                        size = cache.scrollBarSize;
                    }
                    scrollBar.setParameters(computeHorizontalScrollRange(),
                                            computeHorizontalScrollOffset(),
                                            computeHorizontalScrollExtent(), false);
                    final int verticalScrollBarGap = drawVerticalScrollBar ?
                            getVerticalScrollbarWidth() : 0;
                    top = scrollY + height - size - (mUserPaddingBottom & inside);
                    left = scrollX + (mPaddingLeft & inside);
                    right = scrollX + width - (mUserPaddingRight & inside) - verticalScrollBarGap;
                    bottom = top + size;
                    onDrawHorizontalScrollBar(canvas, scrollBar, left, top, right, bottom);
                    if (invalidate) {
                        invalidate(left, top, right, bottom);
                    }
                }
                if (drawVerticalScrollBar) {
                    int size = scrollBar.getSize(true);
                    if (size <= 0) {
                        size = cache.scrollBarSize;
                    }
                    scrollBar.setParameters(computeVerticalScrollRange(),
                                            computeVerticalScrollOffset(),
                                            computeVerticalScrollExtent(), true);
                    int verticalScrollbarPosition = mVerticalScrollbarPosition;
                    if (verticalScrollbarPosition == SCROLLBAR_POSITION_DEFAULT) {
                        verticalScrollbarPosition = isLayoutRtl() ?
                                SCROLLBAR_POSITION_LEFT : SCROLLBAR_POSITION_RIGHT;
                    }
                    switch (verticalScrollbarPosition) {
                        default:
                        case SCROLLBAR_POSITION_RIGHT:
                            left = scrollX + width - size - (mUserPaddingRight & inside);
                            break;
                        case SCROLLBAR_POSITION_LEFT:
                            left = scrollX + (mUserPaddingLeft & inside);
                            break;
                    }
                    top = scrollY + (mPaddingTop & inside);
                    right = left + size;
                    bottom = scrollY + height - (mUserPaddingBottom & inside);
                    onDrawVerticalScrollBar(canvas, scrollBar, left, top, right, bottom);
                    if (invalidate) {
                        invalidate(left, top, right, bottom);
                    }
                }
            }
        }
    }
 public void invalidate(int l, int t, int r, int b) {
        if (skipInvalidate()) {
            return;
        }
        if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) ||
                (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID ||
                (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED) {
            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
            mPrivateFlags |= PFLAG_INVALIDATED;
            mPrivateFlags |= PFLAG_DIRTY;
            final ViewParent p = mParent;
            final AttachInfo ai = mAttachInfo;
            //noinspection PointlessBooleanExpression,ConstantConditions
            if (!HardwareRenderer.RENDER_DIRTY_REGIONS) {
                if (p != null && ai != null && ai.mHardwareAccelerated) {
                    // fast-track for GL-enabled applications; just invalidate the whole hierarchy
                    // with a null dirty rect, which tells the ViewAncestor to redraw everything
                    p.invalidateChild(this, null);
                    return;
                }
            }
            if (p != null && ai != null && l < r && t < b) {
                final int scrollX = mScrollX;
                final int scrollY = mScrollY;
                final Rect tmpr = ai.mTmpInvalRect;
                tmpr.set(l - scrollX, t - scrollY, r - scrollX, b - scrollY);
                p.invalidateChild(this, tmpr);
            }
        }
    }也会明确当向右移动视图时候,为什么getScrollX()返回值会是负的了。以下做一个測试的demo,来练习一下这两个方法的使用。
package com.kince.scrolldemo;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity implements OnClickListener {
     private Button mButton1;
     private Button mButton2;
     private Button mButton3;
     private TextView mTextView;
     @Override
     protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);
          mTextView = (TextView) this.findViewById(R.id.tv);
          mButton1 = (Button) this.findViewById(R.id.button_scroll1);
          mButton2 = (Button) this.findViewById(R.id.button_scroll2);
          mButton3 = (Button) this.findViewById(R.id.button_scroll3);
          mButton1.setOnClickListener(this);
          mButton2.setOnClickListener(this);
          mButton3.setOnClickListener(this);
     }
     @Override
     public void onClick(View v) {
          // TODO Auto-generated method stub
          switch (v.getId()) {
          case R.id.button_scroll1:
               mTextView.scrollTo(-10, -10);
               break;
          case R.id.button_scroll2:
               mTextView.scrollBy(-2, -2);
               break;
          case R.id.button_scroll3:
               mTextView.scrollTo(0, 0);
               break;
          default:
               break;
          }
     }
}<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="400dp"
        android:background="@android:color/holo_green_light" >
        <TextView
            android:id="@+id/tv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:background="@android:color/holo_blue_dark"
            android:textSize="20sp"
            android:text="SCROLL" />
    </RelativeLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:orientation="horizontal" >
        <Button
            android:id="@+id/button_scroll1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="SCROLL_TO" />
        <Button
            android:id="@+id/button_scroll2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="SCROLL_BY" />
        <Button
            android:id="@+id/button_scroll3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="复位" />
    </LinearLayout>
</LinearLayout>结果却是视图相对于自身的移动,事实上还是对于这种方法包含 mScrollX、mScrollY的理解不全面,回过头来再看一下
再看一下效果,
那这两个方法在实际开发中是怎样运用的呢?光凭上面的样例是看不出什么作用的。可是就像文章开头部分说的那样,在视图滑动的情况下,这两个方法发挥了巨大的作用。以相似Launcher左右滑屏为例,
/**
*
*/
package com.kince.scrolldemo;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
/**
* @author kince
*
*
*/
public class CusScrollView extends ViewGroup {
     private int lastX = 0;
     private int currX = 0;
     private int offX = 0;
     /**
     * @param context
     */
     public CusScrollView(Context context) {
          this(context, null);
          // TODO Auto-generated constructor stub
     }
     /**
     * @param context
     * @param attrs
     */
     public CusScrollView(Context context, AttributeSet attrs) {
          this(context, attrs, 0);
          // TODO Auto-generated constructor stub
     }
     /**
     * @param context
     * @param attrs
     * @param defStyle
     */
     public CusScrollView(Context context, AttributeSet attrs, int defStyle) {
          super(context, attrs, defStyle);
          // TODO Auto-generated constructor stub
     }
     /*
     * (non-Javadoc)
     *
     * @see android.view.ViewGroup#onLayout(boolean, int, int, int, int)
     */
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
          // TODO Auto-generated method stub
          for (int i = 0; i < getChildCount(); i++) {
               View v = getChildAt(i);
               v.layout(0 + i * getWidth(), 0, getWidth() + i * getWidth(),
                         getHeight());
          }
     }
     @Override
     public boolean onTouchEvent(MotionEvent event) {
          // TODO Auto-generated method stub
          switch (event.getAction()) {
          case MotionEvent.ACTION_DOWN:
               // 仅仅考虑水平方向
               lastX = (int) event.getX();
               return true;
              
          case MotionEvent.ACTION_MOVE:
               currX = (int) event.getX();
               offX = currX - lastX;
               scrollBy(-offX, 0);
               break;
              
          case MotionEvent.ACTION_UP:
               scrollTo(0, 0);
               break;
          }
          invalidate();
          return super.onTouchEvent(event);
     }
}package com.kince.scrolldemo;
import android.app.Activity;
import android.app.ActionBar;
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.os.Build;
public class LauncherActivity extends Activity {
     private int[] images = { R.drawable.jy1, R.drawable.jy2, R.drawable.jy3,
               R.drawable.jy4, R.drawable.jy5, };
     private CusScrollView mCusScrollView;
     @Override
     protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_launcher);
          mCusScrollView = (CusScrollView) this.findViewById(R.id.CusScrollView);
          for (int i = 0; i < images.length; i++) {
               ImageView mImageView = new ImageView(this);
               mImageView.setScaleType(ScaleType.FIT_XY);
               mImageView.setBackgroundResource(images[i]);
               mImageView.setLayoutParams(new LayoutParams(
                         LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
               mCusScrollView.addView(mImageView);
          }
     }
}<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <com.kince.scrolldemo.CusScrollView
        android:id="@+id/CusScrollView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
       
    </com.kince.scrolldemo.CusScrollView>
</LinearLayout>  这个样例对CusScrollView里面的图片进行左右滑动,在 onTouchEvent(MotionEvent event)的MotionEvent.ACTION_MOVE中对图片进行移动,使用的是 scrollBy()方法,由于手指每次移动都会产生差值。利用 scrollBy()方法就能够尾随手指进行左右滑动。在MotionEvent.ACTION_UP事件中,也就是手指抬起时候,直接使用scrollTo()方法让视图回到初始位置。再强调一遍。注意无论是scrollBy()还是scrollTo()方法,都是对CusScrollView内容视图进行移动。效果例如以下:
只是通过上面的样例,发现一个问题就是滑动速度非常快,尤其是scrollTo()方法,差点儿是瞬间移动到指定位置。这样倒不能说是缺点。只是在某些情况下,是希望能够缓慢的移动或者有一个明显的移动效果。就像側滑菜单那样。仿佛有一个移动的动画。
这时候Scroller闪亮登场了。
 /**
     * Called by a parent to request that a child update its values for mScrollX
     * and mScrollY if necessary. This will typically be done if the child is
     * animating a scroll using a {@link android.widget.Scroller Scroller}
     * object.
     */
    public void computeScroll() {
    }而且在绘制View时,会在draw()过程调用该方法。能够看到这种方法是一个空的方法。因此须要子类去重写该方法来实现逻辑。那该方法在何处被触发呢?继续看看View的draw()方法,上面说到会在子视图中调用该方法,也就是说绘制子视图的时候,那么在draw()等等的第四部。
 // Step 4, draw the children 
           dispatchDraw(canvas); /**
    * Called by draw to draw the child views. This may be overridden
    * by derived classes to gain control just before its children are drawn
    * (but after its own view has been drawn).
    * @param canvas the canvas on which to draw the view
    */ 
   protected void dispatchDraw(Canvas canvas) { 
 
   }  @Override
    protected void dispatchDraw(Canvas canvas) {
        ...
        ...
        ...
        if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {
            for (int i = 0; i < count; i++) {
                final View child = children[i];
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                    more |= drawChild(canvas, child, drawingTime);
                }
            }
        } else {
            for (int i = 0; i < count; i++) {
                final View child = children[getChildDrawingOrder(count, i)];
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                    more |= drawChild(canvas, child, drawingTime);
                }
            }
        }
        // Draw any disappearing views that have animations
        if (mDisappearingChildren != null) {
            final ArrayList<View> disappearingChildren = mDisappearingChildren;
            final int disappearingCount = disappearingChildren.size() - 1;
            // Go backwards -- we may delete as animations finish
            for (int i = disappearingCount; i >= 0; i--) {
                final View child = disappearingChildren.get(i);
                more |= drawChild(canvas, child, drawingTime);
            }
        }
        ...
        ...
        ...
        }
    }
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        ...
        ...
        ...
    if (!concatMatrix && canvas.quickReject(cl, ct, cr, cb, Canvas.EdgeType.BW) &&
                (child.mPrivateFlags & DRAW_ANIMATION) == 0) {
            return more;
        }
        child.computeScroll();
        final int sx = child.mScrollX;
        final int sy = child.mScrollY;
        boolean scalingRequired = false;
        Bitmap cache = null;
        ...
        ...
        ...
}也就是ViewGroup在分发绘制自己的孩子的时候,会对其子View调用computeScroll()方法。
public class Scroller  {
    private int mMode;
    private int mStartX;
    private int mStartY;
    private int mFinalX;
    private int mFinalY;
    private int mMinX;
    private int mMaxX;
    private int mMinY;
    private int mMaxY;
    private int mCurrX;
    private int mCurrY;
    private long mStartTime;
    private int mDuration;
    private float mDurationReciprocal;
    private float mDeltaX;
    private float mDeltaY;
    private boolean mFinished;
    private Interpolator mInterpolator;
    private float mVelocity;
    private float mCurrVelocity;
    private int mDistance;
    private float mFlingFriction = ViewConfiguration.getScrollFriction();
    private static final int DEFAULT_DURATION = 250;
    private static final int SCROLL_MODE = 0;
    private static final int FLING_MODE = 1;
    /**
     * Create a Scroller with the default duration and interpolator.
     */
    public Scroller(Context context) {
        this(context, null);
    }
    /**
     * Create a Scroller with the specified interpolator. If the interpolator is
     * null, the default (viscous) interpolator will be used. "Flywheel" behavior will
     * be in effect for apps targeting Honeycomb or newer.
     */
    public Scroller(Context context, Interpolator interpolator) {
        this(context, interpolator,
                context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB);
    }
    /**
     * Create a Scroller with the specified interpolator. If the interpolator is
     * null, the default (viscous) interpolator will be used. Specify whether or
     * not to support progressive "flywheel" behavior in flinging.
     */
    public Scroller(Context context, Interpolator interpolator, boolean flywheel) {
        mFinished = true;
        mInterpolator = interpolator;
        mPpi = context.getResources().getDisplayMetrics().density * 160.0f;
        mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction());
        mFlywheel = flywheel;
        mPhysicalCoeff = computeDeceleration(0.84f); // look and feel tuning
    }
    /**
     * Call this when you want to know the new location.  If it returns true,
     * the animation is not yet finished.
     */
    public boolean computeScrollOffset() {
        if (mFinished) {
            return false;
        }
        int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
   
        if (timePassed < mDuration) {
            switch (mMode) {
            case SCROLL_MODE:
                float x = timePassed * mDurationReciprocal;
   
                if (mInterpolator == null)
                    x = viscousFluid(x);
                else
                    x = mInterpolator.getInterpolation(x);
   
                mCurrX = mStartX + Math.round(x * mDeltaX);
                mCurrY = mStartY + Math.round(x * mDeltaY);
                break;
            case FLING_MODE:
                final float t = (float) timePassed / mDuration;
                final int index = (int) (NB_SAMPLES * t);
                float distanceCoef = 1.f;
                float velocityCoef = 0.f;
                if (index < NB_SAMPLES) {
                    final float t_inf = (float) index / NB_SAMPLES;
                    final float t_sup = (float) (index + 1) / NB_SAMPLES;
                    final float d_inf = SPLINE_POSITION[index];
                    final float d_sup = SPLINE_POSITION[index + 1];
                    velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
                    distanceCoef = d_inf + (t - t_inf) * velocityCoef;
                }
                mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f;
               
                mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
                // Pin to mMinX <= mCurrX <= mMaxX
                mCurrX = Math.min(mCurrX, mMaxX);
                mCurrX = Math.max(mCurrX, mMinX);
               
                mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
                // Pin to mMinY <= mCurrY <= mMaxY
                mCurrY = Math.min(mCurrY, mMaxY);
                mCurrY = Math.max(mCurrY, mMinY);
                if (mCurrX == mFinalX && mCurrY == mFinalY) {
                    mFinished = true;
                }
                break;
            }
        }
        else {
            mCurrX = mFinalX;
            mCurrY = mFinalY;
            mFinished = true;
        }
        return true;
    }
   
    /**
     * Start scrolling by providing a starting point and the distance to travel.
     * The scroll will use the default value of 250 milliseconds for the
     * duration.
     *
     * @param startX Starting horizontal scroll offset in pixels. Positive
     *        numbers will scroll the content to the left.
     * @param startY Starting vertical scroll offset in pixels. Positive numbers
     *        will scroll the content up.
     * @param dx Horizontal distance to travel. Positive numbers will scroll the
     *        content to the left.
     * @param dy Vertical distance to travel. Positive numbers will scroll the
     *        content up.
     */
    public void startScroll(int startX, int startY, int dx, int dy) {
        startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
    }
    /**
     * Start scrolling by providing a starting point, the distance to travel,
     * and the duration of the scroll.
     *
     * @param startX Starting horizontal scroll offset in pixels. Positive
     *        numbers will scroll the content to the left.
     * @param startY Starting vertical scroll offset in pixels. Positive numbers
     *        will scroll the content up.
     * @param dx Horizontal distance to travel. Positive numbers will scroll the
     *        content to the left.
     * @param dy Vertical distance to travel. Positive numbers will scroll the
     *        content up.
     * @param duration Duration of the scroll in milliseconds.
     */
    public void startScroll(int startX, int startY, int dx, int dy, int duration) {
        mMode = SCROLL_MODE;
        mFinished = false;
        mDuration = duration;
        mStartTime = AnimationUtils.currentAnimationTimeMillis();
        mStartX = startX;
        mStartY = startY;
        mFinalX = startX + dx;
        mFinalY = startY + dy;
        mDeltaX = dx;
        mDeltaY = dy;
        mDurationReciprocal = 1.0f / (float) mDuration;
    }
    /**
     * Start scrolling based on a fling gesture. The distance travelled will
     * depend on the initial velocity of the fling.
     *
     * @param startX Starting point of the scroll (X)
     * @param startY Starting point of the scroll (Y)
     * @param velocityX Initial velocity of the fling (X) measured in pixels per
     *        second.
     * @param velocityY Initial velocity of the fling (Y) measured in pixels per
     *        second
     * @param minX Minimum X value. The scroller will not scroll past this
     *        point.
     * @param maxX Maximum X value. The scroller will not scroll past this
     *        point.
     * @param minY Minimum Y value. The scroller will not scroll past this
     *        point.
     * @param maxY Maximum Y value. The scroller will not scroll past this
     *        point.
     */
    public void fling(int startX, int startY, int velocityX, int velocityY,
            int minX, int maxX, int minY, int maxY) {
        // Continue a scroll or fling in progress
        if (mFlywheel && !mFinished) {
            float oldVel = getCurrVelocity();
            float dx = (float) (mFinalX - mStartX);
            float dy = (float) (mFinalY - mStartY);
            float hyp = FloatMath.sqrt(dx * dx + dy * dy);
            float ndx = dx / hyp;
            float ndy = dy / hyp;
            float oldVelocityX = ndx * oldVel;
            float oldVelocityY = ndy * oldVel;
            if (Math.signum(velocityX) == Math.signum(oldVelocityX) &&
                    Math.signum(velocityY) == Math.signum(oldVelocityY)) {
                velocityX += oldVelocityX;
                velocityY += oldVelocityY;
            }
        }
        mMode = FLING_MODE;
        mFinished = false;
        float velocity = FloatMath.sqrt(velocityX * velocityX + velocityY * velocityY);
    
        mVelocity = velocity;
        mDuration = getSplineFlingDuration(velocity);
        mStartTime = AnimationUtils.currentAnimationTimeMillis();
        mStartX = startX;
        mStartY = startY;
        float coeffX = velocity == 0 ? 1.0f : velocityX / velocity;
        float coeffY = velocity == 0 ? 1.0f : velocityY / velocity;
        double totalDistance = getSplineFlingDistance(velocity);
        mDistance = (int) (totalDistance * Math.signum(velocity));
       
        mMinX = minX;
        mMaxX = maxX;
        mMinY = minY;
        mMaxY = maxY;
        mFinalX = startX + (int) Math.round(totalDistance * coeffX);
        // Pin to mMinX <= mFinalX <= mMaxX
        mFinalX = Math.min(mFinalX, mMaxX);
        mFinalX = Math.max(mFinalX, mMinX);
       
        mFinalY = startY + (int) Math.round(totalDistance * coeffY);
        // Pin to mMinY <= mFinalY <= mMaxY
        mFinalY = Math.min(mFinalY, mMaxY);
        mFinalY = Math.max(mFinalY, mMinY);
    }
   
}
  public void startScroll(int startX, int startY, int dx, int dy, int duration) {
        mMode = SCROLL_MODE;
        mFinished = false;
        mDuration = duration;
        mStartTime = AnimationUtils.currentAnimationTimeMillis();
        mStartX = startX;
        mStartY = startY;
        mFinalX = startX + dx;
        mFinalY = startY + dy;
        mDeltaX = dx;
        mDeltaY = dy;
        mDurationReciprocal = 1.0f / (float) mDuration;
    }scroller.startScroll(getScrollX(), 0, distance, 0);
invalidate(); // 刷新视图
       @Override
     public void computeScroll() {
          if (scroller.computeScrollOffset()) {
               scrollTo(scroller.getCurrX(), 0);
          }
     }       @Override
     public void computeScroll() {
          if (scroller.computeScrollOffset()) {
               scrollTo(scroller.getCurrX(), 0);
               invalidate();
          }
     }仅仅需把代码略微改一下就能够了,例如以下:
/**
*
*/
package com.kince.scrolldemo;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;
/**
* @author kince
*
*
*/
public class CusScrollView extends ViewGroup {
     private int lastX = 0;
     private int currX = 0;
     private int offX = 0;
     private Scroller mScroller;
     /**
     * @param context
     */
     public CusScrollView(Context context) {
          this(context, null);
          // TODO Auto-generated constructor stub
     }
     /**
     * @param context
     * @param attrs
     */
     public CusScrollView(Context context, AttributeSet attrs) {
          this(context, attrs, 0);
          // TODO Auto-generated constructor stub
     }
     /**
     * @param context
     * @param attrs
     * @param defStyle
     */
     public CusScrollView(Context context, AttributeSet attrs, int defStyle) {
          super(context, attrs, defStyle);
          // TODO Auto-generated constructor stub
          mScroller = new Scroller(context);
     }
     /*
     * (non-Javadoc)
     *
     * @see android.view.ViewGroup#onLayout(boolean, int, int, int, int)
     */
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
          // TODO Auto-generated method stub
          for (int i = 0; i < getChildCount(); i++) {
               View v = getChildAt(i);
               v.layout(0 + i * getWidth(), 0, getWidth() + i * getWidth(),
                         getHeight());
          }
     }
     @Override
     public boolean onTouchEvent(MotionEvent event) {
          // TODO Auto-generated method stub
          switch (event.getAction()) {
          case MotionEvent.ACTION_DOWN:
               // 仅仅考虑水平方向
               lastX = (int) event.getX();
               return true;
          case MotionEvent.ACTION_MOVE:
               currX = (int) event.getX();
               offX = currX - lastX;
               // scrollBy(-offX, 0);
               mScroller.startScroll(getScrollX(), 0, -offX, 0);
               break;
          case MotionEvent.ACTION_UP:
//               scrollTo(0, 0);
               mScroller.startScroll(getScrollX(), 0, -100, 0);
               break;
          }
          invalidate();
          return super.onTouchEvent(event);
     }
     @Override
     public void computeScroll() {
          // TODO Auto-generated method stub
          if (mScroller.computeScrollOffset()) {
               scrollTo(mScroller.getCurrX(), 0);
               invalidate();
          }
     }
}Android scrollTo() scrollBy() Scroller解说及应用
原文:http://www.cnblogs.com/yxysuanfa/p/7087267.html