今天给大家带来这篇源码解读,首先很感谢大家能"赏脸。本文秉着思路清晰,细致分析源码脉络。从根本上带领大家学会自定义控件和分析源码,学会举一反三。"自定义,何等熟悉的名词。到底,它有多深奥,其实不然,咱们github千万自定义控件让人眼花缭乱,先克服恐惧。拒绝一味"承袭"人家控件。毕竟,人家的始终是人家的,自己的才是根本。好了,开篇不说多,咱们进入正题吧!分析源码第一要点,分析继承关系(快捷键F4)。我们通过图3可以看见ScrollView继承自FrameLayout布局。
然后我们先大致了解一下ScrollView中所有的方法。该部分为读者自己回顾整体查询,为节省篇幅,所以比较紧凑。先请大家快速地大致预览一下方法名,有个大致过目。为后面的讲解做准备。
首先,我们看到4个构造方法。由上往下互相调用,最终调用的是含有4个参数的构造方法。第四个构造方法的最后一个参数传入的是0。调用了父类的构造方法。而该构造方法
由View的构造方法继承而来。第四个构造方法里看似非常简单,首先得到属性集对象TypedArray,然后通过其getBoolean方法判断是否进行请求重新布局。
    public ScrollView(Context context) {
        this(context, null);
    }
    public ScrollView(Context context, AttributeSet attrs) {
        this(context, attrs, com.android.internal.R.attr.scrollViewStyle);
    }
    public ScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }
//调用第四个构造方法
    public ScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        initScrollView();//初始化ScrollView的一些参数,后面会讲
        final TypedArray a = context.obtainStyledAttributes(
                attrs, com.android.internal.R.styleable.ScrollView, defStyleAttr, defStyleRes);//获取属性集对象
        setFillViewport(a.getBoolean(R.styleable.ScrollView_fillViewport, false));//根据布局文件判断是否布局是否溢出当前窗口
        a.recycle();//TypedArray对象回收
    }在构造方法里面,首先通过obtainStyleAttributes方法去得到属性集对象TypeArray。里面的四个参数同上面的解释。
final TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.ScrollView, defStyleAttr, defStyleRes);
接下来调用了setFillViewport方法,这个方法是用来设置是否需要重新请求布局。在什么时候会调用requestLayout进行布局请求呢?秘法技就隐藏在TypeArray里的getBoolean方法里。该方法能判断传入的布局的是否溢出父布局。先声明:requestLayout方法不一样会执行,视XML布局文件而定。
private boolean mFillViewport;
 public void setFillViewport(boolean fillViewport) {
        if (fillViewport != mFillViewport) {
            mFillViewport = fillViewport;//当布局文件满足溢出情况的时候,fillViewport为true
            requestLayout();//请求布局
        }
    }
在这里传入的是a.getBoolean(R.styleable.ScrollView_fillViewport, false)的值。如果得到的值是true,那么就需要扩展。会调用requestLayout方法,请求布局来充满全屏。
setFillViewport(a.getBoolean(R.styleable.ScrollView_fillViewport, false));
 public boolean getBoolean(@StyleableRes int index, boolean defValue) {
        if (mRecycled) {//如果结果集对象不存在
            throw new RuntimeException("Cannot make calls to a recycled instance!");
        }
        index *= AssetManager.STYLE_NUM_ENTRIES;
        final int[] data = mData;
        final int type = data[index+AssetManager.STYLE_TYPE];
        if (type == TypedValue.TYPE_NULL) {
            return defValue;
        } else if (type >= TypedValue.TYPE_FIRST_INT
                && type <= TypedValue.TYPE_LAST_INT) {
            return data[index+AssetManager.STYLE_DATA] != 0;
        }
        final TypedValue v = mValue;
        if (getValueAt(index, v)) {
            StrictMode.noteResourceMismatch(v);
            return XmlUtils.convertValueToBoolean(v.coerceToString(), defValue);
        }
        // type的值不会等于0,这个已经检测过了
        throw new RuntimeException("getBoolean of bad type: 0x" + Integer.toHexString(type));
    }接下来,我们看到requestLayout方法。调用了父类的请求布局方法。
  @Override
    public void requestLayout() {
        mIsLayoutDirty = true;
        super.requestLayout();
    }
