Android - View 繪制流程

image.png

簡(jiǎn)介
我們知道阳啥,在 Android 中趴拧,View 繪制主要包含 3 大流程:

measure(測(cè)量):主要用于確定 View 的測(cè)量寬/高紧唱。

layout(布局):主要用于確定 View 在父容器中的放置位置肘交。

draw(繪制):結(jié)合前面兩步結(jié)果氯窍,將 View 真正繪制到屏幕上缔杉。

Android 中锤躁,主要有兩種視圖:View和ViewGroup,其中:

View:就是一個(gè)獨(dú)立的視圖
ViewGroup:一個(gè)容器組件或详,該容器可容納多個(gè)子視圖系羞,即ViewGroup可容納多個(gè)View或ViewGroup,且支持嵌套霸琴。
雖然ViewGroup繼承于View椒振,但是在 View 繪制三大流程中,某些流程需要區(qū)分View和ViewGroup梧乘,它們之間的操作并不完全相同澎迎,比如:

View和ViewGroup都需要進(jìn)行 measure,確定各自的測(cè)量寬/高选调。View只需直接測(cè)量自身即可夹供,而ViewGroup通常都必須先測(cè)量所有子View,最后才能測(cè)量自己
通常ViewGroup先定位自己的位置(layout)仁堪,然后再定位其子View 位置(onLayout)
View需要進(jìn)行 draw 過程哮洽,而ViewGroup通常不需要(當(dāng)然也可以進(jìn)行繪制),因?yàn)閂iewGroup更多作為容器存在弦聂,起存儲(chǔ)放置功能
measure 流程
對(duì) View 進(jìn)行測(cè)量鸟辅,主要包含兩個(gè)步驟:

求取 View 的測(cè)量規(guī)格MeasureSpec。
依據(jù)上一步求得的MeasureSpec莺葫,對(duì) View 進(jìn)行測(cè)量剔桨,求取得到 View 的最終測(cè)量寬/高。
MeasureSpec
對(duì)于第一個(gè)步驟徙融,即求取 View 的MeasureSpec洒缀,首先我們來看下MeasureSpec的源碼定義:

// frameworks/base/core/java/android/view/View.java
public static class MeasureSpec {
    private static final int MODE_SHIFT = 30;
    private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

    /**
     * Measure specification mode: The parent has not imposed any constraint
     * on the child. It can be whatever size it wants.
     */
    public static final int UNSPECIFIED = 0 << MODE_SHIFT;

    /**
     * Measure specification mode: The parent has determined an exact size
     * for the child. The child is going to be given those bounds regardless
     * of how big it wants to be.
     */
    public static final int EXACTLY     = 1 << MODE_SHIFT;

    /**
     * Measure specification mode: The child can be as large as it wants up
     * to the specified size.
     */
    public static final int AT_MOST     = 2 << MODE_SHIFT;

    // 生成測(cè)量規(guī)格
    public static int makeMeasureSpec(int size, int mode) {
        if (sUseBrokenMakeMeasureSpec) {
            return size + mode;
        } else {
            return (size & ~MODE_MASK) | (mode & MODE_MASK);
        }
    }

    // 獲取測(cè)量模式
    public static int getMode(int measureSpec) {
        return (measureSpec & MODE_MASK);
    }

    // 獲取測(cè)量大小
    public static int getSize(int measureSpec) {
        return (measureSpec & ~MODE_MASK);
    }
    ...
}

MeasureSpec是View的一個(gè)公有靜態(tài)內(nèi)部類,它是一個(gè) 32 位的int值,高 2 位表示 SpecMode(測(cè)量模式)树绩,低 30 位表示 SpecSize(測(cè)量尺寸/測(cè)量大腥浴)。
MeasureSpec將兩個(gè)數(shù)據(jù)打包到一個(gè)int值上饺饭,可以減少對(duì)象內(nèi)存分配渤早,并且其提供了相應(yīng)的工具方法可以很方便地讓我們從一個(gè)int值中抽取出 View 的 SpecMode 和 SpecSize。

一個(gè)MeasureSpec表達(dá)的是:該 View 在該種測(cè)量模式(SpecMode)下對(duì)應(yīng)的測(cè)量尺寸(SpecSize)瘫俊。其中鹊杖,SpecMode 有三種類型:

UNSPECIFIED:表示父容器對(duì)子View 未施加任何限制,子View 尺寸想多大就多大扛芽。

EXACTLY:如果子View 的模式為EXACTLY骂蓖,則表示子View 已設(shè)置了確切的測(cè)量尺寸,或者父容器已檢測(cè)出子View 所需要的確切大小川尖。
這種模式對(duì)應(yīng)于LayoutParams.MATCH_PARENT和子View 設(shè)置具體數(shù)值兩種情況登下。

AT_MOST:表示自適應(yīng)內(nèi)容,在該種模式下叮喳,View 的最大尺寸不能超過父容器的 SpecSize被芳,因此也稱這種模式為 最大值模式。
這種模式對(duì)應(yīng)于LayoutParams.WRAP_CONTENT馍悟。

LayoutParams
對(duì) View 進(jìn)行測(cè)量畔濒,最關(guān)鍵的一步就是計(jì)算得到 View 的MeasureSpec,子View 在創(chuàng)建時(shí)锣咒,可以指定不同的LayoutParams(布局參數(shù))侵状,LayoutParams的源碼主要內(nèi)容如下所示:

// frameworks/base/core/java/android/view/ViewGroup.java
public static class LayoutParams {
    ...
    /**
     * Special value for the height or width requested by a View.
     * MATCH_PARENT means that the view wants to be as big as its parent,
     * minus the parent's padding, if any. Introduced in API Level 8.
     */
    public static final int MATCH_PARENT = -1;

    /**
     * Special value for the height or width requested by a View.
     * WRAP_CONTENT means that the view wants to be just large enough to fit
     * its own internal content, taking its own padding into account.
     */
    public static final int WRAP_CONTENT = -2;

    /**
     * Information about how wide the view wants to be. Can be one of the
     * constants FILL_PARENT (replaced by MATCH_PARENT
     * in API Level 8) or WRAP_CONTENT, or an exact size.
     */
    public int width;

    /**
     * Information about how tall the view wants to be. Can be one of the
     * constants FILL_PARENT (replaced by MATCH_PARENT
     * in API Level 8) or WRAP_CONTENT, or an exact size.
     */
    public int height;
    ...
}

其中:

  • LayoutParams.MATCH_PARENT:表示子View 的尺寸與父容器一樣大(注:需要減去父容器padding部分空間,讓父容器padding生效)
  • LayoutParams.WRAP_CONTENT:表示子View 的尺寸自適應(yīng)其內(nèi)容大谐韬濉(注:需要包含子View 本身的padding空間)
  • width/height:表示 View 的設(shè)置寬/高壹将,即layout_widthlayout_height設(shè)置的值,其值有三種選擇:LayoutParams.MATCH_PARENT毛嫉、LayoutParams.WRAP_CONTENT具體數(shù)值诽俯。

LayoutParams會(huì)受到父容器的MeasureSpec的影響,測(cè)量過程會(huì)依據(jù)兩者之間的相互約束最終生成子View 的MeasureSpec承粤,完成 View 的測(cè)量規(guī)格暴区。

簡(jiǎn)而言之,View 的MeasureSpec受自身的LayoutParams和父容器的MeasureSpec共同決定(DecorViewMeasureSpec是由自身的LayoutParams和屏幕尺寸共同決定辛臊,參考后文)仙粱。也因此,如果要求取子View 的MeasureSpec彻舰,那么首先就需要知道父容器的MeasureSpec伐割,層層逆推而上候味,即最終就是需要知道頂層View(即DecorView)的MeasureSpec,這樣才能一層層傳遞下來隔心,這整個(gè)過程需要結(jié)合Activity的啟動(dòng)過程進(jìn)行分析白群。

Activity 視圖基本結(jié)構(gòu)

我們知道,在 Android 中硬霍,Activity是作為視圖組件存在帜慢,主要就是在手機(jī)上顯示視圖界面,可以供用戶操作唯卖,Activity就是 Andorid 中與用戶直接交互最多的系統(tǒng)組件粱玲。

Activity的基本視圖層次結(jié)構(gòu)如下所示:

image

