【android】ScrollView源码分析

来源:互联网 发布:印度军事实力 知乎 编辑:程序博客网 时间:2024/06/15 07:52

想象一下你拿着放大镜贴很近的看一副巨大的清明上河图, 那放大镜里可以看到的内容是很有限的,
而随着放大镜的上下左右移动,就可以看到不同的内容了

android中手机屏幕就相当于这个放大镜, 而看到的内容是画在一个无限大的画布上~ 
画的内容有限, 而手机屏幕可以看到的东西更有限~ 但是背景画布是无限的

如果把放大镜的移动比作scroll操作,那么可以理解,这个scroll的距离是无限制的~ 
只不过scroll到有图的地方才能看到内容


参考ScrollView理解, 当child内容过长时,有一部分内容是"看不到"的,相当于"在屏幕之外",
而随着我们的拖动滚动,则慢慢看到剩下的内容,相当于我们拿着放大镜向下移动~

而代码中的这个scroll方法系统提供了两个:
scrollTo和scrollBy

源码如下

    /**
     * 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
     */
    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();
            }
        }
    }

    /**
     * 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);
    }

mScrollX 表示离视图起始位置的x水平方向的偏移量
mScrollY表示离视图起始位置的y垂直方向的偏移量
可以通过getScrollX() 和getScrollY()方法分别获得

两个方法的区别就是to参数是绝对值,by是相对于当前滚动到的位置的增量值

比如:
mScrollX=100, mScrollY=100
scrollTo(20, 20) -> mScrollX=20, mScrollY=20;
scrollBy(20, 20) -> mScrollX=120,mScrollY=120;

注意:
这里mScrollX和mScrollY的值是偏移量,是相对于视图起始位置的偏移量~
所以任何view,无论布局是怎么样的,只要是刚初始化未经过scroll的,偏移量都是0~
即mScrollX/Y是相对于自己初始位置的偏移量,而不是相对于其容器的位置坐标

-----------------------------------------------------------------------------


下面是就ScrollView的源码拆开了的分析,并加入了一些补充扩展,
主要内容包括
1.最基本的随着touch滚动的效果
2.fling效果,即滑动后抬起手后继续关心滚动的效果
3.over scroll效果,即拖动超出边界的处理

上述123系统都有提供相关实现方法,但是ScrollView默认只有1,2的实现效果,
over scroll需要我们自行进行一定处理后才可以看到~
下面就ScrollView的源码进行分析,且提供三个自定义ScrollView(难度依次递进)实现上面的三种效果,已打包成demo


后面源码分析时,系统是乱七八糟直接写一起时,分析的被比较细也比较乱,
demo中三个自定义ScrollView相当于按照难度梯度抽取出来的,
即view2是在view1基础上修改添加功能的,view3是在view2基础上修改添加功能的
可以从demo下手帮助理解其中原理
-----------------------------------------------------------------------------

scroll相当于一个拖动,我们可以用scrollTo/By控制其滚动到某个位置,
那一般ScrollView控件这种都是随着我们的手势生效的,内部原理是如何的呢~

下面来研究下系统ScrollView控件源码里面的具体实现~
系统考虑的东西比较多,研究起来较为复杂,所以先就核心部分拆开一点点研究~

手的拖动肯定是跟touch即触摸事件挂钩了~直接定位到ScrollView中的该方法
(onTouchEvent干什么用的就不扫盲了)

首先是ACTION_DOWN
case MotionEvent.ACTION_DOWN: {
    mIsBeingDragged = getChildCount() != 0;
    if (!mIsBeingDragged) {
        return false;
    }

    /*
     * If being flinged and user touches, stop the fling. isFinished
     * will be false if being flinged.
     */
    if (!mScroller.isFinished()) {
        mScroller.abortAnimation();
        if (mFlingStrictSpan != null) {
            mFlingStrictSpan.finish();
            mFlingStrictSpan = null;
        }
    }

    // Remember where the motion event started
    mLastMotionY = ev.getY();
    mActivePointerId = ev.getPointerId(0);
    break;
}
有三段代码,第二三段带注释,下面是介绍:

1.ScrollView没有child则不做处理,这个不解释,都没child滚个蛋啊
如果有child则设置标志位mIsBeingDragged即"开始拖动"(看英文就可以理解了)

