《Android 開發(fā)藝術(shù)探索》筆記5--View工作原理

View工作原理.png

ViewRoot和DecorView

這是在View三大流程之前(measure, layout, draw),需要了解的概念.

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

View繪制流程是從ViewRoot的PerformTraversals()開始的. 經(jīng)過三大流程才能將一個(gè)View繪制出來.

PerformTraversals()會(huì)依次調(diào)用performMeasure, performLayout, performDraw. 而前兩種內(nèi)部的調(diào)用基本一致,都是先調(diào)用measure()/layout(),然后再調(diào)用onMeasure()/onLayout()在這個(gè)方法中會(huì)對(duì)所有子元素進(jìn)行測(cè)量和繪制.依次向內(nèi)部傳遞. performDraw()有點(diǎn)不同是在draw調(diào)用的dispatchDraw().

  • measure過程: 決定了View寬高, measure后可以通過getMeasureWidth和getMeasureHeight來獲取View的寬高. 一般情況下是最終寬高.
  • layout過程: 決定了View的頂點(diǎn)坐標(biāo)和實(shí)際View的寬高. 完成后通過getTop, getBottom, getLeft, getRight獲得四個(gè)頂點(diǎn), 通過getWidth,和getHeight獲得寬高
  • draw過程: 只有draw()方法完成之后View的內(nèi)容才會(huì)顯示出來.

setContentView(R.layout.activity_inside_intercept);

((ViewGroup) getWindow().getDecorView().findViewById(android.R.id.content)).getChildAt(0);

上面第一行可以說無時(shí)無刻不存在. 而下面這行在上一章說過就是獲得我們?cè)O(shè)置的布局.那DecorView布局究竟是怎么樣的, 下圖.

image

DecorView就是一個(gè)FrameLayout. 而一般情況下它的布局就如上面圖那樣(具體和主題有關(guān)系). 而我們經(jīng)常setContentView(xxx). 就是把我們編寫的xml的布局添加到了DecorViewandroid.R.id.content的控件布局中. 所以也就能說通為什么getChildAt(0)會(huì)獲得我們的的布局.
并且為什么我們用的關(guān)聯(lián)布局的方法是setContent…

總結(jié)圖:

measure流程.png

MeasureSpec

很大程度上決定一個(gè)View的尺寸規(guī)格, 之所以不是絕對(duì), 是因?yàn)檫@個(gè)過程還受父容器的影響.

理解MeasureSpec

MeasureSpec本身是一個(gè)32位的int值, 但是卻表示了兩種信息.

  • 高2位: 代表了SpecMode, 測(cè)量模式
  • 低30位: 代表了SpecSize, 在上述測(cè)量模式中的大小

public static class MeasureSpec {

    private static final int MODE_SHIFT = 30;

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

    public static final int UNSPECIFIED = 0 << MODE_SHIFT;

    public static final int EXACTLY     = 1 << MODE_SHIFT;

    public static final int AT_MOST     = 2 << MODE_SHIFT;

    public static int makeMeasureSpec(int size, int mode) {

      if (sUseBrokenMakeMeasureSpec) {

          return size + mode;

      } else {

          return (size & ~MODE_MASK) | (mode & MODE_MASK);

      }

    }

    public static int makeSafeMeasureSpec(int size, int mode) {

      if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {

          return 0;

      }

      return makeMeasureSpec(size, mode);

    }

    public static int getMode(int measureSpec) {

      return (measureSpec & MODE_MASK);

    }

    public static int getSize(int measureSpec) {

      return (measureSpec & ~MODE_MASK);

   }

   .....

}

是不是挺有意思. 三種類型分別高二位01, 00, 10來代表. 直接利用位運(yùn)算. 來實(shí)現(xiàn)可以讓頻繁計(jì)算的東西使用最接近計(jì)算機(jī)的運(yùn)算方式. 不需要額外的轉(zhuǎn)換. 也避免了過多的對(duì)象內(nèi)存分配.

