在android中,Scroller是用来实现view的滑动效果。使用的步骤如下:
1. 创建Scroller对象
2. 调用Scroller.startScoller()或者fling()方法。
3. 在调用startScroller()或者fling()方法后调用invalidate(),促使view重绘
4. 重写view的computeScroll()方法。
其中View的computeScroll()是在view树draw()的时候调用。
Scroller类中主要的成员变量有:
private int mMode;
65
66 private int mStartX;
67 private int mStartY;
68 private int mFinalX;
69 private int mFinalY;
70
71 private int mMinX;
72 private int mMaxX;
73 private int mMinY;
74 private int mMaxY;
75
76 private int mCurrX;
77 private int mCurrY;
78 private long mStartTime;
79 private int mDuration;
80 private float mDurationReciprocal;
81 private float mDeltaX;
82 private float mDeltaY;
83 private boolean mFinished;
84 private Interpolator mInterpolator;
87 private float mVelocity;
88 private float mCurrVelocity;
89 private int mDistance;
其中mMode代表Scroller的模式,FLING或者SCROLL;mStartX,mStartY代表起始的坐标;mFinalX、mFinalY代表重点坐标;mCurrX、mCurrY代表当前坐标;mDeltaX、mDeltaY代表需要移动的偏移量,mFinished代表是否完成;mInterpolator用来计算下一次移动的位置,可以用它来控制Scroller的移动效果。
在使用过程中,我们会调用scroller.startScroll()方法,startScroll()方法如下:
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
399 mMode = SCROLL_MODE; // 设置mMode为SCROLL_MODE模式
400 mFinished = false; // 结束标记为false
401 mDuration = duration; // 滚动的时间间隔
402 mStartTime = AnimationUtils.currentAnimationTimeMillis(); // 当前时间戳
403 mStartX = startX; // 起始
404 mStartY = startY;
405 mFinalX = startX + dx; // 结束
406 mFinalY = startY + dy;
407 mDeltaX = dx; // 偏移量
408 mDeltaY = dy;
409 mDurationReciprocal = 1.0f / (float) mDuration;
410 }
这个方法进行一些初始化操作,给关键的变量赋值。同样fling()方法类似。
public void fling(int startX, int startY, int velocityX, int velocityY,
432 int minX, int maxX, int minY, int maxY) {
433 // Continue a scroll or fling in progress
434 if (mFlywheel && !mFinished) {
435 float oldVel = getCurrVelocity();
436
437 float dx = (float) (mFinalX - mStartX);
438 float dy = (float) (mFinalY - mStartY);
439 float hyp = FloatMath.sqrt(dx * dx + dy * dy);
440
441 float ndx = dx / hyp;
442 float ndy = dy / hyp;
443
444 float oldVelocityX = ndx * oldVel;
445 float oldVelocityY = ndy * oldVel;
446 if (Math.signum(velocityX) == Math.signum(oldVelocityX) &&
447 Math.signum(velocityY) == Math.signum(oldVelocityY)) {
448 velocityX += oldVelocityX;
449 velocityY += oldVelocityY;
450 }
451 }
452
453 mMode = FLING_MODE;
454 mFinished = false;
455
456 float velocity = FloatMath.sqrt(velocityX * velocityX + velocityY * velocityY);
457
458 mVelocity = velocity;
459 mDuration = getSplineFlingDuration(velocity);
460 mStartTime = AnimationUtils.currentAnimationTimeMillis();
461 mStartX = startX;
462 mStartY = startY;
463
464 float coeffX = velocity == 0 ? 1.0f : velocityX / velocity;
465 float coeffY = velocity == 0 ? 1.0f : velocityY / velocity;
466
467 double totalDistance = getSplineFlingDistance(velocity);
468 mDistance = (int) (totalDistance * Math.signum(velocity));
469
470 mMinX = minX;
471 mMaxX = maxX;
472 mMinY = minY;
473 mMaxY = maxY;
474
475 mFinalX = startX + (int) Math.round(totalDistance * coeffX);
476 // Pin to mMinX <= mFinalX <= mMaxX
477 mFinalX = Math.min(mFinalX, mMaxX);
478 mFinalX = Math.max(mFinalX, mMinX);
479
480 mFinalY = startY + (int) Math.round(totalDistance * coeffY);
481 // Pin to mMinY <= mFinalY <= mMaxY
482 mFinalY = Math.min(mFinalY, mMaxY);
483 mFinalY = Math.max(mFinalY, mMinY);
484 }
接着调用invalidate()导致view重绘,调用computeScroll(), 在computeScroll()方法里面需要调用Scroller.computeScrollOffset()方法,这个方法判断滚动是否结束,
public boolean computeScrollOffset() {
306 if (mFinished) {
307 return false;
308 }
309
310 int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
311
312 if (timePassed < mDuration) {
313 switch (mMode) {
314 case SCROLL_MODE:
315 float x = timePassed * mDurationReciprocal;
316
317 if (mInterpolator == null)
318 x = viscousFluid(x);
319 else
320 x = mInterpolator.getInterpolation(x);
321
322 mCurrX = mStartX + Math.round(x * mDeltaX);
323 mCurrY = mStartY + Math.round(x * mDeltaY);
324 break;
325 case FLING_MODE:
326 final float t = (float) timePassed / mDuration;
327 final int index = (int) (NB_SAMPLES * t);
328 float distanceCoef = 1.f;
329 float velocityCoef = 0.f;
330 if (index < NB_SAMPLES) {
331 final float t_inf = (float) index / NB_SAMPLES;
332 final float t_sup = (float) (index + 1) / NB_SAMPLES;
333 final float d_inf = SPLINE_POSITION[index];
334 final float d_sup = SPLINE_POSITION[index + 1];
335 velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
336 distanceCoef = d_inf + (t - t_inf) * velocityCoef;
337 }
338
339 mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f;
340
341 mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
342 // Pin to mMinX <= mCurrX <= mMaxX
343 mCurrX = Math.min(mCurrX, mMaxX);
344 mCurrX = Math.max(mCurrX, mMinX);
345
346 mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
347 // Pin to mMinY <= mCurrY <= mMaxY
348 mCurrY = Math.min(mCurrY, mMaxY);
349 mCurrY = Math.max(mCurrY, mMinY);
350
351 if (mCurrX == mFinalX && mCurrY == mFinalY) {
352 mFinished = true;
353 }
354
355 break;
356 }
357 }
358 else {
359 mCurrX = mFinalX;
360 mCurrY = mFinalY;
361 mFinished = true;
362 }
363 return true;
364 }
在这个方法里会去判断当前MODE类型,如果是滚动类型,计算一个x的权值,给mCurrX和mCurrY赋值,然后再调用View.scrollBy()或者View.scrollTo()方法来实现滚动。如果是Fling类型,类似滚动类型,根据速度和时间计算偏移量,赋值mCurrX, mCurrY,再调用View.scrollBy()或者View.scrollTo()实现滚动,接着调用invalidate(),继续重绘,从而实现滚动效果。
在Scroller类中有一个成员变量Interpolator,这个变量是用来描述滚动过程中的动画效果。他的调用时在computeScrollOffset()中,通过Interpolator.getInterpolation()来控制移动时的偏移量。如果Interpolator为null,则通过Scroll内部viscousFluid()来控制偏移量。