2.看注释理解~如果还在滑动用户触碰了屏幕,则立刻停止滑动
mScroller是一个OverScroller对象,是处理滚动的,
类介绍里提到这个功能和Scroller类差不多,大部分情况下可以替代之,
区别可以简单的理解为OverScroller允许超出边界,后面会介绍Scroller类~
至于mFlingStrictSpan无视之

3.看注释理解~记住点击的位置
scrollerView,处理垂直滚动,这里就只记录Y坐标了
mActivePointerId是用来处理多点触控时的稳定性的,这里先记住作用就行了

-----------------------------------------------------------------------------

然后是ACTION_MOVE,这个是重点,随着move我们希望控件也能随着我们的手的拖动滚动到所需位置
case MotionEvent.ACTION_MOVE:
    if (mIsBeingDragged ) {
        // Scroll to follow the motion event
        final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
        final float y = ev.getY(activePointerIndex);
        final int deltaY = ( int) ( mLastMotionY - y);
        mLastMotionY = y;

        final int oldX = mScrollX;
        final int oldY = mScrollY;
        final int range = getScrollRange();
        final int overscrollMode = getOverScrollMode();
        final boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
                (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);

        if (overScrollBy(0, deltaY, 0, mScrollY,
                0, range, 0, mOverscrollDistance, true)) {
            // Break our velocity if we hit a scroll barrier.
            mVelocityTracker.clear();
        }
        onScrollChanged( mScrollX, mScrollY , oldX, oldY);

        if (canOverscroll) {
            final int pulledToY = oldY + deltaY;
            if (pulledToY < 0) {
                mEdgeGlowTop.onPull((float) deltaY / getHeight());
                if (! mEdgeGlowBottom.isFinished()) {
                    mEdgeGlowBottom.onRelease();
                }
            } else if (pulledToY > range) {
                mEdgeGlowBottom.onPull((float) deltaY / getHeight());
                if (! mEdgeGlowTop.isFinished()) {
                    mEdgeGlowTop.onRelease();
                }
            }
            if ( mEdgeGlowTop != null
                    && (! mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished())) {
                invalidate();
            }
        }
    }
    break;

系统注释还是很良心的,一般以功能点为单位注释,这里还是根据注释去看
分两段:随着触摸事件的滚动,还有顶部底部边缘阴影的处理
阴影处理的先忽视

这里的触摸点获取不是直接event.getY,而是通过下面两句代码获取的
        final int activePointerIndex = ev.findPointerIndex( mActivePointerId);
        final float y = ev.getY(activePointerIndex);
上面已经介绍过了mActivityPointerId的保证多点触碰稳定性的作用,
包括onTouchEvent里面的ACTION_POINTER_DOWN/UP也是为了处理多点情况的,
为了不发散太多就不细介绍了(其实我也不是研究太透彻)
知道这样处理能防止多点触控的干扰,可以稳定获取到我们需要的触摸的y坐标就行了


根据现在的触摸坐标y和上次位置的y坐标mLastMotionY算出差值,即这次移动的距离deltaY
最后以获取到的这些数据进行滚动操作~
ScrollView中在这里使用的是overScrollBy方法,该方法是其父类view的方法,定位过去看下
其实如果要简单处理的话直接掉scrollTo方法就可以了~参见demo中MyScrollView1
如果觉得ScrollView里逻辑无法理解,那就可以先把上面demo的view1研究懂以后再继续下文

注意,scroll由于是没有限制的,即可以滚动到任何位置,显然不符合我们的需要,
所以我们要限制滚动范围,只在有内容的绘制部分滚动


由于ScrollView只是纵向Y轴上滚动,所以只限定y上滚动范围即可,
如下图示,红框是scrollview,蓝框是child,滚动范围应该是箭头所示部分~
即0 到 child.height-scrollview,height
 


而demo里view1中也添加了这么一段(demo是横向滚动)
// Clamp values if at the limits and record
final int left = 0;
final int right = getScrollRangeX();
// 防止滚动超出边界
if(scrollX > right) {
      scrollX = right;
} else if(scrollX < left) {
      scrollX = left;
}

-----------------------------------------------------------------------------

