Scroller的原理与简单使用
Scroller是Android中处理滑动效果的核心类。它通过配合View所提供的scrollTo()和scrollBy()这两个方法,就能够使用多种多样的滑动效果。网上也有很多文章介绍关于Scroller的使用,那么在这篇文章中,我尽可能缩减篇幅将Scroller的原理和实现介绍一下。
1. scrollTo()和scrollBy()
首先介绍一下scrollTo()和scrollBy()这两个方法:
/**
* 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();
}
}
}
scrollTo()是让当前的View能够平移到所给定的位置,然后通过起始位置和结束位置的坐标,根据onScrolledChanged()方法的调用最终完成绘制。
/**
* 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);
}
而scrollBy()方法的实现是根据scrollTo()而完成的,唯一的区别在于scrollBy()是根据当前已经位移的坐标为基础再继续进行位移。也就是说scrollTo()实现的是输入参数的绝对滑动,而scrollBy()实现的是输入参数的相对滑动。
另外,scrollTo()和scrollBy()方法中都提到了mScrollX和mScrollY两个属性,这两个属性可以分别通过getScrollX()和getScrollY()获得。
mScrollXY.png如上图,我们得到以下结论:
(1)View的左边界位于View内容左边界的左侧时,mScrollX < 0;反之,mScrollX > 0。
(2)View的上边界超过View内容上边界时,mScrollY < 0;反之,mScrollY > 0。
2. Scroller的滑动偏移计算
通过阅读Scroller的源码我们发现,Scroller虽然作为View滑动的重要依据,但是却不能真正操纵View滑动,他是通过旧的坐标值和新的坐标值算出偏移距离,配合View的computeScroll()方法完成滑动操作。
/**
* 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;
}
startX - 起始滑动的X坐标
startY - 起始滑动的Y坐标
dx - X方向上的滑动偏移距离
dy - Y方向上的滑动偏移距离
duration - 完成滑动的时间
通过代码我们可以看出,Scroller的startScroll()方法作为View的滑动依据只是单纯的对于变量进行和一系列的赋值操作。
3. 实现滑动效果
通过上面的startScroll()方法的分析,我们了解到该方法只是针对View滑动偏移的计算和赋值工作,并没有真正的执行滑动效果。而真正实现滑动的是View类的computeScroll()方法完成的。
在源码中,我们可以看到View类的computeScroll()方法只是一个空实现,因此我们要完成滑动效果需要复写此方法。如下图:
QQ截图20160819145459.png在方法中,我们利用Scroller的computeScrollOffset()方法去判断是否View有滑动事件的发生,如果有,即可通过scrollTo()方法完成滑动偏移。
/**
* 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:
final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
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;
}
4. invalidate()
computeScroll()方法并非凭空实现的,它需要调用invalidate()去进行View的重新绘制,invalidate()的源码中有对于computeScroll()方法的调用。
网上关于Scroller的源码讲解也有很多,我也是借鉴别人的列子通过自己的理解完成的本文,因此不能完全说的上是原创。也希望大家能够理解。
献上代码
ScrollDemo
小结,通过Scroller我们可以实现很多弹性滑动的效果。