Activity中,實(shí)際承載視圖的組件是Window(更具體來說為PhoneWindow)拜轨,頂層View 是DecorView抽减,它是一個(gè)FrameLayoutDecorView內(nèi)部是一個(gè)LinearLayout撩轰,該LinearLayout由兩部分組成(不同 Android 版本或主題稍有差異):TitleViewContentView胯甩,其中昧廷,TitleView就是標(biāo)題欄堪嫂,也就是我們常說的TitleBarActionBarContentView就是內(nèi)容欄木柬,它也是一個(gè)FrameLayout皆串,主要用于承載我們的自定義根布局,即當(dāng)我們調(diào)用setContentView(...)時(shí)眉枕,其實(shí)就是把我們自定義的布局設(shè)置到該ContentView中恶复。

當(dāng)Activity啟動(dòng)完成后,最終就會(huì)渲染出上述層次結(jié)構(gòu)的視圖速挑。

DecorView 測(cè)量規(guī)格

因此谤牡,如果我們要求取得到子View 的MeasureSpec,那么第一步就是求取得到頂層View(即DecorView)的MeasureSpec姥宝。大致過程如下所示:

  1. Activity啟動(dòng)過程中翅萤,會(huì)調(diào)用到ActivityThread.handleResumeActivity(...),該方法就是 View 視圖繪制的起始之處:

    // frameworks/base/core/java/android/app/ActivityThread.java
    final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume) {
        ...
        ActivityClientRecord r = performResumeActivity(token, clearHide);
        ...
        // 此處的 window 為與 Activity 綁定的 PhoneWindow腊满,即 Activity.mWindow
        r.window = r.activity.getWindow();
        // PhoneWindow 綁定的頂層視圖:DecorView
        View decor = r.window.getDecorView();
        decor.setVisibility(View.INVISIBLE);
        // 獲取與 Activity 綁定的 WindowManager套么,實(shí)際上是 PhoneWindow 的 WindowManager
        ViewManager wm = a.getWindowManager();
        WindowManager.LayoutParams l = r.window.getAttributes();
        ...
        // 添加 DecorView 到 PhoneWindow 上(相當(dāng)于設(shè)置 Activity 根視圖)
        wm.addView(decor, l);
        ...
    }
    
    

    其中,r.window.getDecorView()實(shí)際調(diào)用的是PhoneWindow.getDecorView()碳蛋,其會(huì)返回頂層DecorView(不存在時(shí)會(huì)自動(dòng)實(shí)例化):

    // frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
    public class PhoneWindow extends Window implements MenuBuilder.Callback {
        // This is the top-level view of the window, containing the window decor.
        private DecorView mDecor;
        ...
    
        @Override
        public final View getDecorView() {
            if (mDecor == null) {
                installDecor();
            }
            return mDecor;
        }
    
        private void installDecor() {
            if (mDecor == null) {
                mDecor = generateDecor();
                ...
            }
            ...
        }
    
        protected DecorView generateDecor() {
            // 實(shí)例化 DecorView
            return new DecorView(getContext(), -1);
        }
        ...
    }
    
    

    然后胚泌,r.window.getAttributes()實(shí)際調(diào)用的是Window.getAttributes()

    // frameworks/base/core/java/android/view/Window.java
    public abstract class Window {
        private final WindowManager.LayoutParams mWindowAttributes =
            new WindowManager.LayoutParams();
        ...
    
        public final WindowManager.LayoutParams getAttributes() {
            return mWindowAttributes;
        }
    }
    // frameworks/base/core/java/android/view/WindowManager.java
    public interface WindowManager extends ViewManager {
        ...
        public static class LayoutParams extends ViewGroup.LayoutParams
                implements Parcelable {
            public LayoutParams() {
                // DecorView 的布局參數(shù)為 MATCH_PARENT
                super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
                ...
            }
        }
    }
    
    

    這里可以看到,此處r.window.getAttributes()返回的是一個(gè)WindowManager.LayoutParams實(shí)例肃弟,對(duì)應(yīng)的最終寬/高布局參數(shù)為LayoutParams.MATCH_PARENT玷室,最后通過wm.addView(decor,l)DecorView添加到WindowManager上(最終其實(shí)是設(shè)置到ViewRootImpl上)零蓉,所以DecorView的布局參數(shù)為MATCH_PARENT

  2. View 的繪制流程真正開始的地方為ViewRootImpl.performTraversals()穷缤,在其中壁公,有如下代碼片段:

    // frameworks/base/core/java/android/view/ViewRootImpl.java
    private void performTraversals() {
        ...
        int desiredWindowWidth;
        int desiredWindowHeight;
        ...
        // Ask host how big it wants to be
        windowSizeMayChange |= measureHierarchy(host, lp, res,
                desiredWindowWidth, desiredWindowHeight);
        ...
    }
    
    private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
        final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
        ...
        childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
        childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
        ...
    }
    
    

    此處的desiredWindowWidthdesiredWindowHeight是屏幕的尺寸,內(nèi)部最終會(huì)調(diào)用到ViewRootImpl.getRootMeasureSpec(...)绅项,其源碼如下所示:

    // frameworks/base/core/java/android/view/ViewRootImpl.java
    private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {
            case ViewGroup.LayoutParams.MATCH_PARENT:
                // Window can't resize. Force root view to be windowSize.
                measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
                break;
            case ViewGroup.LayoutParams.WRAP_CONTENT:
                // Window can resize. Set max size for root view.
                measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
                break;
            default:
                // Window wants to be an exact size. Force root view to be that size.
                measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
                break;
        }
        return measureSpec;
    }
    
    

    ViewRootImpl.getRootMeasureSpec(...)見名知意紊册,其實(shí)就是用來獲取頂層View(即DecorView)的MeasureSpec,其邏輯如下:

    1. 當(dāng)DecorViewLayoutParamsMATCH_PARENT時(shí)快耿,說明DecorView的大小與屏幕一樣大囊陡,而又由于屏幕大小是確定的,因此掀亥,其 SpecMode 為EXACTLY撞反,SpecSize 為windowSize,搪花;
    2. 當(dāng)DecorViewLayoutParamsWRAP_CONTENT時(shí)遏片,說明DecorView自適應(yīng)內(nèi)容大小,因此它的大小不確定撮竿,但是最大不能超過屏幕大小吮便,故其 SpecMode 為AT_MOST,SpecSize 為windowSize幢踏;
    3. 其余情況為DecorView設(shè)置了具體數(shù)值大小或UNSPECIFIED髓需,故以DecorView為主,其 SpecMode 為EXACTLY房蝉,SpecSize 就是自己設(shè)置的值僚匆,即rootDimension

    結(jié)合我們上面的分析搭幻,由于DecorViewLayoutParamsMATCH_PARENT咧擂,因此,DecorViewMeasureSpec最終為:MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY)檀蹋,即DecorView的 SpecMode 為EXACTLY松申,SpecSize 為屏幕大小。

默認(rèn)測(cè)量(measure)

經(jīng)過上述步驟求取得到 View 的MeasureSpec后续扔,接下來就可以真正對(duì) View 進(jìn)行測(cè)量攻臀,求取 View 的最終測(cè)量寬/高:

Android 內(nèi)部對(duì)視圖進(jìn)行測(cè)量的過程是由View#measure(int, int)方法負(fù)責(zé)的,但是對(duì)于ViewViewGroup纱昧,其具體測(cè)量過程有所差異刨啸。

