日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

android 二次绘制 layout,View的三次measure,两次layout和一次draw

發布時間:2023/12/2 60 豆豆
生活随笔 收集整理的這篇文章主要介紹了 android 二次绘制 layout,View的三次measure,两次layout和一次draw 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

我在《Android視圖結構》這篇文章中已經描述了Activity,Window和View在視圖架構方面的關系。前天,我突然想到為什么在setContentView中能夠調用findViewById函數?View那時不是還沒有被加載,測量,布局和繪制啊。然后就搜索了相關的條目,發現findViewById只需要在inflate結束之后就可以。于是,我整理了Activity生命周期和View的生命周期的關系,并再次做一下總結。

為了節約你的時間,本篇文章的主要內容為:

Activity的生命周期和它包含的View的生命周期的關系

Activity初始化時View為什么會三次measure,兩次layout但只一次draw?

ViewRoot的初始化過程

Activity的生命周期和View的生命周期

我通過一個簡單的demo來驗證Activity生命周期方法和View的生命周期方法的調用先后順序。請看如下截圖

在onCreate函數中,我們通常都調用setContentView來設置布局文件,此時Android系統就會讀取布局文件,但是視圖此時并沒有加載到Window上,并且也沒有進入自己的生命周期。

只有等到Activity進入resume狀態時,它所擁有的View才會加載到Window上,并進行測量,布局和繪制。所以我們會發現相關函數的調用順序是:

onResume(Activity)

onPostResume(Activity)

onAttachedToWindow(View)

onMeasure(View)

onMeasure(View)

onLayout(View)

onSizeChanged(View)

onMeasure(View)

onLayout(View)

onDraw(View)

大家會發現,為什么onMeasure先調用了兩次,然后再調用onLayout函數,最后還有在依次調用onMeasure,onLayout和onDraw函數呢?

ViewGroup的measure

大家應該都知道,有些ViewGroup可能會讓自己的子視圖測量兩次。比如說,父視圖先讓每個子視圖自己測量,使用View.MeasureSpec.UNSPECIFIED,然后在根據每個子視圖希望得到的大小不超過父視圖的一些限制,就讓子視圖得到自己希望的大小,否則就用其他尺寸來重新測量子視圖。這一類的視圖有FrameLayout,RelativeLayout等。

在《Android視圖結構》中,我們已經知道Android視圖樹的根節點是DecorView,而它是FrameLayout的子類,所以就會讓其子視圖繪制兩次,所以onMeasure函數會先被調用兩次。

// FrameLayout的onMeasure函數,DecorView的onMeasure會調用這個函數。

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

int count = getChildCount();

.....

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

final View child = getChildAt(i);

if (mMeasureAllChildren || child.getVisibility() != GONE) {

measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);

......

}

}

........

count = mMatchParentChildren.size();

if (count > 1) {

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

........

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

}

}

}

你以為到了這里就能解釋通View初始化時的三次measure,兩次layout卻只一次draw嗎?那你就太天真了!我們都知道,視圖結構中不僅僅是DecorView是FrameLayout,還有其他的需要兩次measure子視圖的ViewGroup,如果每次都導致子視圖兩次measure,那效率就太低了。所以View的measure函數中有相關的機制來防止這種情況。

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {

......

// 當FLAG_FORCE_LAYOUT位為1時,就是當前視圖請求一次布局操作

//或者當前當前widthSpec和heightSpec不等于上次調用時傳入的參數的時候

//才進行從新繪制。

if (forceLayout || !matchingSize &&

(widthMeasureSpec != mOldWidthMeasureSpec ||

heightMeasureSpec != mOldHeightMeasureSpec)) {

......

onMeasure(widthMeasureSpec, heightMeasureSpec);

......

}

......

}

源碼看到這里,我幾乎失望的眼淚掉下來!沒辦法,只能再想其他的方法來分析這個問題。

斷點調試大法好

為了分析函數調用的層級關系,我想到了斷點調試法。于是,我果斷在onMeasure和onLayout函數中設置了斷點,然后進行調試。

在《Android視圖架構》一文中,我們知道ViewRoot是DecorView的父視圖,雖然它自己并不是一個View,但是它實現了ViewParent的接口,Android正是通過它來實現整個視圖系統的初始化工作。而它的performTraversals函數就是視圖初始化的關鍵函數。

對比兩次onMeasure被調用時的函數調用幀,我們可以輕易發現ViewRootImpl的performTraversals函數中直接和間接的調用了兩次performMeasure函數,從而導致了View最開始的兩次measure過程。

然后在相同的performTraversals函數中會調用performLayout函數,從而導致View進行一輪layout過程。

但是為什么這次performTraversals并沒有觸發View的draw過程呢?反而是View又將重新進行一輪measure,layout過程之后才進行draw。

兩次performTraversals

通過斷點調試,我們發現在View初始化的過程中,系統調用了兩次performTraversals函數,第一次performTraversals函數導致了View的前兩次的onMeasure函數調用和第一次的onLayout函數調用。后一次的performTraversals函數導致了最后的onMeasure,onLayout,和onDraw函數的調用。但是,第二次performTraversals為什么會被觸發呢?我們研究一下其源碼就可知道。

private void performTraversals() {

......

boolean newSurface = false;

//TODO:決定是否讓newSurface為true,導致后邊是否讓performDraw無法被調用,而是重新scheduleTraversals

if (!hadSurface) {

if (mSurface.isValid()) {

// If we are creating a new surface, then we need to

// completely redraw it. Also, when we get to the

// point of drawing it we will hold off and schedule

// a new traversal instead. This is so we can tell the

// window manager about all of the windows being displayed

// before actually drawing them, so it can display then

// all at once.

newSurface = true;

.....

}

}

......

if (!cancelDraw && !newSurface) {

if (!skipDraw || mReportNextDraw) {

......

performDraw();

}

} else {

if (viewVisibility == View.VISIBLE) {

// Try again

scheduleTraversals();

} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {

for (int i = 0; i < mPendingTransitions.size(); ++i) {

mPendingTransitions.get(i).endChangingAnimations();

}

mPendingTransitions.clear();

}

}

......

}

由源代碼可以看出,當newSurface為真時,performTraversals函數并不會調用performDraw函數,而是調用scheduleTraversals函數,從而再次調用一次performTraversals函數,從而再次進行一次測量,布局和繪制過程。

我們由斷點調試可以輕易看到,第一次performTraversals時的newSurface為真,而第二次時是假。

總結

雖然我已經通過源碼得知View初始化時measure三次,layout兩次,draw一次的原因,但是Android系統設計時,為什么要將整個初始化過程設計成這樣?我卻還沒有明白,為什么當Surface為新的時候,要推遲繪制,重新進行一輪初始化,這些可能都要涉及到Surface的相關內容,我之后要繼續學習相關內容!

總結

以上是生活随笔為你收集整理的android 二次绘制 layout,View的三次measure,两次layout和一次draw的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。