[Andorid开发艺术探索 读书笔记]View的事件体系(三)

这篇我们讨论弹性滑动。

产品需要良好的用户体验,上篇中我们介绍的滑动方式都是比较生硬的滑动过去,有时我们需要实现渐近式滑动,也就是我们这里说的弹性滑动。

实现的方法思路有很多,但是他们都有一个共同的思想:将一次大的滑动分成若干次小的滑动,并在一个时间段内完成。这里我们讨论几种方式:


使用Scroller


这是使用scroller的通常代码片段:

    Scroller mScroller = new Scroller(mContext);
    
    private void smoothScrollTo(int destX, int destY){ 
        int scrollX = getScrollX(); 
        int deltaX = destX - scrollX; 
        mScroller.startScroll(scrollX, 0, deltaX, 0, 1000); 
        invalidate(); 
    
    }
    
    @Override 
    public void computeScroll() { 
        if (mScroller.computeScrollOffset()){
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            postInvalidate(); 
        } 
    }


 
这里我们简单分析一下它的原理:调用smoothScrollTo(int destX, int destY)方法时,内部会调用Scroller的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;
    }

原来这个方法里啥逻辑也没做,就是保存了几个传递进去的参数。这几个参数的意义也很明确,mStartX,mStartY表示滑动的起点,mFinalX,mFinalY表示滑动的终点,mDeltaX,mDeltaY表示滑动的距离。
值得注意的一点是:这样的滑动实际上是View内容的滑动,而不是View本身位置的改变,所以这里仅仅调用startScroll方法是无法让View滑动的,因为它内部没有做任何滑动相关的事。那么scroller到底怎么实现的弹性滑动的呢?答案就在于,startScroll方法之后我们调用了invalidate方法。我们知道这个方法会引起View的重绘,重绘过程中在View的draw方法里又会调用computeScroll方法,这个方法在View的源码中是个空实现,所以我们重写了这个方法。

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

代码很短,思路也很清晰,就是调用scroller的computeScrollOffset方法,如果返回true, 就调用View本身的scrollTo方法,再调用postInvalidate()方法引起View的重绘,而重绘又会在draw方法中调用computeScroll方法,如果computeScrollOffset方法返回true,又会滑动一段距离,引起重绘,循环以往,直到computeScrollOffset方法返回false。那么我们所有的关注点都应该集中到computeScrollOffset这个方法上,它返回的true和false又代表着什么意思呢?

 /**
     * 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;
            .....
            }
        }
        else {
            mCurrX = mFinalX;
            mCurrY = mFinalY;
            mFinished = true;
        }
        return true;
    }

看到这个方法的注释,基本上就已经明白了,它返回true意味着滑动还没有结束,返回false意味着滑动已经结束。
这个方法会根据时间流逝来计算当前的scrollX和scrollY的值。大意就是根据时间流逝的百分比来算出scrollX和scrollY改变的百分比并计算出当前的值。这个过程类似于动画中插值器概念,具体的细节我们留到动画的部分来讨论。

通过这里分析,我们应该很清楚Scroller的工作原理了,将一次滑动分成若干段小的滑动,每次小的滑动之后重绘一次,直到滑动结束。这样若干次小的滑动组成了弹性滑动。
值得给满分的是Google的设计,这里的scroller甚至连view的引用都没有,就完成了这个过程。

通过动画

动画本身就是渐近的过程,类似于这样:
ObjectAnimator.ofFloat(targetView, "translationX", 0, 100).setDuration(100).start();

如果我们想实现类似于Scroller那样的特性,可以这样:

    final int startX = 0;
    final int deltaX = 100;
    ValueAnimator animator = ValueAnimator.ofInt(0, 1).setDuration(1000);
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener(){
        @Override
        onAnimationUpdate(ValueAnimator animator){
            float fraction = animator.getAnimatedFraction();
            mButton.scrollTo(startX + (int)(deltaX * fraction), 0);
        }
    });
    animator.start();



通过延时策略

具体来说,就是通过Handler或者View里面自带的postDelayed方法,也可以使用线程的sleep方法。
这个方法很简单,代码就不贴了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值