View源碼繪製流程

本文基於Android API 28

繪製三大方法

onMeasure

onMeasure(int, int)

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {         
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
    getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

在onMeasure方法用來測量view和他的內容,來決定view的寬高,該方法被measure(int, int)調用,並且需要被子類重寫去測量view的內容的精確、有效的寬高;

重寫該方法時,必須調用setMeasureDimension(int, int)方法去保存該view測量的寬和高;如果不這樣做,measure(int, int)方法會拋出IllegalStateException異常,調用父類的onMeasure(int, int)方法可以避免這樣;

基類實現了測量默認的背景尺寸,除非MeasureSpec允許更大的尺寸,子類應該重寫onMeasure(int, int)方法去提供更好的測量他們的內容;

如果重寫該方法,子類有責任確保實測寬高至少是view的最小寬高;

該方法在View類裏面直接調用setMeasureDimension方法去保存默認的view的尺寸,參數直接調用getDefaultSize方法獲取默認寬高;

getDefaultSize(int, int);

public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
            case MeasureSpec.UNSPECIFIED:
                result = size;
                break;
            case MeasureSpec.AT_MOST:
            case MeasureSpec.EXACTLY:
                result = specSize;
                break;
        }
        return result;
}

這個方法返回默認的尺寸,如果MeasureSpec沒有添加約束,則使用提供的尺寸,如果MeasureSpec允許,則將尺寸放大;即當MeasureSpec的Mode爲UNSPECIFIED時,沒有約束,使用默認的大小尺寸,爲AT_MOST和EXACTLY時,需要計算出尺寸的大小;

getSuggestedMinimumHeight() /Width()

protected int getSuggestedMinimumHeight() {
        return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}

protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}

這兩個方法返回view建議的最小寬高以及背景的最小寬高的最大值;調用這兩個方法時,需要注意返回的寬高應該在父View的要求範圍內;

setMeasuredDimension(int, int)

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int opticalWidth  = insets.left + insets.right;
            int opticalHeight = insets.top  + insets.bottom;

            measuredWidth  += optical ? opticalWidth  : -opticalWidth;
            measuredHeight += optical ? opticalHeight : -opticalHeight;
        }
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}

該方法必須由onMeasure(int, int)方法調用來保存實測的view寬高,否則將拋出異常;這個方法裏面涉及到要判斷View及其父View是否爲ViewGroup及是否有光學效果,來決定是否要處理關於這些效果產生的尺寸影響;

isLayoutModeOptical(Object)

public static boolean isLayoutModeOptical(Object o) {
        return o instanceof ViewGroup && ((ViewGroup) o).isLayoutModeOptical();
}

該方法返回該View是否爲ViewGroup,並且是否由光效效果,例如陰影、亮光等,這些也將影響View的尺寸測量;

getOpticalInsets()

public Insets getOpticalInsets() {
    if (mLayoutInsets == null) {
            mLayoutInsets = computeOpticalInsets();
    }
    return mLayoutInsets;
}

該方法返回當前View的插圖屬性,如果爲空,則去計算;

setOpticalInsets(Insets)

public void setOpticalInsets(Insets insets) {
    mLayoutInsets = insets;
}

爲該View設置Insets,該方法不請求layout,如果手動調用該方法設置了Insets,則需要調用requestLayout()方法;

computeOpticalInsets()

Insets computeOpticalInsets() {
    return (mBackground == null) ? Insets.NONE : mBackground.getOpticalInsets();
}

如果當前View沒有背景圖片,則返回Insets.NONE,否則去計算背景圖片的光學效果範圍;

setMeasuredDimensionRaw(int, int)

private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;
        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}

該方法中最終保存View的尺寸,並設置標誌位爲PFLAG_MEASURED_DIMENSION_SET;

MeasureSpec

MeasureSpec類封裝了從父View傳遞到子View的佈局要求;包含size和mode;

private static final int MODE_SHIFT = 30;
private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

三種Mode

·UNSPECIFIED(00....)

public static final int UNSPECIFIED = 0 << MODE_SHIFT;

父佈局對子View沒有任何限制,子View可以是任何大小;

·EXACTLY(01....)

public static final int EXACTLY     = 1 << MODE_SHIFT;

父佈局給了子View一個準確的大小約束,不管子View想要多大,都會受到該限制;

·AT_MOST(10....)

public static final int AT_MOST     = 2 << MODE_SHIFT;

子View可是達到他想要的任何指定大小;

makeMeasureSpec(int, int)