因此,對(duì)于測(cè)量過程识脆,我們分別對(duì)ViewViewGroup進(jìn)行分析:

  • View測(cè)量View的測(cè)量過程由View.measure(...)方法負(fù)責(zé)设联,其源碼如下所示:

    // frameworks/base/core/java/android/view/View.java
    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        ...
        // measure ourselves, this should set the measured dimension flag back
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        ...
    }
    
    

    View#measure(int, int)中參數(shù)widthMeasureSpecheightMeasureSpec是由父容器傳遞進(jìn)來的善已,具體的測(cè)量過程請(qǐng)參考后文內(nèi)容。

    需要注意的是离例,View#measure(int, int)是一個(gè)final方法换团,因此其不可被覆寫,實(shí)際真正測(cè)量 View 自身使用的是View#onMeasure(int, int)方法宫蛆,如下所示:

    // frameworks/base/core/java/android/view/View.java
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
    
    

    onMeasure(...)主要做了三件事:

    1. 首先通過getSuggestedMinimumWidth()/getSuggestedMinimumHeight()方法獲取得到 View 的推薦最小測(cè)量寬/高:

      // frameworks/base/core/java/android/view/View.java
      protected int getSuggestedMinimumWidth() {
          return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
      }
      
      protected int getSuggestedMinimumHeight() {
          return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
      }
      
      

      這兩個(gè)方法的實(shí)現(xiàn)原理是一致的艘包,這里就只分析getSuggestedMinimumWidth()方法實(shí)現(xiàn),該方法內(nèi)部是一個(gè)三目運(yùn)算符耀盗,可以很清晰看出想虎,當(dāng) View 沒有設(shè)置背景時(shí),它的寬度就為mMinWidth叛拷,mMinWidth就是android:minWidth這個(gè)屬性對(duì)應(yīng)設(shè)置的值(未設(shè)置android:minWidth時(shí)舌厨,其值默認(rèn)為0),當(dāng) View 設(shè)置了背景時(shí)忿薇,它的寬度就是mMinWidthmBackground.getMinimumWidth()之中的較大值裙椭,其中,mBackground.getMinimumWidth()源碼如下:

      // frameworks/base/graphics/java/android/graphics/drawable/Drawable.java
      /*
       * @return The minimum width suggested by this Drawable. If this Drawable
       *         doesn't have a suggested minimum width, 0 is returned.
       */
      public int getMinimumWidth() {
          final int intrinsicWidth = getIntrinsicWidth();
          return intrinsicWidth > 0 ? intrinsicWidth : 0;
      }
      
      // 不同子類可實(shí)現(xiàn)具體大小
      public int getIntrinsicWidth() {
          return -1;
      }
      
      

      Drawable.getMinimumWidth()就是返回 Drawable 的原始寬度署浩,如果該 Drawable 未設(shè)置寬度揉燃,則返回0

      綜上瑰抵,getSuggestedMinimumWidth()/getSuggestedMinimumHeight()其實(shí)就是用于獲取 View 的最小測(cè)量寬/高你雌,其具體邏輯為:當(dāng) View 沒有設(shè)置背景時(shí)器联,其最小寬/高為android:minWidth/android:mMinHeight所指定的值二汛,當(dāng) View 設(shè)置了背景時(shí),其最小測(cè)量寬/高為android:minWidth/android:minHeight與其背景圖片寬/高的較大值拨拓。

      簡(jiǎn)而言之肴颊,View 的最小測(cè)量寬/高為android:minWidth/android:minHeight和其背景寬/高之間的較大值。

    2. 通過getDefaultSize(...)獲取到 View 的默認(rèn)測(cè)量寬/高渣磷,具體獲取過程如下所示:

      // frameworks/base/core/java/android/view/View.java
      public static int getDefaultSize(int size, int measureSpec) {
          int result = size;
          // 測(cè)量模式
          int specMode = MeasureSpec.getMode(measureSpec);
          // 測(cè)量大小
          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;
      }
      
      

      此處的size是通過getSuggestedMinimumWidth()/getSuggestedMinimumHeight()方法獲取得到系統(tǒng)建議 View 的最小測(cè)量寬/高婿着。

      參數(shù)measureSpec是經(jīng)由View.measure(...)->View.onMeasure(...)->View.getDefaultSize(...)調(diào)用鏈傳遞進(jìn)來的,表示的是當(dāng)前 View 的MeasureSpec醋界。

      getDefaultSize(...)內(nèi)部首先會(huì)獲取 View 的測(cè)量模式和測(cè)量大小竟宋,然后當(dāng) View 的測(cè)量模式為UNSPECIFIED時(shí),也即未限制 View 的大小形纺,因此此時(shí) View 的大小就是其原生大星鹣馈(也即android:minWidth或背景圖片大小)逐样,當(dāng) View 的測(cè)量模式為AT_MOSTEXACTLY時(shí)蜗字,此時(shí)不對(duì)這兩種模式進(jìn)行區(qū)分打肝,一律將 View 的大小設(shè)置為測(cè)量大小(即 SpecSize)挪捕。
      :實(shí)際上粗梭,這里可以看到,默認(rèn)情況下级零,View 不區(qū)分AT_MOSTEXACTLY措嵌,也即,當(dāng)自定義 View 時(shí)耗美,LayoutParams.WRAP_CONTENTLayoutParams.MATCH_PARENT效果是一樣的鸟顺,均為MATCH_PARENT的效果,原因是 子View 的MeasureSpec是由父容器傳遞進(jìn)來的亥贸,父容器是通過ViewGroup#getChildMeasureSpec(...)方法獲取得到 子View 的MeasureSpec躬窜,在該方法內(nèi)部,子View 的測(cè)量模式無論是AT_MOST或是EXACTLY炕置,其測(cè)量大小都為父容器大腥侔ぁ(確定的說,是父容器剩余空間大衅犹)默垄,因此其效果就等同于MATCH_PARENT,具體源碼詳情分析請(qǐng)參考后文甚纲。

      總之口锭,一般自定義 View 時(shí),都需要覆寫onMeasure(...)介杆,并為其LayoutParams.WRAP_CONTENT設(shè)置一個(gè)默認(rèn)大小鹃操,如下所示:

       @Override
       protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
      
           // 先進(jìn)行默認(rèn)測(cè)量
           super.onMeasure(widthMeasureSpec, heightMeasureSpec);
      
           // 默認(rèn)大小依據(jù)自己靈活配置,這里為 400px
           int defaultSize = 400;
      
           // 獲取默認(rèn)測(cè)量寬/高
           int width = this.getMeasuredWidth();
           int height = this.getMeasuredHeight();
      
           // 獲取 View 的布局參數(shù)
           ViewGroup.LayoutParams lp = this.getLayoutParams();
      
           // 寬度為自適應(yīng)春哨,則設(shè)置一個(gè)默認(rèn)大小
           if(lp.width == ViewGroup.LayoutParams.WARP_CONTENT) {
               width = defaultSize;
           }
      
           // 高度為自適應(yīng)荆隘,則設(shè)置一個(gè)默認(rèn)大小
           if(lp.height == ViewGroup.LayoutParams.WARP_CONTENT) {
               height = defaultSize;
           }
      
           this.setMeasuredDimension(width, height);
       }
      
      
    3. 獲取到 View 的測(cè)量寬/高后,通過setMeasuredDimension(...)記錄 View 的測(cè)量寬/高:

      // frameworks/base/core/java/android/view/View.java
      protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
          ...
          setMeasuredDimensionRaw(measuredWidth, measuredHeight);
      }
      
      // 記錄測(cè)量寬/高
      private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
          mMeasuredWidth = measuredWidth;
          mMeasuredHeight = measuredHeight;
      
          mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
      }
      
      

      setMeasuredDimension(...)其實(shí)就是將 View 的最終測(cè)量寬/高設(shè)置到View.mMeasuredWidth/View.mMeasuredHeight屬性中赴背,完成測(cè)量過程椰拒。

  • ViewGroup測(cè)量ViewGroup是一個(gè)抽象類,其繼承于View

    public abstract class ViewGroup extends View implements ViewParent, ViewManager {...}
    
    

    ViewGroup的測(cè)量過程也是由View.measure(...)負(fù)責(zé)凰荚,因此實(shí)際負(fù)責(zé)測(cè)量的是ViewGroup.onMeasure(...)方法燃观,但是由于ViewGroup的作用是用于容納子View,如果想測(cè)量ViewGroup便瑟,則必須先測(cè)量其子View缆毁,而又由于不同的ViewGroup有不同的布局特性,因此無法抽象出一套標(biāo)準(zhǔn)的測(cè)量流程胳徽,所以ViewGroup本身沒有覆寫onMeasure(...)方法(交由具體自定義ViewGroup覆寫)积锅,但是它提供了一些測(cè)量子View 的輔助方法爽彤,比如:measureChildren(...)measureChildrenWithMargins(...)缚陷、measureChild(...)适篙、getChildMeasureSpec(...)等等,自定義ViewGroup可借助這些輔助方法箫爷,在onMeasure(...)中完成子View 的測(cè)量嚷节,然后最終才能完成自己的測(cè)量。

    我們隨便選擇一個(gè)輔助方法虎锚,比如ViewGroup#measureChildWithMargins(...)硫痰,查看其源碼:

    // android/view/ViewGroup.java
    protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        // 獲取 子View 的 LayoutParams
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    
        // 獲取 子View 的 MeasureSpec
        // 父容器已使用的空間為:自身已使用空間 + 自身的 padding + 子View的 margin
        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);
    
        // 測(cè)量子View
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
    
    

    代碼非常簡(jiǎn)潔易懂,其核心就是先獲取得到 子View 的MeasureSpecgetChildMeasureSpec(...))窜护,然后就可以對(duì) 子View 進(jìn)行測(cè)量(child.measure(...))效斑。

    View#measure(...)的測(cè)量詳情上述我們已經(jīng)介紹過了,這里我們主要來看下ViewGroup#getChildMeasureSpec(...)獲取 子View 測(cè)量規(guī)格的具體過程:

    // android/view/ViewGroup.java
    /**
     *
     * @param spec 父容器的 MeasureSpec
     * @param padding 父容器已使用的空間(比如:父View自身的 padding + 子View的 margin)
     * @param childDimension 子View的 LayoutParams
     * @return 子View 的 MeasureSpec
     */
    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        // 當(dāng)前View(即父容器)的測(cè)量模式
        int specMode = MeasureSpec.getMode(spec);
        // 父容器的測(cè)量大小
        int specSize = MeasureSpec.getSize(spec);
    
        // 父容器剩余可用空間
        int size = Math.max(0, specSize - padding);
    
        // 子View 最終測(cè)量大小
        int resultSize = 0;
        // 子View 最終測(cè)量模式
        int resultMode = 0;
    
        switch (specMode) {
        // Parent has imposed an exact size on us
        // 父容器大小已確定
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) { 
            // 子View 設(shè)置了具體大兄恪(精確數(shù)值)
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) { 
            // 子View 大小撐滿父容器
                // Child wants to be our size. So be it.
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // 子View 自適應(yīng)內(nèi)容大小
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;
    
        // Parent has imposed a maximum size on us
        // 父容器自適應(yīng)內(nèi)容大小
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;
    
        // Parent asked to see how big we want to be
        // 父容器大小無限制
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                // Child wants a specific size... let him have it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        // 子View 的最終測(cè)量規(guī)格
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }
    
    

    getChildMeasureSpec(...)其實(shí)就是ViewGroup對(duì)其內(nèi)部 子View 的默認(rèn)測(cè)量過程缓屠,其核心邏輯為:

    1. 如果父容器的測(cè)量模式為EXACTLY:即父容器測(cè)量大小是確切的,且其剩余空間精確為size护侮,此時(shí):

      • 如果 子View 的LayoutParams為具體數(shù)值:則表示 子View 已明確設(shè)置了具體大小敌完,因此,此時(shí) 子View 的測(cè)量大小即為自己設(shè)置的值羊初,即childDimension滨溉,測(cè)量模式為EXACTLY
      • 如果 子View 的LayoutParamsMATCH_PARENT:表示 子View 的大小撐滿父容器长赞,由于父容器是EXACTLY晦攒,即大小已知,因此涧卵,子View 也是大小已知勤家,故其測(cè)量模式為EXACTLY,且其測(cè)量大小就是父容器剩余空間大小柳恐,具體為size
      • 如果 子View 的LayoutParamsWRAP_CONTENT:表示 子View 自適應(yīng)內(nèi)容大小热幔,但是其尺寸最大不能超過父容器剩余空間乐设,因此其測(cè)量模式為AT_MOST,測(cè)量大小為父容器剩余空間size绎巨。
    2. 如果父容器的測(cè)量模式為AT_MOST:即父容器自適應(yīng)其內(nèi)容大小近尚,也即父容器大小不確定,此時(shí):

      • 如果 子View 的LayoutParams為具體數(shù)值:則表示 子View 已明確設(shè)置了具體大小场勤,因此戈锻,此時(shí) 子View 的測(cè)量大小即為自己設(shè)置的值歼跟,即childDimension,測(cè)量模式為EXACTLY格遭。
      • 如果 子View 的LayoutParamsMATCH_PARENT:表示 子View 的大小撐滿父容器哈街,由于父容器是AT_MOST,即大小未知拒迅,因此骚秦,子View 也是大小未知,即其測(cè)量模式為AT_MOST璧微,且其測(cè)量大小不超過父容器剩余空間大小size作箍。
      • 如果 子View 的LayoutParamsWRAP_CONTENT:表示 子View 自適應(yīng)內(nèi)容大小,但是其尺寸最大不能超過父容器剩余空間前硫,因此其測(cè)量模式為AT_MOST胞得,測(cè)量大小為父容器剩余空間size
    1. 如果父容器的測(cè)量模式為UNSPECIFIED:即父容器大小無限制屹电,此時(shí):

      • 如果 子View 的LayoutParams為具體數(shù)值:則表示 子View 已明確設(shè)置了具體大小懒震,因此,此時(shí) 子View 的測(cè)量大小即為自己設(shè)置的值嗤详,即childDimension个扰,測(cè)量模式為EXACTLY
      • 如果 子View 的LayoutParamsMATCH_PARENT:表示 子View 的大小撐滿父容器葱色,由于父容器大小無限制递宅,因此,子View 的大小也是無限制的苍狰,所以办龄,子View 的測(cè)量模式為UNSPECIFIED,測(cè)量大小未知淋昭,通常設(shè)置為0俐填,表示無限。
      • 如果 子View 的LayoutParamsWRAP_CONTENT:表示 子View 自適應(yīng)內(nèi)容大小翔忽,由于父容器大小無限制英融,因此,子View 的測(cè)量大小也是無限制的歇式,所以其模式為UNSPECIFIED驶悟,測(cè)量大小無限,通常使用0進(jìn)行表示材失。

    上述的邏輯總結(jié)如下圖所示:(:圖片來源于互聯(lián)網(wǎng)痕鳍,侵刪)

    image

    :前面我們一直強(qiáng)調(diào):子View 的MeasureSpec是由其LayoutParams和父容器的MeasureSpec共同約束構(gòu)造而成,其實(shí)這部分邏輯就是ViewGroup#getChildMeasureSpec(...)方法負(fù)責(zé)的,可以很清晰看到笼呆,子View 的MeasureSpec就是在父容器MeasureSpec約束下熊响,與其自身LayoutParams共同協(xié)商決定的。

綜上诗赌,無論是對(duì)View的測(cè)量還是ViewGroup的測(cè)量汗茄,都是由View#measure(int widthMeasureSpec, int heightMeasureSpec)方法負(fù)責(zé),然后真正執(zhí)行 View 測(cè)量的是 View 的onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法境肾。

具體來說剔难,View直接在onMeasure(...)中測(cè)量并設(shè)置自己的最終測(cè)量寬/高。在默認(rèn)測(cè)量情況下奥喻,View的測(cè)量寬/高由其父容器的MeasureSpec和自身的LayoutParams共同決定偶宫,當(dāng)View自身的測(cè)量模式為LayoutParams.UNSPECIFIED時(shí),其測(cè)量寬/高為android:minWidth/android:minHeight和其背景寬/高之間的較大值环鲤,其余情況皆為自身MeasureSpec指定的測(cè)量尺寸纯趋。

而對(duì)于ViewGroup來說,由于布局特性的豐富性冷离,只能自己手動(dòng)覆寫onMeasure(...)方法吵冒,實(shí)現(xiàn)自定義測(cè)量過程,但是總的思想都是先測(cè)量 子View 大小西剥,最終才能確定自己的測(cè)量大小痹栖。

layout 流程

當(dāng)確定了 View 的測(cè)量大小后,接下來就可以來確定 View 的布局位置了瞭空,也即將 View 放置到屏幕具體哪個(gè)位置揪阿。

View layout

View 的布局過程由View#layout(...)負(fù)責(zé),其源碼如下:

// android/view/View.java
/**
 * @param l Left position, relative to parent
 * @param t Top position, relative to parent
 * @param r Right position, relative to parent
 * @param b Bottom position, relative to parent
 */
@SuppressWarnings({"unchecked"})
public void layout(int l, int t, int r, int b) {
    ...
    setFrame(l, t, r, b);
    ...
    onLayout(changed, l, t, r, b);
    ...
}

View#layout(...)主要就做了兩件事:

  1. setFrame(...):首先通過View#setFrame(...)來確定自己的布局位置咆畏,其源碼如下:

    // android/view/View.java
    protected boolean setFrame(int left, int top, int right, int bottom) {
        ...
        // Invalidate our old position
        invalidate(sizeChanged);
    
        mLeft = left;
        mTop = top;
        mRight = right;
        mBottom = bottom;
    }
    
    

    setFrame(...)其實(shí)就是更新記錄 View 的四個(gè)頂點(diǎn)位置南捂,這樣 View 在父容器中的坐標(biāo)位置就確定了。

  2. onLayout(...)setFrame(...)是用于確定 View 自身的布局位置旧找,而onLayout(...)主要用于確定 子View 的布局位置:

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

    由于 View 不包含子組件溺健,因此其onLayout是一個(gè)空實(shí)現(xiàn)。

