Android UI繪制之View繪制的工作原理

這是AndroidUI繪制流程分析的第二篇文章,主要分析界面中View是如何繪制到界面上的具體過(guò)程。

1、ViewRoot和DecorView

ViewRoot對(duì)應(yīng)于ViewRootImpl類,它是連接WindowManagerDecorView的紐帶,View的三大流程均是通過(guò)ViewRoot來(lái)完成的。在ActivityThread中,當(dāng)Activity對(duì)象被創(chuàng)建完畢后,會(huì)將DecorView添加到Window中,同時(shí)會(huì)創(chuàng)建ViewRootImpl對(duì)象,并將ViewRootImpl對(duì)象和DecorView建立關(guān)聯(lián)。

View的繪制流程是從ViewRootperformTraversals方法開(kāi)始的,它經(jīng)過(guò)measure、layoutdraw三個(gè)過(guò)程才最終將一個(gè)View繪制出來(lái)。

measure過(guò)程決定了View的寬/高,Measure完成以后,可以通過(guò)getMeasuredWidthgetMeasuredHeight方法來(lái)獲取View測(cè)量后的寬/高,在幾乎所有的情況下,它等同于View的最終的寬/高,但是特殊情況除外。Layout過(guò)程決定了View的四個(gè)頂點(diǎn)的坐標(biāo)和實(shí)際的寬/高,完成以后,可以通過(guò)getTop、getBottom、getLeftgetRight來(lái)拿到View的四個(gè)頂點(diǎn)的位置,可以通過(guò)getWidthgetHeight方法拿到View的最終寬/高。Draw過(guò)程決定了View的顯示,只有draw方法完成后View的內(nèi)容才能呈現(xiàn)在屏幕上。

DecorView作為頂級(jí)View,一般情況下,它內(nèi)部會(huì)包含一個(gè)豎直方向的LinearLayout,在這個(gè)LinearLayout里面有上下兩個(gè)部分,上面是標(biāo)題欄,下面是內(nèi)容欄。在Activity中,我們通過(guò)setContentView所設(shè)置的布局文件其實(shí)就是被加到內(nèi)容欄中的,而內(nèi)容欄id為content??梢酝ㄟ^(guò)下面方法得到content:ViewGroup content = findViewById(R.android.id.content)。通過(guò)content.getChildAt(0)可以得到設(shè)置的view。DecorView其實(shí)是一個(gè)FrameLayout,View層的事件都先經(jīng)過(guò)DecorView,然后才傳遞給我們的View

MeasureSpec

MeasureSpec代表一個(gè)32位的int值,高2位代表SpecMode,低30位代表SpecSize,SpecMode是指測(cè)量模式,而SpecSize是指在某種測(cè)量模式下的規(guī)格大小。
SpecMode有三類,如下所示:

UNSPECIFIED

父容器不對(duì)View有任何限制,要多大給多大,這種情況一般用于系統(tǒng)內(nèi)部。

EXACTLY

父容器已經(jīng)檢測(cè)出View所需要的精確大小,這個(gè)時(shí)候View的最終大小就是SpecSize所指定的值,對(duì)應(yīng)于LayoutParams中的match_parent和具體的數(shù)值這兩種模式。

AT_MOST

父容器指定一個(gè)可用大小即SpecSize,View的大小不能大于這個(gè)值,對(duì)應(yīng)于LayoutParams中的wrap_content

LayoutParams需要和父容器一起才能決定View的MeasureSpec,從而進(jìn)一步?jīng)Q定View的寬/高。

對(duì)于頂級(jí)View,即DecorView和普通View來(lái)說(shuō),MeasureSpec的轉(zhuǎn)換過(guò)程略有不同。對(duì)于DecorView,其MeasureSpec由窗口的尺寸和其自身的LayoutParams共同確定;


1645852708(1).png

對(duì)于普通View,其MeasureSpec由父容器的MeasureSpec和自身的Layoutparams共同決定;


783729886c5041d381369b10ba2bcd2b.png

MeasureSpec一旦確定,onMeasure就可以確定View的測(cè)量寬/高。

小結(jié)一下

當(dāng)子 View 采用具體的寬/高時(shí),不管父容器的MeasureSpec 是什么,ViewMeasureSpec 都是精確模式+子ViewLayoutParams 中指定的具體的大小。

當(dāng)子 View的寬/高采用match_parent 時(shí),這時(shí)候父容器的 MeasureSpec 會(huì)發(fā)揮作用,子 View 的模式總是跟父容器的模式一樣:

  • 如果父容器的模式是精確模式(EXACTLY),那么子 ViewMeasureSpec 就是精確模式+父容器的剩余空間(或者說(shuō)父容器的可用空間);
  • 如果父容器的模式是最大模式(AT_MOST),那么子 ViewMeasureSpec 就是最大模式+父容器的剩余空間(或者說(shuō)父容器的可用空間);
  • 如果父容器的模式是未指定模式(UNSPECIFIED),那么子 ViewMeasureSpec 就是未指定模式+父容器的剩余空間或者0

當(dāng)子 View 的寬高采用 wrap_content時(shí),這時(shí)候父容器的 MeasureSpec 同樣會(huì)發(fā)揮作用:

  • 如果父容器的模式是精確模式(EXACTLY),那么子ViewMeasureSpec 就是最大模式+父容器的剩余空間(或者說(shuō)父容器的可用空間);
  • 如果父容器的模式是最大模式(AT_MOST),那么子ViewMeasureSpec 就是最大模式+父容器的剩余空間(或者說(shuō)父容器的可用空間);
  • 如果父容器的模式是未指定模式(UNSPECIFIED),那么子 ViewMeasureSpec 就是未指定模式+父容器的剩余空間或者0。

當(dāng)子 View 的寬高采用 wrap_content 時(shí),不管父容器的模式是精確模式還是最大模式,子 View的模式總是最大模式+父容器的剩余空間。

View的工作流程

View的工作流程主要是指measurelayout、draw三大流程,即測(cè)量、布局、繪制。其中measure確定View的測(cè)量寬/高,layout確定view的最終寬/高和四個(gè)頂點(diǎn)的位置,而draw則將View繪制在屏幕上。

measure過(guò)程

measure過(guò)程要分情況,如果只是一個(gè)原始的view,則通過(guò)measure方法就完成了其測(cè)量過(guò)程,如果是一個(gè)ViewGroup,除了完成自己的測(cè)量過(guò)程外,還會(huì)遍歷調(diào)用所有子元素的measure方法,各個(gè)子元素再遞歸去執(zhí)行這個(gè)流程。

View的measure過(guò)程

如果是一個(gè)原始的 View,那么通過(guò) measure 方法就完成了測(cè)量過(guò)程,在 measure 方法中會(huì)去調(diào)用 View 的 onMeasure 方法,View 類里面定義了 onMeasure 方法的默認(rèn)實(shí)現(xiàn):

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // 先獲取建議的最小寬度和高度
    int suggestedMinimumWidth = getSuggestedMinimumWidth();
    int suggestedMinimumHeight = getSuggestedMinimumHeight();
    // 再通過(guò) getDefaultSize 方法獲取寬度和高度的測(cè)量值
    int measuredWidth = getDefaultSize(suggestedMinimumWidth, widthMeasureSpec);
    int measuredHeight = getDefaultSize(suggestedMinimumHeight, heightMeasureSpec);
    // 最后調(diào)用 setMeasuredDimension 方法設(shè)置 View 寬度和高度的測(cè)量值。
    setMeasuredDimension(measuredWidth, measuredHeight);
}

先看一下 getSuggestedMinimumWidthgetSuggestedMinimumHeight 方法的源碼:

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