說一下SpecMode的三種模式

  • UNSPECIFIED: 父容器不對(duì)View有任何的限制,要多大就給多大, 這種情況一般用于系統(tǒng)內(nèi)部,表示一中測(cè)量狀態(tà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
image.png

MeasureSpec和LayoutParams關(guān)系

通常設(shè)置的LayoutParams,系統(tǒng)會(huì)在父容器的的約束下轉(zhuǎn)換成對(duì)應(yīng)的MeasureSpec,然后根據(jù)這個(gè)MeasureSpec來確定View測(cè)量后的寬高. 所以View自身的MeasureSpec是需要LayoutParams和父容器一起組合生成的.

上面講述的是普通View, 但是頂級(jí)View(DecorView)有所不同. DecorView是物理窗口尺寸和自身的LayoutParams決定的. 具體在ViewRootImpl類measureHierarchy()進(jìn)行生成的.

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

對(duì)于我們?nèi)粘2僮鞯腣iew

View的measure過程是由ViewGroup傳遞而來的. 看ViewGroup#measureChildWithMargins()方法

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);

  }

上面會(huì)對(duì)子元素進(jìn)行measure, 而在此之前,會(huì)通過getChildMeasureSpec()來得到子元素的MeasureSpec. 通過調(diào)用方法傳入的參數(shù)看到. 生成View的MeasureSpec和父容器的MeasureSpec, View自身方向的padding``margin, 和自身的LayoutParams這三個(gè)因素相關(guān)聯(lián).

而其中的getChildmeasureSpec()方法: 就是根據(jù)父容器的MeasureSpec同時(shí)結(jié)合View自身的LayoutParams來確定子元素的MeasureSpec.這個(gè)方法總結(jié)如下:

  • dp/px: 不管父容器的MeasureSpec是什么. View都是EXACTLY(精確模式), 而大小遵循自身LayoutParams的大小.
  • match_parent: 如果父容器是EXACTLY(精確模式),那么子View也是EXACTLY(精確模式)并且大小是父容器的剩余空間. 如果父容器是AT_MOST(最大模式),那么子View也是AT_MOST(最大模式)并且大小不會(huì)超過父容器的剩余空間.
  • wrap_content: 不管父容器是什么. View都是AT_MOST(最大模式), 并且大小不能超過父容器剩余空間.

上述沒有說明UNSPECIFIEDmatch_parentwrap_content中. 因?yàn)檫@個(gè)模式主要用于系統(tǒng)多次Measure的情形,一般來說不需要關(guān)注.

View的工作流程

主要指measure, layout, draw三大流程. 即測(cè)量,布局,繪制.

measure過程

這里面存在兩種場(chǎng)景:

  • View: 通過了measure方法就完成了測(cè)量過程
  • ViewGroup: 除了測(cè)量自己,還會(huì)遍歷去調(diào)用所有子元素的measure方法. 各個(gè)子元素在遞歸去執(zhí)行這個(gè)流程

View的measure過程

View的measure過程由其measure()方法來完成, measure()方法是一個(gè)final類型, 而在內(nèi)部調(diào)用了onMeasure()這個(gè)可不是final, 所以也可以自定義的時(shí)候復(fù)寫. 看一下內(nèi)部.

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

   setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),

           getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));

}

setMeasureDimension()會(huì)設(shè)置View寬高的測(cè)量值.

這里需要看一下getDefaultSize()這個(gè)方法.


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;

}

看到如果這個(gè)view是EXACTLY(精準(zhǔn)模式), 那么返回的大小就是SpecSize. UNSPECIFIED一般用于系統(tǒng)測(cè)量先不說. 而AT_MOST(最大模式)的時(shí)候. 雖然是不同模式但是默認(rèn)情況下和精確模式是一樣的結(jié)果.

getSuggestedMinimumWidth()getSuggestedMinimumHeight(). 看一下實(shí)現(xiàn).

protected int getSuggestedMinimumWidth() {

    return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());

}

protected int getSuggestedMinimumHeight() {

    return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());

}