ViewGroup layout

ViewGroup 的布局流程由ViewGroup#layout(...)負(fù)責(zé)钮蛛,其源碼如下:

// android/view/ViewGroup.java
@UiThread
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
    ...
    @Override
    public final void layout(int l, int t, int r, int b) {
        ...
        super.layout(l, t, r, b);
        ...
    }

可以看到鞭缭,ViewGroup#layout(...)最終也是通過View#layout(...)完成自身的布局過程,一個(gè)注意的點(diǎn)是愿卒,ViewGroup#layout(...)是一個(gè)final方法缚去,因此子類無法覆寫該方法,主要是ViewGroup#layout(...)方法內(nèi)部對(duì)子視圖動(dòng)畫效果進(jìn)行了相關(guān)設(shè)置琼开。

由于ViewGroup#layout(...)內(nèi)部最終調(diào)用的還是View#layout(...),因此枕荞,ViewGroup#onLayout(...)就會(huì)得到回調(diào)柜候,用于處理 子View 的布局放置搞动,其源碼如下:

// android/view/ViewGroup.java
@Override
protected abstract void onLayout(boolean changed,
        int l, int t, int r, int b);

由于不同的ViewGroup,其布局特性不同渣刷,因此ViewGroup#onLayout(...)是一個(gè)抽象方法鹦肿,交由ViewGroup子類依據(jù)自己的布局特性,擺放其 子View 的位置辅柴。

draw 流程

當(dāng) View 的測(cè)量大小箩溃,布局位置都確定后,就可以最終將該 View 繪制到屏幕上了碌嘀。

View 的繪制過程由View#draw(...)方法負(fù)責(zé)涣旨,其源碼如下:

// android/view/View.java
public void draw(Canvas canvas) {
    ...
    /*
     * Draw traversal performs several drawing steps which must be executed
     * in the appropriate order:
     *
     *      1\. Draw the background
     *      2\. If necessary, save the canvas' layers to prepare for fading
     *      3\. Draw view's content
     *      4\. Draw children
     *      5\. If necessary, draw the fading edges and restore layers
     *      6\. Draw decorations (scrollbars for instance)
     */

    // Step 1, draw the background, if needed
    drawBackground(canvas);

    // skip step 2 & 5 if possible (common case)
    ...
    // Step 2, save the canvas' layers
    if (drawTop) {
        canvas.saveLayer(left, top, right, top + length, null, flags);
    }

    if (drawBottom) {
        canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
    }

    if (drawLeft) {
        canvas.saveLayer(left, top, left + length, bottom, null, flags);
    }

    if (drawRight) {
        canvas.saveLayer(right - length, top, right, bottom, null, flags);
    }
    ...
    // Step 3, draw the content
    if (!dirtyOpaque) onDraw(canvas);

    // Step 4, draw the children
    dispatchDraw(canvas);

    // Step 5, draw the fade effect and restore layers
    ...
    if (drawTop) {
        ...
        canvas.drawRect(left, top, right, top + length, p);
    }

    if (drawBottom) {
        ...
        canvas.drawRect(left, bottom - length, right, bottom, p);
    }

    if (drawLeft) {
        ...
        canvas.drawRect(left, top, left + length, bottom, p);
    }

    if (drawRight) {
        ...
        canvas.drawRect(right - length, top, right, bottom, p);
    }
    ...
    // Step 6, draw decorations (foreground, scrollbars)
    onDrawForeground(canvas);
}

其實(shí)注釋已經(jīng)寫的很清楚了,View#draw(...)主要做了以下 6 件事:

  1. 繪制背景:drawBackground(...)

  2. 如果有必要的話股冗,保存畫布圖層:Canvas.saveLayer(...)

  3. 繪制自己onDraw(...)霹陡,其源碼如下:

    // android/view/View.java
    protected void onDraw(Canvas canvas) {
    }
    
    

    View#onDraw(...)是一個(gè)空方法,因?yàn)槊總€(gè) View 的繪制都是不同的止状,自定義 View 時(shí)烹棉,通常會(huì)覆寫該方法,手動(dòng)繪制該 View 內(nèi)容怯疤。

  4. 繪制子ViewdispatchDraw(...)浆洗,其源碼如下:

    // android/view/View.java
    protected void dispatchDraw(Canvas canvas) {
    }
    
    

    由于 View 沒有子元素,因此其dispatchDraw是一個(gè)空實(shí)現(xiàn)集峦。

    查看下ViewGroup#dispatchDraw(...)伏社,其源碼如下:

    // android/view/ViewGroup.java
    @Override
    protected void dispatchDraw(Canvas canvas) {
        ...
        final int childrenCount = mChildrenCount;
        final View[] children = mChildren;
        ...
        for (int i = 0; i < childrenCount; i++) {
            ...
            more |= drawChild(canvas, child, drawingTime);
            ...
        }
        ...
    }
    
    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        return child.draw(canvas, this, drawingTime);
    }
    
    

    可以看到,其內(nèi)部主要就是遍歷子View少梁,最后通過child.draw(...)讓子View自己進(jìn)行繪制洛口。

  5. 如果有必要的話,繪制淡化效果并恢復(fù)圖層:Canvas.drawRect(...)

  6. 繪制裝飾:onDrawForeground(...)凯沪,其源碼如下:

    // android/view/View.java
    public void onDrawForeground(Canvas canvas) {
        onDrawScrollIndicators(canvas);
        onDrawScrollBars(canvas);
    
        final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
        ...
        foreground.draw(canvas);
        }
    }
    
    

    其實(shí)主要就是繪制滾動(dòng)條第焰,前景圖片等視圖相關(guān)的裝飾。

繪制起始流程
我們知道妨马,在Activity啟動(dòng)過程中挺举,會(huì)調(diào)用到ActivityThread.handleResumeActivity(...),該方法就是 View 視圖繪制的起始之處:

// frameworks/base/core/java/android/app/ActivityThread.java
final void handleResumeActivity(IBinder token,
       boolean clearHide, boolean isForward, boolean reallyResume) {
   ...
   // 回調(diào) Activity.onResume() 方法
   ActivityClientRecord r = performResumeActivity(token, clearHide);
   ...
   // 獲取當(dāng)前 Activity 實(shí)例
   final Activity a = r.activity;
   ...
   // 此處的 window 為與 Activity 綁定的 PhoneWindow烘跺,即 Activity.mWindow
   r.window = r.activity.getWindow();
   // PhoneWindow 綁定的頂層視圖:DecorView
   View decor = r.window.getDecorView();
   decor.setVisibility(View.INVISIBLE);
   // 獲取與 Activity 綁定的 WindowManager湘纵,實(shí)際上是 PhoneWindow 的 WindowManager
   ViewManager wm = a.getWindowManager();
   WindowManager.LayoutParams l = r.window.getAttributes();
   ...
   // 添加 DecorView 到 PhoneWindow 上(相當(dāng)于設(shè)置 Activity 根視圖)
   wm.addView(decor, l);
   ...
}

可以看到,ActivityThread.handleResumeActivity(...)主要就是獲取到當(dāng)前Activity綁定的ViewManager滤淳,最后調(diào)用ViewManager.addView(...)方法將DecorView設(shè)置到PhoneWindow上梧喷,也即設(shè)置到當(dāng)前Activity上。ViewManager是一個(gè)接口,WindowManager繼承ViewManager铺敌,而WindowManagerImpl實(shí)現(xiàn)了接口WindowManager汇歹,此處的ViewManager.addView(...)實(shí)際上調(diào)用的是WindowManagerImpl.addView(...),源碼如下所示:

// frameworks/base/core/java/android/view/WindowManagerImpl.java
public final class WindowManagerImpl implements WindowManager {
   private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
   ...
   @Override
   public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
       applyDefaultToken(params);
       mGlobal.addView(view, params, mDisplay, mParentWindow);
   }
   ...
}
WindowManagerImpl.addView(...)內(nèi)部轉(zhuǎn)發(fā)到WindowManagerGlobal.addView(...):

// frameworks/base/core/java/android/view/WindowManagerGlobal.java
public final class WindowManagerGlobal {
   ...
   public void addView(View view, ViewGroup.LayoutParams params,
           Display display, Window parentWindow) {
       ...
       ViewRootImpl root;
       ...
       // 實(shí)例化一個(gè) ViewRootImpl
       root = new ViewRootImpl(view.getContext(), display);
       ...
       // 將 ViewRootImpl 與 DecorView 關(guān)聯(lián)到一起
       root.setView(view, wparams, panelParentView);
       ...
   }
   ...
}

在WindowManagerGlobal.addView(...)內(nèi)部偿凭,會(huì)創(chuàng)建一個(gè)ViewRootImpl實(shí)例产弹,然后調(diào)用ViewRootImpl.setView(...)將ViewRootImpl與DecorView關(guān)聯(lián)到一起:

// frameworks/base/core/java/android/view/ViewRootImpl.java
public final class ViewRootImpl implements ViewParent,
       View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
   ...
   public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
       ...
       // 將 DecorView 綁定到 ViewRootImpl.mView 屬性上
       mView = view;
       ...
       mWindowAttributes.copyFrom(attrs);
       ...
       // Schedule the first layout -before- adding to the window
       // manager, to make sure we do the relayout before receiving
       // any other events from the system.
       requestLayout();
       ...
   }
   ...
   @Override
   public void requestLayout() {
       if (!mHandlingLayoutInLayoutRequest) {
           // 檢查是否處于主線程
           checkThread();
           ...
           scheduleTraversals();
       }
   }
   ...
}

ViewRootImpl.setView(...)內(nèi)部首先關(guān)聯(lián)了傳遞過來的DecorView(通過屬性mView指向DecorView即可建立關(guān)聯(lián)),然后最終調(diào)用requestLayout()弯囊,而requestLayout()內(nèi)部又會(huì)調(diào)用方法scheduleTraversals():

// frameworks/base/core/java/android/view/ViewRootImpl.java
public final class ViewRootImpl implements ViewParent,
       View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
   ...
   Choreographer mChoreographer;
   ...
   final class TraversalRunnable implements Runnable {
       @Override
       public void run() {
           // 開始執(zhí)行繪制
           doTraversal();
       }
   }
   final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
   ...
   void scheduleTraversals() {
       if (!mTraversalScheduled) { // 同一幀內(nèi)不會(huì)多次調(diào)用遍歷
           mTraversalScheduled = true;
           // 發(fā)送一個(gè)同步屏障
           mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
           // 將 UI 繪制任務(wù)發(fā)送到 Choreographer痰哨,回調(diào)觸發(fā) mTraversalRunnable,執(zhí)行繪制操作
           mChoreographer.postCallback(
                   Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
           ...
       }
   }
   ...
   void doTraversal() {
       ...
       performTraversals();
       ...
   }
   ...
}

ViewRootImpl.scheduleTraversals()內(nèi)部主要做了兩件事:

調(diào)用MessageQueue.postSyncBarrier()方法發(fā)送一個(gè)同步屏障匾嘱,同步屏障可以攔截Looper對(duì)同步消息的獲取與分發(fā),即加入同步屏障后奄毡,此時(shí)Looper只會(huì)獲取和處理異步消息折欠,如果沒有異步消息,則進(jìn)入阻塞狀態(tài)吼过。
通過Choreographer.postCallback(...)發(fā)送一個(gè)Choreographer.CALLBACK_TRAVERSAL的異步視圖渲染消息锐秦。因?yàn)榍懊嬉呀?jīng)發(fā)送了一個(gè)同步屏障,因此此處的視圖繪制渲染消息會(huì)優(yōu)先被處理盗忱。
Choreographer.postCallback(...)會(huì)申請(qǐng)一次 VSYNC 中斷信號(hào)酱床,當(dāng) VSYNC 信號(hào)到達(dá)時(shí),便會(huì)回調(diào)Choreographer.doFrame(...)方法趟佃,內(nèi)部會(huì)觸發(fā)已經(jīng)添加的回調(diào)任務(wù)扇谣,Choreographer的回調(diào)任務(wù)有以下四種類型:

// 回調(diào) INPUT 任務(wù)
doCallbacks(Choreographer.CALLBACK_INPUT, mframeTimeNanos);
// 回調(diào) ANIMATION
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
// 回調(diào) View 繪制任務(wù) TRAVERSAL
doCallbacks(Choreographer,CALLBACK_TRAVERSAL, frameTimeNanos);
// API Level 23 新增闲昭,COMMIT 
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);

因此罐寨,ViewRootImpl.scheduleTraversals(...)內(nèi)部通過

<meta charset="utf-8">

mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null)發(fā)送的異步視圖渲染消息就會(huì)得到回調(diào),即回調(diào)mTraversalRunnable.run()方法序矩,最終會(huì)執(zhí)行doTraversal()方法鸯绿,而doTraversal()內(nèi)部又會(huì)調(diào)用performTraversals()方法,該方法才是真正開始執(zhí)行 View 繪制流程的地方簸淀,其源碼如下所示:

// frameworks/base/core/java/android/view/ViewRootImpl.java
public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
    ...
    private void performTraversals() {
        int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
        int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
        ...
        // Ask host how big it wants to be
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
        ...
        performLayout(lp, desiredWindowWidth, desiredWindowHeight);
        ...
        performDraw();
        ...
    }
    ...
}

綜上瓶蝴,performTraversals()會(huì)依次調(diào)用performMeasure(...)performLayout(...)performDraw()三個(gè)方法租幕,這三個(gè)方法會(huì)依次完成頂層View(即DecorView)的測(cè)量(measure)舷手、布局(layout)和繪制(draw)流程,具體詳情請(qǐng)參考后文劲绪。

到此男窟,我們才真正進(jìn)入 View 繪制流程盆赤,總結(jié)一下上述流程,如下圖所示:

image

performMeasure

書接前文蝎宇,我們知道弟劲,真正開始 View 繪制流程是ViewRootImpl.performTraversals()祷安,該方法內(nèi)部首先進(jìn)行的是performMeasure(...)流程:

// frameworks/base/core/java/android/view/ViewRootImpl.java
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
    try {
        // 調(diào)用 DecorView.measure(...) 
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

此處的mView其實(shí)就是DecorView姥芥,其賦值指向在ViewRootImpl.setView(...)中進(jìn)行,可以看到汇鞭,performMeasure(...)實(shí)際調(diào)用的是DecorView.measure(...)凉唐,所以最終會(huì)回調(diào)DecorView#onMeasure(...)方法,其源碼如下:

// frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
public class PhoneWindow extends Window implements MenuBuilder.Callback {
    ...
    private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
        ...
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            ...
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            ...
        }
    ...
}

可以看到霍骄,DecorView#onMeasure(...)內(nèi)部將測(cè)量過程交由其父類台囱,即FrameLayout進(jìn)行處理,那我們看下FrameLayout#onMeasure(...)源碼:

// frameworks/base/core/java/android/widget/FrameLayout.java
public class FrameLayout extends ViewGroup {
    ...
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // 獲取 子View 數(shù)量 
        int count = getChildCount();
        ...
        // 最大高度
        int maxHeight = 0;
        // 最大寬度
        int maxWidth = 0;
        int childState = 0;

        for (int i = 0; i < count; i++) {
            // 獲取 子View
            final View child = getChildAt(i);
            // 只對(duì)可見的 子View 進(jìn)行測(cè)量
            if (mMeasureAllChildren || child.getVisibility() != GONE) {
                // 測(cè)量子View
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                // 獲取 子View 的布局參數(shù)
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                // 獲取當(dāng)前子View的寬度读整,包含其外邊距簿训,記錄子View的最大寬度
                maxWidth = Math.max(maxWidth,
                        child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                // 記錄子View的最大高度
                maxHeight = Math.max(maxHeight,
                        child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
                ...
            }
        }

        // Account for padding too
        // 最大寬度包含前景偏移量:padding
        maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
        // 最大高度包含前景偏移量:padding
        maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();

        // Check against our minimum height and width
        // 比較子View 和 系統(tǒng)建議的 子View 最小高度,獲取兩者中的較大值
        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
        // 比較子View 和 系統(tǒng)建議的 子View 最小寬度米间,獲取兩者中的較大值
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

        // Check against our foreground's minimum height and width
        final Drawable drawable = getForeground();
        if (drawable != null) {
            // 子View 高度和 前景圖片高度比較强品,記錄其中較大值
            maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
            // 子View 高度和 前景圖片寬度比較,記錄其中較大值
            maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
        }

        // 記錄測(cè)量結(jié)果
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                resolveSizeAndState(maxHeight, heightMeasureSpec,
                        childState << MEASURED_HEIGHT_STATE_SHIFT));

        ...
    }
    ...
}

