美文网首页
Android View的事件体系(三)弹性滑动

Android View的事件体系(三)弹性滑动

作者: 怡红快绿 | 来源:发表于2019-04-08 19:11 被阅读0次

    比较生硬地滑动一个View,用户体验实在是太差了,所以我们需要实现优雅的弹性滑动效果。那么如何实现弹性滑动呢?

    主要思想:把一次滑动分成若干次小距离滑动,并在一定时间内完成以达到弹性滑动的效果。

    具体实现方法有很多,例如使用Scroller、Handler#postDelay和Thread#sleep。

    一、使用Scroller

    Scroller典型用法

    //创建实例
    Scroller scroller = new Scroller(context);
    
    //设置滑动参数
    scroller.startScroll(scroller.getStartX(), scroller.getStartY(), 1000, 1000, 1000);
    //重绘View
    invalidate();
    
    //重写computeScroll方法
    @Override
    public void computeScroll() {
        if (mScroller != null) {
            if (mScroller.computeScrollOffset()) {
                mScrollX = mScroller.getCurrX();
                mScrollY = mScroller.getCurrY();
                scrollTo(mScrollX, mScrollY );
                postInvalidate();  // So we draw again
            }
        }
    }
    

    我们先描述上述代码主要逻辑:当我们构造Scroller对象并调用它的startScroll方法时,Scroller内部其实什么也没做,它只是保存我们传递的几个参数,我们根据源码看看这些参数的含义

        /**
         * 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 整个滑动过程完成所需要的时间

    注意这里的滑动指的是View内容的滑动,而不是它本身位置的改变。

    既然startScroll方法没有做与滑动相关的动作,那么Scroller是怎么让View滑动的呢?答案就是invalidate方法,invalidate方法会导致View重绘,View重绘时draw方法中又会去调用computeScroll方法,这是一个空方法,需要我们自己去实现,这个方法才是View能够实现弹性滑动的关键所在。具体过程是这样的:

    1. View重绘时在draw方法中调用computeScroll方法,computeScroll方法会向当前的Scroller获取新的mScrollX、mScrollY;
    2. 通过scrollTo(mScrollX, mScrollY )实现滑动;
    3. 调用postInvalidate方法再次重绘,绘制过程与第一次一样;
    4. 如此反复直到滑动到指定位置。

    开始重复绘制当然是有条件的,难道要任由View一直重绘下去?我们显然不能这样做。阅读TextView源码我们发现computeScroll方法开始执行之前有这么一个判断语句"if (mScroller.computeScrollOffset()) ",我们来看看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;
                    ……
            }
        } 
      ……
        return true;
    }
    

    我们可以看到,方法内部是根据时间流逝的百分比来计算出mStartX 、mStartY变化的百分比,从而最终计算出mCurrX、mCurrY的值,从而确定新位置。方法computeScrollOffset返回true时表示滑动未结束,否则表示滑动结束。

    二、通过动画

    动画本身就是一个渐变的过程,所以我们可以用它来实现弹性效果。我们尝试着使用动画让一个View的内容在1秒内向右移动100像素:

    ObjectAnimator.ofFloat(hello, "translationX", 0, 100)
            .setDuration(1000).start();
    

    像上面这样通过动画来实现View的内容滑动还不是我想介绍的重点,我们还可以利用动画的特性,配合scrollTo()方法来实现一些动画不能实现的效果,例如模仿Scroller实现View的弹性滑动。用法如下:

    ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 1).setDuration(1000);
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            float animatedFraction = animation.getAnimatedFraction();
            Log.d(TAG, "onAnimationUpdate: " + animatedFraction);
            hello.scrollTo((int) (-100 * animatedFraction), 0);
        }
    });
    valueAnimator.start();
    

    上面定义的动画valueAnimator 本质上并没有作用在任何对象上,它只是在1000ms内完成了整个动画的过程。
    这个方法的思想其实和Scroller非常类似,都是将一次滑动分割为多次小距离滑动:通过一个渐变的百分比animatedFraction 计算出每次需要滑动的距离,然后配合scrollTo完成整个滑动过程。
    AnimatorUpdateListener监听事件作用非常大,我们可以在它的onAnimationUpdate方法内实现各种自定义滑动效果。

    三、使用延时策略

    核心思想:发送一系列延时消息,然后在这些消息中进行View的内容滑动,从而达到弹性滑动的效果。

    我们可以使用Handler、View的postDelayed方法或者使用线程的sleep方法。下面以Handler为例来实现弹性滑动,其他方法思想都是类似的。

    private static final int SCROLL_TO = 1;
    private static final int SCROLL_COUNT = 50;
    private int count;
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case SCROLL_TO:
                    count++;
                    if (count <= SCROLL_COUNT) {
                        //计算百分比
                        float fraction = count / (float)SCROLL_COUNT;
                        Log.d(TAG, "handleMessage: "+fraction);
                        //根据百分比计算滑动的距离
                        int scrollX = (int) (fraction * -500);
                        hello.scrollTo(scrollX, 0);
                        //发送第count+1次消息
                        sendMessageDelay();
                    }
            }
        }
    };
    

    注意:这里主要目的是为了介绍延迟策略,所以没有考虑Handler内存泄漏的问题,如何正确使用Handler请参考Android中常见的内存泄漏 & 解决方案

    四、总结

    上面几种实现弹性滑动的方法,都只是简单介绍了它们的实现思想,在实际使用中肯定比这要复杂得多,我们需要对它们进行灵活地扩展,这样它们才能在开发工作中发挥更大的作用。


    参考

    《Android开发艺术探索》

    相关文章

      网友评论

          本文标题:Android View的事件体系(三)弹性滑动

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