弹性滑动的实现与工作原理

弹性滑动的实现与工作原理

使用Scroller

  • 典型代码如下

    Scroller scroller = new Scroller(mContext);
    
    //缓慢滚动到指定位置
    private void smoothScrollTo(int destX,int destY){
        int scrollX = getScrollX();
        int delta = destX-scrollX;
        //1000ms内滑向destX,效果就是慢慢滑动
        mScroller.startScroll(scrollX,0,delta,0,1000);
        invalidate();
    }
    
    @Override
    public void computeScroll(){
        if(mScroller.computeScrollOffset()){
            scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
            postInvalidate();
        }
    }
    

1.通过如下的startScroll源码,我们可以看出startScroll方法仅仅是保存了传递的几个参数,并没有进行滑动操作

/**
 * Start scrolling by providing a starting point, the distance to travel,
 * and the duration of the scroll.
 * 
 * @param startX Starting horizontal scroll offset in pixels. Positive
 *        numbers will scroll the content to the left.
 * @param startY Starting vertical scroll offset in pixels. Positive numbers
 *        will scroll the content up.
 * @param dx Horizontal distance to travel. Positive numbers will scroll the
 *        content to the left.
 * @param dy Vertical distance to travel. Positive numbers will scroll the
 *        content up.
 * @param duration Duration of the scroll in milliseconds.
 */
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;
}
  • startX和startY表示的是滑动的起点
  • dx和dy表示的是要滑动的距离
  • duration表示的是滑动时间
  • 可以发现,调用startScroll方法并不能实现滑动,实现滑动的是下面的invalidate方法
  • invalidate方法会导致View重绘,在View的draw方法中又会去调用computeScroll方法,这个方法是空实现,需要用户自己实现(典型代码中已实现)
  • computeScroll方法会去向Scroller获取当前的scrollX和scrollY,然后通过scrollTo方法实现滑动
  • 接着又会调用postInvalidate()来进行第二次重绘(同第一次一样),computeScroll()——>获取当前scrollX和scrollY——>scrollTo()滑动到新位置,如此反复,直到整个滑动过程结束(mScroller.computeScrollOffset()为false)

2.computeScrollOffset方法的实现

/**
 * Call this when you want to know the new location.  If it returns true,
 * the animation is not yet finished.
 */ 
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;
}
  • 这个方法根据时间的流逝来计算出当前的scrollX和scrollY的值,返回true表示滑动还未结束,返回false表示滑动已经结束
  • Scroller本事并不能实现View的滑动,需要配合View的computeScroll才能完成弹性滑动的效果,不断地让View重绘,而每一次重绘距滑动起始时间会有一个时间间隔,通过这个时间间隔Scroller可以得出View当前的滑动位置,然后通过scrollTo方法来完成View的滑动
  • View的每一次重绘都会导致View进行小幅度的滑动,多次小幅度滑动就组成了弹性滑动

通过动画实现

利用动画的特性,模仿Scroller来实现View的弹性滑动

final int startX = 0;
final int deltaX = 100;
ValueAnimator animator = ValueAnimator.ofInt(0,1).setDuration(1000);
animator.addUpdateListener(new AnimatorUpdateListener(){
   @Override
   public void onAnimationUpdate(ValueAnimator animator){
        float fraction = animator.getAnimatedFraction();
        mButton1.scrollTo(startX+(int)(deltaX*fraction),0);
   }
});
animator.start();
  • 在动画的每一帧到来时获取动画完成的比例,然后根据这个比例计算出当前View所要滑动的距离
  • 这个的滑动针对的是View的内容而非View本身

使用延时策略

核心思想:通过发送一系列延时消息从而达到一种渐近式的效果,比如使用postDelayed方法,可以通过延时发送一个消息,然后在消息中来进行View的滑动,如果接连不断地发送这种延时消息,就可以实现弹性滑动的效果;对于Sleep方法来说,通过在while循环中不断地滑动View和sleep,就可以实现弹性滑动效果

如下代码用发送延时消息实现了弹性滑动

private static final int MESSAGE_SCROLL_TO = 1;
private static final int FRAME_COUNT = 30;
private static final int DELAYED_TIME = 33;

private int mCount = 0;
private Handler mHandler = new Handler(){
    public void handleMessage(Message msg){
        switch(msg.what){
            case MESSAGE_SCROLL_TO:
                mCount++;
                if(mCount <= FRAME_COUNT){
                    float fraction = mCount / (float)FRAME_COUNT;
                    int scrollX = (int)(fraction * 100);
                    mButton1.scrollTo(scrollX,0);
                    mHandler.sendEmptyMessageDelayed(MESSAGE_SCROLL_TO,DELAYED_TIME);
                }
                break;
            default:
                break;
        }
    }
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值