view系列源碼分析之三大常用控件之scrollview

scrollview作為一個android的基礎(chǔ)的控件,用途十分的廣泛麻惶,下面來分析它他的原理

關(guān)于onMeasure,onLayout

scrollview的onMeasure如下:

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        if (!mFillViewport) {
            return;
        }

        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        if (heightMode == MeasureSpec.UNSPECIFIED) {
            return;
        }

        if (getChildCount() > 0) {
            final View child = getChildAt(0);
            final int widthPadding;
            final int heightPadding;
            final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
            final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
            if (targetSdkVersion >= VERSION_CODES.M) {
                widthPadding = mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin;
                heightPadding = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin;
            } else {
                widthPadding = mPaddingLeft + mPaddingRight;
                heightPadding = mPaddingTop + mPaddingBottom;
            }

            final int desiredHeight = getMeasuredHeight() - heightPadding;
            if (child.getMeasuredHeight() < desiredHeight) {
                final int childWidthMeasureSpec = getChildMeasureSpec(
                        widthMeasureSpec, widthPadding, lp.width);
                final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                        desiredHeight, MeasureSpec.EXACTLY);
                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            }
        }
    }

可以看到,在onMeasure中是調(diào)用父view的onMeasure的邏輯進(jìn)行測量的猛计,onMeasure的目的就是讓子view測量洲脂,然后獲取到寬高后,然后在給自身的寬高賦值裹虫,而在onMeasure中,要測量子view必定會調(diào)用measureChildWithMargins方法融击,此方法很明顯是根據(jù)父view的measureSpec減去自身的padding和子view的margin筑公,然后根據(jù)子view的xml的寬高,計算出子view的寬高的尊浪,很明顯匣屡,scrollview打破了這種規(guī)則封救,來看下其重寫的方法

@Override
    protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int usedTotal = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin +
                heightUsed;
        final int childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
                Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - usedTotal),
                MeasureSpec.UNSPECIFIED);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

可以看到在寬度的計算上沒啥問題,而在高度上捣作,無論子view的高度是那種模式誉结,scrollview都給子view賦值為MeasureSpec.UNSPECIFIED模式,高度就是自身的高度券躁,所以這也是為啥scrollview嵌套listview時惩坑,高度顯示不全的原因了,因為listview沒有對UNSPECIFIED模式做處理也拜,而此時子view是linearLayout,linearLayout在UNSPECIFIED的模式下的高度是根據(jù)子view的高度再加上其子view也就是textview的margin和自己的padding決定的以舒,所以measure的步驟分析完了,簡單來說就是根據(jù)子view的高度決定自己的高度(LinearLayout的高度比scrollview要高)
而onLayout用的也是FrameLayout的onLayout

 @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        mIsLayoutDirty = false;
        // Give a child focus if it needs it
        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));

            // Don't forget to clamp
            if (mScrollY > scrollRange) {
                mScrollY = scrollRange;
            } else if (mScrollY < 0) {
                mScrollY = 0;
            }
        }

        // Calling this with the present values causes it to re-claim them
        scrollTo(mScrollX, mScrollY);
    }

可以看到又是熟悉的super

關(guān)于滑動的分析

在分析前慢哈,如果對事件分發(fā)不熟悉的話可以看下事件分發(fā)結(jié)論
簡單來說就是在viewRootImp中先注冊了一個inputChannel對象蔓钟,然后底層會回調(diào)

 private void dispatchInputEvent(int seq, InputEvent event, int displayId) {
        mSeqMap.put(event.getSequenceNumber(), seq);
        onInputEvent(event, displayId);
    }

繼而間接的調(diào)用view的dispatchPointerEvent方法

public final boolean dispatchPointerEvent(MotionEvent event) {
        if (event.isTouchEvent()) {
            return dispatchTouchEvent(event);
        } else {
            return dispatchGenericMotionEvent(event);
        }
    }

繼而調(diào)用decorview的dispatchTouEvent方法,在activity中此decorview進(jìn)行了重寫卵贱,會調(diào)用activity的dispatchTouEvent方法滥沫,而我們在popwindow中,此方法如下

 @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) {
                return true;
            }
            return super.dispatchTouchEvent(ev);
        }

