转载须注明出处,谢谢!http://blog.csdn.net/chdjj
这篇文章我将从源码的角度深入分析Scroller类。在阅读的时候,建议大家打开源码对照着看,否则可能看的云里雾里。
一.Scroller的用途
熟悉android的同学必然对Scroller不陌生,Scroller是一个
弹性滑动对象,可以制作很多酷炫的滑动效果,Lancher中的滑屏效果就有使用到Scroller。
我们知道,View类中的scrollTo和scrollBy方法提供了滑动操作,但是这种滑动操作是瞬间完成的,就是说你为scrollTo提供终点坐标,该方法只要一调用,我们就会发现已经滚动到目的地了,这种方式很显然用户体验是不好的,因而android工程师为我们封装了Scroller类,这个类可以为View带来
缓慢移动的效果。
具体使用方式通常是
通过在你自定义的View中调用Scroller的startScroll并刷新视图,另外需重写computeScroll方法(刷新视图过程中会调用此方法),在该方法中调用
Scroller的computeScrollOffset计算应该滚动到的位置,然后使用scrollTo滚动到该位置,再调用invalidate刷新,就可以实现滚动效果了(不了解的同学建议先去熟悉Scroller用法)。
Scroller使用示例(代码片段):
对这些变量有个大致印象之后,我们就开始看startScroll源码了:
- public class MyView extends LinearLayout
- {
- private Scroller mScroller;
- ... ...
- private void smoothScrollTo(int destX,int destY)
- {
- int scrollX = getScrollX();
- int scrollY = getScrollY();
- int deltaX = destX-scrollX;
- int deltaY = destY-scrollY;
- mScroller.startScroll(scrollX,scrollY,deltaX, deltaY, 1000);
- invalidate();
- }
- @Override
- public void computeScroll()
- {
- if(mScroller != null)
- {
- if(mScroller.computeScrollOffset())
- {
- scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
- Log.d(TAG,"scrollX="+getScrollX()+",scrollY="+getScrollY());
- postInvalidate();
- }
- }
- }
- }
public class MyView extends LinearLayout
{
private Scroller mScroller;
... ...
private void smoothScrollTo(int destX,int destY)
{
int scrollX = getScrollX();
int scrollY = getScrollY();
int deltaX = destX-scrollX;
int deltaY = destY-scrollY;
mScroller.startScroll(scrollX,scrollY,deltaX, deltaY, 1000);
invalidate();
}
@Override
public void computeScroll()
{
if(mScroller != null)
{
if(mScroller.computeScrollOffset())
{
scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
Log.d(TAG,"scrollX="+getScrollX()+",scrollY="+getScrollY());
postInvalidate();
}
}
}
}
二.ScrollTo、ScrollBy、getScrollX、getScrollY方法分析
在分析Scroller源码之前,我们必须先了解ScrollTo,ScrollBy,getScrollX,getScrollY这几个方法的作用。这四个方法都是View类提供的,这点先明确。
我们先分析getScrollX和getScrollY方法,查看源码实现:
- /**
- * Return the scrolled left position of this view. This is the left edge of
- * the displayed part of your view. You do not need to draw any pixels
- * farther left, since those are outside of the frame of your view on
- * screen.
- *
- * @return The left edge of the displayed part of your view, in pixels.
- */
- public final int getScrollX() {
- return mScrollX;
- }
- /**
- * Return the scrolled top position of this view. This is the top edge of
- * the displayed part of your view. You do not need to draw any pixels above
- * it, since those are outside of the frame of your view on screen.
- *
- * @return The top edge of the displayed part of your view, in pixels.
- */
- public final int getScrollY() {
- return mScrollY;
- }
/**
* Return the scrolled left position of this view. This is the left edge of
* the displayed part of your view. You do not need to draw any pixels
* farther left, since those are outside of the frame of your view on
* screen.
*
* @return The left edge of the displayed part of your view, in pixels.
*/
public final int getScrollX() {
return mScrollX;
}
/**
* Return the scrolled top position of this view. This is the top edge of
* the displayed part of your view. You do not need to draw any pixels above
* it, since those are outside of the frame of your view on screen.
*
* @return The top edge of the displayed part of your view, in pixels.
*/
public final int getScrollY() {
return mScrollY;
}
getScrollX和getScrollY方法返回的是mScrollX和mScrollY变量。这两个变量是什么呢?
这里我直接告诉大家,
mScrollX和mScrollY指的是视图内容相对于视图原始起始坐标的偏移量,mScrollX和mScrollY的默认值为0,因为默认是没有偏移的。另外需注意偏移量的正负问题,因为是相对视图起始坐标的,所以如果你是
向右偏移那么mScrollX应该是负数,而向左偏移mScrollX为正数。再举个例子,比如你定义了一个ImageView,其左上角的坐标为(100,80),此时mScrollX和mScrollY值都为0(没有偏移),现在你要把该ImageView移到(120,100)处,也就是右下方,那么你的mScrollX应该是100-120=-20,mScrollY应该是80-100=-20,这下你该明白了吧。
明白了这个问题,scrollBy和scrollTo也就不在话下了,我们查看源码:
- 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();
- }
- }
- }
- public void scrollBy(int x, int y) {
- scrollTo(mScrollX + x, mScrollY + y);
- }
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();
}
}
}
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
先看scrollTo方法,它首先判断x,y方向的偏移量(即mScrollX,mScrollY)是否和传进来的偏移量(即x,y)相同,如果相同那么直接返回,否则我们将更新mScrollX和mScrollY,并且通知界面发生改变请求重绘。通过这种方式就可以实现滚动效果了,只是是瞬间移动的。再看这个scrollBy,直接调用的是scrollTo,根据参数很容易发现,这个方法的作用是在当前偏移基础上,再继续偏移(x,y)单位。需要注意的是这两个方法的
参数是偏移量而不是实际位置哦!
这里还有一点需要注意,那就是你
调用一个View的scrollTo方法进行滚动时,滚动的并不是该View本身,而是该View的内容。比如你要对一个Button进行滚动的话,应该在Button外面包一个ViewGroup,然后调用ViewGroup的scrollTo方法。这一点也很重要,大家需要注意下!
三.Scroller源码分析
Scroller的精华在于computeScrollOffset和startScroll方法,所以我们重点分析这两个方法。
分析之前,先看这个类中定义的一些变量:
- private int mMode;//模式,有SCROLL_MODE和FLING_MODE
- private int mStartX;//起始x方向偏移
- private int mStartY;//起始y方向偏移
- private int mFinalX;//终点x方向偏移
- private int mFinalY;//终点y方向偏移
- private int mCurrX;//当前x方向偏移
- private int mCurrY;//当前y方向偏移
- private long mStartTime;//起始时间
- private int mDuration;//滚动持续时间
- private float mDurationReciprocal;//持续时间的倒数
- private float mDeltaX;//x方向应该滚动的距离,mDeltaX=mFinalX-mStartX
- private float mDeltaY;//y方向应该滚动的距离,mDeltaY=mFinalY-mStartY
- private boolean mFinished;//是否结束
private int mMode;//模式,有SCROLL_MODE和FLING_MODE
private int mStartX;//起始x方向偏移
private int mStartY;//起始y方向偏移
private int mFinalX;//终点x方向偏移
private int mFinalY;//终点y方向偏移
private int mCurrX;//当前x方向偏移
private int mCurrY;//当前y方向偏移
private long mStartTime;//起始时间
private int mDuration;//滚动持续时间
private float mDurationReciprocal;//持续时间的倒数
private float mDeltaX;//x方向应该滚动的距离,mDeltaX=mFinalX-mStartX
private float mDeltaY;//y方向应该滚动的距离,mDeltaY=mFinalY-mStartY
private boolean mFinished;//是否结束
对这些变量有个大致印象之后,我们就开始看startScroll源码了:
- public void startScroll(int startX, int startY, int dx, int dy) {
- startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
- }
- 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;
- }