滑动的概念,很不好理解,想通过这一篇博文,让自己和不明白的人加深下理解,希望我的语言能够使你明白:
首先我们要理解,为什么使用滑动?
因为手机屏幕,给我们的视图就那么大,我们看到的视图有限,要想在固定的视图,去展示更多的东西,我们就需要去滑动,把隐藏的那部分变为可视视图。我们可以把能展示的大小看成一个很大的画板,我们展示看到的区域是里面的一部分,当我们需要看到更多的时候,就需要去滑动画板,让自己看到那些被隐藏的。
然后,还有一个很重要的概念,就是我们滑动的是View内容,而并不是view本身,一个特别好理解的例子就是listview,我们把listview设置宽和高都沾满屏幕,当我们上下滑动时,我们滑动的是listview里面内容,而不是listview本身,listview还是铺满整个屏幕当我们滑动的时候,所以当我们想一个button发生位置改变时,要调用的不是button.scroolTo(),或者button.scroolBy(),而是应该调用它的父控件,layout.scroolTo()或者layout.scrooBy()。此时,button就是layout中的内容。当调用button的scroolTo或者button.scroolBy时,button里面的内容会被移动看不见,但button还在最初的位置。
区分scroolTo和scroolBy的区别:
看scroolTo的源码:
/** * 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();
}
}
}
参数x,y是想要滚动的偏移x,和y值,mScroll、mScrollY表示的是离视图起始位置的偏移量,意思就是当你滚动了,这个才有值,如果没有发生滚动,这两个值就是0。
scrollTo的滚动是相对于这个view的起始位置,只执行一次,因为起始位置就一个,所以,当执行一次后,在取调用这个view的scrollTo就不在起作用了,第一次调用后,oldX,oldY会被赋值成想x,y的值,当第二次调用时,x,y值不发生变化时,因为mScrollX的值就是x的值,mScrollY的值就是y的值,所以,在if(mScrollX !=x || mScrollY !=Y)这句下面不会被执行,那么onScrollChanged()方法就不会被执行,因此,当第二次调用时,如果x,y值不变,那么就不会发生滚动。
看scrollBy的源码:
/** * 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这个方法,但里面的x,y值每次调用都会发生改变,参数x,y同样是想要滚动的偏移量,但mScrollX+x,mScrollY+y就相当于在上一次基础上,因为现在的mScrollX,mScrollY的值就是上一次滚动后的view的x,y的偏移量。
在举个例子,我要让你和我下次看到后不会不明白为之:
mScrollX,mScrolly不滚动时,值为0.
当我们调用scrollTo(20,20)时,这时,mScrollX的值就是20,mScrollY的值也是20,当我们再次调用scrollBy(20,20)时,我们的mScrollX的值就是40,mScrollY的值也是40。
但当我们再一次执行,strollTo(20,20)时,mScrollX的值就是20,mScrollY的值也是20,而执行同样的scrollBy(20,20)时,在mScrollX,和mScrollY等于40的情况下,这时mScrollX就是60,mScrollY也是60。简而言之,mScrollTo执行后,mScrollX就是x的值,mScrollY值就是y的值,而scrollBy执行后,是在原来的基础上加上x,y值。注意一点,这里的正值是水平方向是想做,竖直方向是像上,向右和向下则是负值。够明白了吧,我对我自己说…..
scrollTo和scrollBy都是瞬时滑动的,当我们需要弹性滑动,有个滑动过程时,就需要用到Scroller。
一般我们代码中这么写:
Scroller mScroller=new Scroller(mContext);
private void smoothScrollTo(int destX,int destY){
int scrollX=getScrollX();
int deltaX=destX-scrollX;
mScroller.startScroll(scrollX,0,deltaX,0,2000);
invalidate();
}
@Override
public void computeScroll() {
if(mScroller.computeScrollOffset()){
scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
postInvalidate();
}
}
参数destX,destY表示要滚动的偏移量, startScroll方法中的的5个参数的意思是:
mScroller.startScroll(int startX,int startY,int dx,int dy,int duration);
startX:起始的x方向偏移量,或者说目前的x方向偏移量
startY:起始的y方向偏移量,或者说目前的y方向偏移量
dx:x方向要滚动的偏移量
dy:y方向要滚动的偏移量
duration:多长时间完成这个过程 单位ms
我们打开startScroll这个方法,代码如下:
/** * 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;
}
从这里我们可以看到,调用startScroll方法是无法让view滑动的,方法里只是去给一些变量去赋值,里面并没有滑动的相关操作,那么view怎么去滑动呢,答案是在它下面的invalidate这个方法。invalidate这个方法会将view重新绘制,在重新绘制的过程中,我们会调用View的draw方法,在draw方法里面我们会调用dispatchDraw()这个方法,在dispatchDraw()方法里面我们会调用drawChild()方法,在这个方法里就有我们要用的computerScroll()这个方法,而computerScroll方法在View中是一个空的方法,需要我们自己去实现,也正是因为有了computerScroll方法,我们的view才能去滚动。
整个过程是这样的:当View重绘后,会调用draw方法里的computeScroll方法,而computeScroll又会去向Scroller获取当前的scrollX,scrollY的值,然后通过scrollTo方法去实现滑动,接着有在此调用postInvalidate方法进行第二次重绘,这次有会在draw方法中调用computeScroll方法,,再一次通过Scroller去获得scrollX,scrollY的值并通过scrollTo去滑动,如此反复,直到在固定的时间内滑动完成。
看一下scrollX、scrollY值是怎样改变的,computeScrollOffset源码如下:
/** * 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;
}
方法的大意是通过时间流逝的百分比去计算偏移量的改变的百分比,从而计算出当前偏移量的值,最后的返回值true代表整个滑动过程位结束,false代表滑动结束。可见Scroller本身并不能让View去滑动,一定要配合computeScroll整个方法才可以,经过许多次重绘,每次重绘又去调用scrollTo这个滚动方法,去一点点改变scrollX、scrollY的值,去实现滑动,在规定的时间内,每一次微小的滑动过程累加,就是弹性滑动的整个过程。
参考:
Scroll原理-附ScrollView源码分析
任玉刚老师android开发艺术探索中的View滑动