因为scrollView考虑的比较多,所以处理麻烦点,按照源码追踪到view中的overScrollerBy方法

    /**
     * Scroll the view with standard behavior for scrolling beyond the normal
     * content boundaries. Views that call this method should override
     * {@link #onOverScrolled(int, int, boolean, boolean)} to respond to the
     * results of an over -scroll operation.
     *
     * Views can use this method to handle any touch or fling -based scrolling.
     *
     * @param deltaX Change in X in pixels
     * @param deltaY Change in Y in pixels
     * @param scrollX Current X scroll value in pixels before applying deltaX
     * @param scrollY Current Y scroll value in pixels before applying deltaY
     * @param scrollRangeX Maximum content scroll range along the X axis
     * @param scrollRangeY Maximum content scroll range along the Y axis
     * @param maxOverScrollX Number of pixels to overscroll by in either direction
     *          along the X axis.
     * @param maxOverScrollY Number of pixels to overscroll by in either direction
     *          along the Y axis.
     * @param isTouchEvent true if this scroll operation is the result of a touch event.
     * @return true if scrolling was clamped to an over -scroll boundary along either
     *          axis, false otherwise.
     */
    @SuppressWarnings({"UnusedParameters"})
    protected boolean overScrollBy( int deltaX, int deltaY,
            int scrollX, int scrollY,
            int scrollRangeX, int scrollRangeY,
            int maxOverScrollX, int maxOverScrollY,
            boolean isTouchEvent) {
        final int overScrollMode = mOverScrollMode;
        final boolean canScrollHorizontal =
                computeHorizontalScrollRange() > computeHorizontalScrollExtent();
        final boolean canScrollVertical =
                computeVerticalScrollRange() > computeVerticalScrollExtent();
        final boolean overScrollHorizontal = overScrollMode == OVER_SCROLL_ALWAYS ||
                (overScrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollHorizontal);
        final boolean overScrollVertical = overScrollMode == OVER_SCROLL_ALWAYS ||
                (overScrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollVertical);

        int newScrollX = scrollX + deltaX;
        if (!overScrollHorizontal) {
            maxOverScrollX = 0;
        }

        int newScrollY = scrollY + deltaY;
        if (!overScrollVertical) {
            maxOverScrollY = 0;
        }

        // Clamp values if at the limits and record
        final int left = -maxOverScrollX;
        final int right = maxOverScrollX + scrollRangeX;
        final int top = -maxOverScrollY;
        final int bottom = maxOverScrollY + scrollRangeY;

        boolean clampedX = false;
        if (newScrollX > right) {
            newScrollX = right;
            clampedX = true;
        } else if (newScrollX < left) {
            newScrollX = left;
            clampedX = true;
        }

        boolean clampedY = false;
        if (newScrollY > bottom) {
            newScrollY = bottom;
            clampedY = true;
        } else if (newScrollY < top) {
            newScrollY = top;
            clampedY = true;
        }

        onOverScrolled(newScrollX, newScrollY, clampedX, clampedY);

        return clampedX || clampedY;
    }

方法的作用:
总结起来就是计算over scroll时scrollX/Y的值~ 并将其记录在onOverScrolled方法里

参数意义:
前8个分两组,1357是针对x轴的,2468则是y轴的,这里scrollView只纵向滚动,所以只处理y轴
比较难理解的是后俩参数

1.scrollRange
是某方向上滚动的范围,可以参考上面的图片,只不过要把padding部分考虑进去
下面是系统获取范围的方法
    private int getScrollRange () {
        int scrollRange = 0;
        if (getChildCount() > 0) {
            View child = getChildAt(0);
            scrollRange = Math. max(0,
                    child.getHeight() - (getHeight() - mPaddingBottom - mPaddingTop));
        }
        return scrollRange;
    }
不难理解,最大距离的情况就是childview移动到了最顶部,然后滑动到最底部,上面这个方法算的就是这个最大距离

2.maxOverScroll
为越界滚动最大距离,即在之前范围的基础上再加上这个越界最大距离~
ScrollView在这里设置的是系统默认值0


比如以前纵向的滚动范围是0~300,那如果这个值设为50,则最终over scroll的范围就是-50~350,方法内算法如下

        // Clamp values if at the limits and record
        final int left = -maxOverScrollX;
        final int right = maxOverScrollX + scrollRangeX;
        final int top = -maxOverScrollY;
        final int bottom = maxOverScrollY + scrollRangeY;

top=-maxOverScrollY ~ bottom=maxOverScrollY+scrollRangY
带入我们假设的值,那就是0~300, 注意,这个300是Y坐标300~


这里假设我们的maxOverScrollY不是系统默认的Y而是 50,
那虽然滚动范围不变还是500-200=300~ 但是实际上可以滚动的范围是大于300的~ 
效果类似于ios那种,listview到达顶部以后继续拖还可以移动~ 也可以脑补下拉刷新listview的效果

