最近在github看到一个开源项目——下拉刷新上拉加载listview组件。
地址:https://github.com/JosephPeng/XListView-Android
学习下来,我发现了一个特别有趣的东西,应该当时我在研究这个代码是给我造成成吨伤害的就是这个——Scroller。之前也有了解过这个东东,也用过。我实在没想到这个能对我造成困扰。特此记录下来,已做留念。哈哈
好了,不说废话了。代码早已饥渴难耐。有看过android源代码知道view.java里面有两个方法:scrollTo()和scrollBy()方法,从名字我们可以知道滚动view。
public void scrollTo(int x, int y) {
if (mScrollX != x || mScrollY != y) {
int oldX = mScrollX;
int oldY = mScrollY;
mScrollX = x; //更新mScrollX和mScrollY
mScrollY = y;
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
if (!awakenScrollBars()) {
invalidate();
}
}
}
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
这里说明下:mScrollX和mScrollY是view类中的两个成员变量,用来保存当前view的偏移量。也就是我们让view偏移位置就可以调用这两个方法。这样有什么问题呢?我们假设要从(0,0)坐标滚动(600,800)坐标。然后我们调用scrollTo(600,800),不要眨眼,你看到就是该view刷的一下就飞到了你所要的位置。这样的用户体验不能再差了。我们要view慢下来,不要急,在规定的时间到达就行了。这时Scroller出现了,对view老兄说:“hey,兄弟,我来帮你”。scroller开启的功能startScroll()
public void startScroll(int startX, intstartY, 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;
// This controls the viscous fluid effect (how much of it)
mViscousFluidScale = 8.0f;
// must be set to 1.0 (used in viscousFluid())
mViscousFluidNormalize = 1.0f;
mViscousFluidNormalize = 1.0f /viscousFluid(1.0f);
}
但我们很快发现被Scroller忽悠了,scroller中startScroll好像没送view到指定地点。这其中究竟有什么阴谋,它的背后又想隐藏什么?startScroll这个方法只是保存了一些变量而已,其他什么都没做。其实它还有帮手,一般会在这后面调用invalidate();这里大家就清楚了view将重绘dispatchDraw,这个函数过长,这里不贴源代码,在diapatchDraw里接着调用drawChild()函数。drawChild里面会调用一个computeScroll方法。但发现view类中此方法是个空方法,也就是需要我们自己重写。我们就是在这个方法控制view的偏移。
@Override
publicvoid computeScroll() {
if(mScroller.computeScrollOffset()) {
if(mScrollBack == SCROLLBACK_HEADER) {
/**
* computeScrollOfferset此方法就是产生动画
* 而是通过getCurrentY参数设置可见headerview高度
*/
mHeaderView.setVisiableHeight(mScroller.getCurrY());
}else {
/**
* 同上 只需要一个从bottomMargin~0不断变小的值
*/
Log.i("computeScroll","scrollerX="+mScroller.getCurrX()+"scrollerY="+mScroller.getCurrY());
mFooterView.setBottomMargin(mScroller.getCurrY());
}
postInvalidate();
invokeOnScrolling();
}
super.computeScroll();
}
public boolean computeScrollOffset() {
if (mFinished) {
return false;
}
int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() -mStartTime);
if (timePassed < mDuration) {
switch (mMode) {
case SCROLL_MODE:
float x = (float)timePassed *mDurationReciprocal;
if (mInterpolator == null)
x = viscousFluid(x);
else
x =mInterpolator.getInterpolation(x);
mCurrX = mStartX + Math.round(x* mDeltaX);
mCurrY = mStartY + Math.round(x* mDeltaY);
break;
case FLING_MODE:
float timePassedSeconds =timePassed / 1000.0f;
float distance = (mVelocity *timePassedSeconds)
- (mDeceleration *timePassedSeconds * timePassedSeconds / 2.0f);
mCurrX = mStartX +Math.round(distance * mCoeffX);
// Pin to mMinX <= mCurrX<= mMaxX
mCurrX = Math.min(mCurrX,mMaxX);
mCurrX = Math.max(mCurrX,mMinX);
mCurrY = mStartY +Math.round(distance * mCoeffY);
// Pin to mMinY <= mCurrY<= mMaxY
mCurrY = Math.min(mCurrY,mMaxY);
mCurrY = Math.max(mCurrY,mMinY);
break;
}
}
else {
mCurrX = mFinalX;
mCurrY = mFinalY;
mFinished = true;
}
return true;
}
我们可以看出此函数就是通过当前动画时间计算当前偏移值currX和currY。返回值滚动是否结束。所以我们在computeScroll调用scrollTo(getCurrX(),getCurrY())实现平滑滚动。
至此view滚动流程原理分析完了。但跟我说的有毛线关系啊。对啊,说了这么还没到高潮部分。接下来讲我很感兴趣的地方。
这个感兴趣的地方是这里的滚动压根没用到上面所说的scrollTo()和scrollBy().我们知道了Scroller类只是保存了一些变量。那我们也可以利用这些变量做其他事,比如说利用currY设置listview的header的高度
public void computeScroll() {
if(mScroller.computeScrollOffset()) {
if(mScrollBack == SCROLLBACK_HEADER) {
/**
* 并不是真正意义上的滚动没用scrolltoor scrollby 一直在这犯错了。。。
* 而是通过getCurrentY参数设置可见headerview高度
*/
mHeaderView.setVisiableHeight(mScroller.getCurrY());
}else {
/**
* 同上 只需要一个从bottomMargin~0不断变小的值
*/
Log.i("computeScroll","scrollerX="+mScroller.getCurrX()+"scrollerY="+mScroller.getCurrY());
mFooterView.setBottomMargin(mScroller.getCurrY());
}
postInvalidate();
invokeOnScrolling();
}
super.computeScroll();
}
这样就不会对着下面代码看着官方文档都流泪了,什么Y正值向上滚动,负值向下滚动,都是浮云。
mScroller.startScroll(0, bottomMargin, 0,-bottomMargin,
SCROLL_DURATION);
mScroller.startScroll(0, height, 0,finalHeight - height,
SCROLL_DURATION);