首先會(huì)看是否設(shè)置了背景.

  • 無背景: 那么寬度為mMinWidth,這個(gè)值對(duì)應(yīng)布局中的android:minWidth屬性,默認(rèn)為0.
  • 有背景: 那么取mMinWidthmBackground.getMinimumHeight()最大值.

getMinimumHeight()根據(jù)看一下:


public int getMinimumHeight() {

   final int intrinsicHeight = getIntrinsicHeight();

   return intrinsicHeight > 0 ? intrinsicHeight : 0;

}

原來getMinimumHeight()返回的就是Drawable的原始高度. 如果沒有就返回0. 關(guān)于原始高度舉個(gè)例子ShapeDrawable無原始寬高, BitmapDrawble有原始寬高就是圖片的尺寸.

整理getDefaultSize(): 直接繼承View的自定義控件需要重寫onMeasure()方法并設(shè)置wrap_content時(shí)的自身大小,否則在布局中使用wrap_content雖然View自身的MeasureSpec的低30位保存了父容器計(jì)算自身的剩余大小. 但是在自定義的時(shí)候如果不進(jìn)行處理wrap_content,那么就會(huì)調(diào)用默認(rèn)setMeasureDimension()方法. 而默認(rèn)中方法的實(shí)參傳遞的是getDefaultSize()這個(gè)方法中對(duì)AT_MOST這種模式?jīng)]有處理. 直接沿用和精確模式的大小(相當(dāng)于設(shè)置了wrap_content卻得到了match_parent的顯示結(jié)果)

可以針對(duì)這個(gè)問題, 做出對(duì)應(yīng)的編碼進(jìn)行解決:


@Override

   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

       super.onMeasure(widthMeasureSpec, heightMeasureSpec);

       int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec);

       int widthSpaceMode = MeasureSpec.getMode(widthMeasureSpec);

       int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec);

       int heightSpaceMode = MeasureSpec.getMode(heightMeasureSpec);

       //設(shè)置兩個(gè)默認(rèn)值寬高

       int defaultHeight = 100;

       int defaultWidth = 100;

       // 針對(duì)AT_MOST模式進(jìn)行特殊處理

       if (widthSpaceMode == MeasureSpec.AT_MOST 

               && heightSpaceMode == MeasureSpec.AT_MOST){

           setMeasuredDimension(defaultWidth, defaultHeight);

       }else if (widthSpaceMode == MeasureSpec.AT_MOST){

           setMeasuredDimension(defaultWidth, heightSpaceSize);

       }else if (heightSpaceMode == MeasureSpec.AT_MOST){

           setMeasuredDimension(widthMeasureSpec, defaultHeight);

       }

   }

ViewGroup的Measure

對(duì)于ViewGroup不光會(huì)測(cè)量自己,還會(huì)遍歷調(diào)用所有的子元素的measure(). 和View不同的是ViewGroup是一個(gè)抽象類,它沒有重寫onMeasure,但提供了measureChildren()的方法.

這個(gè)measureChildren()方法內(nèi)部比較簡(jiǎn)單就是遍歷自己的孩子然后調(diào)用->measureChild()

這個(gè)measureChild()這個(gè)方法前面貼過源碼. 就是取出子元素的LayoutParams,并調(diào)用->getChildMeasureSpec(). 通過傳入子元素的LayoutParams里面的寬高屬性, 子元素的padding和margin, 父元素當(dāng)前(當(dāng)前ViewGroup)的MeasureSpec屬性來計(jì)算出子元素的MeasureSpec最后調(diào)用->child.measure()傳入之前計(jì)算的測(cè)量規(guī)格.

ViewGroup為什么沒有定義測(cè)量的具體過程? 因?yàn)榫唧w的測(cè)量過程需要交給子類去實(shí)現(xiàn)的. 比如LinearLayout,RelativeLayout.

看一下LinearLayoutonMeasure()是如何定義的.


@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

   if (mOrientation == VERTICAL) {

       measureVertical(widthMeasureSpec, heightMeasureSpec);

   } else {

       measureHorizontal(widthMeasureSpec, heightMeasureSpec);

   }

}