在拖到顶部以后,还可以overScroll继续拖动,
而继续拖动的最大距离就是maxOverScrollY,如图左边小箭头的长度
底部同理

那整个边界范围就变了~
从原来的y = 0 ~ 300 变成了
-maxOverScrollY ~ scrollRangeY + maxOverScrollY 
看图很好理解,就变成了
y = -50 ~ 350
横向同理


下面代码就是判断了
如果现在滚动的新坐标超过了over的极限值,则将极限值赋值给新坐标~
简单而言就是滑动到极限越界距离以后就卡住他,让他"划不动",
这里英文clamp为"钳住"的意思,英语好的可以帮助快速理解~

x/y方向达到极限距离的同时会分别记录下一个标志符,最后|作为返回值,
即任何一个方向上有划不动的情况时则返回true,否则false

最后通过onOverScrolled暴露给子类,这里view里面的onOverScrolled方法是empty不做任何处理的~里面注释也是卖萌,"我是故意的~" 



上面一大串overScrollBy方法源码分析这里总结一下
该方法就相当于在scrollTo/By的基础上添加了对overScroll情况的处理, 但父类view中只处理数据,没有实际的scroll操作,父类view处理完数据后将其记录在onOverScrolled方法中,
子类继承onOverScrolled方法再根据得到的数据scrollTo/By处理即可~

举个简单的例子帮助理解,比如这个view相当于一个大加工厂,
那overScrollBy方法相当于一个加工车间,比如是做串串的(竹签上有海带素鸡一类的那种)- -
把原材料加工好成串串后,直接就丢到一个储藏的仓库里~不做任何其他操作


而继承view的子类ScrollView就相当于来拿货的销售商,来了以后不管生产过程,直接去这个储藏的仓库里,
把货拿出来然后该卖的卖,该自己吃的自己吃,该二次加工的二次加工~进行具体的操作~
这个仓库就可以理解为onOverScrolled方法~

可以理解为一个监听,比如scrollview提供一个onScroll监听,父类只用该方法记录数据,
而子类复写之就可以获取到所需数据,如当前滚动到位置,根据需要处理了


回到源码ScrollView的onOverScrolled方法
其实简单处理的话直接在onOverScrolled里面scrollTo就可以了,但是系统考虑到scrollbar,animating scroll等情况所以处理的比较复杂
如果下面这段代码无法理解,可以先跳过本段,直接到ACTION_UP部分,
可以在看完后面OverScroll实现(demo中view3)介绍,研究懂后再回头看这段代码

    @Override
    protected void onOverScrolled( int scrollX, int scrollY,
            boolean clampedX, boolean clampedY) {
        // Treat animating scrolls differently; see #computeScroll() for why.
        if (!mScroller.isFinished()) {
            mScrollX = scrollX;
            mScrollY = scrollY;
            invalidateParentIfNeeded();
            if ( clampedY) {
                mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange());
            }
        } else {
            super.scrollTo(scrollX, scrollY);
        }
        awakenScrollBars();
    }