调用了父类的requestLayout方法。在这里我们知道了大致的原理。首先通过属性集的getBoolean方法获取值来判断是否需要进行请求内容扩展。如果为true,就进行请求更新
布局。在requestLayout方法里,利用一个标志来判断是否改变了当前的状态。
控件测量由onMeasure方法负责,此方法中传入的2个分别为widthMeasureSpec和widthMeasureSpec。在该方法中分别需要对三种模式进行测量。首先,我们来了解一下这三种模式。在三种模式下,我们测得的宽高最终会调用measure(int width,int height)设定最终测量结果。该三种模式为View类中的静态内部类MeasureSpec类的静态成员。标志着三种模式。ScrollView中主要对子控件进行了精确测量。而自身的高宽不做太多处理。
在UNSPECIFIED(未指定模式),父布局传递下来的宽高传入多少就是多少,也可以自行设置,设置多少就是多少,不受父类影响。而在EXACTLY(精确模式),当设置为wrap_contenxt的时候,它会默认设置为父布局传递下来的宽高。所以在此时需要重新测量。而在AT_MOST(至多模式),一般不考虑。所以我们只需要对精确模式下进行重新测量即可。我们看到源码如下:
   @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//溢出与否,就要通过之前我们的TypedArray里面的getBoolean里参数的布局文件来判断了。
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (!mFillViewport) {//如果mFillViewport为true,则子布局充满当前可见区域,宽高即不需要重新测量。
            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);
            }
        }
    }如果没有溢出,ScrollView自身的宽高即按照父布局来处理,子类按照父类宽高进行处理,但是当为精确模式下,子类设置高度为wrap_content不会生效,需要指定其宽高。此时ScrollView的高度始终和父布局高度一致。当溢出的时候,对子类布局进行测量过程中,首先判断ScrollView中是否包含子布局,如果包含的话,ScrollView子布局的宽高在精确模式下进行测量。此时子布局的宽度等于ScrollView的宽度减去ScrollView的padding左右内边距,高度等于ScrollView的高度减去padding的上下内边距。在这里需要注意的一点是,在SDK版本大于18的时候,子控件的宽高和原来的测量方式不一样了。在此之后,子控件的宽高等于ScrollView的宽高减去ScrollView本身的padding内边距再减去子控件自身的外边距。在SDK版本小于等于18的时候,子控件的测量宽高就是ScrollView的宽高减去子控件的内边距。这一点需要我们注意。 @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) { //mSavedState对象为SavedState实例对象,而SavedState类为ScrollView的内部类。SavedState继承自BaseSavedState类。
                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;
            }
        }mSavedState对象为SavedState实例对象,而SavedState类为ScrollView的内部类。SavedState继承自BaseSavedState类。SavedState对象可以重写其writeToParcel方法实现序列化。具体代码如下:
    static class SavedState extends BaseSavedState {
        public int scrollPosition;
        SavedState(Parcelable superState) {
            super(superState);
        }
        public SavedState(Parcel source) {
            super(source);
            scrollPosition = source.readInt();
        }
        @Override
        public void writeToParcel(Parcel dest, int flags) {
            super.writeToParcel(dest, flags);
            dest.writeInt(scrollPosition);
        }
        @Override
        public String toString() {
            return "HorizontalScrollView.SavedState{"
                    + Integer.toHexString(System.identityHashCode(this))
                    + " scrollPosition=" + scrollPosition + "}";
        }
        public static final Parcelable.Creator<SavedState> CREATOR
                = new Parcelable.Creator<SavedState>() {
            public SavedState createFromParcel(Parcel in) {
                return new SavedState(in);
            }
            public SavedState[] newArray(int size) {
                return new SavedState[size];
            }
        };
    }所以我们实现当前属性的序列化的步骤为:
 @Override
    protected void onRestoreInstanceState(Parcelable state) {
        if (mContext.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR2) {
            // Some old apps reused IDs in ways they shouldn't have.
            // Don't break them, but they don't get scroll state restoration.
            super.onRestoreInstanceState(state);
            return;
        }
        SavedState ss = (SavedState) state;
        super.onRestoreInstanceState(ss.getSuperState());
        mSavedState = ss;
        requestLayout();
    }  @Override
    protected Parcelable onSaveInstanceState() {
        if (mContext.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR2) {
            // Some old apps reused IDs in ways they shouldn't have.
            // Don't break them, but they don't get scroll state restoration.
            return super.onSaveInstanceState();
        }
        Parcelable superState = super.onSaveInstanceState();
        SavedState ss = new SavedState(superState);
        ss.scrollPosition = mScrollY;
        return ss;
    }
