View測量寬高的時機

View的繪製過程

View繪製過程爲measure(測量),layout(決定位置),draw(繪製)
由於measure方法爲final類型,所以我們無法去重寫該方法,但是在測量結束後會回調onMeasure方法,在該方法中可以獲取到測量寬/高,之所以說是測量,是因爲可能由於某種原因導致最後顯示出的寬高並不和測量的一致,但是大部分情況下實際高度等於測量寬/高.
不推薦在onMeasure中去獲取測量寬高,某些情況下,view可能會多次測量纔會獲取到測量寬/高,在這種情況下獲取到的很大概率是不準確的.
推薦在onLayout中獲取測量寬/高或者最終寬/高


測量寬高的時機

前言
例如在Activity中去獲取view的寬高,很多人會選擇在onCreateonPause中去獲取,但是這樣很有可能測量獲得的寬高爲0
TIPS:View的繪製過程並不是和Activity的生命週期同步的,在Activity回調 onCreateonPause 方法時並不能保證View已經繪製完畢

Activity/View#onWindowFocusChanged

此方法的含義爲:View已經繪製完畢,已經獲取到焦點.當然,此時的寬高也已經確定了,但是需要注意,在視圖獲取焦點或者失去焦點都會被調用一次,很容易造成頻繁的調用

@Override
public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    if (hasFocus){
        int width = view.getMeasuredWidth();
        int width = view.getMeasuredHeight();
    }
}

view.post(runnable)

通過post可以將一個runnable投遞到消息隊列的尾部,然後等待Looper調用此runnable的時候,View也已經初始化好了.

view.post(new Runnable() {
   @Override
    public void run() {
        int width = view.getMeasuredWidth();
        int width = view.getMeasuredHeight();     
    }
});

ViewTreeObserver

使用ViewTreeObserver的回調方法,比如OnGlobalLayoutListener接口,當View樹的狀態發生改變,或者可見性發生改變時,都會回調onGlobalLayout方法,但是需要注意,隨着狀態發生改變,onGlobalLayout會被調用多次

ViewTreeObserver observer = view.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
        int width = view.getMeasuredWidth();
        int width = view.getMeasuredHeight();
    }
});

view.measure

view的測量需要根據當前view的LayoutParams和父容器的MeasureSpec手動進行測量
MeasureSpec代表一個32位的int值,高2位代表SpecMode,低30位代表SpecSize
SpecMode:EXACTLY, AT_MOST,UNSPECIFIED
SpecSize:dp/px,match_parent,wrap_content

parentSpecMode
EXACTLY
parentSpecMode
AT_MOST
parentSpecMode
UNSPECIFIED
childLayoutParams
dp/px
childSize
EXACTLY
childSize
EXACTLY
childSize
EXACTLY
childLayoutParams
match_parent
parentSize
EXACTLY
parentSize
AT_MOST
0
UNSPECIFIED
childLayoutParams
wrap_content
parentSize
AT_MOST
parentSize
AT_MOST
0
UNSPECIFIED

根據view的LayoutParams分爲如下幾種情況

  • match_parent
    此時的SpecSizeparentSize,然而此時無法知道parentSize的大小,無法構造出MeasureSpec

  • 具體的數值dp/px
    假設寬高都是100px,可以構造以下MeasureSpec

int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
view.measure(widthMeasureSpec,heightMeasureSpec);
  • wrap_content
    表格中的wrap_content行中的parentSize表示view的最大寬/高爲parentSize,我們可以使用View理論上支持的最大值(1<<30)-1 (30位二進制)去構造是合理的
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
view.measure(widthMeasureSpec,heightMeasureSpec);
  • 錯誤的用法
    違背了系統的內部實現規範(無法通過錯誤的MeasureSpec去得出合法的SpecMode,從而導致measure過程出錯),並且不能保證一定能measure出正確的結果

    int widthMeasureSpec = MeasureSpec.makeMeasureSpec(-1, MeasureSpec.UNSPECIFIED);
    int heightMeasureSpec = MeasureSpec.makeMeasureSpec(-1, MeasureSpec.UNSPECIFIED);
    view.measure(widthMeasureSpec,heightMeasureSpec);

    PS:此處實際實驗得知….設置爲-1根本不能編譯通過…..明確給出了錯誤提示Value must be >= 0,不知道書中爲什麼還要列舉這個…..

    view.measure(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);

本文參考Android開發藝術探索進行整理

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章