Android 獲取 View 寬高的常用正確方式,避免為零

亦楓發表於2017-04-05

相信有很多朋友都有過在 Activity 中通過 getWidth() 之類的方法獲取 View 的寬高值,可能在 onCreate() 生命週期方法中,也可能在 onResume() 生命週期方法中。然而,不幸的是,並不能獲取所要的結果,寬高值均為 0。

如果對 View 的繪製顯示流程熟悉的話,就會知道問題所在。我們知道,在自定義 View 時,通常都要重寫 onMeasure、onLayout、onDraw 這幾個方法。同理,Activity 的內容顯示到裝置上時,這些 View 也要經歷這些階段。

所以,當我們在 Activity 的生命週期方法中直接獲取 View 的寬高時,View 也許還沒執行完 measure 階段,那麼自然獲取到的寬高結果為 0。這也提醒我們一點,在 onCreate 方法中只適合做些一些初始化設定工作,使用 View 執行動畫或者其他操作時,一定要注意考慮 View 繪製的耗時過程。

那麼問題來了,怎麼樣才能在 Activity 程式碼中獲取到 View 的實際寬高值呢?這裡給大家總結一些常用方法。

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,顧名思義,檢視樹的觀察者,可以監聽 View 的全域性變化事件。比如,layout 變化,draw 事件等。可以閱讀原始碼瞭解更多事件。

注意:使用時需要注意及時移除該事件的監聽,避免後續每一次發生全域性 View 變化均觸發該事件,影響效能。這裡用的是 OnGlobalLayoutListener,移除監聽時注意 API 的版本相容處理。

addOnPreDrawListener


view.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() 方法用的更多一些。

關於我:亦楓,部落格地址:yifeng.studio/,新浪微博:IT亦楓

微信掃描二維碼,歡迎關注我的個人公眾號:安卓筆記俠

不僅分享我的原創技術文章,還有程式設計師的職場遐想

Android 獲取 View 寬高的常用正確方式,避免為零

相關文章