FrameLayout的布局特性為:所有 子View 層疊在一起屈糊,所以FrameLayout的測(cè)量寬/高就是其所有 子View 中最大的寬和高的榛,因此FrameLayout#onMeasure(...)的核心邏輯就是遍歷其所有子View,然后通過measureChildWithMargins(...)(該方法前面內(nèi)容已詳細(xì)介紹)測(cè)量子View逻锐,然后就可以獲取 子View 的寬/高夫晌,記錄其中最大的寬/高值,作為自己的測(cè)量寬/高昧诱。

經(jīng)過以上步驟晓淀,DecorView的測(cè)量就已經(jīng)完成了。

綜上盏档,ViewRootImpl#performMeasure(...)其實(shí)就是對(duì)DecorView的測(cè)量過程(DecorView#measure(...))凶掰,DecorView是一個(gè)FrameLayout,其測(cè)量過程主要由FrameLayout#onMeasure(...)負(fù)責(zé)妆丘,內(nèi)部主要測(cè)量邏輯是先遍歷所有子View锄俄,讓 子View 先自己進(jìn)行測(cè)量(child.measure(...))勺拣,然后就可以獲取 子View 的測(cè)量大小毅戈,記錄所有 子View 中占比最大的測(cè)量寬/高,作為自己的最終測(cè)量大小。

<meta charset="utf-8">

performLayout

ViewRootImpl#performMeasure(...)完成對(duì)DecorView的測(cè)量后,接下來執(zhí)行的是ViewRootImpl#performLayout(...),其源碼如下:

// frameworks/base/core/java/android/view/ViewRootImpl.java
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
        int desiredWindowHeight) {
    ...
    // cache mView since it is used so much below...
    final View host = mView;
    ...
    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    ...
}

其中,參數(shù)lpwidthheight均為MATCH_PARENT宾巍,desiredWindowWidthdesiredWindowHeight為屏幕寬/高蜀漆,mViewDecorView

所以鲜侥,performLayout(...)內(nèi)部其實(shí)就是調(diào)用DecorView#layout(...)狐粱,前面 layout 流程中介紹過,ViewGroup#layout(...)內(nèi)部最終會(huì)通過View#layout(...)進(jìn)行布局判莉,而View#layout(...)內(nèi)部最終通過View#setFrame(...)方法記錄四個(gè)頂點(diǎn)位置膛檀,這樣DecorView自己的布局位置就已確定了,即host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight())互站。

確定了DecorView自身的布局位置后僵缺,接下來就是要布局其 子View 了踩叭,因此磕潮,這里最終回調(diào)的是DecorView#onLayout(...)方法容贝,其源碼如下所示:

// frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
public class PhoneWindow extends Window implements MenuBuilder.Callback {
    ...
    private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
        ...
        @Override
        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
            super.onLayout(changed, left, top, right, bottom);
            ...
        }
    ...
}

DecorView#onLayout(...)內(nèi)部轉(zhuǎn)交給FrameLayout#onLayout(...)進(jìn)行 子View 布局操作焕参,其源碼如下:

// frameworks/base/core/java/android/widget/FrameLayout.java
public class FrameLayout extends ViewGroup {
    ...
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        // 布局子View
        layoutChildren(left, top, right, bottom, false /* no force left gravity */);
    }

    void layoutChildren(int left, int top, int right, int bottom,
                                  boolean forceLeftGravity) {
        // 獲取 子View 數(shù)量
        final int count = getChildCount();

        // 左邊可放置起始點(diǎn)坐標(biāo)
        final int parentLeft = getPaddingLeftWithForeground();
        // 右邊可放置終點(diǎn)坐標(biāo)
        final int parentRight = right - left - getPaddingRightWithForeground();

        // 頂部可放置起始點(diǎn)坐標(biāo)
        final int parentTop = getPaddingTopWithForeground();
        // 底部可放置終點(diǎn)坐標(biāo)
        final int parentBottom = bottom - top - getPaddingBottomWithForeground();

        // 遍歷 子View
        for (int i = 0; i < count; i++) {
            // 獲取 子View
            final View child = getChildAt(i);
            // 不放置狀態(tài)為 GONE 的子View
            if (child.getVisibility() != GONE) {
                // 獲取 子View 布局參數(shù)
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();

                // 獲取 子View 測(cè)量寬/高
                final int width = child.getMeasuredWidth();
                final int height = child.getMeasuredHeight();

                // 當(dāng)前 子View 的布局左邊界
                int childLeft;
                // 當(dāng)前 子View 的布局右邊界
                int childTop;
                ...
                child.layout(childLeft, childTop, childLeft + width, childTop + height);
            }
        }
    }
    ...
}

FrameLayout#onLayout(...)內(nèi)部是通過FrameLayout#layoutChildren(...)進(jìn)行 子View 的布局操作,其主要邏輯就是遍歷所有 子View潦嘶,計(jì)算得到 子View 的四個(gè)頂點(diǎn)位置坐標(biāo)涩嚣,最后將結(jié)果傳遞給child.layout(...),讓 子View 記錄自己在父容器中的布局位置掂僵,完成 子View 的布局過程航厚。

綜上,ViewRootImpl#performLayout(...)就是對(duì)DecorView的布局過程锰蓬,此過程會(huì)遞歸計(jì)算各個(gè) 子View 的布局位置幔睬,調(diào)用 子View 的布局方法,完成各個(gè) 子View 的布局互妓。

performDraw

完成了performMeasure(...)performLayout(...)后溪窒,最后一步就是performDraw(...)過程坤塞,其源碼如下:

// frameworks/base/core/java/android/view/ViewRootImpl.java
private void performDraw() {
    ...
    draw(fullRedrawNeeded);
    ...
}

private void draw(boolean fullRedrawNeeded) {
    ...
    if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
        return;
    }
    ...
}

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
        boolean scalingRequired, Rect dirty) {
    ...
    mView.draw(canvas);
    ...
}

可以看到,ViewRootImpl#performDraw()內(nèi)部會(huì)經(jīng)由ViewRootImpl#draw(...)澈蚌、ViewRootImpl#drawSoftware(...)摹芙,最終執(zhí)行的還是DecorView#draw(...)過程,其源碼如下:

// frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
public class PhoneWindow extends Window implements MenuBuilder.Callback {
    ...
    private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
        @Override
        public void draw(Canvas canvas) {
            super.draw(canvas);

            if (mMenuBackground != null) {
                mMenuBackground.draw(canvas);
            }
        }
    ...
}

由于FrameLayout沒有覆寫draw(...)方法宛瞄,因此浮禾,super.draw(...)最終調(diào)用的是View#draw(...)方法,所以DecorView默認(rèn)采用的就是 View 的繪制方法份汗,具體繪制詳情上文已介紹過了盈电,主要就是對(duì)DecorView的背景、內(nèi)容杯活、子View匆帚、滾動(dòng)條等裝飾視圖進(jìn)行繪制。

至此旁钧,View 繪制的整個(gè)流程已基本介紹完畢吸重。

總結(jié)

