Android中View的滑动

     实现滑动效果的方式

    在Android中有三种方式可以实现View的滑动效果:
        1、通过View本身提供的 scrollTo(int x, int y)或者scrollBy(int x, int y)方法来实现View中内容滑动的效果。(内容滑动)
        2、通过动画(最好使用属性动画)给View施加平移效果来实现滑动。(View滑动)
        3、通过改变View的LayoutParams使得View重新布局来实现滑动效果。

    三者对比:
        1、scrollTo/scrollBy:操作简单适合对View内容的滑动。
        2、动画:操作简单,主要适合没有交互的View和实现复杂的动画效果。
        3、改变布局参数:操作稍微复杂,适用于有交互的View。


     scroll滑动原理

    在此处主要分析scroll方式的滑动。
  •     首先,弄清楚两个变量的概念:mScrollX和mScrollY
   
    源码 
<span style="color:#009900;">/**
     * The offset, in pixels, by which the content of this view is scrolled
     * horizontally.
     * {@hide}
     */
    @ViewDebug.ExportedProperty(category = "scrolling")</span>
    protected int mScrollX;
    <span style="color:#009900;">/**
     * The offset, in pixels, by which the content of this view is scrolled
     * vertically.
     * {@hide}
     */
    @ViewDebug.ExportedProperty(category = "scrolling")</span>
    protected int mScrollY;


    mScrollXmScrollY的变化规律:

    (1).mScrollX的值总是等于View左边缘View内容左边缘水平方向距离(mScrollX=X1-X2,,其中X1,表示View的左边缘,其中X2,表示View内容的左边缘),当View内容的左边缘位于View的左边缘的左边时,mScrollX大于零,即mScrollX为正值,反之为负值;

    (2).mScrollY的值总是等于View上边缘View内容上边缘竖直方向距离(mScrollY=Y1-Y2,,其中Y1,表示View的上边缘,其中Y2,表示View内容的上边缘),当View内容的上边缘位于View的上边缘的上边时,mScrollY大于零,即mScrollY为正值,反之为负值;

    默认情况下,mScrollX和mScrollY都等于0(我的理解是在刚进入程序,scroll之前mScrollX=0和mScrollY=0)。


  • scrollTo(int x,int y)的源码:
           
<span style="color:#009900;">/**
     * Set the scrolled position of your view. This will cause a call to
     * {@link #onScrollChanged(int, int, int, int)} and the view will be
     * invalidated.
     * @param x the x position to scroll to
     * @param y the y position to scroll to
     */</span>
    public void scrollTo(int x, int y) {
        if (mScrollX != x || mScrollY != y) {
            int oldX = mScrollX;
            int oldY = mScrollY;
            mScrollX = x;
            mScrollY = y;
            invalidateParentCaches();
            onScrollChanged(mScrollX, mScrollY, oldX, oldY);
            if (!awakenScrollBars()) {
                postInvalidateOnAnimation();
            }
        }
    }

    在源码中,该方法是将View的内容移动到指定位置,可以看出每次 scrollTo(int x,int y)时,都会进行if (mScrollX!= x || mScrollY!= y)判断,检查目的点的坐标是否和偏移量一样,因为 scrollTo()是移动到指定的点,如果这次移动的点的坐标和上次偏移量一样,也就是说这次移动和上次移动的坐标是同一个,那么就没有必要进行移动了。
    效果图:
Android中View的滑动_第1张图片
Android中View的滑动_第2张图片

Android中View的滑动_第3张图片


    上面几幅图就是,通过scrollTo进行上下左右移动后的效果,如果仔细看就会发现为什么当参数的正负与坐标系的正负相反呢?等会儿介绍方向问题。
  •     scrollBy(int x,int y)的源码
