OverScroller类和Scroller类

一开始看到网上有人说这两个类是一样的,但是如果一样的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多了几个方法。

  1. isOverScrolled()
  2. springBack(int startX, int startY, int minX, int maxX, int minY, int maxY)
  3. fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY, int overX, int overY)
  4. notifyHorizontalEdgeReached(int startX, int finalX, int overX)
  5. notifyVerticalEdgeReached(int startY, int finalY, int overY)

所以在API上面来说,它们两是一样的。
所以下面直接来看看OverScroller类的一些常用API。

  1. computeScrollOffset() 这个就是来判断当前的滑动动作是否完成的,用法很单一,就是在computeScroll()里面来做判断滚动是否完成
  2. getCurrX() 这个就是获取当前滑动的坐标值,因为Scrollers只是一个辅助计算类,所以如果我们想获取滑动时的时时坐标,就可以通过这个方法获得,然后在computeScroll()里面调用
  3. getFinalX() 这个是用来获取最终滑动停止时的坐标,已过时
  4. isFinished() 用来判断当前滚动是否结束
  5. startScroll(int startX, int startY, int dx, int dy) 用来开始滚动,这个是很重要的一个触发computeScroll()的方法,调用这个方法之后,我们就可以在computeScroll里面获取滚动的信息,然后完成我们的需要。
  6. startScroll(int startX, int startY, int dx, int dy, int duration)带有滚动持续时间。
  7. fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY)如果你想实现滑动之后,布局能够根据移动速度,慢慢减速的话,就需要用这个来实现,这里需要加速度的参数,我们可以通过VelocityTracker这个类来获取。
    我们来自定义一个TextView来使用这些的方法
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就可以了。

你可能感兴趣的:(android应用)