View 的繪制主要有以下一些核心內(nèi)容:

  1. 三大流程:View 繪制主要包含如下三大流程:

    • measure:測(cè)量流程,主要負(fù)責(zé)對(duì) View 進(jìn)行測(cè)量歪今,其核心邏輯位于View#measure(...)嚎幸,真正的測(cè)量處理由View#onMeasure(...)負(fù)責(zé)。默認(rèn)的測(cè)量規(guī)則為:如果 View 的布局參數(shù)為LayoutParams.WRAP_CONTENTLayoutParams.MATCH_PARENT寄猩,那么其測(cè)量大小為 SpecSize嫉晶;如果其布局參數(shù)為LayoutParams.UNSPECIFIED,那么其測(cè)量大小為android:minWidth/android:minHeight和其背景之間的較大值田篇。

    自定義View 通常覆寫onMeasure(...)方法替废,在其內(nèi)一般會(huì)對(duì)WRAP_CONTENT預(yù)設(shè)一個(gè)默認(rèn)值,區(qū)分WARP_CONTENTMATCH_PARENT效果斯辰,最終完成自己的測(cè)量寬/高舶担。而ViewGrouponMeasure(...)方法中,通常都是先測(cè)量子View彬呻,收集到相應(yīng)數(shù)據(jù)后衣陶,才能最終測(cè)量自己。

    • layout:布局流程闸氮,主要完成對(duì) View 的位置放置剪况,其核心邏輯位于View#layout(...),該方法內(nèi)部主要通過View#setFrame(...)記錄自己的四個(gè)頂點(diǎn)坐標(biāo)(記錄與對(duì)應(yīng)成員變量中即可)蒲跨,完成自己的位置放置译断,最后會(huì)回調(diào)View#onLayout(...)方法,在其內(nèi)完成對(duì) 子View 的布局放置或悲。

      :不同于 measure 流程首先對(duì) 子View 進(jìn)行測(cè)量孙咪,最后才測(cè)量自己堪唐,layout 流程首先是先定位自己的布局位置,然后才處理放置 子View 的布局位置翎蹈。

    • draw:繪制流程淮菠,就是將 View 繪制到屏幕上,其核心邏輯位于View#draw(...)荤堪,主要就是對(duì) 背景合陵、自身內(nèi)容(onDraw(...)子View(dispatchDraw(...)澄阳、裝飾(滾動(dòng)條拥知、前景等) 進(jìn)行繪制。

      :通常自定義View 覆寫onDraw(...)方法碎赢,完成自己的繪制即可低剔,ViewGroup 一般充當(dāng)容器使用,因此通常無需覆寫onDraw(...)揩抡。

  2. Activity 的根視圖(即DecorView)最終是綁定到ViewRootImpl户侥,具體是由ViewRootImpl#setView(...)進(jìn)行綁定關(guān)聯(lián)的,后續(xù) View 繪制的三大流程都是均有ViewRootImpl負(fù)責(zé)執(zhí)行的峦嗤。

  3. 對(duì) View 的測(cè)量流程中,最關(guān)鍵的一步是求取 View 的MeasureSpec屋摔,View 的MeasureSpec是在其父容器MeasureSpec的約束下烁设,結(jié)合自己的LayoutParams共同測(cè)量得到的,具體的測(cè)量邏輯由ViewGroup#getChildMeasureSpec(...)負(fù)責(zé)钓试。
    DecorViewMeasureSpec取決于自己的LayoutParams和屏幕尺寸装黑,具體的測(cè)量邏輯位于ViewRootImpl#getRootMeasureSpec(...)

最后弓熏,稍微總結(jié)一下 View 繪制的整個(gè)流程:

  1. 首先恋谭,當(dāng) Activity 啟動(dòng)時(shí),會(huì)觸發(fā)調(diào)用到ActivityThread#handleResumeActivity(..)挽鞠,其內(nèi)部會(huì)經(jīng)歷一系列過程疚颊,生成DecorViewViewRootImpl等實(shí)例,最后通過ViewRootImpl#setView(decor,MATCH_PARENT)設(shè)置 Activity 根View信认。

    ViewRootImpl#setView(...)內(nèi)容通過將其成員屬性ViewRootImpl#mView指向DecorView材义,完成兩者之間的關(guān)聯(lián)。

  2. ViewRootImpl成功關(guān)聯(lián)DecorView后嫁赏,其內(nèi)部會(huì)設(shè)置同步屏障并發(fā)送一個(gè)CALLBACK_TRAVERSAL異步渲染消息其掂,在下一次 VSYNC 信號(hào)到來時(shí),CALLBACK_TRAVERSAL就會(huì)得到響應(yīng)潦蝇,從而最終觸發(fā)執(zhí)行ViewRootImpl#performTraversals(...)款熬,真正開始執(zhí)行 View 繪制流程深寥。

  3. ViewRootImpl#performTraversals(...)內(nèi)部會(huì)依次調(diào)用ViewRootImpl#performMeasure(...)ViewRootImpl#performLayout(...)ViewRootImpl#performDraw(...)三大繪制流程贤牛,其中:

    • performMeasure(..):內(nèi)部主要就是對(duì)DecorView執(zhí)行測(cè)量流程:DecorView#measure(...)惋鹅。DecorView是一個(gè)FrameLayout,其布局特性是層疊布局盔夜,所占的空間就是其 子View 占比最大的寬/高负饲,因此其測(cè)量邏輯(onMeasure(...))是先對(duì)所有 子View 進(jìn)行測(cè)量,具體是通過ViewGroup#measureChildWithMargins(...)方法對(duì) 子View 進(jìn)行測(cè)量喂链,子View 測(cè)量完成后返十,記錄最大的寬/高,設(shè)置為自己的測(cè)量大型治ⅰ(通過View#setMeasuredDimension(...))洞坑,如此便完成了DecorView的測(cè)量流程。

    • performLayout(...):內(nèi)部其實(shí)就是調(diào)用DecorView#layout(...)蝇率,如此便完成了DecorView的布局位置迟杂,最后會(huì)回調(diào)DecorView#onLayout(...),負(fù)責(zé) 子View 的布局放置本慕,核心邏輯就是計(jì)算出各個(gè) 子View 的坐標(biāo)位置排拷,最后通過child.layout(...)完成 子View 布局。

    • performDraw():內(nèi)部最終調(diào)用到的是DecorView#draw(...)锅尘,該方法內(nèi)部并未對(duì)繪制流程做任何修改本缠,因此最終執(zhí)行的是View#draw(...)者疤,所以主要就是依次完成對(duì)DecorView背景子View(dispatchDraw(...)視圖裝飾(滾動(dòng)條、前景等) 的繪制阶女。

作者:Whyn
鏈接:http://www.reibang.com/p/ee5d3bb5ab90
來源:簡(jiǎn)書
著作權(quán)歸作者所有蛔屹。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán)刷允,非商業(yè)轉(zhuǎn)載請(qǐng)注明出處割捅。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市璧榄,隨后出現(xiàn)的幾起案子特漩,更是在濱河造成了極大的恐慌,老刑警劉巖犹菱,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拾稳,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡腊脱,警方通過查閱死者的電腦和手機(jī)访得,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人悍抑,你說我怎么就攤上這事鳄炉。” “怎么了搜骡?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵拂盯,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我记靡,道長(zhǎng)谈竿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任摸吠,我火速辦了婚禮空凸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘寸痢。我一直安慰自己呀洲,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布啼止。 她就那樣靜靜地躺著道逗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪献烦。 梳的紋絲不亂的頭發(fā)上滓窍,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音巩那,去河邊找鬼贰您。 笑死,一個(gè)胖子當(dāng)著我的面吹牛拢操,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播舶替,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼令境,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了顾瞪?” 一聲冷哼從身側(cè)響起舔庶,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎陈醒,沒想到半個(gè)月后惕橙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡钉跷,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年弥鹦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡彬坏,死狀恐怖朦促,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情栓始,我是刑警寧澤务冕,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站幻赚,受9級(jí)特大地震影響禀忆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜落恼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一箩退、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧领跛,春花似錦乏德、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至矢棚,卻和暖如春郑什,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蒲肋。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工蘑拯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人兜粘。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓申窘,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親孔轴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子剃法,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 簡(jiǎn)介 我們知道,在 Android 中路鹰,View 繪制主要包含 3 大流程: measure(測(cè)量):主要用于確定...
    Whyn閱讀 4,924評(píng)論 1 14
  • View的繪制和事件處理是兩個(gè)重要的主題贷洲,上一篇《圖解 Android事件分發(fā)機(jī)制》已經(jīng)把事件的分發(fā)機(jī)制講得比較詳...
    Kelin閱讀 119,459評(píng)論 100 845
  • View的加載流程view布局一直貫穿于整個(gè)android應(yīng)用中,不管是activity還是fragment都給我...
    ZEKI安卓學(xué)弟閱讀 263評(píng)論 0 0
  • 標(biāo)簽: Android 源碼解析 View 關(guān)于View的繪制流程晋柱,或者說 View 的工作流程(說繪制流程容易讓...
    koguma閱讀 1,965評(píng)論 1 18
  • 最近重新看了一下任玉剛大佬的《Android 開發(fā)藝術(shù)探索》优构,寫了篇筆記,分享給大家雁竞。 1. ViewRootIm...
    燈不利多閱讀 965評(píng)論 0 5