/**
     * Move the scrolled position of your view. This will cause a call to
     * {@link #onScrollChanged(int, int, int, int)} and the view will be
     * invalidated.
     * @param x the amount of pixels to scroll by horizontally
     * @param y the amount of pixels to scroll by vertically
     */
    public void scrollBy(int x, int y) {
        scrollTo(mScrollX + x, mScrollY + y);
    }


    从源码中可以看出,  scrollBy(int x,int y)的本质与  scrollTo(int x,int y)是一致的,  scrollTo(int x,int y)是从当前偏移量移动到指定点,而  scrollBy(int x,int y)是在当前偏移量的基础上根据参数提供的偏移量移动,所以  在参数不变的情况下,scrollTo(int x,int y)只能移动一次,而  scrollBy(int x,int y)可以一直地移动下去。
    
  •      scrollTo(int x,int y)移动的方向
        在源码中,当滑动时会经过 invalidate(left, top, right, bottom)方法,而在这个方法中有 tmpr.set(l - scrollX, t - scrollY, r - scrollX, b - scrollY)这步操作,所以当 scrollTo(int x,int y)的参数为正数时,绘制内容的区域会向坐标反方向移动。
        所以scrollTo的移动方向:
                                             Android中View的滑动_第4张图片


弹性滑动
    
    由于 scrollTo(int x,int y)滑动时,会瞬间移动,实际的体验效果不好,所以我们要实现渐进式滑动。渐进式滑动的思想是:将一次大的滑动分成若干次小的滑动,并在一个时间段内完成。
    能够实现弹性滑动的方式很多,如通过Scroller、动画、(Handler+postDelay)或(Thread+sleep)等。
    在此处使用Scroller实现。
    首先,介绍Scroller的几个常用方法:
  •     startScroll(int startX, int startY, int dx, int dy, int duration)
             从方法名字来看应该是滑动开始的地方,事实上我们在使用的时候也是先调用这个方法的,该方法的主要作用是:一个构造方法用来初始化赋值的,比如设置滚动模式、开始时间,持续时间、起始坐标、结束坐标等等,并没有任何对View的滚动操作。
            源码:
 /**
     * 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;
    }



        源码中验证了,上述结论。使用Scroller进行滑动时,在startScroll(int startX, int startY, int dx, int dy, int duration),方法中只是进行了初始化。
  •     computeScrollOffset()
             方法主要是根据当前已经消逝的时间来计算当前的坐标点,并且保存在mCurrX和mCurrY值中。之前在startScroll()方法的时候获取了当前的动画毫秒并赋值给了mStartTime,在computeScrollOffset()中再一次调用AnimationUtils.currentAnimationTimeMillis()来获取动画毫秒减去mStartTime就是消逝时间了。然后进去if判断,如果动画持续时间小于设置的滚动持续时间mDuration,则是SCROLL_MODE,再根据Interpolator来计算出在该时间段里面移动的距离,移动的距离是根据这个消逝时间乘以mDurationReciprocal,就得到一个相对偏移量,再进行Math.round(x * mDeltaX)计算,就得到最后的偏移量,然后赋值给mCurrX, mCurrY,所以mCurrX、 mCurrY 的值也是一直变化的。总结一下该方法的作用就是,计算在0到mDuration时间段内滚动的偏移量,并且判断滚动是否结束,true代表还没结束,false则表示滚动结束了。

        Scroller实现弹性滑动的流程:
                
    首先是View通过Scroller的 startScroll(int startX, int startY, int dx, int dy, int duration)方法进行初始化。
    然后,会调用View的 invalidate()或postInvalidate()进行重绘。
    接着,绘制View的时候会触发computeScroll()方法,接着重写computeScroll(),在computeScroll()里面先调用Scroller的computeScrollOffset()方法来判断滚动是否结束,如果滚动没有结束就调用scrollTo()方法来进行滚动。
    最后,scrollTo()方法虽然会重新绘制View,但是还是要手动调用下invalidate()或者postInvalidate()来触发界面重绘,重新绘制View又触发computeScroll(),所以就进入一个递归循环阶段,这样就实现在某个时间段里面滚动某段距离的一个平滑的滚动效果。
    





















你可能感兴趣的:(android)