根據(jù)設(shè)置的排列方式這里分之了兩種測(cè)量方法. 稍微看一下大概輪廓,選擇measureVertical()不貼源碼了這個(gè)方法300行呢!

首先這個(gè)方法會(huì)遍歷每個(gè)子元素并執(zhí)行->measureChildBeforeLayout()方法.這個(gè)方法內(nèi)部會(huì)調(diào)用子元素的measure(), 這樣子元素會(huì)依次測(cè)量. 并且會(huì)通過mTotalLenght這個(gè)變量來存儲(chǔ)LinearLayout在豎直方向上的初步高度, 每測(cè)量一個(gè)就會(huì)增加. 當(dāng)子元素測(cè)量完之后,LinearLayout會(huì)測(cè)量自己的大小.


在對(duì)自己進(jìn)行測(cè)量的時(shí)候. 如果布局中的高度采用的是match_parent或者具體數(shù)值, 那么它的測(cè)量過程和View一樣,即高度為specSize. 如果布局中采用wrap_content那么高度就是所有的子元素總和但是不能超過父元素剩余空間, 還有豎直方向LinearLayout的padding. 具體可參考resolveSizeAndState()的實(shí)現(xiàn).

到這里基本上measure測(cè)量過程已經(jīng)做了比較詳細(xì)的分析. 這個(gè)過程也是三大過程中最復(fù)雜的一個(gè). 在measure完成之后就可以通過getMeasuredWidth/Height方法獲取View的測(cè)量寬高. 但是請(qǐng)注意:某些極端情況下,measure可能執(zhí)行多次. 所以盡量在onLayout()方法中去獲得最終寬高.

image.png

正確獲取寬高方法

首先明確一點(diǎn):View的measure和Activity的生命周期方法不是同步執(zhí)行.所以無法保證在某個(gè)生命周期(onCreate,onStart)獲取到正確的測(cè)量寬高

  • onWindowFocusChanged()
  • view.post(runnable)
  • ViewTreeObserve
  • view.measure()
  1. onWindowFocusChanged():View已經(jīng)初始化完畢,寬高已經(jīng)準(zhǔn)備好. 這里需要注意只要Activity的焦點(diǎn)發(fā)生變化此方法就會(huì)被調(diào)用.所以如果你的界面會(huì)頻繁的進(jìn)行onPauseonResume.并且里面有很多關(guān)聯(lián)依賴的方法. 那就請(qǐng)注意這不是一個(gè)好辦法.
  2. 通過post可以將一個(gè)runnable投遞到消息隊(duì)列的尾部,然后等待Looper調(diào)用此runnable的時(shí)候.View已經(jīng)初始化完畢.
  3. 使用ViewTreeObserver. 當(dāng)View的可見性發(fā)生了改變的時(shí)候.onGlobalLayout()將發(fā)生回調(diào).注意伴隨著View樹的狀態(tài)改變等,這個(gè)回調(diào)方法可能會(huì)被調(diào)用多次. 使用代碼如下

ViewTreeObserver viewTreeObserver = tv_main.getViewTreeObserver();

       viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

           @Override

           public void onGlobalLayout() {

               tv_main.getViewTreeObserver().removeOnGlobalLayoutListener(this);

               tv_main.getMeasuredHeight();

               tv_main.getMeasuredWidth();

           }

       });

  1. view.measure(widthMeasureSpec, heightMeasureSpec)

也可以手動(dòng)進(jìn)行測(cè)量,但是需要分情況處理.

match_parent

當(dāng)View是此屬性的時(shí)候無法使用measure(),首先使用這種方法需要的參數(shù),是通過父容器和子元素組合來生成的子元素的MeasureSpec屬性. 所以在外部我們不知道父元素的參數(shù)值得時(shí)候只能處理不需要父元素?cái)?shù)據(jù)就可以生成子元素的MeasureSpec的模式

所以很清楚, 這個(gè)match_patch這個(gè)模式,在給其子元素構(gòu)造MeasureSpec的時(shí)候需要得值parentSize,所以得到的也是無效.