@Override
    public boolean onTouchEvent(MotionEvent ev) {
        initVelocityTrackerIfNotExists();//判断VelocityTracker对象是否存在,该对象用来计算速度
        MotionEvent vtev = MotionEvent.obtain(ev);//复制已经存在的MotionEvent对象
        final int actionMasked = ev.getActionMasked();//得到具体的手势事件行为。此处有三种手势行为的表示。
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            mNestedYOffset = 0;
        }
        vtev.offsetLocation(0, mNestedYOffset);
        switch (actionMasked) {
            case MotionEvent.ACTION_DOWN: {
              case MotionEvent.ACTION_MOVE:
                break;
            case MotionEvent.ACTION_UP:
                ...
                }
                break;
            case MotionEvent.ACTION_CANCEL:
                ...
                break;
            case MotionEvent.ACTION_POINTER_DOWN: {
                ...
                break;
            }
            case MotionEvent.ACTION_POINTER_UP:
               ...
                break;
        }
        if (mVelocityTracker != null) {
            mVelocityTracker.addMovement(vtev);
        }
        vtev.recycle();
        return true;
    }
    private void initVelocityTrackerIfNotExists() {
        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
    }上面我们说过,通过obtain会将已经存在的VelocityTracker对象复制一个。    private void initVelocityTrackerIfNotExists() {
        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
    }velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
{
                if (getChildCount() == 0) {//没有子View时候
                    return false;
                }
                if ((mIsBeingDragged = !mScroller.isFinished())) {//通过OverScroller对象的isFinish方法判断是否滑动完毕
                    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
                        mFlingStrictSpan = null;
                    }
                }
                // Remember where the motion event started
                mLastMotionY = (int) ev.getY();
                mActivePointerId = ev.getPointerId(0);
                startNestedScroll(SCROLL_AXIS_VERTICAL);
                break;
            }private void initScrollView() {
        mScroller = new OverScroller(getContext());
        setFocusable(true);
        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
        setWillNotDraw(false);
        final ViewConfiguration configuration = ViewConfiguration.get(mContext);
        mTouchSlop = configuration.getScaledTouchSlop();
        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
        mOverscrollDistance = configuration.getScaledOverscrollDistance();
        mOverflingDistance = configuration.getScaledOverflingDistance();
    }    */
    public void fling(int velocityY) {
        if (getChildCount() > 0) {
            int height = getHeight() - mPaddingBottom - mPaddingTop;
            int bottom = getChildAt(0).getHeight();
            mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0,
                    Math.max(0, bottom - height), 0, height/2);
            if (mFlingStrictSpan == null) {
                mFlingStrictSpan = StrictMode.enterCriticalSpan("ScrollView-fling");//获取StrictMode.Span类对象,Span为StrictMode的内部类。
            }
            postInvalidateOnAnimation();
        }
    } public void fling(int startX, int startY, int velocityX, int velocityY,
            int minX, int maxX, int minY, int maxY, int overX, int overY) {
        // Continue a scroll or fling in progress
        if (mFlywheel && !isFinished()) {
            float oldVelocityX = mScrollerX.mCurrVelocity;
            float oldVelocityY = mScrollerY.mCurrVelocity;
            if (Math.signum(velocityX) == Math.signum(oldVelocityX) &&
                    Math.signum(velocityY) == Math.signum(oldVelocityY)) {
                velocityX += oldVelocityX;
                velocityY += oldVelocityY;
            }
        }
        mMode = FLING_MODE;
        mScrollerX.fling(startX, velocityX, minX, maxX, overX);
        mScrollerY.fling(startY, velocityY, minY, maxY, overY);
    }mFlingStrictSpan = StrictMode.enterCriticalSpan("ScrollView-fling"); 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;原文:http://blog.csdn.net/qq_21004057/article/details/52985299