我們只看 getSuggestedMinimumWidth 方法:如果 View 的背景 mBackground == null 成立,即View 沒(méi)有設(shè)置背景,那么 View 的建議最小寬度就是 mMinWidthmMinWidth 對(duì)應(yīng)于 android:minWidth 這個(gè)屬性所指定的值);如果 View 設(shè)置了背景,那么 View 的建議最小寬度為 mMinWidthmBackground.getMinimumWidth() 中較大的那個(gè)。mBackground是一個(gè) Drawable 對(duì)象,所以去看Drawable 類下的 getMinimumWidth 方法:

public int getMinimumWidth() {
    final int intrinsicWidth = getIntrinsicWidth();
    return intrinsicWidth > 0 ? intrinsicWidth : 0;
}

可以看到,getMinimumWidth 方法獲取的是 Drawable 的原始寬度。如果存在原始寬度(即滿足 intrinsicWidth > 0),那么直接返回原始寬度即可;如果不存在原始寬度(即不滿足intrinsicWidth > 0),那么就返回 0。

ShapeDrawable 沒(méi)有原始寬度和高度,而BitmapDrawable有原始寬度和高度。

接著看最重要的 getDefaultSize 方法:

// 這是一個(gè) static 修飾的方法,所以是一個(gè)工具方法
/**
 * Utility to return a default size. Uses the supplied size if the
 * MeasureSpec imposed no constraints. Will get larger if allowed
 * by the MeasureSpec.
 *
 * @param size Default size for this view
 * @param measureSpec Constraints imposed by the parent
 * @return The size this view should be.
 */
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;
}

如果specModeMeasureSpec.UNSPECIFIED即未指定模式,那么返回由方法參數(shù)傳遞過(guò)來(lái)的尺寸作為 View 的測(cè)量寬度和高度;
如果specMode不是MeasureSpec.UNSPECIFIED 即是最大模式或者精確模式,那么返回從 measureSpec 中取出的specSize作為 View 測(cè)量后的寬度和高度。

看一下剛才的表格:


當(dāng) specModeEXACTLY 或者 AT_MOST 時(shí),View 的布局參數(shù)為 wrap_content 或者 match_parent 時(shí),給 ViewspecSize 都是 parentSize。這會(huì)比建議的最小寬高要大。這是不符合我們的預(yù)期的。因?yàn)槲覀兘oView設(shè)置wrap_content是希望View的大小剛好可以包裹它的內(nèi)容。

因此:

直接繼承 View 的自定義控件需要重寫(xiě) onMeasure 方法并設(shè)置 wrap_content 時(shí)的自身大?。ńo View 指定一個(gè)默認(rèn)的內(nèi)部寬高)。
@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
  
        if(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode==MeasureSpec.AT_MOST){
        setMeasuredDimension(mWidth, mHeight);
}else if(widthSpecMode == MeasureSpec.AT_MOST){
        setMeasuredDimension(mWidth, heightSpecSize);
}else if(heightSpecSize == MeasureSpec.AT_MOST){
         setMeasuredDimension(widthSpecSize, mHeight);
}
}
ViewGroup的measure過(guò)程

如果是一個(gè) ViewGroup,除了完成自己的 measure 過(guò)程以外,還會(huì)遍歷去調(diào)用所有子元素的 measure 方法,各個(gè)子元素再遞歸去執(zhí)行 measure 過(guò)程。

ViewGroup 并沒(méi)有重寫(xiě) View 的 onMeasure 方法,但是它提供了 measureChildren、measureChild、measureChildWithMargins 這幾個(gè)方法專門用于測(cè)量子元素。

