Android 獲取 View 寬高的常用正確方式,避免為零
6 人贊了文章
相信有很多朋友都有過在 Activity 中通過 getWidth() 之類的方法獲取 View 的寬高值,可能在 onCreate() 生命周期方法中,也可能在 onResume() 生命周期方法中。然而,不幸的是,並不能獲取所要的結果,寬高值均為 0。
如果對 View 的繪製顯示流程熟悉的話,就會知道問題所在。我們知道,在自定義 View 時,通常都要重寫 onMeasure、onLayout、onDraw 這幾個方法。同理,Activity 的內容顯示到設備上時,這些 View 也要經歷這些階段。所以,當我們在 Activity 的生命周期方法中直接獲取 View 的寬高時,View 也許還沒執行完 measure 階段,那麼自然獲取到的寬高結果為 0。這也提醒我們一點,在 onCreate 方法中只適合做些一些初始化設置工作,使用 View 執行動畫或者其他操作時,一定要注意考慮 View 繪製的耗時過程。那麼問題來了,怎麼樣才能在 Activity 代碼中獲取到 View 的實際寬高值呢?這裡給大家總結一些常用方法。addOnGlobalLayoutListenerview.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,顧名思義,視圖樹的觀察者,可以監聽 View 的全局變化事件。比如,layout 變化,draw 事件等。可以閱讀源碼了解更多事件。注意:使用時需要注意及時移除該事件的監聽,避免後續每一次發生全局 View 變化均觸發該事件,影響性能。這裡用的是 OnGlobalLayoutListener,移除監聽時注意 API 的版本兼容處理。addOnPreDrawListenerview.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override public boolean onPreDraw() { view.getViewTreeObserver().removeOnPreDrawListener(this); int width = view.getWidth(); return false;}
});這裡同樣是利用 ViewTreeObserver 觀察者類,只不過這裡監聽的是 draw 事件。view.post()view.post(new Runnable() {
@Override public void run() { int width = view.getWidth(); }});利用 Handler 通信機制,添加一個 Runnable 到 message queue 中,當 view layout 處理完成時,自動發送消息,通知 UI 線程。藉此機制,巧妙獲取 View 的寬高屬性。代碼簡潔,使用簡單,相比 ViewTreeObserver 監聽處理,還不需要手動移除觀察者監聽事件。
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() 方法獲取寬高值,這也是為一個不需要藉助其他類或者方法,僅靠自己就能完成獲取寬高屬性的手段。但是局限性在於,在 Activity 中使用代碼創建 View 的場景較少,一般都是獲取 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(); }});監聽 View 的 onLayout() 繪製過程,一旦 layout 觸發變化,立即回調onLayoutChange
方法。注意,用完也要注意調用 remove 方法移除監聽事件。ViewCompat.isLaidOut(view)
if (ViewCompat.isLaidOut(view)) {
int width = view.getWidth();}嚴格意義上來講,這不能作為一個獲取寬高的方式之一。充其量只能是一個判斷條件。只有當 View 至少經歷過一次 layout 時,isLaidOut() 方法才能返回 true,繼而才能獲取到 View 的真實寬高。所以,當我們的代碼中有多次調用獲取寬高時,才有可能使用這個方法判斷處理。getMeasuredWidth()最後,藉此地兒補充一點知識,getMeasuredWidth() 與 getWidth() 或者 getMeasuredHeight() 與 getHeight() 的區別。很多人容易對此產生混淆,不知道這兩個方法到底有什麼區別,使用時應該如何取捨。其實,官方文檔介紹 View class 時,對於 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() 的區別和聯繫所在。說得直白一點,measuredWidth 與 width 分別對應於視圖繪製 的 measure 與 layout 階段。很重要的一點是,我們要明白,View 的寬高是由 View 本身和 parent 容器共同決定的,要知道有這個 MeasureSpec 類的存在。比如,View 通過自身 measure() 方法向 parent 請求 100x100 的寬高,那麼這個寬高就是 measuredWidth 和 measuredHeight 值。但是,在 parent 的 onLayout() 階段,通過 childview.layout() 方法只分配給 childview 50x50 的寬高。那麼,這個 50x50 寬高就是 childview 實際繪製並顯示到屏幕的寬高,也就是 width 和 height 值。
如果你對自定義 View 過程很熟練的話,理解這部分內容就比較輕鬆一些。事實上,開發過程中,getWidth() 和 getHeight() 方法用的更多一些。推薦閱讀: