看一看ScrollView的源碼

欣賞
你對明天有沒有美好的期待呢跨蟹,它是否會如期而來呢.....
線索
  1. 主要的成員標記
  2. 核心功能與方法
1. 成員
mScroller: 用來計算內(nèi)部內(nèi)容滾動的輔助類OverScroller雳殊,但是主要的滾動并沒有使用到他哦

mEdgeGlowTop: 當滾動到最頂部的時候的上邊界陰影效果

mEdgeGlowBottom: 當滾動到最底部時候的下邊界陰影效果

mChildToScrollTo: 記錄擁有焦點的view, 在layout的時候會將位置滾動到他的地方

mIsBeingDragged: true表示scrollview在滾動;注意在嵌套滾動的時候,不屬于scrollView的滾動窗轩,他為false;

mFillViewport: true, ScrollView模式不是MeasureSpec.UNSPECIFIED夯秃, 會用scrollView的高度來當自己的高度,重新在給子view測量一遍;一般他都不會是這個模式, 什么情況下ScrollView是UNSPECIFIED模式呢痢艺,從scollView的測量可以看出仓洼,他給子view測量的規(guī)格就是UNSPECIFIED,因此scrollView嵌套scrollView的時候堤舒, 第二個scrollView如果設置了mFillViewport色建,依然是不會重新測量他的子view的哦。

mActivePointerId: 當前激活的手指; 這個是scrollView處理多手指滑動的原則舌缤。他的基本規(guī)則是這樣的箕戳,當?shù)诙€手指落下滑動的時候某残,那么前面第一個手指滑動就失效了,因為將激活的手指給到了第二個手指陵吸,當?shù)诙€手指抬起的時候玻墅,又會把激活的點給到第一個手指。

其他的就不一一列出來了......
2. 功能與方法

普通的LinearLayout, RelativeLayout, FrameLayout他們的頁面內(nèi)容都是有限的壮虫,限于一屏之內(nèi)澳厢,而ScrollView卻不是哦,這就是他主要功能旨指。

  • ScrollView的構造與初始化:
public ScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
    initScrollView();
    final TypedArray a = context.obtainStyledAttributes(
        attrs, com.android.internal.R.styleable.ScrollView, defStyleAttr, defStyleRes);
  //讀取是否設置了viewport屬性赏酥,讓子view填充scrollView高度
    setFillViewport(a.getBoolean(R.styleable.ScrollView_fillViewport, false));

    a.recycle();
}


private void initScrollView() {
    mScroller = new OverScroller(getContext());
    setFocusable(true);
    //優(yōu)先讓子view獲得焦點
    setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
    setWillNotDraw(false);
    final ViewConfiguration configuration = ViewConfiguration.get(mContext);
    //滾動識別距離,
    mTouchSlop = configuration.getScaledTouchSlop();
    //fling滑行的最小速度
    mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
     //fling滑行的最大速度
    mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
    mOverscrollDistance = configuration.getScaledOverscrollDistance();
    mOverflingDistance = configuration.getScaledOverflingDistance();
}
  • 總結一下谆构,其實構造和初始化比較簡單裸扶,就是讀取scrollView的一個_fillViewport屬性,如果他為true, 他的子child一般會經(jīng)過兩側(cè)測量搬素,最終的高度和scollView是一樣的; 然后就是讀取系統(tǒng)的默認配置呵晨,比如移動多少算是滾動,多大速度可以在松手時候認為是要去滑翔熬尺。

  • ScrollView只能有一個child摸屠,否則會拋異常, 看這里:

     public void addView(View child) {
         if (getChildCount() > 0) {
             throw new IllegalStateException("ScrollView can host only one direct child");
         }
    
         super.addView(child);
     }
    
  • setFillViewport,動態(tài)設定ScrollView的填充屬性

    public void setFillViewport(boolean fillViewport) {
        if (fillViewport != mFillViewport) {
            mFillViewport = fillViewport;
            //因為要改變子child的高度粱哼,自然要重新測量季二,布局一次啦。
            requestLayout();
        }
    }
    
  • ScrollView的測量入口, onMeasure:

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //主要的測量還是調(diào)用父類FrameLayout的測量揭措,
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //mFillViewport為true, 才需要重新測量一次
        if (!mFillViewport) {
            return;
        }
    
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        //scrollView本身的模式一般不會是這個胯舷,除非是scrollView的父容器也是scrollView
        if (heightMode == MeasureSpec.UNSPECIFIED) {
            return;
        }
    
        if (getChildCount() > 0) {
            final View child = getChildAt(0);
            int height = getMeasuredHeight();
            //如果child的高度小于scrollView的高度啦,
            if (child.getMeasuredHeight() < height) {
                final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
                int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                                                                mPaddingLeft + mPaddingRight, lp.width);
                height -= mPaddingTop;
                height -= mPaddingBottom;
                int childHeightMeasureSpec =
                MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
              //重新測量子child
                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            }
        }
    }
    
    
    • 總結一下, 可以看出ScrollView的本身測量主要是調(diào)用父類FrameLayout的測量方法绊含,簡單說說FrameLayout的測量桑嘶,他其實是先遍歷所有的子view, 找到一個高度最大的view作為預備的高度,然后通過FrameLayout本身的測量規(guī)格(包含規(guī)格和父容器的剩余高度)躬充,來決定他的高度逃顶。比如FrameLayout的onMeasure傳遞過來的規(guī)格是EXACTLY,那么最終高度就是measureSpec解析出來的高度充甚,比如傳遞過來的是AT_MOST以政,并且我們的預備高度小于measureSpec解析的高度,那么最終高度就是我們前面計算的預備高度啦, 也就是我們的scrollView的最終高度伴找!
  • ScrollView的測量之二妙蔗,子Child的測量measureChildWithMargins:

    protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
                                           int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
      
        //獲取child的寬度規(guī)格,
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                                                              mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                                                              + widthUsed, lp.width);
        //構建高度的測量規(guī)格疆瑰,可以看到給到size只是一個上下margin, mode是無限制的眉反。根據(jù)標準的測量原則
        //可以知道昙啄,子child想要多高就給多高啦。
        final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
            lp.topMargin + lp.bottomMargin, MeasureSpec.UNSPECIFIED);
      //測量寸五。
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
    
    • 總結一下梳凛,在測量scrollview的時候,其實是要在FrameLayout中先測量他的子view的寬高的梳杏,然后根據(jù)子view的高度來設定Scrollview的高度韧拒。scrollview重寫的measureChildWithMargins方法,他在測量子child時候給child的規(guī)格是MeasureSpec.UNSPECIFIED十性, 這個是很少見的叛溢,但原來也是很有用的,他可以讓子child得到他想要的任意高度劲适,ScrollView不限制他的子view高度楷掉, 在滾動容器中正好是需要的啦!
  • ScrollView的布局

    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        //依然還是調(diào)用frameLayout去布局他的child啦;
        super.onLayout(changed, l, t, r, b);
        //
        mIsLayoutDirty = false;
        // 如果記錄了焦點的child,那么就滾到到該child的位置霞势,注意可能不是ScrollView的直接子child哦
        if (mChildToScrollTo != null && isViewDescendantOf(mChildToScrollTo, this)) {
            scrollToChild(mChildToScrollTo);
        }
        mChildToScrollTo = null;
    
        if (!isLaidOut()) {
            if (mSavedState != null) {
                mScrollY = mSavedState.scrollPosition;
                mSavedState = null;
            } // mScrollY default value is "0"
    
            final int childHeight = (getChildCount() > 0) ? getChildAt(0).getMeasuredHeight() : 0;
            //計算可以滾動的距離
            final int scrollRange = Math.max(0,
                                             childHeight - (b - t - mPaddingBottom - mPaddingTop));
    
            // 范圍糾正
            if (mScrollY > scrollRange) {
                mScrollY = scrollRange;
            } else if (mScrollY < 0) {
                mScrollY = 0;
            }
        }
    
        // 重新布局的時候不會丟失原來的滾動位置
        scrollTo(mScrollX, mScrollY);
    }
    
    
    • 總結一下烹植,可以看出,布局的主要手段還是借助了FrameLayout愕贡,這個其實也很簡單草雕,就一個child. ScrollView不本身做了焦點child的位置滾動,以及還原以前的scroll位置.
  • ScrollView的繪制固以,draw方法

    public void draw(Canvas canvas) {
        //調(diào)用fm.draw繪制自己的內(nèi)容
        super.draw(canvas);
        if (mEdgeGlowTop != null) {
            final int scrollY = mScrollY;
            if (!mEdgeGlowTop.isFinished()) {//拖到了邊界處沒有松手
                final int restoreCount = canvas.save();
                final int width = getWidth() - mPaddingLeft - mPaddingRight;
              
                canvas.translate(mPaddingLeft, Math.min(0, scrollY));
                mEdgeGlowTop.setSize(width, getHeight());
                //繪制他的上邊界
                if (mEdgeGlowTop.draw(canvas)) {
                    postInvalidateOnAnimation();
                }
                canvas.restoreToCount(restoreCount);
            }
            if (!mEdgeGlowBottom.isFinished()) {//拖到了邊界處沒有松手
                final int restoreCount = canvas.save();
                final int width = getWidth() - mPaddingLeft - mPaddingRight;
                final int height = getHeight();
    
                canvas.translate(-width + mPaddingLeft,
                                 Math.max(getScrollRange(), scrollY) + height);
                canvas.rotate(180, width, 0);
                mEdgeGlowBottom.setSize(width, height);
                //繪制他的下邊界
                if (mEdgeGlowBottom.draw(canvas)) {
                    postInvalidateOnAnimation();
                }
                canvas.restoreToCount(restoreCount);
            }
        }
    }
    
    
    • 總結一下墩虹,除了借助Fm去繪制child和本身外,這里主要的內(nèi)容就是當滾動了邊界處的時候憨琳,繪制上邊界的陰影和下邊界的陰影效果诫钓。
  • ScollView滾動之攔截,onInterceptTouchEvent, 如果攔截了觸摸栽渴,那么子view就不能順利地使用觸摸事件啦,比如ScrollView下面的Recyclerview等稳懒,看看代碼吧:

    public boolean onInterceptTouchEvent(MotionEvent ev) {
           
            final int action = ev.getAction();
          //如果之前攔截了闲擦,檔次又是move,那么就攔下來,不去后面的計算啦
            if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
                return true;
            }
    
              //沒得去滑了场梆,而且還要沒滑動過不攔截墅冷,奇怪,為什么要getScrollY==或油?
            if (getScrollY() == 0 && !canScrollVertically(1)) {
                return false;
            }
    
            switch (action & MotionEvent.ACTION_MASK) {
                case MotionEvent.ACTION_MOVE: {
         
                    final int activePointerId = mActivePointerId;
                    if (activePointerId == INVALID_POINTER) {
                        // If we don't have a valid id, the touch down wasn't on content.
                        break;
                    }
    
                    final int pointerIndex = ev.findPointerIndex(activePointerId);
                    if (pointerIndex == -1) {
                        Log.e(TAG, "Invalid pointerId=" + activePointerId
                                + " in onInterceptTouchEvent");
                        break;
                    }
    
                    final int y = (int) ev.getY(pointerIndex);
                    final int yDiff = Math.abs(y - mLastMotionY);
                   
                    //如果move達到了滾動寞忿,并且不是嵌套滾動,立即攔截顶岸。
                    if (yDiff > mTouchSlop && (getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) {
                        mIsBeingDragged = true;
                        mLastMotionY = y;
                        initVelocityTrackerIfNotExists();
                        mVelocityTracker.addMovement(ev);
                        mNestedYOffset = 0;
                        if (mScrollStrictSpan == null) {
                            mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll");
                        }
                        final ViewParent parent = getParent();
                        if (parent != null) {
                            parent.requestDisallowInterceptTouchEvent(true);
                        }
                    }
                    break;
                }
    
                case MotionEvent.ACTION_DOWN: {
                    final int y = (int) ev.getY();
                    if (!inChild((int) ev.getX(), (int) y)) {
                        //不攔截
                        mIsBeingDragged = false;
                        recycleVelocityTracker();
                        break;
                    }
    
                  ........
                    
                    //如果在fling狀態(tài)腔彰,立即攔截叫编。
                    mIsBeingDragged = !mScroller.isFinished();
                    if (mIsBeingDragged && mScrollStrictSpan == null) {
                        mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll");
                    }
                    startNestedScroll(SCROLL_AXIS_VERTICAL);
                    break;
                }
    
                case MotionEvent.ACTION_CANCEL:
                case MotionEvent.ACTION_UP:
                    //取消攔截
                    mIsBeingDragged = false;
                    mActivePointerId = INVALID_POINTER;
                    recycleVelocityTracker();
                    if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) {
                        postInvalidateOnAnimation();
                    }
                    stopNestedScroll();
                    break;
                case MotionEvent.ACTION_POINTER_UP:
                    onSecondaryPointerUp(ev);
                    break;
            }
    
          //攔截與否,主要還是i看這個標記呢霹抛,
            return mIsBeingDragged;
        }
    
    • 總結一下搓逾,ScrollView有幾個地方會去攔截:當在down事件時候,如果當前的ScrollView在滑行狀態(tài)杯拐,會攔截下來不給子view使用霞篡,這個時候down事件都并不會下發(fā)下去。當move的時候端逼,如果不是嵌套滾動朗兵,一般ScrollView也將它攔截下來自己使用, 還有就是之前攔截了,這次又是move事件也會立刻攔截下來顶滩,整體上ScrollView就是這么處理攔截的啦余掖,不過攔截了也不是說子view就不能用,畢竟子child可以用requestDisallowInterceptTouchEvent來禁止他的攔截生效诲祸。
  • ScollView滾動之觸摸浊吏,這個算是滾動的最重要的地方之一啦, onTouchEvent, 根據(jù)手指的事件一個個地看吧:

    case MotionEvent.ACTION_DOWN: {
        //沒有child,不消耗啦,沒搞頭啊
        if (getChildCount() == 0) {
            return false;
        }
        //如果當前在滾動狀態(tài)救氯,說明之前自己消耗的找田,這次也要自己來,請求父容器不要消耗.
        if ((mIsBeingDragged = !mScroller.isFinished())) {
            final ViewParent parent = getParent();
            if (parent != null) {
                parent.requestDisallowInterceptTouchEvent(true);
            }
        }
    
      //這里有意思着憨,當上一次的滾動還在fling狀態(tài)的時候墩衙,手指一放下,那么就會停止滑動啦甲抖,不信試試
        if (!mScroller.isFinished()) {
            //停止滑動漆改。
            mScroller.abortAnimation();
            if (mFlingStrictSpan != null) {
                mFlingStrictSpan.finish();
                mFlingStrictSpan = null;
            }
        }
    
        // Remember where the motion event started
        mLastMotionY = (int) ev.getY();
        mActivePointerId = ev.getPointerId(0);
        //觸發(fā)嵌套滑動,ScrollView的嵌套滑動太雞肋准谚,這里覺得沒有分析的必要
        startNestedScroll(SCROLL_AXIS_VERTICAL);
        break;
    }
    
    ........
        
    return true;
    
    
    case MotionEvent.ACTION_MOVE:
      //找到當前激活的手指挫剑,獲取他的手勢事件
        final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
        if (activePointerIndex == -1) {
            Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
            break;
        }
    
        final int y = (int) ev.getY(activePointerIndex);
      //向上滑動,大于0
        int deltaY = mLastMotionY - y;
      //嵌套滾動相關的柱衔,在ScrollView中的實現(xiàn)是很雞肋的,沒什么好說的
        if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset)) {
            deltaY -= mScrollConsumed[1];
            vtev.offsetLocation(0, mScrollOffset[1]);
            mNestedYOffset += mScrollOffset[1];
        }
    //如果當前沒有被認定為scrollview的滾動樊破,會根據(jù)情況讓scollView去滾動,這也是scrollView實現(xiàn)
    //內(nèi)容滾動的核心地方唆铐。
        if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) {
            final ViewParent parent = getParent();
            //這里的意思是說哲戚,當move傳遞到了scrollView了,并且達到了滾動距離艾岂,就認為該讓scrollview去
            //消耗事件了,請求不要攔截后續(xù)的事件就交給scrollview處理了顺少。但是這種實現(xiàn)有點詭異,因為如果ScollView
            //的父容器攔截了move,根本就不會走到這里來了脆炎,這里的請求不要攔截也不會生效梅猿,只有當父容器在move中攔截的條件還沒有生效,在這里設定才會起到作用腕窥。
            if (parent != null) {
                parent.requestDisallowInterceptTouchEvent(true);
            }
            
            mIsBeingDragged = true;
            if (deltaY > 0) {
                deltaY -= mTouchSlop;
            } else {
                deltaY += mTouchSlop;
            }
        }
        if (mIsBeingDragged) {
            // Scroll to follow the motion event
            mLastMotionY = y - mScrollOffset[1];
    
            final int oldY = mScrollY;
            //子child.height - scrollview的height,得出可以滾動的范圍
            final int range = getScrollRange();
            final int overscrollMode = getOverScrollMode();
            //如果是OVER_SCROLL_IF_CONTENT_SCROLLS粒没,那么必須child的內(nèi)容高度大于scollview的高度才可
            //認為繪制過度滾動視覺陰影
            boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
                (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
    
           //overScollBy進行了主要的內(nèi)容哦你那個滾動啦;
            if (overScrollBy(0, deltaY, 0, mScrollY, 0, range, 0, mOverscrollDistance, true)
                && !hasNestedScrollingParent()) {
                // Break our velocity if we hit a scroll barrier.
                mVelocityTracker.clear();
            }
          //計算scrolledDeltaY滾動過的距離
            final int scrolledDeltaY = mScrollY - oldY;
            //計算剩下的距離
            final int unconsumedY = deltaY - scrolledDeltaY;
            //雞肋的嵌套滾動,讓剩下的滾動發(fā)給他的父容器......
            if (dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, mScrollOffset)) {
                mLastMotionY -= mScrollOffset[1];
                vtev.offsetLocation(0, mScrollOffset[1]);
                mNestedYOffset += mScrollOffset[1];
            } else if (canOverscroll) {//支持過度繪制
                final int pulledToY = oldY + deltaY;
                if (pulledToY < 0) {//如果累計的滾動已經(jīng)小于0了簇爆,說明滾動到了上邊緣了癞松,那么就
                    //開始計算我們的上邊緣效果啦,這個改變可以根據(jù)你的手勢的特點改變陰影的效果入蛆。
                    mEdgeGlowTop.onPull((float) deltaY / getHeight(),
                                        ev.getX(activePointerIndex) / getWidth());
                    if (!mEdgeGlowBottom.isFinished()) {
                        mEdgeGlowBottom.onRelease();
                    }
                } else if (pulledToY > range) {//如果大于我們剩下可以滾動的范圍响蓉,說明已經(jīng)拉到了
                    //下邊緣,計算下邊緣的陰影內(nèi)容哨毁,在繪制的時候可以統(tǒng)一繪制枫甲。
                    mEdgeGlowBottom.onPull((float) deltaY / getHeight(),
                                           1.f - ev.getX(activePointerIndex) / getWidth());
                    if (!mEdgeGlowTop.isFinished()) {
                        mEdgeGlowTop.onRelease();
                    }
                }
                if (mEdgeGlowTop != null
                    && (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished())) {
                    postInvalidateOnAnimation();
                }
            }
        }
    break;
    
    
    • 總結一下,在onTouchEvent中的move中扼褪,處理scrollView的內(nèi)容滾動的關鍵地方想幻,同時還根據(jù)滑動的位置,去計算將要繪制的上下陰影的圖形效果呢话浇。這里要有一個地方注意的是脏毯,在scrollView的move中會根據(jù)實際滑動的距離來請求他的父容器不要攔截,但是這里我認為這樣的設計有太多的風險了幔崖,因為他要求父容器的攔截計算要很精確食店,如果在scrollview的請求不要攔截之前已經(jīng)預先攔截了,那么后續(xù)的move根本不會走到這個請求的邏輯赏寇,就涼涼了吉嫩。所以嵌套scrollview寫攔截邏輯,是要小心一點的.......
    • 對了嗅定, ScrollView中關鍵滾動內(nèi)容的地方在overScrollBy這里自娩,還沒有說明呢:
    ----view.java
    
    
    protected boolean overScrollBy(int deltaX, int deltaY,
                                   int scrollX, int scrollY,
                                   int scrollRangeX, int scrollRangeY,
                                   int maxOverScrollX, int maxOverScrollY,
                                   boolean isTouchEvent) {
        //獲取scrollView的模式:三種模式啦,if_content, none, alaways;
        final int overScrollMode = mOverScrollMode;
        
        .......
            
            //computeVerticalScrollRange, 高度呢渠退,說白了child的實際高度;
            //computeVerticalScrollExtent---就是scrollView的高度
        final boolean canScrollVertical =
            computeVerticalScrollRange() > computeVerticalScrollExtent();
       
        final boolean overScrollVertical = overScrollMode == OVER_SCROLL_ALWAYS ||
            (overScrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollVertical);
       .......
           
       //新的滾動距離忙迁,等于老的+這次move移動的距離
        int newScrollY = scrollY + deltaY;
        if (!overScrollVertical) {
            maxOverScrollY = 0;
        }
    
        // maxOverScrollY一般都是0,這里沒看頭,掠過
        final int left = -maxOverScrollX;
        final int right = maxOverScrollX + scrollRangeX;
        final int top = -maxOverScrollY;
        final int bottom = maxOverScrollY + scrollRangeY;
    
       .........
    
        //糾正智什,讓我們的新的滾動距離不要超過可滾動的界限
        boolean clampedY = false;
        if (newScrollY > bottom) {
            newScrollY = bottom;
            clampedY = true;
        } else if (newScrollY < top) {
            newScrollY = top;
            clampedY = true;
        }
    
        //這里計算新的滾動距離后就調(diào)用scrollView的滾動方法动漾,他重寫了這個方法,
        onOverScrolled(newScrollX, newScrollY, clampedX, clampedY);
      //表示滾動到了邊界啦......
        return clampedX || clampedY;
    }
    
    • 總結一下, 這個方法之view中的方法丁屎,主要思路是根據(jù)scollView當次move的距離荠锭,進行一次距離糾正讓他不會劃出我們子child提供的最大范圍, 沒什么東西了.
  • 接著再看上面的onOverScrolled方法:

protected void onOverScrolled(int scrollX, int scrollY,
            boolean clampedX, boolean clampedY) {
    //.
    if (!mScroller.isFinished()) {//還沒滾動完
        final int oldX = mScrollX;
        final int oldY = mScrollY;
        mScrollX = scrollX;
        mScrollY = scrollY;
        invalidateParentIfNeeded();
        onScrollChanged(mScrollX, mScrollY, oldX, oldY);
        if (clampedY) {
            mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange());
        }
    } else {
        //進行內(nèi)容的滾動哦!
        super.scrollTo(scrollX, scrollY);
    }

    //喚醒scrollbar
    awakenScrollBars();
}

  • 從上面可以看到晨川,如果當次沒滾動完证九,是不會滾動的删豺,要等到下次再滾動哦.如果可以滾動就調(diào)用View.scrollTo去滑動內(nèi)容。
  • 好了愧怜,繼續(xù)把onTouchEvent中的事件閱讀完:
 case MotionEvent.ACTION_UP://主手指抬起
    if (mIsBeingDragged) {
        //計算速度
        final VelocityTracker velocityTracker = mVelocityTracker;
        velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
        int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
      //判斷速度能不能達到滑行的標準
        if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
            //滑行
            flingWithNestedDispatch(-initialVelocity);
        } else if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0,
                                        getScrollRange())) {
            postInvalidateOnAnimation();
        }
      //清除手指活動目標
        mActivePointerId = INVALID_POINTER;
        endDrag();
    }
    break;
    case MotionEvent.ACTION_CANCEL:
    if (mIsBeingDragged && getChildCount() > 0) {
        if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) {
            postInvalidateOnAnimation();
        }
        //清除手指活動目標
        mActivePointerId = INVALID_POINTER;
        endDrag();
    }
    break;
    case MotionEvent.ACTION_POINTER_DOWN: {//副手指手指放下
        final int index = ev.getActionIndex();
        mLastMotionY = (int) ev.getY(index);
        //將副手指替換成主手指
        mActivePointerId = ev.getPointerId(index);
        break;
    }
    case MotionEvent.ACTION_POINTER_UP://副手指抬起
  //將原來的第一個手指替換成現(xiàn)在的主手指呀页,或者抬起的是第一個手指拥坛,那么什么都不用做了丸氛。
    onSecondaryPointerUp(ev);
    mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
    break;

  • 總結一下谍咆,當主手指抬起來的時候,判斷一下要不要去滑行一下螃成。除此之外,這里有多手指操作的判斷望忆,他的邏輯是稿壁,當?shù)诙€手指放下的時候,會將前面記錄主手指的第一個手指換成第二個,然后后面的滑動第一個手指就沒有效果,滑動第二個才有效果,因為計算只是跟蹤主手指。現(xiàn)在第一個手指是副手指,第二個手指是主手指了。還有抬起的時候,如果抬起的是第二個手指即主手指抬起, 那么還要將前面的第一個手指又還原成主手指,后面滑動又跟蹤他了,如果抬起的是原來的第一個手指即非主手指嘉赎,那么就不做什么了....這種多指操作公条,也是android系統(tǒng)的標準行為拇囊。
  • 看看onSecondaryPointerUp的實現(xiàn)吧....