public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size, @MeasureSpecMode int mode) {
    if (sUseBrokenMakeMeasureSpec) {
        return size + mode;
    } else {
        return (size & ~MODE_MASK) | (mode & MODE_MASK);
    }
}

該方法提供了Size和Mode的算法;

getMode(int)、getSize(int)

這兩個方法分別獲取View的MeasureSpec的Mode和Size;

onLayout

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}

該方法是一個空實現,需要子View自己去實現是否需要爲他們的子View重新計算大小和位置;

onDraw

protected void onDraw(Canvas canvas) {
}

該方法也是一個空實現,子View自己定義繪製內容;

View創建

加載xml佈局

LayoutInflater.from(context).inflate(R.layout.activity_base, null);

以上代碼用來加載定義好的xml文件,加載佈局;

LayoutInflater#from(context)

public static LayoutInflater from(Context context) {
    LayoutInflater LayoutInflater =
        (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    if (LayoutInflater == null) {
        throw new AssertionError("LayoutInflater not found.");
    }
    return LayoutInflater;
}

在代碼中,通過Context的getSystemService方法去獲取到LayoutInflater的實例;

LayoutInflater#inflate()

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
    return inflate(resource, root, root != null);
}

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    final Resources res = getContext().getResources();
    if (DEBUG) {
        (TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                + Integer.toHexString(resource) + ")");
    }

    final XmlResourceParser parser = res.getLayout(resource);
    try {
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}

這裏inflate通過內容調用到另一個重載的inflate方法,這裏獲取到了Resource資源,並且獲取到XmlResourceParser對象實例,用來解析Xml佈局,然後最終調用到了inflate(XmlPullParser, ViewGroup, boolean)方法;

LayoutInflater#inflate(XmlPullParser, ViewGroup, boolean)

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    synchronized (mConstructorArgs) {
        ... ...
        final String name = parser.getName();
            ... ...
            if (TAG_MERGE.equals(name)) {
            ... ...
            rInflate(parser, root, inflaterContext, attrs, false);
        } else {
                    // Temp is the root view that was found in the xml
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                    ViewGroup.LayoutParams params = null;
                    if (root != null) {
                        ... ....
                            // Create layout params that match root, if supplied
                            params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                                // Set the layout params for temp if we are not
                                // attaching. (If we are, we use addView, below)
                                temp.setLayoutParams(params);
                            }
                    }
                    ... ...
                    // Inflate all children under temp against its context.
                    rInflateChildren(parser, temp, attrs, true);
                    ... ...
                    // We are supposed to attach all the views we found (int temp)
                    // to root. Do that now.
                    if (root != null && attachToRoot) {
                            root.addView(temp, params);
                    }
                    // Decide whether to return the root that was passed in or the
                    // top view found in xml.
                    if (root == null || !attachToRoot) {
                            result = temp;
                    }
            }
            ... ...
        return result;
    }
}

這裏省略掉部分代碼,使用Xml解析器去解析Xml,獲取Xml標籤,先忽略Xml開始和結束等標籤,直接看創建View的部分,這裏判斷是否爲merge標籤,進入else分支,這裏的核心代碼是調用createViewFromTag(root, name, inflaterContext, attrs)方法去創建View,後面是判斷是否有父佈局,獲取父佈局參數等信息,將創建好的View添加到父佈局;

LayoutInflater#createViewFromTag(View, String, Context, AttributeSet, boolean)

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) {
    ... ...
    try {
        View view;
        if (mFactory2 != null) {
            view = mFactory2.onCreateView(parent, name, context, attrs);
        } else if (mFactory != null) {
            view = mFactory.onCreateView(name, context, attrs);
        } else {
            view = null;
        }
        if (view == null && mPrivateFactory != null) {
            view = mPrivateFactory.onCreateView(parent, name, context, attrs);
        }
        if (view == null) {
            final Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = context;
            try {
                if (-1 == name.indexOf('.')) {
                    view = onCreateView(parent, name, attrs);
                        } else {
                    view = createView(name, null, attrs);
                        }
            } finally {
                mConstructorArgs[0] = lastContext;
            }
            }
            return view;
    ... ...
}

在該方法中,首先先判斷了三個factory,這兩個factory在通過LayoutInflater.from().inflate()方法在加載佈局的時候都爲空,這個Factory是在創建View的時候可以去手動設置View的創建規則,實現自己定義View的創建,例如在AppComAppCompatActivity中就會初始化一個Factory對象去創建屬於AppCompat風格的View,下面分析;

所以方法進入到下面if處,這裏判斷name中是否包含“.”,即是在判斷是否爲自定義View,其實兩個方法最終調用的都是同一個方法,只是在系統的View時,會爲其加上android.view的包名;

protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
    return createView(name, "android.view.", attrs);
}

