天天看點

[Android執行個體] Scroll原理-附ScrollView源碼分析

想象一下你拿着放大鏡貼很近的看一副巨大的清明上河圖, 那放大鏡裡可以看到的内容是很有限的,

而随着放大鏡的上下左右移動,就可以看到不同的内容了

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 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

     * @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) {

           }

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();

系統注釋還是很良心的,一般以功能點為機關注釋,這裡還是根據注釋去看

分兩段:随着觸摸事件的滾動,還有頂部底部邊緣陰影的處理

陰影處理的先忽視

這裡的觸摸點擷取不是直接event.getY,而是通過下面兩句代碼擷取的

        final int activePointerIndex = ev.findPointerIndex( mActivePointerId);

上面已經介紹過了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 =

                        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;

        boolean clampedY = false;

        if (newScrollY > bottom) {

            newScrollY = bottom;

            clampedY = true;

        } else if (newScrollY < top) {

            newScrollY = top;

        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 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,方法内算法如下

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 // Treat animating scrolls differently; see #computeScroll() for why.

        if (!mScroller.isFinished()) {

            mScrollX = scrollX;

            mScrollY = scrollY;

            invalidateParentIfNeeded();

            if ( mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange());

        } else {

            super.scrollTo(scrollX, scrollY);

        awakenScrollBars();

這裡是if分成兩部分的,注釋說明為,對于還在進行中的滾動要差別處理,

區分處理的原因可以參考computeScroll()方法

    public void 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, mOverflingDistance, false);

                onScrollChanged(mScrollX, mScrollY, oldX, oldY);

                if (canOverscroll) {

                    if (y < 0 && oldY >= 0) {

                        mEdgeGlowTop.onAbsorb(( int) mScroller.getCurrVelocity());

                    } else if (y > range && oldY <= range) {

                        mEdgeGlowBottom.onAbsorb(( int) mScroller.getCurrVelocity());

                    }

            awakenScrollBars();

            // Keep on drawing until the animation has finished.

            postInvalidate();

            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)) {

                                                    } 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) {

            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類處理~詳見

<a href="http://www.eoeandroid.com/thread-553376-1-1.html" target="_blank">http://www.eoeandroid.com/thread-553376-1-1.html</a>

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://www.cnblogs.com/zhoug2020/p/6083366.html,如需轉載請自行聯系原作者

繼續閱讀