这里是if分成两部分的,注释说明为,对于还在进行中的滚动要区别处理,
区分处理的原因可以参考computeScroll()方法

    @Override
    public void computeScroll () {
        if (mScroller.computeScrollOffset()) {
            // This is called at drawing time by ViewGroup.  We don't want to
            // re-show the scrollbars at this point, which scrollTo will do,
            // so we replicate most of scrollTo here.
            //
            //         It's a little odd to call onScrollChanged from inside the drawing.
            //
            //         It is, except when you remember that computeScroll() is used to
            //         animate scrolling. So unless we want to defer the onScrollChanged()
            //         until the end of the animated scrolling, we don't really have a
            //         choice here.
            //
            //         I agree.  The alternative, which I think would be worse, is to post
            //         something and tell the subclasses later.  This is bad because there
            //         will be a window where mScrollX/Y is different from what the app
            //         thinks it is.
            //
            int oldX = mScrollX;
            int oldY = mScrollY;
            int x = mScroller.getCurrX();
            int y = mScroller.getCurrY();

            if (oldX != x || oldY != y) {
                final int range = getScrollRange();
                final int overscrollMode = getOverScrollMode();
                final boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
                        (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);

                overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range,
                        0, mOverflingDistancefalse);
                onScrollChanged(mScrollX, mScrollY, oldX, oldY);

                if (canOverscroll) {
                    if (y < 0 && oldY >= 0) {
                        mEdgeGlowTop.onAbsorb(( intmScroller.getCurrVelocity());
                    } else if (y > range && oldY <= range) {
                        mEdgeGlowBottom.onAbsorb(( intmScroller.getCurrVelocity());
                    }
                }
            }

            awakenScrollBars();

            // Keep on drawing until the animation has finished.
            postInvalidate();
        } else {
            if (mFlingStrictSpan != null) {
                mFlingStrictSpan.finish();
                mFlingStrictSpan = null;
            }
        }
    }

注释翻译下
这个方法会在viewgroup draw绘制的时候调用,
我们不想在这个时候再次显示scrollbar滚动条,这个scrollTo方法会处理,
所以我们在这里复制了scrollTo方法的大部分内容

下面代码和onTouch里的MOVE中代码一样(上文有引用过这部分代码)
回到onOverScrolled方法中,结合看意思就是
如果mScroller.isFinished滚动动画已经结束了,那正常scrollTo方法滚动
如果未结束,那调用一个不会显示scrollBar滚动条的scrollTo方法
(上面注释说过,处理原因就是为了方法滚动条再次显示,
且代码大部分复制scrollTo,即相当于一个不显示滚动条的scrollTo方法)

且如果未滚动完成,还要加个判断, 如果是滚动到不能再滚动了,即clampedY=true
则进行回弹操作mScroller.springBack


再次引用上面车间加工串串的例子加深理解
demo中自己处理就相当于,卖串串的自己加工串串(计算数据), 直接卖(scrollTo)~
系统的ScrollView呢,就是把串串交给加工车间view精加工一下(overScrollBy),
最后从onOverScrolled仓库中取加工好的串串再去卖(scrollTo)

-----------------------------------------------------------------------------

回到onTouch,之后是ACTION_UP操作

按照平常滚屏的习惯,UP抬起时,一般还会有一个惯性继续滚动一段距离~
(这里我们把滚动叫做scroll,这个有惯性的甩~抛~的动作叫做fling~)
首先用VelocityTracket获取y向的速度,根据速度去处理fling
(按照通常思维,速度越快,甩的越远~)

             case MotionEvent. ACTION_UP:
                if ( mIsBeingDragged) {
                    final VelocityTracker velocityTracker = mVelocityTracker;
                    velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                    int initialVelocity = ( int) velocityTracker.getYVelocity(mActivePointerId );

                    if (getChildCount() > 0) {
                        if ((Math. abs(initialVelocity) > mMinimumVelocity)) {
                            fling(-initialVelocity);
                        } else {
                            if ( mScroller.springBack(mScrollX, mScrollY, 0, 0, 0,
                                    getScrollRange())) {
                                invalidate();
                            }
                        }
                    }

                    mActivePointerId = INVALID_POINTER;
                    endDrag();
                }
                break;

这里有判断,当速度超过一个最小阀值的时候,就视为一个fling~则调用fling()方法
否则视为普通的scroll,那判断这个时候是否需要回弹操作,有的话invalidate刷新页面

下面是核心方法~甩~
    /**
     * Fling the scroll view
     *
     * @param velocityY The initial velocity in the Y direction. Positive
     *                  numbers mean that the finger/cursor is moving down the screen,
     *                  which means we want to scroll towards the top.
     */
    public void fling(int velocityY) {
        if (getChildCount() > 0) {
            int height = getHeight() - mPaddingBottom - mPaddingTop;
            int bottom = getChildAt(0).getHeight();

            mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0,
                    Math. max(0, bottom - height), 0, height/2);

            final boolean movingDown = velocityY > 0;

            if (mFlingStrictSpan == null) {
                mFlingStrictSpan = StrictMode.enterCriticalSpan("ScrollView-fling" );
            }

            invalidate();
        }
    }

诸如这类"高级"的滚动,都使用了Scroller类处理~详见
http://www.eoeandroid.com/thread-553376-1-1.html


fling里面实际上调用的是mScroller的fling方法,我们定位到OverScroller类该方法