LayoutInflater#creatView(String, String, AttrbuteSet)

public final View createView(String name, String prefix, AttributeSet attrs)
    throws ClassNotFoundException, InflateException {
    Constructor<? extends View> constructor = sConstructorMap.get(name);
    ... ...
    Class<? extends View> clazz = null;
    try {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
            if (constructor == null) {
            // Class not found in the cache, see if it's real, and try to add it
            clazz = mContext.getClassLoader().loadClass(prefix != null ? (prefix + name) : name).asSubclass(View.class);
            ... ...
            constructor = clazz.getConstructor(mConstructorSignature);
            constructor.setAccessible(true);
            sConstructorMap.put(name, constructor);
        } else {
            // If we have a filter, apply it to cached constructor
            if (mFilter != null) {
                // Have we seen this name before?
                Boolean allowedState = mFilterMap.get(name);
                if (allowedState == null) {
                    // New class -- remember whether it is allowed
                    clazz = mContext.getClassLoader().loadClass(prefix != null ? (prefix + name) : name).asSubclass(View.class);
                    ... ...
                } else if (allowedState.equals(Boolean.FALSE)) {
                    failNotAllowed(name, prefix, attrs);
                }
            }
        }
        final View view = constructor.newInstance(args);
        ... ...
        return view;
        ... ...
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

這是是通過反射拿到View類,通過反射去調用對應的View的構造方法去創建VIew,所以這裏明白了通過Xml創建佈局的時候系統是通過反射來創建的;走到這裏就可以回到View的構造方法了;

通過setContentView創建

在Activity的onCreate()方法中,通過setContentView()方法將佈局加載進去,Activity繼承自AppCompatActivity;

AppCompatActivity#setContentView()

public void setContentView(@LayoutRes int layoutResID) {
    this.getDelegate().setContentView(layoutResID);
}

這裏調用geDelegate()的setContentView方法;

public AppCompatDelegate getDelegate() {
    if (this.mDelegate == null) {
        this.mDelegate = AppCompatDelegate.create(this, this);
    }
    return this.mDelegate;
}

AppCompatDelegate類中的getDelegate()方法返回一個AppCompatDelegate的實現類AppCompatDelegateImpl,所以方法的具體實現由AppCompatDelegateImpl類完成;

AppCompatDelegateImpl#setContentView()

public void setContentView(int resId) {
    this.ensureSubDecor();
    ViewGroup contentParent = (ViewGroup)this.mSubDecor.findViewById(16908290);
    contentParent.removeAllViews();
    LayoutInflater.from(this.mContext).inflate(resId, contentParent);
    this.mOriginalWindowCallback.onContentChanged();
}

到這裏又看到了LayoutInflater的inflate()方法,流程又回到了步驟2.1中;不過這裏需要注意的是,在Xml創建的流程中,在createViewFromTag()方法中有一個對factory的判斷,在Xml創建時爲空,但是在這裏就不爲空了,這裏在AppCompatActivity中對其做了處理,在Factory中去創建自己風格的View,因爲AppCompatActivity和之前的Activity的風格是略有不一樣;

Activity繼承自AppCompatActivity,看onCreate()方法;

AppCompatActivity#onCreate()

protected void onCreate(@Nullable Bundle savedInstanceState) {
    delegate.installViewFactory();
    delegate.onCreate(savedInstanceState);
    ... ...
    super.onCreate(savedInstanceState);
}

這裏初始化了一個Factory對象,具體實現還是在AppCompatDelegateImpl中;

AppCompatDelegateImpl#installViewFactory()

public void installViewFactory() {
    LayoutInflater layoutInflater = LayoutInflater.from(this.mContext);
    if (layoutInflater.getFactory() == null) {
        LayoutInflaterCompat.setFactory2(layoutInflater, this);
    } else if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
        Log.i("AppCompatDelegate", "The Activity's LayoutInflater already has a Factory installed so we can not install AppCompat's");
    }
}

在這裏爲LayoutInflater設置了一個Factory2對象;在創建的View的時候,判斷factory不爲空,就通過factory去創建View,最終如果是自定義View,則通過反射去創建View,如果是系統自身的View,則對應到AppCompatViewInflater的createView()方法中,匹配要創建的View,分別有每個View對應的實現類;

通過代碼new

通過代碼new的話,直接去調用對應的View的構造方法;

文章已同步至GitPress博客:https://gitpress.io/@yangshijie/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章