// 遍歷所有的子元素,并使用 measureChild 方法來(lái)對(duì)每一個(gè)子元素進(jìn)行 measure。
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
    final int size = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < size; ++i) {
        final View child = children[i];
        // 只有當(dāng)子元素的可見(jiàn)性不是 GONE 時(shí),才對(duì)它進(jìn)行測(cè)量。這一點(diǎn)是個(gè)細(xì)節(jié)。
        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }
    }
}
// 測(cè)量每一個(gè)子元素:最重要的邏輯是通過(guò) getChildMeasureSpec 方法來(lái)獲取測(cè)量子元素所需要的寬度 MeasureSpec 和 高度 MeasureSpec。
// getChildMeasureSpec(int spec, int padding, int childDimension) 方法需要的參數(shù)必須搞清楚:
// spec 是 ViewGroup 從 onMeasure 方法接收到的 MeasureSpec,
// padding 是 ViewGroup 已使用的空間,childDimension 是子元素的布局參數(shù)的寬和高。
protected void measureChild(View child, int parentWidthMeasureSpec,
        int parentHeightMeasureSpec) {
    final LayoutParams lp = child.getLayoutParams();
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom, lp.height);
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

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 childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                    + heightUsed, lp.height);
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

為什么ViewGroup 類沒(méi)有像 View 一樣對(duì)其 onMeasure 方法做統(tǒng)一的實(shí)現(xiàn),實(shí)際上 ViewGroup 繼承了 View 類對(duì) onMeasure 方法的實(shí)現(xiàn)?這是因?yàn)椴煌?ViewGroup 子類有不同的布局特性,這導(dǎo)致它們的測(cè)量細(xì)節(jié)各不相同,比如 LinearLayout 和 RelativeLayout 這兩者的布局特性顯然不同,因此 ViewGroup 無(wú)法統(tǒng)一實(shí)現(xiàn)。

為什么說(shuō)在 onLayout 方法中去獲取 View 的測(cè)量寬/高是一個(gè)好的習(xí)慣?

正常情況下,View 的 measure 過(guò)程完成以后,通過(guò) getMeasuredWidth/getMeasuredHeight 方法就可以正確地獲取到 View 的測(cè)量寬/高;但是,在某些極端情況下,系統(tǒng)可能需要多次 measure 才能確定最終的測(cè)量寬/高,在這種情形下,在 onMeasure 方法中拿到的測(cè)量寬/高很可能是不準(zhǔn)確的。

在 Activity 啟動(dòng)時(shí),如何獲取某個(gè) View 的寬/高?
方式 解釋
Activity/View.onWindowFocusChanged onWindowFocusChanged 回調(diào)時(shí),表示 View 已經(jīng)初始化完畢了,寬/高已經(jīng)準(zhǔn)備好了,所以這時(shí)去獲取寬/高是沒(méi)有問(wèn)題的。但是,onWindowFocusChanged會(huì)被多次調(diào)用,當(dāng) Activity 的窗口得到焦點(diǎn)和失去焦點(diǎn)時(shí)均會(huì)被調(diào)用一次。
View.post(Runnable runnable) 通過(guò) post 可以將一個(gè) Runnable 對(duì)象放到消息隊(duì)列的尾部,然后等到 Looper 調(diào)用此 Runnable的時(shí)候,View 也已經(jīng)初始化好了。注意:是 View.post方法而不是 Handler.post 方法
ViewTreeObserver 的多個(gè)回調(diào),如 OnGlobalLayoutListener 接口。 OnGlobalLayoutListener 接口:當(dāng) View 樹(shù)的狀態(tài)發(fā)生改變或者 View 樹(shù)內(nèi)部的 View 的可見(jiàn)性發(fā)生改變時(shí),它的 onGlobalLayout 方法將被回調(diào)。需要注意的是,伴隨著 View 樹(shù)的狀態(tài)改變等,onGlobalLayout 會(huì)被調(diào)用多次。使用完接口后,記得移除。
使用 View.measure(int widthMeasureSpec, int heightMeasureSpec)方法進(jìn)行手動(dòng)測(cè)量 這種方法比較復(fù)雜。當(dāng) ViewLayoutParamsmatch_parent 時(shí),這種方式不能測(cè)量出具體的寬/高;當(dāng) ViewLayoutParams 為具體的數(shù)值(如 100 dp)時(shí),這種方式可以測(cè)量出具體的寬/高;當(dāng) View 的 LayoutParamswrap_content 時(shí), 這種方式不能測(cè)量出具體的寬/高。

