美文网首页
3.3 弹性滑动

3.3 弹性滑动

作者: 武安长空 | 来源:发表于2016-06-20 14:47 被阅读39次

    1. Scroller原理分析

    经典使用

    Scroller scroller = new Scroller(getContext());
    
    // 缓慢滚动到指定位置
    private void smoothScrollTo(int destX, int destY) {
        int startX = (int) getX();
        int deltaX = startX - destX;
        int startY = (int) getY();
        int deltaY = startY - destY;
        // 2000ms内滑向目标位置,效果就是慢慢滑动
        // dx,dy的计算是:起始坐标-目标坐标,否则会反向跑
        scroller.startScroll(startX, startY, deltaX, deltaY, 2000);
        invalidate();
    }
    
    @Override
    public void computeScroll() {
        super.computeScroll();
        if (scroller.computeScrollOffset()) {
            scrollTo(scroller.getCurrX(), scroller.getCurrY());
            postInvalidate();
        }
    }
    

    首先看Scroller.startScroll方法

    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;
    }
    

    这个方法只是保存了一些值,没有对View有操作。真正让View弹性滑动的是invalidate方法。invalidate方法会导致View重绘,在viwe的draw方法中会调用computeScroll方法,这个方法在View中是空实现,所以需要我们重写

    /**
     * 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() {
    }
    

    在我们重写的方法中,我们首先调用scroller的computeScrollOffset方法去判断是否继续滚动到新位置,返回true则我们执行scrollTo方法,使View的内容滚动,并继续调用postInvalide方法。直到整个滑动过程结束。

    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;
            case FLING_MODE:
                final float t = (float) timePassed / mDuration;
                final int index = (int) (NB_SAMPLES * t);
                float distanceCoef = 1.f;
                float velocityCoef = 0.f;
                if (index < NB_SAMPLES) {
                    final float t_inf = (float) index / NB_SAMPLES;
                    final float t_sup = (float) (index + 1) / NB_SAMPLES;
                    final float d_inf = SPLINE_POSITION[index];
                    final float d_sup = SPLINE_POSITION[index + 1];
                    velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
                    distanceCoef = d_inf + (t - t_inf) * velocityCoef;
                }
    
                mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f;
                
                mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
                // Pin to mMinX <= mCurrX <= mMaxX
                mCurrX = Math.min(mCurrX, mMaxX);
                mCurrX = Math.max(mCurrX, mMinX);
                
                mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
                // Pin to mMinY <= mCurrY <= mMaxY
                mCurrY = Math.min(mCurrY, mMaxY);
                mCurrY = Math.max(mCurrY, mMinY);
    
                if (mCurrX == mFinalX && mCurrY == mFinalY) {
                    mFinished = true;
                }
    
                break;
            }
        }
        else {
            mCurrX = mFinalX;
            mCurrY = mFinalY;
            mFinished = true;
        }
        return true;
    }
    

    2. 通过动画模拟Scroller

    final int startX = 0;
    final int deltaX = 200;
    ValueAnimator animator = ObjectAnimator.ofInt(0, 1).setDuration(2000);
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            float fraction = animation.getAnimatedFraction();
            // 内容向左滚动是正的
            view.scrollTo(startX + (int) (deltaX * fraction), 0);
        }
    });
    animator.start();
    

    我们动画本质上没有作用于任何对象,它只是在2秒内完成动画过程,利用这个特性,我们在动画每一帧到来时获取动画的完成比例,再根据比例计算当前View要滚动的距离,再通过Vieww本身的scrollTo方法完成滚动。这个过程和Scroller的过程很相似。

    3. 通过延时策略完成弹性滚动

    思路和上面的很相似。以handler为例,如果没有完成滚动距离,就不断发消息让view滚动,完成滚动距离则停止发送。代码如下:

    private final int MESSAGE_SCROLL_TO = 1;
    private int frame_count = 30;
    private int count = 0;
    Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case MESSAGE_SCROLL_TO:
                    count++;
                    if (count<=frame_count){
                        float fraction = count/(float)frame_count;
                        int scrollX = (int) (fraction*200);
                        view.scrollTo(scrollX,0);
                        handler.sendEmptyMessage(MESSAGE_SCROLL_TO);
                    }
                    break;
            }
        }
    };
    

    相关文章

      网友评论

          本文标题:3.3 弹性滑动

          本文链接:https://www.haomeiwen.com/subject/yvpwdttx.html