美文网首页源码原理知识点
view系列源码分析之三大常用控件之scrollview

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

作者: 暴走的小青春 | 来源:发表于2019-12-15 20:44 被阅读0次

    scrollview作为一个android的基础的控件,用途十分的广泛,下面来分析它他的原理

    关于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中是调用父view的onMeasure的逻辑进行测量的,onMeasure的目的就是让子view测量,然后获取到宽高后,然后在给自身的宽高赋值,而在onMeasure中,要测量子view必定会调用measureChildWithMargins方法,此方法很明显是根据父view的measureSpec减去自身的padding和子view的margin,然后根据子view的xml的宽高,计算出子view的宽高的,很明显,scrollview打破了这种规则,来看下其重写的方法

    @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的模式下的高度是根据子view的高度再加上其子view也就是textview的margin和自己的padding决定的,所以measure的步骤分析完了,简单来说就是根据子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

    关于滑动的分析

    在分析前,如果对事件分发不熟悉的话可以看下事件分发结论
    简单来说就是在viewRootImp中先注册了一个inputChannel对象,然后底层会回调

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

    继而间接的调用view的dispatchPointerEvent方法

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

    继而调用decorview的dispatchTouEvent方法,在activity中此decorview进行了重写,会调用activity的dispatchTouEvent方法,而我们在popwindow中,此方法如下

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

    可以看到直接调用了viewGroup的dispatchTouchEvent方法,此方法在down的时候会层层向下调用,一直调用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的坐标
    2.如果此时在滑动的情况下,点下去了,让其停止滑动,也就是调用overScroller的computeScrollOffset方法
    3.进行了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,那他调用父view的onStartNestedScroll方法,如果为true,那它就调用其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会一直循环向上调用
    由于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不能响应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事件,并且由于设置了requestDisallowInterceptTouchEvent,所以scrollview的parent也不会抢走他的事件了,总而言之就是move和up都会确保调用其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;
    

    可以看到由于有了嵌套滑动的逻辑,代码变得很长,首先会调用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数组的Y赋值50)那这个move就不会滑动
    而如果走到了下面的mIsBeingDragged的话,表示可滑动的,继而会调用overScrollBy方法,这个方法也就是核心方法,内部实现是裁剪了点距离,算出了边界,并没有真正的滑动,此方法会间接的调用重载方法

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

    这里会调用重绘,继而调用computeScroll,里面会一直的循环调用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;
                }
            }
        }
    

    在调用完overScrollBy以后,如果有嵌套滑动的逻辑的话,还会调用dispatchNestedScroll,啥意思呢
    简单来说就是当scrollview一直滑动滑到顶部或滑到最下面,然后继续滑动,这时候如果要父view处理的话,就用此逻辑
    当然后面的canOVerscroll是关于滑动光晕的代码。
    在up的时候基本上和move差不多,只不过有fling的逻辑,在fling到头后,也会采用嵌套滑动,让父view处理,这里就不多说了

    总结:

    scrollview作为经典的基础控件,可学习地方非常多包括但不限于:
    1.滑动机制的处理(看了代码才知道,其实scrollview是有嵌套滑动的代码的,只不过没有nestedScrollview支持到5.0以下)
    2.事件分发的处理,很经典,很全面的处理
    3.对于overScroller的运用,囊括了滑动和fling的处理,也是滑动的三种实现之一(scroller,layout,translate)当然scrollerview也支持像ios那样的下拉回弹的效果(只不过默认被clamped),关键代码在其重写的scroll to上,
    4.多点触控以及速度跟踪器的运用,这里没过多的分析,自己写的话照着写就可以了

    相关文章

      网友评论

        本文标题:view系列源码分析之三大常用控件之scrollview

        本文链接:https://www.haomeiwen.com/subject/agiknctx.html