相信有很多朋友都有過在 Activity 中通過 getWidth() 之類的方法獲取 View 的寬高值戒傻,可能在 onCreate() 生命周期方法中脑奠,也可能在 onResume() 生命周期方法中。然而赖钞,不幸的是腰素,并不能獲取所要的結(jié)果,寬高值均為 0雪营。
如果對(duì) View 的繪制顯示流程熟悉的話弓千,就會(huì)知道問題所在。我們知道献起,在自定義 View 時(shí)洋访,通常都要重寫 onMeasure、onLayout谴餐、onDraw 這幾個(gè)方法姻政。同理,Activity 的內(nèi)容顯示到設(shè)備上時(shí)岂嗓,這些 View 也要經(jīng)歷這些階段汁展。
所以,當(dāng)我們?cè)?Activity 的生命周期方法中直接獲取 View 的寬高時(shí)厌殉,View 也許還沒執(zhí)行完 measure 階段食绿,那么自然獲取到的寬高結(jié)果為 0。這也提醒我們一點(diǎn)年枕,在 onCreate 方法中只適合做些一些初始化設(shè)置工作炫欺,使用 View 執(zhí)行動(dòng)畫或者其他操作時(shí),一定要注意考慮 View 繪制的耗時(shí)過程熏兄。
那么問題來了品洛,怎么樣才能在 Activity 代碼中獲取到 View 的實(shí)際寬高值呢?這里給大家總結(jié)一些常用方法摩桶。
addOnGlobalLayoutListener
view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (Build.VERSION.SDK_INT >= 16) {
view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}else {
view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
int width = view.getWidth();
}
});
ViewTreeObserver桥状,顧名思義,視圖樹的觀察者硝清,可以監(jiān)聽 View 的全局變化事件辅斟。比如,layout 變化芦拿,draw 事件等士飒。可以閱讀源碼了解更多事件蔗崎。
注意:使用時(shí)需要注意及時(shí)移除該事件的監(jiān)聽酵幕,避免后續(xù)每一次發(fā)生全局 View 變化均觸發(fā)該事件,影響性能缓苛。這里用的是 OnGlobalLayoutListener芳撒,移除監(jiān)聽時(shí)注意 API 的版本兼容處理。
addOnPreDrawListener
view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
view.getViewTreeObserver().removeOnPreDrawListener(this);
int width = view.getWidth();
return false;
}
});
這里同樣是利用 ViewTreeObserver 觀察者類,只不過這里監(jiān)聽的是 draw 事件笔刹。
view.post()
view.post(new Runnable() {
@Override
public void run() {
int width = view.getWidth();
}
});
利用 Handler 通信機(jī)制芥备,添加一個(gè) Runnable 到 message queue 中,當(dāng) view layout 處理完成時(shí)舌菜,自動(dòng)發(fā)送消息萌壳,通知 UI 線程。借此機(jī)制酷师,巧妙獲取 View 的寬高屬性讶凉。代碼簡(jiǎn)潔,使用簡(jiǎn)單山孔,相比 ViewTreeObserver 監(jiān)聽處理懂讯,還不需要手動(dòng)移除觀察者監(jiān)聽事件。
onLayout()
view = new View(this) {
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
int = view.getWidth();
}
};
利用 View 繪制過程中的 onLayout() 方法獲取寬高值台颠,這也是為一個(gè)不需要借助其他類或者方法褐望,僅靠自己就能完成獲取寬高屬性的手段。但是局限性在于串前,在 Activity 中使用代碼創(chuàng)建 View 的場(chǎng)景較少瘫里,一般都是獲取 layout 文件所加載 View 的寬高。
addOnLayoutChangeListener
view.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
view.removeOnLayoutChangeListener(this);
int width = view.getWidth();
}
});
監(jiān)聽 View 的 onLayout() 繪制過程荡碾,一旦 layout 觸發(fā)變化谨读,立即回調(diào) onLayoutChange
方法。注意坛吁,用完也要注意調(diào)用 remove 方法移除監(jiān)聽事件劳殖。
ViewCompat.isLaidOut(view)
if (ViewCompat.isLaidOut(view)) {
int width = view.getWidth();
}
嚴(yán)格意義上來講,這不能作為一個(gè)獲取寬高的方式之一拨脉。充其量只能是一個(gè)判斷條件哆姻。只有當(dāng) View 至少經(jīng)歷過一次 layout 時(shí),isLaidOut() 方法才能返回 true玫膀,繼而才能獲取到 View 的真實(shí)寬高矛缨。所以,當(dāng)我們的代碼中有多次調(diào)用獲取寬高時(shí)帖旨,才有可能使用這個(gè)方法判斷處理箕昭。
getMeasuredWidth()
最后,借此地兒補(bǔ)充一點(diǎn)知識(shí)解阅,getMeasuredWidth() 與 getWidth() 或者 getMeasuredHeight() 與 getHeight() 的區(qū)別落竹。很多人容易對(duì)此產(chǎn)生混淆瓮钥,不知道這兩個(gè)方法到底有什么區(qū)別筋量,使用時(shí)應(yīng)該如何取舍。其實(shí)碉熄,官方文檔介紹 View class 時(shí)桨武,對(duì)于 Size 部分,有這么一段話:
The size of a view is expressed with a width and a height. A view actually possess two pairs of width and height values.
The first pair is known as measured width and measured height. These dimensions define how big a view wants to be within its parent (see Layout for more details.) The measured dimensions can be obtained by calling getMeasuredWidth() and getMeasuredHeight().
The second pair is simply known as width and height, or sometimes drawing width and drawing height. These dimensions define the actual size of the view on screen, at drawing time and after layout. These values may, but do not have to, be different from the measured width and height. The width and height can be obtained by calling getWidth() and getHeight().
這段話足以解釋 getMeasuredXXX() 與 getXXX() 的區(qū)別和聯(lián)系所在呀酸。說得直白一點(diǎn)性誉,measuredWidth 與 width 分別對(duì)應(yīng)于視圖繪制 的 measure 與 layout 階段。很重要的一點(diǎn)是茎杂,我們要明白错览,View 的寬高是由 View 本身和 parent 容器共同決定的,要知道有這個(gè) MeasureSpec 類的存在煌往。
比如倾哺,View 通過自身 measure() 方法向 parent 請(qǐng)求 100x100 的寬高,那么這個(gè)寬高就是 measuredWidth 和 measuredHeight 值刽脖。但是羞海,在 parent 的 onLayout() 階段,通過 childview.layout() 方法只分配給 childview 50x50 的寬高曲管。那么却邓,這個(gè) 50x50 寬高就是 childview 實(shí)際繪制并顯示到屏幕的寬高,也就是 width 和 height 值院水。
如果你對(duì)自定義 View 過程很熟練的話腊徙,理解這部分內(nèi)容就比較輕松一些。事實(shí)上衙耕,開發(fā)過程中昧穿,getWidth() 和 getHeight() 方法用的更多一些。