Layout

如果是 View 的話,那么在它的 layout 方法中就確定了自身的位置(具體來(lái)說(shuō)是通過(guò) setFrame 方法來(lái)設(shè)定 View 的四個(gè)頂點(diǎn)的位置,即初始化 mLeft,mRightmTop,mBottom 這四個(gè)值),layout 過(guò)程就結(jié)束了。

如果是ViewGroup 的話,那么在它的layout 方法中只是確定了 ViewGroup 自身的位置,要確定子元素的位置,就需要重寫(xiě) onLayout方法;在 onLayout 方法中,會(huì)調(diào)用子元素的 layout 方法,子元素在它的layout 方法中確定自己的位置,這樣一層一層地傳遞下去完成整個(gè) View 樹(shù)的 layout 過(guò)程。

layout 方法和 onLayout 方法的區(qū)別是什么?

layout 方法的作用是確定View本身的位置,即設(shè)定View 的四個(gè)頂點(diǎn)的位置,這樣就確定了 View 在父容器中的位置;
onLayout 方法的作用是父容器確定子元素的位置,這個(gè)方法在 View 中是空實(shí)現(xiàn),因?yàn)?View 沒(méi)有子元素了,在 ViewGroup 中則進(jìn)行抽象化,它的子類必須實(shí)現(xiàn)這個(gè)方法。

View 的 getMeasuredWidth 和 getWidth 這兩個(gè)方法有什么區(qū)別?
  • getMeasuredWidth 獲取的是測(cè)量寬度,定義了一個(gè) View 想要在父 View 里的尺寸,getWidth 獲取的是寬度,有時(shí)也被稱為繪制寬度,定義了繪制時(shí)或者布局后屏幕上的 View 的實(shí)際尺寸。

  • 兩者的賦值時(shí)機(jī)不同,測(cè)量寬/高的賦值時(shí)機(jī)要早于最終寬/高。具體來(lái)說(shuō),View 的測(cè)量寬/高形成于 Viewmeasure 過(guò)程,而View 的最終寬/高形成于 Viewlayout 過(guò)程。

  • 兩者的值多數(shù)情況下是相等的,但在某些特殊情況下會(huì)不一致。例如有兩種特殊情況:

  • 重寫(xiě) View 的 layout 方法:手動(dòng)修改傳入 layout 方法的參數(shù)值;
  • View 需要多次 measure 才能確定自己的測(cè)量寬/高,在前幾次的測(cè)量過(guò)程中,其得出的測(cè)量寬/高有可能和最終寬/高不一致,但最終來(lái)看,測(cè)量寬/高還是會(huì)和最終寬/高相同。

Draw

1.繪制背景(background.draw(canvas););
2.繪制自己(onDraw);
3.繪制 children(dispatchDraw(canvas));
4.繪制裝飾(onDrawScrollBars)。

dispatchDraw 方法的調(diào)用是在 onDraw 方法之后,也就是說(shuō),總是先繪制自己再繪制子 View

對(duì)于 View 類來(lái)說(shuō),dispatchDraw 方法是空實(shí)現(xiàn)的,對(duì)于 ViewGroup 類來(lái)說(shuō),dispatchDraw 方法是有具體實(shí)現(xiàn)的。

ViewGroup 的 draw 過(guò)程是如何傳遞的

通過(guò) dispatchDraw來(lái)傳遞的。dispatchDraw 會(huì)遍歷調(diào)用子元素的 draw 方法,如此 draw 事件就一層一層傳遞了下去。dispatchDraw 在 View 類中是空實(shí)現(xiàn)的,在 ViewGroup 類中是真正實(shí)現(xiàn)的。

