Android源码分析-Scroller

来源:互联网 发布:iphone4s4g网络补丁 编辑:程序博客网 时间:2024/06/06 21:06

本篇文章主要介绍了一下Scroller的使用并对其源码进行了简单的分析,感兴趣的朋友可以看一下。

基本用法:

Scroller有两个比较重要且常用的方法:startScroll和fling,特别是第一个方法我们在定义View或ViewGroup的时候经常用到,我写了一个简单的定义ViewGroup的小demo演示这两个方法的用法,先看下效果图:
startScroll方法:
这个效果比较常见,效果类似于ViewPager控件,当我们松开鼠标后,我们使用startScroll方法又缓慢滑动了一段距离

fling方法:
可以实现一种类似于惯性的效果,当我们松开鼠标时视图没有立即停止滑动而是自己滑动了一小段又停止的,这是一个慢慢减速的过程

主要代码

下面是主要的实现代码,比较简单,全部的代码会在最下面给出:

@Override    public boolean onTouchEvent(MotionEvent event) {        //fling方法用到        if (mVelocityTracker == null) {            mVelocityTracker = VelocityTracker.obtain();        }        mVelocityTracker.addMovement(event);        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:                preX = event.getX();                if (!mScroller.isFinished())                    mScroller.abortAnimation();                break;            case MotionEvent.ACTION_MOVE:                float curX = event.getX();                int dx = (int) (preX - curX);                //滑动的范围是0=<x<=getMeasuredWidth()                if (getScrollX() + (int) dx < 0) {                    this.scrollTo(0, 0);                } else if (getScrollX() + (int) dx > getMeasuredWidth()) {                    this.scrollTo(getMeasuredWidth(), 0);                } else {                    this.scrollBy((int) dx, 0);                }                preX = curX;                break;            case MotionEvent.ACTION_UP:                //判断应该滑动到左右哪一侧(startScroll方法用到)                int sX = this.getScrollX();                int index = sX / getMeasuredWidth();                int d = sX % (getMeasuredWidth());                if (d > getMeasuredWidth() / 2.0f) {                    index += 1;                }                //startScroll用法                smoothScrollToIndex(index);                //fling用法                //smoothFling();                break;        }        //消费事件        return true;    }
    /**     * startScroll的用法     * @param index     */    private void smoothScrollToIndex(int index) {        mScroller.startScroll(getScrollX(), 0, index * getMeasuredWidth() - getScrollX(), 0);        //View重绘的时候draw方法会调用下面的computeScroll()方法        invalidate();    }    /**     * fling的用法     */    private void smoothFling() {        mVelocityTracker.computeCurrentVelocity(1000);        float fXV = mVelocityTracker.getXVelocity();        //mVelocityTracker得到的速度从左向右是正的,而scroller里是从右向左是负的,所认这里在fXV前面加了一个负号        mScroller.fling(getScrollX(), 0, -(int) fXV, 0, 0, getMeasuredWidth(), 0, 0);        //View重绘的时候draw方法会调用下面的computeScroll()方法        invalidate();    }    @Override    public void computeScroll() {        if (mScroller.computeScrollOffset()) {//scroll没有结束            int curX = mScroller.getCurrX();            int curY = mScroller.getCurrY();            this.scrollTo(curX, curY);            //继续调用computeScroll()方法            invalidate();        }    }

源码浅析

先看Scroller的构造方法:

151    public Scroller(Context context) {152        this(context, null);153    }160    public Scroller(Context context, Interpolator interpolator) {161        this(context, interpolator,162                context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB);163    }170    public Scroller(Context context, Interpolator interpolator, boolean flywheel) {171        mFinished = true;172        if (interpolator == null) {173            mInterpolator = new ViscousFluidInterpolator();174        } else {175            mInterpolator = interpolator;176        }177        mPpi = context.getResources().getDisplayMetrics().density * 160.0f;178        mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction());179        mFlywheel = flywheel;180181        mPhysicalCoeff = computeDeceleration(0.84f); // look and feel tuning182    }

Scroller总共有三个构造方法,最终调用的都是第三个,可以看到我们可以传一个插值器Interpolator进来,Interpolator可以控制滑动的速度变化,比如我们可以用它实现均匀滑动或加速滑动以及其他一些效果,不清楚的可以自己google一下,如果我们没有提供Interpolator的话,Scroller会使用一个默认的插值器ViscousFluidInterpolator:

562    static class ViscousFluidInterpolator implements Interpolator {563564        private static final float VISCOUS_FLUID_SCALE = 8.0f;565566        private static final float VISCOUS_FLUID_NORMALIZE;567        private static final float VISCOUS_FLUID_OFFSET;568569        static {570571            // must be set to 1.0 (used in viscousFluid())572            VISCOUS_FLUID_NORMALIZE = 1.0f / viscousFluid(1.0f);573            // account for very small floating-point error574            VISCOUS_FLUID_OFFSET = 1.0f - VISCOUS_FLUID_NORMALIZE * viscousFluid(1.0f);575        }576577        private static float viscousFluid(float x) {578            x *= VISCOUS_FLUID_SCALE;579            if (x < 1.0f) {580                x -= (1.0f - (float)Math.exp(-x));581            } else {582                float start = 0.36787944117f;   // 1/e == exp(-1)583                x = 1.0f - (float)Math.exp(1.0f - x);584                x = start + x * (1.0f - start);585            }586            return x;587        }588589        @Override590        public float getInterpolation(float input) {591            final float interpolated = VISCOUS_FLUID_NORMALIZE * viscousFluid(input);592            if (interpolated > 0) {593                return interpolated + VISCOUS_FLUID_OFFSET;594            }595            return interpolated;596        }597    }

其中getInterpolation方法可以控制scroll的滑动快慢。
再看startScroll方法:

369    public void startScroll(int startX, int startY, int dx, int dy) {370        startScroll(startX, startY, dx, dy, DEFAULT_DURATION);371    }387    public void startScroll(int startX, int startY, int dx, int dy, int duration) {388        mMode = SCROLL_MODE;//模式,startScroll方法是SCROLL_MODE389        mFinished = false;//标识是否滑动结束390        mDuration = duration;//滑动时间391        mStartTime = AnimationUtils.currentAnimationTimeMillis();//开始时间392        mStartX = startX;//开始x位置393        mStartY = startY;//开始y位置394        mFinalX = startX + dx;//结束x位置395        mFinalY = startY + dy;//结束y位置396        mDeltaX = dx;//x方向上的滑动间距397        mDeltaY = dy;//y方向上的滑动间距398        mDurationReciprocal = 1.0f / (float) mDuration;//滑动时间的倒数399    }

startScroll方法有两个实现,第一个方法调用的是第二个方法,如果我们没有提供滑动的时间的话,会有一个默认的,默认是250ms:

private static final int DEFAULT_DURATION = 250;

可以看到startScroll方法只是对Scroller里面的成员变进行了赋值操作。
在上面的小demo中,我们在调用完startScroll方法后接着调用了invalidate()方法,invalidate()方法会导致computeScroll()方法被调用(invalidate()方法会导致view绘,view的draw方法内部会调用computeScroll(),具体过程可以查看相关源码):

    @Override    public void computeScroll() {        if (mScroller.computeScrollOffset()) {//scroll没有结束            int curX = mScroller.getCurrX();            int curY = mScroller.getCurrY();            this.scrollTo(curX, curY);            //继续调用computeScroll()方法            invalidate();        }    }

在computeScroll()方法内部我们调用了Scroller的computeScrollOffset()方法,看一下这个方法是干什么的:

300    public boolean computeScrollOffset() {301        if (mFinished) {//滑动结束,返回false302            return false;303        }304305        int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);//当前过去的时间306    307        if (timePassed < mDuration) {//滑动没有结束308            switch (mMode) {309            case SCROLL_MODE://startScroll的mMode是SCROLL_MODE310                final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);//调用插值器的getInterpolation方法,传进去的是当前经过的时间占总滑动时间的百分比,返回的是重新计算的百分比,这是插值器的一种常用用法。311                mCurrX = mStartX + Math.round(x * mDeltaX);//将当前的x位置赋值给mCurrX成员312                mCurrY = mStartY + Math.round(x * mDeltaY);//将当前的y位置赋值给mCurrY成员313                break;314            case FLING_MODE://fling的mMode是FLING_MODE315                final float t = (float) timePassed / mDuration;316                final int index = (int) (NB_SAMPLES * t);317                float distanceCoef = 1.f;318                float velocityCoef = 0.f;319                if (index < NB_SAMPLES) {320                    final float t_inf = (float) index / NB_SAMPLES;321                    final float t_sup = (float) (index + 1) / NB_SAMPLES;322                    final float d_inf = SPLINE_POSITION[index];323                    final float d_sup = SPLINE_POSITION[index + 1];324                    velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);325                    distanceCoef = d_inf + (t - t_inf) * velocityCoef;326                }327328                mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f;329                330                mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));331                // Pin to mMinX <= mCurrX <= mMaxX332                mCurrX = Math.min(mCurrX, mMaxX);333                mCurrX = Math.max(mCurrX, mMinX);334                335                mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));336                // Pin to mMinY <= mCurrY <= mMaxY337                mCurrY = Math.min(mCurrY, mMaxY);338                mCurrY = Math.max(mCurrY, mMinY);339340                if (mCurrX == mFinalX && mCurrY == mFinalY) {341                    mFinished = true;342                }343344                break;345            }346        }347        else {//滑动结束348            mCurrX = mFinalX;349            mCurrY = mFinalY;350            mFinished = true;351        }352        return true;353    }

看以看出computeScrollOffset()的处理逻辑是首先判断滑动是否结束,结束的话返回fasle,如果还没有结束则根据当前经过的时间计算出当前应该滑动的位置赋值给mCurrX,mCurrY,然后返回true。
所以我们就可以调用computeScrollOffset()计算出最新的位置,如果computeScrollOffset()返回true代表滑动还没有结束,我们就可以使用scrollTo滑动到最新的位置,然后继续通过通过调用invalidate()方法重复上面的逻辑。

fling方法的使用跟startScroll方法是差不多的,只不过计算的过程更复杂一些,这里就不分析了。

演示Demo的下载链接
如果大家有什么疑问或文章中有讲的不对的地方,欢迎大家提出来讨论,谢谢大家。

1 0