首页 > 代码库 > Android scrollTo() scrollBy() Scroller解说及应用
Android scrollTo() scrollBy() Scroller解说及应用
为了更加明了的理解。还是去看一下源代码。在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;
- mScrollX: 该视图内容相当于视图起始坐标的偏移量。 X轴方向
- mScrollY:该视图内容相当于视图起始坐标的偏移量, Y轴方向
)
/** * 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解说及应用