View的滚动与Scroller
view的滚动相关的知识有scrollTo,scrollTo,Scroller,OverScroller。普通的滚动用scrollTo和scrollBy,这是没有过程的,直接跳到指定位置。要想慢慢滚过去(又称渐进式滑动,弹性滑动)得用Scroller和OverScroller。
scrollTo和scrollBy
View的scrollTo和scrollBy方法
public void scrollTo(int x,int y){
//如果当前偏移量变化
if(mScrollX!=x||mScrollY!=y){
int oldX=mScrollX;
int oldY=mScrollY();
//赋值偏移量
mScrollX=x;
mScrollY=y;
invalidateParentCaches();
//回调onScrollChanged方法
onScrollChanged(mScrollX,mScrollY,oldX,oldY);
if(!awakenScrollBars()){
postInvalidateOnAnimation();
}
}
}
public void scrollBy(int x,int y){
scrollTo(mScrollX+x,mScrollY+y);
}
Scroller
Scroller是一个帮助类,主要用来解决弹性滑动问题。下面是Scroller的常见用法
Scroller scroller=new Scroller(mContext);
//缓慢滚动到指定位置
private void smoothScrollTo(int destX,int destY){
int scrollX=getScrollX();
int deltaX=destX-scrollX;
//1000ms内滑动destX,效果就是慢慢移动
mScroller.startScroll(scrollX,0,detaX,0,1000);\
//invalidate方法会导致View的重绘,在View的draw方法中又会去调用computeScroll方法
//invalidate-draw-computeScroll
invalidate();
}
@Override
public void computeScroll(){
if(mScroller.computeScrollOffset()){
scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
//view第二次重绘
postInvalidate();
}
}
上面是Scroller典型的使用方法,当我们构造一个Scroller对象时并且调用它的startScroll方法时,Scroller内部其实什么也不做,它只是保存了我们传入的几个参数,这几个参数从startScroll的原型上就可以看出来,如下所示
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
// mMode 分两种方式 1.滑动:SCROLL_MODE 2. **加速度滑动:FLING_MODE**
mMode = SCROLL_MODE;
// 是否滑动结束 这里是开始所以设置为false
mFinished = false;
// 滑动的时间
mDuration = duration;
// 开始的时间
mStartTime = AnimationUtils.currentAnimationTimeMillis();
// 开始滑动点的X坐标
mStartX = startX;
// 开始滑动点的Y坐标
mStartY = startY;
// 最终滑动到位置的X坐标
mFinalX = startX + dx;
// 最终滑动到位置的Y坐标
mFinalY = startY + dy;
// X方向上滑动的偏移量
mDeltaX = dx;
// Y方向上滑动的偏移量
mDeltaY = dy;
// 持续时间的倒数 最终用来计算得到插值器返回的值
mDurationReciprocal = 1.0f / (float) mDuration;
}
注意这里的滑动是View的内容滑动而非View本身位置的改变,可以看到,仅仅调用sartScroll方法是无法让View滑动的,因为他内部并没有做滑动相关的事,那Scroller到底是如何让View实现弹性滑动的呢?答案就是startScroll方法下面的invalidate方法。invalidate会导致View的重绘,在View的draw方法中又会去调用computeScroll方法,computeScroll方法在View中是一个空实现,因此需要我们自己去实现,正因为这个computeScroll方法,View才实现弹性滑动。这看起来还是很抽象其实是这样的:当View重绘后会在draw方法中调用computeScroll,而computeScroll方法又会去向Scroller获取当前的scrollX和scrollY,然后通过scrollTo方法实现滑动;接着又调用 postInvalidate()方法进行第二次重绘,这一次重绘的过程和第一次一样,又会导致computeScroll方法被调用;然后继续向Scroller方法获取当前的scrollX和scrollY,并通过scrollTo方法滑动到新位置,如此反复直到整个过程结束。
我们再看看Scroller的computeScrollOffset方法实现就豁然开朗了
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:
......
return true;
}
以上代码中第8行和第9行设置的mCurrX和mCurrY即为以上scrollTo的两个参数,表示本次滑动的目标位置。computeScrollOffset方法返回true表示滑动过程还未结束,否则表示结束。
通过以上的分析,我们大概了解了Scroller实现弹性滑动的原理:invaldate方法会导致View的draw方法被调用,而draw会调用computeScroll方法,因此重写了computeScroll方法,computeScroll会调用Scroller的computeScrollOffset并且掉一次scrollto和postInvalidate。 而computeScrollOffset方法会根据时间的流逝动态的计算出很小的一段时间应该滑动多少距离。然后把得到的数据作为scrollTo的参数,然后准备下一次刷新。
也就是把一次滑动拆分成无数次小距离滑动从而实现“弹性滑动”。
@Override
public void computeScroll(){
if(mScroller.computeScrollOffset()){
scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
//view第二次重绘
postInvalidate();
}
}
可滚动的View有AbsListView,ScrollView,HorizontalScrollView,ViewPager,TextView
大部分的Scroller都是用上边的方法来实现的,但是AbsListView很奇怪,他没有重写computeScroll,那他是怎么做到刷新的呢?
其实他主要通过postOnAnimation(Runnable action),这个函数作用是在动画的下一帧执行此action的run方法,在run方法内部一方面调整自己的位置,另一方面调用postOnAnimation(this);其实结果跟使用computeScroll是类似的。
OverScroller
OverScroller在Scroller的基础上,可以让view的内容在滚动的过程中超出边界然后再弹回,比如滚到内容边界之后,还会在小滚一段距离,然后再弹回。
OverScroller相对Scroller多了以下API
其中fling是非常重要的函数,滑动速度够快,手指离开之后,view还会滚动,这时状态叫fling,下边的fling函数就是此时用 的。
比如ScrollView内手指快滑触发fling,smoothScrollBy使用startScroll。一般来说简单调用fling即可,惯性滑动,触底反弹的功能都在里头。
isOverScrolled()
//如果超出边界,那就回弹
springBack(int startX, int startY, int minX, int maxX, int minY, int maxY)
//手指快滑的时候触发此函数,跟Scroller内的startScroll类似
fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY, int overX, int overY)
//达到边界,触发某些事件
notifyHorizontalEdgeReached(int startX, int finalX, int overX)
notifyVerticalEdgeReached(int startY, int finalY, int overY)
官方建议使用OverScroller,因为OverScroller可以知道何时碰到边界了https://developer.android.com/training/gestures/scroll.html
参考文献
http://blog.csdn.net/zhaokaiqiang1992/article/details/43986365
android艺术开发探索