View 類中的 setWillNotDraw 方法的含義及其開(kāi)發(fā)意義是什么?

如果一個(gè) View 不需要繪制任何內(nèi)容,那么就設(shè)置這個(gè)標(biāo)記為 true,系統(tǒng)會(huì)進(jìn)行進(jìn)一步的優(yōu)化。

當(dāng)創(chuàng)建的自定義控件繼承于 ViewGroup 并且不具備繪制功能時(shí),就可以開(kāi)啟這個(gè)標(biāo)記,便于系統(tǒng)進(jìn)行后續(xù)的優(yōu)化;當(dāng)明確知道一個(gè) ViewGroup 需要通過(guò) onDraw 繪制內(nèi)容時(shí),需要關(guān)閉這個(gè)標(biāo)記。

查看 LinearLayout 對(duì)這個(gè)方法的調(diào)用:setWillNotDraw(divider == null);,在有 divider 時(shí)才會(huì)關(guān)閉這個(gè)標(biāo)記,否則是打開(kāi)的;ScrollView 直接使用 setWillNotDraw(false);;ViewStub 類則使用 setWillNotDraw(true);。

自定義View

自定義View的分類
分類 用途 特點(diǎn)
1.繼承 View 重寫(xiě) onDraw 方法 用于實(shí)現(xiàn)一些不規(guī)則的效果,不方便通過(guò)布局的組合方式來(lái)達(dá)到 需要通過(guò)繪制的方式來(lái)實(shí)現(xiàn),即重寫(xiě) onDraw 方法;需要自己支持 wrap_content,處理 padding
2.繼承 ViewGroup 派生特殊的 Layout 用于實(shí)現(xiàn)自定義的布局 稍微復(fù)雜一些,需要合適地處理 ViewGroup 的測(cè)量、布局這兩個(gè)過(guò)程,并同時(shí)處理好子元素的測(cè)量和布局過(guò)程
3.繼承特定的 View 用于擴(kuò)展已有的 View 的功能 不需要自己支持 wrap_content 和 padding 等
4.繼承特定的 ViewGroup 用于實(shí)現(xiàn)幾種 View 組合在一起的效果 不要自己處理 ViewGroup 的測(cè)量和布局這兩個(gè)過(guò)程,方法2能實(shí)現(xiàn)的效果一般方法4也可以實(shí)現(xiàn),但方法2更接近 View 底層
自定義 View 的注意事項(xiàng)
  • 對(duì)于直接繼承 View 或者ViewGroup的控件,要在 onMeasure 方法中對(duì) wrap_content 做特殊處理;
  • 必要時(shí),讓自定義View支持 padding;
  • 盡量不要在自定義 View 中使用 Handler,用 View 自己的 post 方法就行;
  • 及時(shí)關(guān)閉自定義 View 中的線程或者動(dòng)畫(huà),比如在 onDetachedFromWindow 方法中;
  • 自定義 View 帶有嵌套滑動(dòng)時(shí),要處理好滑動(dòng)沖突;
  • 有自定義布局屬性的,在構(gòu)造方法中取得屬性后應(yīng)及時(shí)調(diào)用 recycle方法回收資源;
  • onDrawonTouchEvent方法中都應(yīng)避免創(chuàng)建對(duì)象,過(guò)多操作會(huì)造成卡頓;
  • 自定義 ViewGroup 要重載關(guān)于 LayoutParams 的幾個(gè)方法;
  • 必要時(shí)添加對(duì) View 的狀態(tài)存儲(chǔ)與恢復(fù)的支持;
  • 對(duì)于自定義 ViewGroup,需要繪制內(nèi)容但是又沒(méi)有在布局中設(shè)置background的話,會(huì)畫(huà)不出來(lái),這時(shí)候需要調(diào)用setWillNotDraw方法,并設(shè)置為false。

參考:《Android開(kāi)發(fā)藝術(shù)探索》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容