可以看到直接調(diào)用了viewGroup的dispatchTouchEvent方法键俱,此方法在down的時候會層層向下調(diào)用兰绣,一直調(diào)用scrollview的onInterceptTouchEvent方法,其代碼如下

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        /*
         * This method JUST determines whether we want to intercept the motion.
         * If we return true, onMotionEvent will be called and we do the actual
         * scrolling there.
         */

        /*
        * Shortcut the most recurring case: the user is in the dragging
        * state and he is moving his finger.  We want to intercept this
        * motion.
        */
        final int action = ev.getAction();
        if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
            return true;
        }

        if (super.onInterceptTouchEvent(ev)) {
            return true;
        }

        /*
         * Don't try to intercept touch if we can't scroll anyway.
         */
        if (getScrollY() == 0 && !canScrollVertically(1)) {
            return false;
        }

        switch (action & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_MOVE: {
                /*
                 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
                 * whether the user has moved far enough from his original down touch.
                 */

                /*
                * Locally do absolute value. mLastMotionY is set to the y value
                * of the down event.
                */
                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);
                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;
                }

                /*
                 * Remember location of down touch.
                 * ACTION_DOWN always refers to pointer index 0.
                 */
                mLastMotionY = y;
                mActivePointerId = ev.getPointerId(0);

                initOrResetVelocityTracker();
                mVelocityTracker.addMovement(ev);
                /*
                 * If being flinged and user touches the screen, initiate drag;
                 * otherwise don't. mScroller.isFinished should be false when
                 * being flinged. We need to call computeScrollOffset() first so that
                 * isFinished() is correct.
                */
                mScroller.computeScrollOffset();
                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:
                /* Release the drag */
                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;
        }

        /*
        * The only time we want to intercept motion events is if we are in the
        * drag mode.
        */
        return mIsBeingDragged;
    }

其實代碼在down的時候主要做了這幾件事
1.確定了down在scrollview的坐標(biāo)
2.如果此時在滑動的情況下方妖,點下去了狭魂,讓其停止滑動,也就是調(diào)用overScroller的computeScrollOffset方法
3.進(jìn)行了startNestedScroll也就是嵌套滑動党觅,其實看代碼scrollview在5.0以上是完全兼容嵌套滑動的代碼如下

public boolean startNestedScroll(int axes) {
        if (hasNestedScrollingParent()) {
            // Already in progress
            return true;
        }
        if (isNestedScrollingEnabled()) {
            ViewParent p = getParent();
            View child = this;
            while (p != null) {
                try {
                    if (p.onStartNestedScroll(child, this, axes)) {
                        mNestedScrollingParent = p;
                        p.onNestedScrollAccepted(child, this, axes);
                        return true;
                    }
                } catch (AbstractMethodError e) {
                    Log.e(VIEW_LOG_TAG, "ViewParent " + p + " does not implement interface " +
                            "method onStartNestedScroll", e);
                    // Allow the search upward to continue
                }
                if (p instanceof View) {
                    child = (View) p;
                }
                p = p.getParent();
            }
        }
        return false;
    }

可以看到如果支持嵌套滑動的話雌澄,也就是isNestedScrollingEnabled為true,那他調(diào)用父view的onStartNestedScroll方法杯瞻,如果為true镐牺,那它就調(diào)用其onNestedScrollAccepted方法,我們就以scrollview舉例魁莉,看其這兩個方法是如何寫的

  @Override
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
        return (nestedScrollAxes & SCROLL_AXIS_VERTICAL) != 0;
    }

    @Override
    public void onNestedScrollAccepted(View child, View target, int axes) {
        super.onNestedScrollAccepted(child, target, axes);
        startNestedScroll(SCROLL_AXIS_VERTICAL);
    }

可以看到scrollview的parent的onNestedScrollAccepted會一直循環(huán)向上調(diào)用
由于scrollview的Intercept方法在down的時候返回的false,所以回去找能消耗down事件的view睬涧,也就是mFirstTargart的賦值
如果mFirstTargart找到了,就記錄下mFirstTargart的值
如果沒找到就讓scrollview處理down事件

if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // Child wants to receive touch within its bounds.
                                mLastTouchDownTime = ev.getDownTime();
                                if (preorderedList != null) {
                                    // childIndex points into presorted list, find original index
                                    for (int j = 0; j < childrenCount; j++) {
                                        if (children[childIndex] == mChildren[j]) {
                                            mLastTouchDownIndex = j;
                                            break;
                                        }
                                    }
                                } else {
                                    mLastTouchDownIndex = childIndex;
                                }
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }

                            // The accessibility focus didn't handle the event, so clear
                            // the flag and do a normal dispatch to all children.
                            ev.setTargetAccessibilityFocus(false);
                        }
                        if (preorderedList != null) preorderedList.clear();
                    }

...

主要是viewGroup的這個方法判斷的

在來看下move的情況

也差不多分2種情況
要看viewGroup的mFirstTouch字段是否為null
1.如果是null的話旗唁,說明子view不能響應(yīng)down的事件畦浓,此時將不會走scrollview的Intercept方法,所有的事件都會到scrollview上
2.如果不為null检疫,由于Intercept方法中move的代碼如下