内部原理太多,这里只解释方法的作用和参数的意义了
方法作用:根据一个fling手势开始fling行为~移动的距离取决于fling的初始速度~
参数传进来的值为
mScrollX, mScrollY, 0, velocityY, 0, 0, 0, Math. max(0, bottom - height), 0, height/2
一共10个参数分两组,奇数为x轴处理,偶为y
2 4 6 8 10五个参数分别介绍(x向的同理)
startY 开始甩的y坐标 - 值为当前滚动位置mScrollY
velocityY y轴上的速度 - 值为velocityTracker计算出来的速度值
minY y轴上可以到的最小坐标 - 值为0
maxY y抽上可以到的最大坐标 - 值为child的高度减去viewgroup除padding以外的高度~
(这个值和之前计算的scrollRangeY一样,只不过那个是长度,这个是坐标~
想理解可以参考之前的图片)
overY 越界滑动的范围 - 值为viewgroup除padding高度以外的一半

fling里面的具体速度距离的算法...略过~


插一句
如果是没有over的scroll或者fling,那直接用Scroller类就可以了,系统考虑的比较全,
所以ScrollView里面用的是OverScroller类~
同样的fling方法Scroller就没有最后两个和over相关的参数

下面是一个不考虑over情况用Scroller实现fling效果的demo

-----------------------------------------------------------------------------

回到over scroll的处理
上面提到,系统ScrollView源码中是有处理over scroll的,只不过ScrollView中的对应参数overDistance为0,造成了ScrollView没有over scroll的效果
这里我们可以自己尝试实现下~

其实非常简单,demo12中ouTouch的MOVE里面的scrollTo方法改成和ScrollView一样的,
即利用父类view的overScrollBy方法转一圈~
只不过调用overScrollBy方法时,记得传入的maxOverScrollX/Y不能是0,不然就没意义了

这样拖动的时候就可以over scroll了~

既然有over scroll那我们肯定需要回弹效果,即松手后自动滚动回来~
前面说过这种高级的要用Scroller类,而牵涉到over的,自然就用到OverScroller类了~
fling方法和Scroller一样,多一个overX方法,即fling时over scroll的最大距离,
demo里我设定成了maxOverScrollX的一半,可以自行调整

fling的回弹是自动的,但我们fling行为是有个最小速度判断的,因而在UP时还要加个,
没有fling时,如果开始了一个回弹效果,则刷新视图~

以下是over scroll的优化效果
over scroll从体验上来说,我们希望是有一个阻塞的效果的,
即如果普通状态下,手指移动100距离,那view也滚动100距离,
但是over scroll时,手指移动100距离,view滚动距离应该按比例降低~
这样一种效果才更加"真实"

此外demo中还在onOverScrolled回调用添加了一个拉断效果,
即当拖动到over scroll的极限距离时,虽然没有UP但是强制进行回弹操作,
相当于模拟了一个"拉断"的效果

-----------------------------------------------------------------------------

缺陷, 没有考虑measure,所以MyScrollView中子类size有点问题,demo中暂时写死了child宽度,
measure的原理介绍会在后面有时间整理出~希望大家持续关注


照例,回复可以免积分下载~

本帖隐藏的内容

链接:http://pan.baidu.com/s/1dDCL4nZ 密码:idth



转自http://www.eoeandroid.com/thread-553375-1-1.html



0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 商铺房东涨房租怎么办 生了个畸形孩子怎么办 螃蟹的内脏吃了怎么办 脸上好多黑头在毛孔里怎么办 秋天穿裙子腿冷怎么办 拖鞋穿久了发臭怎么办 付钱了抢不到票怎么办 一号通电讯骗了怎么办 酒店预授权没退怎么办 英国拒签10年怎么办 汽车行驶证丢了怎么办 狗狗剧烈的抖动怎么办 飞度减震那么硬怎么办 抢红包有人开挂怎么办 票买了没身份证怎么办 高铁身份证没带怎么办 在飞机上要拍照怎么办 拍照片人闭眼了怎么办 偏头疼恶心想吐怎么办 健康证怎么办 在哪办 怎么办健康证去哪里办 公司合同不给我怎么办 孕妇吃了酸梅粉怎么办 婧氏牙膏没客源怎么办 乐视手机耗电快怎么办 乐视手机费电快怎么办 最爱最恨都是你怎么办 耳朵长疱疹很痛怎么办 刚怀孕就有霉菌怎么办 车牌选错了想换怎么办 足癣传染到身上怎么办 金鱼生病了立鳞怎么办 脸上有闭口痘痘怎么办 泰迪得了皮肤癣怎么办 qq截图发不出去怎么办 半夜2点肚子饿怎么办 半夜两三点饿了怎么办 科目三预约不上怎么办 心脏支架又堵了怎么办 车辆验车过期了怎么办 车辆年审过期2天怎么办