View的繪製過程
View繪製過程爲measure
(測量),layout
(決定位置),draw
(繪製)
由於measure
方法爲final
類型,所以我們無法去重寫該方法,但是在測量結束後會回調onMeasure
方法,在該方法中可以獲取到測量寬/高,之所以說是測量,是因爲可能由於某種原因導致最後顯示出的寬高並不和測量的一致,但是大部分情況下實際高度等於測量寬/高.
不推薦在onMeasure
中去獲取測量寬高,某些情況下,view可能會多次測量纔會獲取到測量寬/高,在這種情況下獲取到的很大概率是不準確的.
推薦在onLayout
中獲取測量寬/高或者最終寬/高
測量寬高的時機
前言
例如在Activity中去獲取view的寬高,很多人會選擇在onCreate
或onPause
中去獲取,但是這樣很有可能測量獲得的寬高爲0
TIPS:View的繪製過程並不是和Activity的生命週期同步的,在Activity回調onCreate
和onPause
方法時並不能保證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
此時的SpecSize
爲parentSize
,然而此時無法知道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開發藝術探索
進行整理