View的getWidth()和getMeasuredWidth()有什么區(qū)別嗎?
getWidth()的源碼
public final int getWidth(){
return mRight - mLeft;
}
getMeasuredWidth()返回的是view的測(cè)量寬度,getWidth()返回的view的最終的寬度。從默認(rèn)的實(shí)現(xiàn)可以看到,getWidth()方法的返回值剛好是View的測(cè)量寬度,所以這兩個(gè)是相等的。只不過(guò)形成的時(shí)機(jī)不一樣,getMeasuredWidth()形成于view的Measure過(guò)程,而最終的寬度形成于View的layout過(guò)程,也就是說(shuō)兩者的賦值時(shí)機(jī)是不同的。因此,在日常的開(kāi)發(fā)中,我們可以認(rèn)為view的測(cè)量寬度等于最終的寬度,不過(guò)的確存在某些特殊的情況會(huì)導(dǎo)致兩者的結(jié)果不一致,以下例子可以說(shuō)明:
public void layout(int l,int t,int r,int b){
super.layout(l,t,r+100,b+100);
}
上述代碼會(huì)導(dǎo)致在任何情況下View的最終寬度總是比測(cè)量的寬度大100px,雖然這樣會(huì)導(dǎo)致View顯示不正常并且沒(méi)有實(shí)際的意義,但是這也證明了測(cè)量的寬度可以不等于最終的寬度。還有在一些情況下view需要測(cè)量多次才可以確定自己的寬度,在幾次測(cè)量過(guò)程中,測(cè)量的寬度可能與最終的寬度不一致,不過(guò)最終的寬度與實(shí)際的寬度還是相等的。
如何在onCreate中拿到view的寬度和高度?
在onCreate、onStart和onResume中均無(wú)法正確得到某個(gè)view的正確寬/高信息,這是因?yàn)閂iew的measure過(guò)程和activity的生命周期不是同步執(zhí)行的,因此無(wú)法保證Activity執(zhí)行了onCreate、onStart和onResume時(shí)某個(gè)view已經(jīng)測(cè)量完畢了,如果沒(méi)有測(cè)量完畢的話,那么獲取到的寬高就會(huì)為0。下面給出四種方式來(lái)解決這個(gè)問(wèn)題:
Activity/View #onWindowFocusChanged.
onWindowFocusChanged這個(gè)方法的含義是:View已經(jīng)初始化完畢了,寬高已經(jīng)準(zhǔn)備好了,這個(gè)時(shí)候去獲取
寬高是沒(méi)有問(wèn)題的。需要注意的是,onWindowFocusChanged會(huì)被調(diào)用多次,當(dāng)Activity的窗口得到焦點(diǎn)和失
去焦點(diǎn)時(shí)均會(huì)被調(diào)用一次。具體來(lái)說(shuō),當(dāng)Activity繼續(xù)執(zhí)行或停止執(zhí)行時(shí),onWindowFocusChanged均會(huì)被調(diào)
用,如果頻繁地進(jìn)行onResume和onPause,那么onWindowFocusChanged也會(huì)被頻繁地調(diào)用。典型代碼如下:
public void onWindowFocusChanged(boolean hasFocus){
super.onWindowFocusChanged(hasFocus);
if(hasFocus){
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
}
view.post(r)
通過(guò)post可以將一個(gè)runnable投遞到消息隊(duì)列的隊(duì)尾,然后等待Looper調(diào)用此runnable的時(shí)候,View也已經(jīng)初始化好了。典型的代碼如下:
protected void onStart(){
super.onStart();
view.post(new Runnable(){
@Override
public void run(){
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
});
}
ViewTreeObserver
使用ViewTreeObserver的眾多回調(diào)可以完成這個(gè)功能,比如使用OnGlobalLayoutListener這個(gè)接口,當(dāng)View樹(shù)的狀態(tài)發(fā)生改變時(shí),onGlobalLayout方法將被回調(diào),
因此這是獲取View的寬/高一個(gè)很好的時(shí)機(jī)。需要注意的是,伴隨著View樹(shù)的狀態(tài)改變等,onGlobalLayout會(huì)被調(diào)用多次,典型代碼如下:
protected void onStart(){
super.onStart();
ViewTreeObserver observer = view.getViewTreeObserver();
observer.addonGlobalLayoutListener(new OnGlobalLayoutListener(){
@SuppressWarnings("deprecation")
@Override
public void onGlobalLayout(){
if (Build.VERSION.SDK_INT < 16) {
removeLayoutListenerPre16(observer, this);
} else {
removeLayoutListenerPost16(observer this);
}
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
});
}
@SuppressWarnings("deprecation")
private void removeLayoutListenerPre16(ViewTreeObserver observer,
ViewTreeObserver.OnGlobalLayoutListener listener) {
observer.removeGlobalOnLayoutListener(listener);
}
@TargetApi(16)
private void removeLayoutListenerPost16(ViewTreeObserver observer,
ViewTreeObserver.OnGlobalLayoutListener listener) {
observer.removeOnGlobalLayoutListener(listener);
}
view.measure(int widthMeasureSpec,int heightMeasureSpec)
通過(guò)手動(dòng)對(duì)view進(jìn)行measure來(lái)得到View的寬/高。這種方法比較復(fù)雜,這里要分情況處理,根據(jù)View的LayoutParams來(lái)分:
match_parent
直接放棄,無(wú)法measure出具體的寬/高。構(gòu)造此種MeasureSpec需要知道parentSize,即父容器的剩余空間,而這個(gè)時(shí)候我們無(wú)法知道parentSize的大小,所以理論上不可能測(cè)量出View的大小。
具體的數(shù)值(dp/px)
比如寬高都是100px
int widthMeasureSpec = MeasureSpec.makeMeasureSpec(100,MeasureSpec.EXACTLY);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec(100,MeasureSpec.EXACTLY);
view.measure(widthMeasureSpec,heightMeasureSpec);
wrap_content
如下measure:
int widthMeasureSpec = MeasureSpec.makeMeasureSpec((1<<30)-1,MeasureSpec.AT_MOST);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec((1<<30)-1,MeasureSpec.AT_MOST);
view.measure(widthMeasureSpec,heightMeasureSpec);
通過(guò)分析MeasureSpec的實(shí)現(xiàn)可以知道,View的尺寸可以使用30位二進(jìn)制表示,也就是說(shuō)最大是2^30-1,在最大化模式下,我們用View理論上能支持的最大
值去構(gòu)造MeasureSpec是合理的。
關(guān)于View的measure,網(wǎng)絡(luò)上有兩個(gè)錯(cuò)誤的用法。為什么說(shuō)是錯(cuò)誤的,首先其違背了系統(tǒng)的內(nèi)部實(shí)現(xiàn)規(guī)范(因?yàn)闊o(wú)法通過(guò)錯(cuò)誤的MeasureSpec去得出合法的
SpecMode,從而導(dǎo)致measure過(guò)程出錯(cuò)),其次不能保證一定能measure出正確的結(jié)果。
第一種錯(cuò)誤的用法:
int widthMeasureSpec = MeasureSpec.makeMeasureSpec(-1, MeasureSpec.UNSPECIFIED);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec(-1, MeasureSpec.UNSPECIFIED);
view.measure(widthMeasureSpec,heightMeasureSpec);
第二種錯(cuò)誤的用法:
view.measure(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);