private void onSecondaryPointerUp(MotionEvent ev) {
    final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
        MotionEvent.ACTION_POINTER_INDEX_SHIFT;
    final int pointerId = ev.getPointerId(pointerIndex);
    //如果當前抬起的是主手指,就要進行主手指的重新定位靶橱,如果不是那么就啥都不做.....
    if (pointerId == mActivePointerId) {
        // This was our active pointer going up. Choose a new
        // active pointer and adjust accordingly.
        // TODO: Make this decision more intelligent.
        final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
        mLastMotionY = (int) ev.getY(newPointerIndex);
        mActivePointerId = ev.getPointerId(newPointerIndex);
        if (mVelocityTracker != null) {
            mVelocityTracker.clear();
        }
    }
}
3. 結束
  • 好了寥袭, 原來ScollView的滾動還是利用View標準的scrollTo去滾動的呀奢米,對ScrollView的理解就到這里了吧, 說實話感覺代碼有點惡心纠永,代碼不算太多,但是功能不是特別明確谒拴,對應用開發(fā)可造成的隱性的問題不少尝江。
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市英上,隨后出現(xiàn)的幾起案子炭序,更是在濱河造成了極大的恐慌,老刑警劉巖苍日,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件惭聂,死亡現(xiàn)場離奇詭異,居然都是意外死亡相恃,警方通過查閱死者的電腦和手機辜纲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拦耐,“玉大人耕腾,你說我怎么就攤上這事∩迸矗” “怎么了扫俺?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長固翰。 經(jīng)常有香客問我狼纬,道長,這世上最難降的妖魔是什么骂际? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任疗琉,我火速辦了婚禮,結果婚禮上歉铝,老公的妹妹穿的比我還像新娘没炒。我一直安慰自己,他們只是感情好犯戏,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布送火。 她就那樣靜靜地躺著,像睡著了一般先匪。 火紅的嫁衣襯著肌膚如雪种吸。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天呀非,我揣著相機與錄音坚俗,去河邊找鬼镜盯。 笑死,一個胖子當著我的面吹牛猖败,可吹牛的內(nèi)容都是我干的速缆。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼恩闻,長吁一口氣:“原來是場噩夢啊……” “哼艺糜!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起幢尚,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤破停,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后尉剩,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體真慢,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年理茎,在試婚紗的時候發(fā)現(xiàn)自己被綠了黑界。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡皂林,死狀恐怖园爷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情式撼,我是刑警寧澤童社,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站著隆,受9級特大地震影響扰楼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜美浦,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一弦赖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧浦辨,春花似錦蹬竖、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至芽腾,卻和暖如春旦装,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背摊滔。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工阴绢, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留店乐,地道東北人。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓呻袭,卻偏偏與公主長得像眨八,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子左电,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345