具體數(shù)值px/dx

假設(shè)這里是100px, 首先構(gòu)成寬高對(duì)應(yīng)的MeasureSpec屬性

int widthSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
int heightSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
tv_main.measure(widthSpec, heightSpec);

wrap_content

int widthSpec = View.MeasureSpec.makeMeasureSpec(((1 << 30)-1), View.MeasureSpec.AT_MOST);
int heightSpec = View.MeasureSpec.makeMeasureSpec(((1 << 30)-1), View.MeasureSpec.AT_MOST);
tv_main.measure(widthSpec, heightSpec);

通過(1<<30)-1 可以構(gòu)成一個(gè)MeasureSpec低30位的最大值. 用理論上View能支持的最大值去構(gòu)造

關(guān)于網(wǎng)上一些在make的使用傳入UNSPECIFIED,屬于違背了內(nèi)部實(shí)現(xiàn)的規(guī)范.不用最好

關(guān)于網(wǎng)上另一種measure()直接傳入LayoutParams.WRAP_CONTENT. 其實(shí)也只有當(dāng)子元素為wrap_content和子元素為match_parent并且父元素是wrap_conetnt時(shí)會(huì)碰巧有效.

layout過程

ViewGroup中會(huì)先通過layout()方法確定本身的位置. 然后調(diào)用onLayout()方法遍歷所有的子元素,并調(diào)用子元素的layout()方法確定子元素的位置…依次循環(huán).

提出Viewlayout方法, 這里抽取部分代碼

public void layout(int l, int t, int r, int b) {

       int oldL = mLeft;

       int oldT = mTop;

       int oldB = mBottom;

       int oldR = mRight;

       boolean changed = isLayoutModeOptical(mParent) ?

               setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

       if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) 

       { onLayout(changed, l, t, r, b);}

   }

這樣來看,大致流程通過setFrame()方法來設(shè)定View的四個(gè)頂點(diǎn)的位置, 即mLeft,mTop,mBottom,mRight,這四個(gè)頂點(diǎn)一旦確定.當(dāng)前View的位置也就確定. 然后會(huì)調(diào)用onLayout()方法. 這個(gè)方法是確定子元素的View位置.

這里的和onMeasure()類似, onLayout()具體實(shí)現(xiàn)和具體的布局有關(guān), 所以View和ViewGroup均沒有真正實(shí)現(xiàn)onLayout()方法.

看一下LinearLayoutonLayout()源碼

@Override

protected void onLayout(boolean changed, int l, int t, int r, int b) {

   if (mOrientation == VERTICAL) {

       layoutVertical(l, t, r, b);

   } else {

       layoutHorizontal(l, t, r, b);

   }

}

onMeasure()一樣分支,接下來跟進(jìn)layoutVertical()貼出主要代碼

void layoutVertical(int left, int top, int right, int bottom) {

           //省略一部分...

       for (int i = 0; i < count; i++) {

           final View child = getVirtualChildAt(i);

           if (child == null) {

               childTop += measureNullChild(i);

           } else if (child.getVisibility() != GONE) {

               final int childWidth = child.getMeasuredWidth();

               final int childHeight = child.getMeasuredHeight();

               final LinearLayout.LayoutParams lp =

                       (LinearLayout.LayoutParams) child.getLayoutParams();

               int gravity = lp.gravity;

               if (gravity < 0) {

                   gravity = minorGravity;

               }

                //省略一部分...

               if (hasDividerBeforeChildAt(i)) {

                   childTop += mDividerHeight;

               }

               childTop += lp.topMargin;

               setChildFrame(child, childLeft, childTop + getLocationOffset(child),

                       childWidth, childHeight);

               childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

               i += getChildrenSkipCount(child, i);

           }

       }

   }

上面代碼大體邏輯: 首先遍歷所有孩子并調(diào)用setChildFrame()來為子元素指定對(duì)應(yīng)的位置. 其中childTop會(huì)逐漸增大, 這就意味著后面的子元素會(huì)被放置在靠下的位置. 而setChildFrame()內(nèi)部?jī)H有一行代碼, 就是調(diào)用子元素的layout()并傳入它自身應(yīng)該存放的位置.

private void setChildFrame(View child, int left, int top, int width, int height) {        

     child.layout(left, top, left + width, top + height);

 }

而在setChildFrame()中傳入的寬高就是子元素的測(cè)量寬高.

而在子元素的layout()中通過setFrame()來設(shè)置元素的四個(gè)頂點(diǎn).

getWidth()layout中的寬 和getMeasureWidth()中的寬永遠(yuǎn)一樣么?

在一般情況下,測(cè)量measure和layout時(shí)候的值是完全一樣的. 因?yàn)?code>layout()中接受的參數(shù)就是通過測(cè)量的結(jié)果獲取到的. 并且內(nèi)部直接通過setFrame()賦值到自己的四個(gè)成員變量上. 但是如果對(duì)layout()進(jìn)行了復(fù)寫.如下

 @Override

protected void layout(int l, int t, int r, int b) {

   super.layout( l,  t+200,  r,  b+200);

}

如果進(jìn)行了這樣的復(fù)寫, 那么最終寬高永遠(yuǎn)會(huì)與測(cè)量的出來的值相差200.

layout流程.png

draw過程

這個(gè)過程只是將View繪制到屏幕上面.

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

View繪制過程傳遞是通過dispatchDraw()實(shí)現(xiàn)的. 傳遞了自己的畫布. 這個(gè)方法會(huì)遍歷子元素并且調(diào)用元素的draw()

View一個(gè)特有的方法setWillNotDraw(), 這個(gè)方法是設(shè)置了true那么系統(tǒng)會(huì)進(jìn)行相應(yīng)的優(yōu)化. 在View中默認(rèn)是關(guān)閉的. 而ViewGroup默認(rèn)是開啟的. 如果我們繼承了自定義ViewGroup如果還需要繪制自己的內(nèi)容那么需要顯示的關(guān)閉此標(biāo)記.

draw過程.png

參看文章

《Android 開發(fā)藝術(shù)探索》書集
《Android 開發(fā)藝術(shù)探索》 04-View的工作原理
View的繪制-measure流程詳解
View的繪制-layout流程詳解
View的繪制-draw流程詳解

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末趾徽,一起剝皮案震驚了整個(gè)濱河市臂寝,隨后出現(xiàn)的幾起案子哑诊,更是在濱河造成了極大的恐慌铁蹈,老刑警劉巖粥诫,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件十绑,死亡現(xiàn)場(chǎng)離奇詭異漱挎,居然都是意外死亡廉涕,警方通過查閱死者的電腦和手機(jī)典蝌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門曙砂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人骏掀,你說我怎么就攤上這事鸠澈。” “怎么了砖织?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵款侵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我侧纯,道長(zhǎng)新锈,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任眶熬,我火速辦了婚禮妹笆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘娜氏。我一直安慰自己拳缠,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布贸弥。 她就那樣靜靜地躺著窟坐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪绵疲。 梳的紋絲不亂的頭發(fā)上哲鸳,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音盔憨,去河邊找鬼徙菠。 笑死,一個(gè)胖子當(dāng)著我的面吹牛郁岩,可吹牛的內(nèi)容都是我干的婿奔。 我是一名探鬼主播缺狠,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼萍摊!你這毒婦竟也來了挤茄?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤记餐,失蹤者是張志新(化名)和其女友劉穎驮樊,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體片酝,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年挖腰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了雕沿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡猴仑,死狀恐怖审轮,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情辽俗,我是刑警寧澤疾渣,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站崖飘,受9級(jí)特大地震影響榴捡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜朱浴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一吊圾、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧翰蠢,春花似錦项乒、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至廷支,卻和暖如春孩擂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背售貌。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來泰國(guó)打工芽死, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人芝囤。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓似炎,卻偏偏與公主長(zhǎng)得像辛萍,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子羡藐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345