一开始看到网上有人说这两个类是一样的,但是如果一样的android为什么又要提供两个类呢?那么,他们有什么区别呢。让我们先来认识下这两个类。通过源码的比较我们会发现,OverScroller和Scroller这两个类都属于Scrollers类。而Scrollers类是一个滚动封装类,它可以实现view的平滑移动,或者通过插值器先加速再减速,或者先减速再加速。而不是一个瞬移的效果,当我们需要view滚动的时候,它就会帮我们计算出滚动的位置。那么滚动的位置有了,view要什么时候滚动?滚动多少偏移量? 这样我们就需要使用view的一个回调函数computeScroll(),我们先来看看computeScroll()方法源代码:
/**
* Called by a parent to request that a child update its values for mScrollX
* and mScrollY if necessary. This will typically be done if the child is
* animating a scroll using a {@link android.widget.Scroller Scroller}
* object.
*/
public void computeScroll() {
}
它是一个空的回调函数,那这个方法就可以让我们重写来做一些操作。我们就来看下注释。当我们需要使用Scrollers滑动的时候,父视图会在子视图绘制的时候,在draw这个方法里面调用这个computeScroll,从而改变子视图的mScrollX和mScrollY值。那么,现在我们就从源码来看看父视图是在哪里调用的这个方法:
首先从View的draw方法出发:
// Step 4, draw the children
dispatchDraw(canvas);
继续go dispatchDraw;
/**
* Called by draw to draw the child views. This may be overridden
* by derived classes to gain control just before its children are drawn
* (but after its own view has been drawn).
* @param canvas the canvas on which to draw the view
*/
protected void dispatchDraw(Canvas canvas) {
}
是一个空函数,ViewGroup用来绘制子视图的函数。继续go
ViewGroup.java重写dispatchDraw;
// Draw any disappearing views that have animations
if (mDisappearingChildren != null) {
final ArrayList disappearingChildren = mDisappearingChildren;
final int disappearingCount = disappearingChildren.size() - 1;
// Go backwards -- we may delete as animations finish
for (int i = disappearingCount; i >= 0; i--) {
final View child = disappearingChildren.get(i);
more |= drawChild(canvas, child, drawingTime);
}
}
我们看到这里调用了drawChild();
/**
* Draw one child of this View Group. This method is responsible for getting
* the canvas in the right state. This includes clipping, translating so
* that the child's scrolled origin is at 0, 0, and applying any animation
* transformations.
*
* @param canvas The canvas on which to draw the child
* @param child Who to draw
* @param drawingTime The time at which draw is occurring
* @return True if an invalidate() was issued
*/
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
继续看View里面的draw(Canvas canvas, ViewGroup parent, long drawingTime)方法:
computeScroll();
我们可以看到在这里调用了这个方法。
我们再来看看OverScroller,OverScroller比Scroller更加完善。它比Scroller多了几个方法。
所以在API上面来说,它们两是一样的。
所以下面直接来看看OverScroller类的一些常用API。
public class JellyTextView extends TextView{
private OverScroller mScroller;
private float lastX;//记录view停止滑动的X位置
private float lastY;//记录view停止滑动的Y位置
private float startX;//记录最初始位置X坐标
private float startY;//记录最初始位置Y坐标
public JellyTextView(Context context, AttributeSet attrs) {
super(context, attrs);
//实例化对象OverScroller
mScroller = new OverScroller(context, new BounceInterpolator());
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
//第一次获取静止的坐标
lastX = event.getRawX();
lastY = event.getRawY();
return true; //注意,这里是让事件不要被拦截,继续触发ACTION_MOVE,ACTION_UP
case MotionEvent.ACTION_MOVE:
//移动的偏移量
float disX = event.getRawX() - lastX ;
float disY = event.getRawY() - lastY ;
//完成控件的位置移动
offsetLeftAndRight((int)disX);
offsetTopAndBottom((int)disY);
//获取移动后的坐标
lastX =event.getRawX() ;
lastY = event.getRawY() ;
break;
case MotionEvent.ACTION_UP:
//移动view到原位置,同时触发computeScroll();
mScroller.startScroll((int)getX(), (int)getY(), -(int) (getX() - startX),
-(int) (getY() - startY));
Log.d("zgx", "action_up"+getX());
invalidate();
break;
}
return super.onTouchEvent(event);
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) { //判断当前的滑动动作是否完成的
setX(mScroller.getCurrX());
setY(mScroller.getCurrY());
invalidate();
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
Log.d("zgx", "onSizeChanged");
startX = getX();
startY = getY();
}
public void spingBack() {
/*参数,前两个是开始位置,是绝对坐标,minX和maxX是用来设定滚动范围的,
也是绝对坐标范围,如果startX不在这个范围里面,
比如大于maxX,就会触发computeScroll(),我们可以移动距离,
最终回弹到maxX所在的位置,并返回true,从而完成后续的滚动效果,比minX小的话,
就会回弹到minX,一样的道理。所以我们可以像上面代码里面一样,判断是否在范围内,在的话,
就invalidate()一下,触发滚动动画*/
if (mScroller.springBack((int) getX(), (int) getY(), 0, (int) getX(), 0,
(int) getY() - 100)) {
Log.d("TAG", "getX()=" + getX() + "__getY()=" + getY());
invalidate();
}
}
}
效果就是view会随着手指滑动轨迹滑动,手指松下,view回到原位置,并有回弹效果。这里的回弹并不是用spingBack()完成,而是通过startScroll()完成,只要设置好当前的位置和我们需要位移的距离,然后记住invalidate一下,我们就可以去computeScroll()里面实际的改变控件的位置了,通过getCurrX()就可以获取到如果当前滚动应该的位置。
其中,view还有一个颤抖的效果,这个怎么实现呢?也很简单,设置一个BounceInterpolation就可以了。