Android View 的繪制流程

Android 中 Activity 是作為應(yīng)用程序的載體存在耿芹,代表著一個(gè)完整的用戶(hù)界面猩系,提供了一個(gè)窗口來(lái)繪制各種視圖中燥,當(dāng) Activity 啟動(dòng)時(shí),我們會(huì)通過(guò) setContentView 方法來(lái)設(shè)置一個(gè)內(nèi)容視圖吟秩,這個(gè)內(nèi)容視圖就是用戶(hù)看到的界面涵防。


UI 管理系統(tǒng)的層級(jí)

PhoneWindow 是 Android 系統(tǒng)中最基本的窗口系統(tǒng)壮池,每個(gè) Activity 會(huì)創(chuàng)建一個(gè)椰憋。PhoneWindow 是 Activity 和 View 系統(tǒng)交互的借口赔退。DecorView 本質(zhì)上是一個(gè) FrameLayout硕旗,是 Activity 中所有 View 的祖先漆枚。

繪制的整體流程

當(dāng)一個(gè)應(yīng)用啟動(dòng)時(shí)浪读,會(huì)啟動(dòng)一個(gè)主 Activity,Android 系統(tǒng)會(huì)根據(jù) Activity 的布局來(lái)對(duì)它進(jìn)行繪制互订。繪制會(huì)從根視圖 ViewRoot 的 performTraversals() 方法開(kāi)始仰禽,從上到下遍歷整個(gè)視圖樹(shù)吐葵,每個(gè) View 控制負(fù)責(zé)繪制自己温峭,而 ViewGroup 還需要負(fù)責(zé)通知自己的子 View 進(jìn)行繪制操作。視圖操作的過(guò)程可以分為三個(gè)步驟凤藏,分別是測(cè)量(Measure)、布局(Layout)和繪制(Draw)栗菜。performTraversals 方法在類(lèi) ViewRootImpl 內(nèi)疙筹,其核心代碼如下而咆。

  int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
  int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
  ...
  // 測(cè)量
  performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
  ...
  // 布局
  performLayout(lp, mWidth, mHeight);
  ...
  // 繪制
  performDraw();

MeasureSpec

MeasureSpec 表示的是一個(gè) 32 位的整數(shù)值翘盖,它的高 2 位表示測(cè)量模式 SpecMode馍驯,低 30 位表示某種測(cè)量模式下的規(guī)格大小 SpecSize汰瘫。MeasureSpec 是 View 類(lèi)的一個(gè)靜態(tài)內(nèi)部類(lèi)混弥,用來(lái)說(shuō)明應(yīng)該如何測(cè)量這個(gè)View蝗拿。
三種測(cè)量模式蒿涎。

  • UNSPECIFIED:不指定測(cè)量模式仓手,父視圖沒(méi)有限制子視圖的大小玻淑,子視圖可以是想要的任何尺寸补履,通常用于系統(tǒng)內(nèi)部箫锤,應(yīng)用開(kāi)發(fā)中很少使用到驰弄。
  • EXACTLY:精確測(cè)量模式,當(dāng)該視圖的 layout_width 或者 layout_height 指定為具體數(shù)值或者 match_parent 時(shí)生效速客,表示父視圖已經(jīng)決定了子視圖的精確大小,這種模式下 View 的測(cè)量值就是 SpecSize 的值五鲫。
  • AT_MOST:最大值模式溺职,當(dāng)前視圖的 layout_width 或者 layout_height 指定為 wrap_content 時(shí)生效,此時(shí)子視圖的尺寸可以是不超過(guò)父視圖運(yùn)行的最大尺寸的任何尺寸位喂。

對(duì) DecorView 而言浪耘,它的 MeasureSpec 由窗口尺寸和其自身的 LayoutParams 共同決定;對(duì)于普通的 View塑崖,它的 MeasureSpec 由父視圖的 MeasureSpec 和其本身的 LayoutParams 共同決定七冲。

Measure

Measure 用來(lái)計(jì)算 View 的實(shí)際大小。頁(yè)面的測(cè)量流程從 performMeasure 方法開(kāi)始规婆。

  private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    if (mView == null) {
        return;
    }
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
    try {
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
  }

具體操作是分發(fā)給 ViewGroup 的澜躺,由 ViewGroup 在它的 measureChild 方法中傳遞給子 View掘鄙。ViewGroup 通過(guò)遍歷自身所有的子 View饿这,并逐個(gè)調(diào)用子 View 的 measure 方法實(shí)現(xiàn)測(cè)量操作。

  // 遍歷測(cè)量 ViewGroup 中所有的 View
  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];
        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }
    }
  }

  // 測(cè)量某個(gè)指定的 View
  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);
  }

View (ViewGroup) 的 Measure 方法赵抢,最終的測(cè)量是通過(guò)回調(diào) onMeasure 方法實(shí)現(xiàn)的,這個(gè)通常由 View 的特定子類(lèi)自己實(shí)現(xiàn)冒冬,可以通過(guò)重寫(xiě)這個(gè)方法實(shí)現(xiàn)自定義 View摇幻。

  public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    ...
    onMeasure(widthMeasureSpec, heightMeasureSpec);
    ....
  }
  
  // 如果需要自定義測(cè)量,子類(lèi)需重寫(xiě)這個(gè)方法
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
    getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
  }
  
  // 如果 View 沒(méi)有重寫(xiě)onMeasure 方法帜矾,默認(rèn)會(huì)直接調(diào)用 getDefaultSize
   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;
   }

Layout

Layout 過(guò)程用來(lái)確定 View 在父容器的布局位置灭衷,他是父容器獲取子 View 的位置參數(shù)后,調(diào)用子 View 的 layout 方法并將位置參數(shù)傳入實(shí)現(xiàn)的。ViewRootImpl 的 performLayout 代碼如下由缆。

  private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
    ...
    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    ...
  }

View 的 layout 方法代碼舔箭。

  public void layout(int l, int t, int r, int b) {
    onLayout(changed, l, t, r, b);
  }

  // 空方法箫章,子類(lèi)如果是 ViewGroup 類(lèi)型桶至,則重寫(xiě)這個(gè)方法,實(shí)現(xiàn) ViewGroup 中所有 View 控件布局
  protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
  }

Draw

Draw 操作用來(lái)將控件繪制出來(lái)飒泻,繪制的流程從 performDraw 方法開(kāi)始史辙。performDraw 方法在類(lèi) ViewRootImpl 內(nèi)耙蔑,其核心代碼如下。

  private void performDraw() {
    boolean canUseAsync = draw(fullRedrawNeeded);
  }

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

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

最終調(diào)用到每個(gè) View 的 draw 方法繪制每個(gè)具體的 View寥院,繪制基本上可以分為六個(gè)步驟塑煎。

  public void draw(Canvas canvas) {
    ...
    // Step 1, draw the background, if needed
    if (!dirtyOpaque) {
      drawBackground(canvas);
    }
    ...
    // Step 2, save the canvas' layers
    saveCount = canvas.getSaveCount();
    ...
    // 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
    canvas.drawRect(left, top, right, top + length, p);
    ...
    canvas.restoreToCount(saveCount);
    ...
    // Step 6, draw decorations (foreground, scrollbars)
    onDrawForeground(canvas);
  }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末系枪,一起剝皮案震驚了整個(gè)濱河市衬浑,隨后出現(xiàn)的幾起案子助币,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件耘拇,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)法严,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)呈昔,“玉大人,你說(shuō)我怎么就攤上這事≈朗” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)枝恋。 經(jīng)常有香客問(wèn)我鹃骂,道長(zhǎng),這世上最難降的妖魔是什么痊银? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任抵蚊,我火速辦了婚禮,結(jié)果婚禮上溯革,老公的妹妹穿的比我還像新娘贞绳。我一直安慰自己,他們只是感情好鬓照,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布熔酷。 她就那樣靜靜地躺著,像睡著了一般豺裆。 火紅的嫁衣襯著肌膚如雪拒秘。 梳的紋絲不亂的頭發(fā)上号显,一...
    開(kāi)封第一講書(shū)人閱讀 51,562評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音躺酒,去河邊找鬼押蚤。 笑死,一個(gè)胖子當(dāng)著我的面吹牛羹应,可吹牛的內(nèi)容都是我干的揽碘。 我是一名探鬼主播,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼园匹,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼雳刺!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起裸违,我...
    開(kāi)封第一講書(shū)人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤掖桦,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后供汛,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體枪汪,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年怔昨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了雀久。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡趁舀,死狀恐怖赖捌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情赫编,我是刑警寧澤巡蘸,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布奋隶,位于F島的核電站擂送,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏唯欣。R本人自食惡果不足惜嘹吨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望境氢。 院中可真熱鬧蟀拷,春花似錦、人聲如沸萍聊。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)寿桨。三九已至此衅,卻和暖如春强戴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背挡鞍。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工骑歹, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人墨微。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓道媚,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親翘县。 傳聞我的和親對(duì)象是個(gè)殘疾皇子最域,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355