我们知道想把一个View偏移至指定坐标(x,y)处,利用scrollTo()方法直接调用就OK了,但我们不能忽视的是,该方法本身来的的副作用:非常迅速的将View/ViewGroup偏移至目标点,而没有对这个偏移过程有任何控制,对用户而言可能是不太友好的。于是,基于这种偏移控制,Scroller类被设计出来了,该类的主要作用是为偏移过程制定一定的控制流程(后面我们会知道的更多),从而使偏移更流畅,更完美。
getScrollX, Return the scrolled left position of this view. This is the left edge of the displayed part of your view,in pixels.
view实际内容占的大小(画布上画的内容)可能远远超过其显示区域的大小(view布局大小),ScrollX,是当前显示区域的左边界在画布坐标系中的位置。
画布坐标系也是左上角为原点
getScrollY, 同上是显示区域的上边界在画布坐标系中的位置。
scrollBy,scrollTo,移动画布,在画布坐标系上移动x,y轴位置,调整显示区域。
scrollBy(20)相当于画布向左移动了20px
scrollX 加正数表示向左移动
scrollY加正数表示向上移动
模拟Scroller类的实现功能:
假设从上海做动车到武汉需要10个小时,行进距离为1000km ,火车速率200/h 。采用第一种时间控制方法到达武汉的
整个配合过程可能如下:
我们每隔一段时间(例如1小时),计算火车应该行进的距离,然后调用scrollTo()方法,行进至该处。10小时过完后,
我们也就达到了目的地了。
public class Scroller { private int mStartX; //起始坐标点 , X轴方向 private int mStartY; //起始坐标点 , Y轴方向 private int mCurrX; //当前坐标点 X轴, 即调用startScroll函数后,经过一定时间所达到的值 private int mCurrY; //当前坐标点 Y轴, 即调用startScroll函数后,经过一定时间所达到的值 private float mDeltaX; //应该继续滑动的距离, X轴方向 private float mDeltaY; //应该继续滑动的距离, Y轴方向 private boolean mFinished; //是否已经完成本次滑动操作, 如果完成则为 true //构造函数 public Scroller(Context context) { this(context, null); } public final boolean isFinished() { return mFinished; } //强制结束本次滑屏操作 public final void forceFinished(boolean finished) { mFinished = finished; } public final int getCurrX() { return mCurrX; } /* Call this when you want to know the new location. If it returns true, * the animation is not yet finished. loc will be altered to provide the * new location. */ //根据当前已经消逝的时间计算当前的坐标点,保存在mCurrX和mCurrY值中 public boolean computeScrollOffset() { if (mFinished) { //已经完成了本次动画控制,直接返回为false return false; } int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime); if (timePassed < mDuration) { switch (mMode) { case SCROLL_MODE: float x = (float)timePassed * mDurationReciprocal; ... mCurrX = mStartX + Math.round(x * mDeltaX); mCurrY = mStartY + Math.round(x * mDeltaY); break; ... } else { mCurrX = mFinalX; mCurrY = mFinalY; mFinished = true; } return true; } //开始一个动画控制,由(startX , startY)在duration时间内前进(dx,dy)个单位,即到达坐标为(startX+dx , startY+dy)出 public void startScroll(int startX, int startY, int dx, int dy, int duration) { mFinished = false; mDuration = duration; mStartTime = AnimationUtils.currentAnimationTimeMillis(); mStartX = startX; mStartY = startY; mFinalX = startX + dx; mFinalY = startY + dy; mDeltaX = dx; mDeltaY = dy; ... } }
其中比较重要的两个方法为:
public boolean computeScrollOffset()
函数功能说明:根据当前已经消逝的时间计算当前的坐标点,保存在mCurrX和mCurrY值中
public void startScroll(int startX, int startY, int dx, int dy, int duration)
函数功能说明:开始一个动画控制,由(startX , startY)在duration时间内前进(dx,dy)个单位,到达坐标为 (startX+dx , startY+dy)处。
computeScroll()方法介绍
为了易于控制滑屏控制,Android框架提供了 computeScroll()方法去控制这个流程。在绘制View时,会在draw()过程调用该
方法。因此, 再配合使用Scroller实例,我们就可以获得当前应该的偏移坐标,手动使View/ViewGroup偏移至该处。该方法位于ViewGroup.java类中
第一、调用Scroller实例去产生一个偏移控制(对应于startScroll()方法),手动调用invalid()方法去重新绘制。
第二、ViewGroup重绘时会调用computeScroll(), 剩下的就是在 computeScroll()里根据当前已经逝去的时间,获取当前 应该偏移的坐标(由Scroller实例对应的computeScrollOffset()计算而得),
第三、当前应该偏移的坐标,调用scrollBy()方法去缓慢移动至该坐标处。
public void startMove() { // 使用动画控制偏移过程 , 3s内到位 mScroller.startScroll((curScreen - 1) * getWidth(), 0, getWidth(), 0, 3000); // 其实点击按钮的时候,系统会自动重新绘制View,我们还是手动加上吧。 invalidate(); // 使用scrollTo一步到位 // scrollTo(curScreen * MultiScreenActivity.screenWidth, 0); }
重写ViewGoup的computeScroll方法:
@Override public void computeScroll() { // 如果返回true,表示动画还没有结束 // 因为前面startScroll,所以只有在startScroll完成时 才会为false if (mScroller.computeScrollOffset()) {// 产生了动画效果,根据当前值 每次滚动一点 scrollTo(mScroller.getCurrX(), mScroller.getCurrY());// 此时同样也需要刷新View ,否则效果可能有误差 postInvalidate(); } }
public void startScroll(int startX, int startY, int dx, int dy, int duration)
finish状态后,getCurrX得到的值是 startX + dx,
原理即根据插值器和duration,将dx的值分成小片加到startX上,同理getCurrY
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
一个替代依靠invalide 和 computeScroll 来使用 Scroller的方法:
private class FlingRunnable implements Runnable { private Scroller mScroller; public FlingRunnable() { mScroller = new Scroller(getContext()); } public void startScroll(int distance) { //mScroller.startScroll(0, distance, 0, -distance, 1000); //调用scroller的方法 post(this); } public void stop() { removeCallbacks(this); mScroller.forceFinished(true); } public void run() { final Scroller scroller = mScroller; boolean more = scroller.computeScrollOffset(); if (more) { // 在这里使用Scroller的x,y值 post(this); } else { stop(); } } }