3.3 弹性滑动

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;
        }
    }
};
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 内容是博主照着书敲出来的,博主码字挺辛苦的,转载请注明出处,后序内容陆续会码出。 当了解了Android坐标系和触...
    Blankj阅读 11,710评论 3 60
  • 什么是View View 是 Android 中所有控件的基类。 View的位置参数 View 的位置由它的四个顶...
    acc8226阅读 5,052评论 0 7
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 176,095评论 25 709
  • 这两个月可真忙,一边要教学,一边还要准备论文和活动,头都忙大了,我正在琢磨着论文从何入手呢?选题一定不能...
    嘎鱼嘎鱼阅读 1,040评论 0 2
  • 时间: 2017年12月7日 地点:天津豆丁公寓 作者:阮博杰 1.叩谢庄园哥的理解 2.叩谢今晚大家伙准备的能量...
    阮博杰阅读 1,670评论 0 0

友情链接更多精彩内容