Android Scroller

Scoller 弹性滑动对象,用于实现View的弹性滑动。在说弹性滑动之前,我们先来看一下View常用的滑动方式。

  • 第一种:通过View本身提供的scrollTo / scrollBy 方法来实现滑动。
  • 第二种:通过动画给View 施加平移效果来实现滑动。
  • 第三种:通过改变View的LayoutParams 使得View 重新布局从而实现滑动

1.使用scrollTo /scrollBy

View提供了专门的方法来实现这个功能,那就是scrollerTo 和 scrollerBy,我们先来看一下这俩个方法的实现

   /**设置View的滚动位置,
     *这会调用 onScrollChanged(int,int,int,int)并且视图会失效
     *
     * @param x 要滚动到的X位置
     * @param y 要滚动到的y位置
     */
    public void scrollTo(int x,int y){
        if(mScrollX !=x || mScrollY !=y){
            int oldX= mScrollX; //当前控件x,y的位置,移动前
            int oldY= mScrollY;
            mScrollX=x;//x,y将要移动到的位置,传入值
            mScrollY=y;
            invalidateParentCaches();
            //进行滑动改变
            onScrollChanged(mScrollX,mScrollY,oldX,oldY);
            if(!awakenScrollBars()){
                postInvalidateOnAnimation();
            }
        }
    }


     /**
     * 移动视图的滚动位置。
     * 这将导致调用onScrollChanged(int,int,int,int)
     * 并且视图会失效
     * 
     * @param x 水平滚动的像素数量
     * @param y 垂直滚动的像素数量
     */
    public void scrollBy(int x, int y) {
        scrollTo(mScrollX + x, mScrollY + y);
    }

从源码可以看出:scrollBy 也调用了scrollTo方法,只不过

  • scrollTo实现了基于所传参数的滑动
  • scrollBy实现了基于当前位置的相对滑动

在滑动过程中

  • mScrollX:它的值总是等于View左边缘和View内容左边缘在水平方向上的距
  • mScrollY:它的值总是等于View上边缘和View内容上边缘在竖直方向上的距离

View边缘是指View的位置,由四个顶点组成
View内容边缘是指View中内容的边缘

即只能将View的内容进行移动,并不能将View本身进行移动

mScrollX,mScrollY 的单位是像素,可以通过getScrollX / getScrollY 获得。并且
当View左边缘在View内容左边缘的右边时,mScrollX 为正值,反之为负
当View上边缘在View内容上边缘的下边时,mScrollY为正值,反之为负

从传值角度来讲,如果你想让

  • View从左往右移动,那么mScrollX传负值,即scrollTo / scrollBy 第一个参数传负值

  • View从上往下移动,那么mScrollY传负值,即scrollTo / scrollBy 第而个参数传负值

反之,为正值。如下图
这里写图片描述

贴上代码
这里写图片描述

2.使用动画

通过动画我们能让一个View进行平移,而平移就是滑动,使用动画来移动View,主要是操作View的translationX和 translationY 属性,既可以采用传统的View动画,也可以采用属性动画,如果采用属性动画的话,为了兼容3.0一下版本,需要采用开源动画库nineoldandroids。动画相关可以看这里

compile'com.nineoldandroids:library:2.4.0'

采用View动画的代码,如下

<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:fillAfter="true"
    >
    <!--fillAfter 为true表示 保留动画后的状态-->
    <!--interpolator 插值器-->

    <translate
        android:duration="3000"
        android:toXDelta="200"
        android:toYDelta="0"
        android:fromXDelta="0"
        android:fromYDelta="0"
        android:interpolator="@android:anim/linear_interpolator"
        >
    </translate>
</set>

使用View动画的缺点:

View 动画是对View 的影像做操作,它并不能正真改变View的位置参数,包括宽/高。假如我们通过View动画将一个Button向右移动100px,并且这个View设置的有单击事件,你会惊奇的发现,单击新位置无法触发onClick事件,而单击原始位置则可以触发onClick事件。因为不管Button怎么做变换,但是他的位置信息(四个顶点和宽 / 高)并不会随着动画而改变,因此在系统眼里,这个Button没有发生任何改变,它的真身仍然在原始位置

上述平移如果采用属性动画,就更简单了,

 ObjectAnimator.ofFloat(tv2,"translationX",0,100)
 .setDuration(3000).start();

