ScrollView 可以在垂直方向做滑动显示内容,并且ScrollView中只能添加一个子View,下面对源代码进行分析。

源码解析

ScrollView 继承关系

onMeasure方法

@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    super.onMeasure(widthMeasureSpec, heightMeasureSpec);    //这个属性允许 ScrollView中的组件去充满它。    if (!mFillViewport) {        return;    }    final int heightMode = MeasureSpec.getMode(heightMeasureSpec);    //如果不确定高度直接返回    if (heightMode == MeasureSpec.UNSPECIFIED) {        return;    }    //如果没有子布局直接返回    if (getChildCount() > 0) {        final View child = getChildAt(0);  //只有一个子布局        final int height = getMeasuredHeight();        if (child.getMeasuredHeight() < height) {            final int widthPadding;            final int heightPadding;            final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();            final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;            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 childWidthMeasureSpec = getChildMeasureSpec(                    widthMeasureSpec, widthPadding, lp.width);            final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(                    height - heightPadding, MeasureSpec.EXACTLY);            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);        }    }}

onLayout方法

@Overrideprotected 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; //是否还未添加过window中去    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);}

draw方法

@Overridepublic void draw(Canvas canvas) {    super.draw(canvas);    if (mEdgeGlowTop != null) {        final int scrollY = mScrollY;        final boolean clipToPadding = getClipToPadding();        if (!mEdgeGlowTop.isFinished()) {            final int restoreCount = canvas.save();            final int width;            final int height;            final float translateX;            final float translateY;            if (clipToPadding) {                width = getWidth() - mPaddingLeft - mPaddingRight;                height = getHeight() - mPaddingTop - mPaddingBottom;                translateX = mPaddingLeft;                translateY = mPaddingTop;            } else {                width = getWidth();                height = getHeight();                translateX = 0;                translateY = 0;            }            canvas.translate(translateX, Math.min(0, scrollY) + translateY);            mEdgeGlowTop.setSize(width, height);            if (mEdgeGlowTop.draw(canvas)) {                postInvalidateOnAnimation();            }            canvas.restoreToCount(restoreCount);        }        if (!mEdgeGlowBottom.isFinished()) {            final int restoreCount = canvas.save();            final int width;            final int height;            final float translateX;            final float translateY;            if (clipToPadding) {                width = getWidth() - mPaddingLeft - mPaddingRight;                height = getHeight() - mPaddingTop - mPaddingBottom;                translateX = mPaddingLeft;                translateY = mPaddingTop;            } else {                width = getWidth();                height = getHeight();                translateX = 0;                translateY = 0;            }            canvas.translate(-width + translateX,                        Math.max(getScrollRange(), scrollY) + height + translateY);            canvas.rotate(180, width, 0);            mEdgeGlowBottom.setSize(width, height);            if (mEdgeGlowBottom.draw(canvas)) {                postInvalidateOnAnimation();            }            canvas.restoreToCount(restoreCount);        }    }}

从上面的三个方法来看都没有做特殊的处理。

触摸事件

ScrollView并没有重写dispatchTouchEvent方法,所以onInterceptTouchEvent() 若返回true, 则调用 onTouchEvent方法处理触摸事件。

onInterceptTouchEvent 方法

@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {    //如果处于滚动状态,返回true, 事件交给自身处理, 防止在点击的过程中出现view 的点击事件    final int action = ev.getAction();    if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {        return true;    }    /*     * 如果垂直方向没有滚动直接交给子View 处理     */    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.            */            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;    }    //如果处于滑动状态拦截该事件    return mIsBeingDragged;}

由上面分析可知,一般只有在滑动并且滑动距离大于最小值的情况下会返回true,也就是会截取触摸事件(子view就不会处理),调用onTouchEvent方法,触摸事件的大致流程是

ACTION_DOWN ->ACTION_MOVE -> … -> ACTION_MOVE -> ACTION_UP

onTouchEvent方法

@Overridepublic boolean onTouchEvent(MotionEvent ev) {    initVelocityTrackerIfNotExists();    MotionEvent vtev = MotionEvent.obtain(ev);    final int actionMasked = ev.getActionMasked();    if (actionMasked == MotionEvent.ACTION_DOWN) {        mNestedYOffset = 0;    }    vtev.offsetLocation(0, mNestedYOffset);    switch (actionMasked) {        case MotionEvent.ACTION_DOWN: {            if (getChildCount() == 0) {                return false;            }            if ((mIsBeingDragged = !mScroller.isFinished())) {                final ViewParent parent = getParent();                if (parent != null) {                    parent.requestDisallowInterceptTouchEvent(true);                }            }            /*             * 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 = (int) ev.getY();            mActivePointerId = ev.getPointerId(0);            startNestedScroll(SCROLL_AXIS_VERTICAL);            break;        }        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;        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:            onSecondaryPointerUp(ev);            mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));            break;    }    if (mVelocityTracker != null) {        mVelocityTracker.addMovement(vtev);    }    vtev.recycle();    return true;}onOverScrolled方法如下:@Overrideprotected 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();}

到这里整个源码就分析完了

更多相关文章

  1. Android(安卓): 自己写个HTTP框架
  2. Android(安卓)中 Bitmap 和 Drawable 相互转换的方法
  3. android安卓手机分屏多窗口实现方法
  4. android使用文件进行存储
  5. android 关于listview item设置高度的问题解决方法
  6. Day03
  7. Android(安卓)butterknife注入完毕后不起作用
  8. Android的常用方法(转载)
  9. @BindView问题 Attempt to invoke virtual method 'void android

随机推荐

  1. Android中设置动画循环旋转的方法
  2. Android 中文API (33) ―― Checkable
  3. EditText高度的改变
  4. Mono For Android 之 配置环境
  5. Android NDK 学习之传递类对象
  6. Android Studio升级后projectBuild faile
  7. Android中的EditText属性说明
  8. 在Android(安卓)Studio中自定义字体库报
  9. Android弹幕实现:基于B站弹幕开源系统(2)
  10. android 中文API:android