case MotionEvent.ACTION_MOVE: {
                /*
                 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
                 * whether the user has moved far enough from his original down touch.
                 */

                /*
                * Locally do absolute value. mLastMotionY is set to the y value
                * of the down event.
                */
                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);
                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;
            }

也就是說如果滑動的絕對值大于最小移動值讶请,就其返回true,此時他的子view會收到cancel事件屎媳,并且由于設(shè)置了requestDisallowInterceptTouchEvent夺溢,所以scrollview的parent也不會搶走他的事件了论巍,總而言之就是move和up都會確保調(diào)用其onTouchEvent的方法,那重點看下其onTouchEvent方法

 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);
                int deltaY = mLastMotionY - y;
                if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset)) {
                    deltaY -= mScrollConsumed[1];
                    vtev.offsetLocation(0, mScrollOffset[1]);
                    mNestedYOffset += mScrollOffset[1];
                }
                if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) {
                    final ViewParent parent = getParent();
                    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;
                    final int range = getScrollRange();
                    final int overscrollMode = getOverScrollMode();
                    boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
                            (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);

                    // Calling overScrollBy will call onOverScrolled, which
                    // calls onScrollChanged if applicable.
                    if (overScrollBy(0, deltaY, 0, mScrollY, 0, range, 0, mOverscrollDistance, true)
                            && !hasNestedScrollingParent()) {
                        // Break our velocity if we hit a scroll barrier.
                        mVelocityTracker.clear();
                    }

                    final int scrolledDeltaY = mScrollY - oldY;
                    final int unconsumedY = deltaY - scrolledDeltaY;
                    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) {
                            mEdgeGlowTop.onPull((float) deltaY / getHeight(),
                                    ev.getX(activePointerIndex) / getWidth());
                            if (!mEdgeGlowBottom.isFinished()) {
                                mEdgeGlowBottom.onRelease();
                            }
                        } else if (pulledToY > range) {
                            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;

可以看到由于有了嵌套滑動的邏輯风响,代碼變得很長嘉汰,首先會調(diào)用dispatchNestedPreScroll方法

 public boolean dispatchNestedPreScroll(int dx, int dy,
            @Nullable @Size(2) int[] consumed, @Nullable @Size(2) int[] offsetInWindow) {
        if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
            if (dx != 0 || dy != 0) {
                int startX = 0;
                int startY = 0;
                if (offsetInWindow != null) {
                    getLocationInWindow(offsetInWindow);
                    startX = offsetInWindow[0];
                    startY = offsetInWindow[1];
                }

                if (consumed == null) {
                    if (mTempNestedScrollConsumed == null) {
                        mTempNestedScrollConsumed = new int[2];
                    }
                    consumed = mTempNestedScrollConsumed;
                }
                consumed[0] = 0;
                consumed[1] = 0;
                mNestedScrollingParent.onNestedPreScroll(this, dx, dy, consumed);

                if (offsetInWindow != null) {
                    getLocationInWindow(offsetInWindow);
                    offsetInWindow[0] -= startX;
                    offsetInWindow[1] -= startY;
                }
                return consumed[0] != 0 || consumed[1] != 0;
            } else if (offsetInWindow != null) {
                offsetInWindow[0] = 0;
                offsetInWindow[1] = 0;
            }
        }
        return false;
    }

返回值由父view的onNestedPreScroll返回值決定,也就是消耗的意思

public void onNestedPreScroll(View target, int dx, int dy, int[] consumed);

舉個例子状勤,在scrollview上滑動了50像素鞋怀,但是父view消耗了50
像素,(就是拿consumed數(shù)組的Y賦值50)那這個move就不會滑動
而如果走到了下面的mIsBeingDragged的話持搜,表示可滑動的接箫,繼而會調(diào)用overScrollBy方法,這個方法也就是核心方法朵诫,內(nèi)部實現(xiàn)是裁剪了點距離,算出了邊界薄扁,并沒有真正的滑動剪返,此方法會間接的調(diào)用重載方法

@Override
    protected void onOverScrolled(int scrollX, int scrollY,
            boolean clampedX, boolean clampedY) {
        // Treat animating scrolls differently; see #computeScroll() for why.
        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 {
            super.scrollTo(scrollX, scrollY);
        }

        awakenScrollBars();
    }