贴上完整代码

/**
* View 的滑动 2
* 动画
* 操作简单,主要用于没有否交互的View 和实现复杂的动画效果
*/
tv2=findViewById(R.id.tv2);
tv2.setOnClickListener(new View.OnClickListener() {
  @Override
public void onClick(View v) {

 tv2.startAnimation(AnimationUtils.loadAnimation(MainActivity.this,R.anim.translate_1));
 //或
  ObjectAnimator.ofFloat(tv2,"translationX",0,100).setDuration(3000).start();
 }
});

3.改变布局参数

改变布局参数,即改变LayouParams.比如我们想把一个Button向右平移100px,我们只需要将这个Button的LayoutParams 里的 marginLeft 参数的值增加100px 即可。我们还可以再Button左边放一个View 默认宽度为0,当需要Button右移时,给这个View设置宽度。

tv3.setOnClickListener(new View.OnClickListener() {
     @Override
     public void onClick(View v) {
     ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) tv3.getLayoutParams();
     params.leftMargin += 100;
     tv3.setLayoutParams(params);
  }
});

各种滑动方式对比

  • scrollTo /scrollBy :操作简单,适合对View内容的滑动
  • 动画:操作简单,主要适用于没有交互的View和实现复杂的动画效果
  • 改变布局参数:操作 稍微复杂,适用于有交互的View

Scroller

应用场景:当我们需要实现有过度效果的动画时就可以使用 Scroller。应为scrollTo /scrollBy 的滑动过程是瞬间完成的

先看一下Scroller的 典型代码它是固定的

 Scroller mScroller=new Scroller(mContent);

    //缓慢滚动到指定位置
    private void smoothScrollTo(int destX,int destY){
        int scrollX=getScrollX();
        int deltaX=destX-scrollX;
        //1000ms 内滑向destX,效果就是慢慢滑动
        mScroller.startScroll(scrollX,0,deltaX,0,1000);
        invalidate();
    }

    @Override
    public void computeScroll() {
        if(mScroller.computeScrollOffset()){
            scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
            postInvalidate();
        }
    }

接下来我们再看一下startScroll 方法。

/**
 * startX ,startY 表示的是滑动的起点
 *
 * dx,dy 表示的是要滑动的距离
 *
 * duration 表示的是整个滑动过程完成所需要的时间
 */
 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 方法内部只是保留了我们传递的几个参数而已,并没有做滑动操作,那么Scroller是如何实现弹性滑动的呢 ?

其答案就在 startScroll 下面的 invalidate 方法。invalidate 方法导致View重绘,在View的 draw 方法中又回去调用computeScroll 方法,(computeScroll需要我们自己去实现) 而computeScroll 方法又会去向Scroller 获取当前的scrollX 和scrollY ,然后通过scrollTo 方法实现滑动,接着有调用 postInvalidate 方法来进行第二次重绘,这一次重绘的过程和第一次重绘一样,还是会导致computeScroll 方法被调用;然后继续向Scroller 获取当前的scrollX 和 scrollY ,并通过scrollTo 方法滑动到新的位置。View每一次的重绘都会导致View进行小幅度的滑动,而多次的小幅度滑动就组成了弹性滑动,这就是Scroller 的工作机制。如此反复,直到整个滑动过程结束。

接下来我们看一下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;
           ......
           }
        }
}

可以看到,这个方法是根据时间流逝百分比来算出scrollX 和 scrollY 改变的百分比并计算出当前的值。这个过程类似动画中的插值器。 动画相关可以看这里

同样的,可以使用动画来实现View的弹性滑动,代码如下

private void performAnimation(final View target, final int start, final int end) {
        ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            //整型估值器
            private IntEvaluator intEvaluator = new IntEvaluator();

            @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB_MR1)
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {

                Log.e(TAG, "onAnimationUpdate: 进度:" + animation.getAnimatedValue());

                //获取当前进度占整个动画的比例,浮点型 0-1之间
                float fraction = animation.getAnimatedFraction();
                target.getLayoutParams().width = intEvaluator.evaluate(fraction, start, end);
                target.requestLayout();
            }
        });
        valueAnimator.setDuration(5000).start();
    }

猜你喜欢

转载自blog.csdn.net/qq_26057629/article/details/80916413