眾所周知,scroller的滑動依靠的是不斷的invidate邓梅,然后改變畫布的位置脱盲,其實所有view本身的位置并沒有變化,那重繪的代碼呢日缨?其實在awakenScrollBars這里面

 protected boolean awakenScrollBars(int startDelay, boolean invalidate) {
        final ScrollabilityCache scrollCache = mScrollCache;

        if (scrollCache == null || !scrollCache.fadeScrollBars) {
            return false;
        }

        if (scrollCache.scrollBar == null) {
            scrollCache.scrollBar = new ScrollBarDrawable();
            scrollCache.scrollBar.setState(getDrawableState());
            scrollCache.scrollBar.setCallback(this);
        }

        if (isHorizontalScrollBarEnabled() || isVerticalScrollBarEnabled()) {

            if (invalidate) {
                // Invalidate to show the scrollbars
                postInvalidateOnAnimation();
            }

這里會調(diào)用重繪钱反,繼而調(diào)用computeScroll,里面會一直的循環(huán)調(diào)用scrollerby

 @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, 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());
                    }
                }
            }

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

在調(diào)用完overScrollBy以后匣距,如果有嵌套滑動的邏輯的話面哥,還會調(diào)用dispatchNestedScroll,啥意思呢
簡單來說就是當(dāng)scrollview一直滑動滑到頂部或滑到最下面毅待,然后繼續(xù)滑動尚卫,這時候如果要父view處理的話,就用此邏輯
當(dāng)然后面的canOVerscroll是關(guān)于滑動光暈的代碼尸红。
在up的時候基本上和move差不多吱涉,只不過有fling的邏輯,在fling到頭后外里,也會采用嵌套滑動怎爵,讓父view處理,這里就不多說了

總結(jié):

scrollview作為經(jīng)典的基礎(chǔ)控件盅蝗,可學(xué)習(xí)地方非常多包括但不限于:
1.滑動機制的處理(看了代碼才知道鳖链,其實scrollview是有嵌套滑動的代碼的,只不過沒有nestedScrollview支持到5.0以下)
2.事件分發(fā)的處理风科,很經(jīng)典撒轮,很全面的處理
3.對于overScroller的運用乞旦,囊括了滑動和fling的處理,也是滑動的三種實現(xiàn)之一(scroller,layout,translate)當(dāng)然scrollerview也支持像ios那樣的下拉回彈的效果(只不過默認(rèn)被clamped),關(guān)鍵代碼在其重寫的scroll to上题山,
4.多點觸控以及速度跟蹤器的運用兰粉,這里沒過多的分析,自己寫的話照著寫就可以了

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末顶瞳,一起剝皮案震驚了整個濱河市玖姑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌慨菱,老刑警劉巖焰络,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異符喝,居然都是意外死亡闪彼,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門协饲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來畏腕,“玉大人,你說我怎么就攤上這事茉稠∶柘冢” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵而线,是天一觀的道長铭污。 經(jīng)常有香客問我,道長膀篮,這世上最難降的妖魔是什么嘹狞? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮各拷,結(jié)果婚禮上刁绒,老公的妹妹穿的比我還像新娘。我一直安慰自己烤黍,他們只是感情好知市,可當(dāng)我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著速蕊,像睡著了一般嫂丙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上规哲,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天跟啤,我揣著相機與錄音,去河邊找鬼。 笑死隅肥,一個胖子當(dāng)著我的面吹牛竿奏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播腥放,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼泛啸,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了秃症?” 一聲冷哼從身側(cè)響起候址,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎种柑,沒想到半個月后岗仑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡聚请,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年荠雕,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片驶赏。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡舞虱,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出母市,到底是詐尸還是另有隱情,我是刑警寧澤损趋,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布患久,位于F島的核電站,受9級特大地震影響浑槽,放射性物質(zhì)發(fā)生泄漏蒋失。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一桐玻、第九天 我趴在偏房一處隱蔽的房頂上張望篙挽。 院中可真熱鬧,春花似錦镊靴、人聲如沸铣卡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽煮落。三九已至,卻和暖如春踊谋,著一層夾襖步出監(jiān)牢的瞬間蝉仇,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留轿衔,地道東北人沉迹。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像害驹,于是被迫代替她去往敵國和親鞭呕。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 本文分析版本: Android API 23 1.簡介 ScrollView是我們在開發(fā)中經(jīng)常使用的控件裙秋。當(dāng)我們需...
    SkyKai閱讀 8,925評論 3 54
  • 前言 ScrollView可以說是android里最簡單的滑動控件琅拌,但是其中也蘊含了很多的知識點。今天嘗試通過Sc...
    chefish閱讀 2,372評論 2 15
  • 本筆記整理自: https://www.gitbook.com/book/tom510230/android_...
    01_小小魚_01閱讀 1,001評論 0 4
  • 一摘刑、View事件體系 1.什么是 View 和 View的位置坐標(biāo) View是什么: View 是一種界面層的控件...
    sssssss_閱讀 1,315評論 0 7
  • 所謂斷舍離进宝,是將握在手里會怦然心動的東西留下。 人到了一定年紀(jì)枷恕,是往回收的党晋,收到最后,三兩知己徐块、一杯淺茶........
    靖理閱讀 408評論 0 6