Android獲取窗口可視區域大小: getWindowVisibleDisplayFrame()

 

getWindowVisibleDisplayFrame()方法

getWindowVisibleDisplayFrame()是View類下的一個方法,從方法的名字就可以看出,它是用來獲取當前窗口可視區域大小的。

此方法的原型爲

public void getWindowVisibleDisplayFrame(Rect outRect);
  • 1

它接受一個Rect對象作爲參數,執行過程中會根據當前窗口可視區域大小更新outRect的值,執行完畢後,就可以根據更新後的outRect來確定窗口可視區域的大小。所以正如outRect的名字所見,它是一個輸出參數,後面如果提到getWindowVisibleDisplayFrame()方法的返回結果,指的也是參數outRect更新後的結果,getWindowVisibleDisplayFrame()本身是沒有返回值的。此外,由於是輸出參數,outRect必須不爲null,一般在使用前會先new一個沒有大小的Rect對象,將其作爲參數傳給getWindowVisibleDisplayFrame()方法。

Rect rect = new Rect();
view.getWindowVisibleDisplayFrame(rect);
  • 1
  • 2

getWindowVisibleDisplayFrame()的執行結果和View對象選取的關係

由於getWindowVisibleDisplayFrame()方法是View類下的一個方法,所以只能通過View對象來調用。一個窗口中通常都會有多個View,getWindowVisibleDisplayFrame()方法的返回結果和該窗口中選取的View並沒有關係。在某個時刻,使用當前窗口中的任意View執行getWindowVisibleDisplayFrame()返回的結果都是一樣的。這也很容易理解,getWindowVisibleDisplayFrame()方法返回的是窗口的可視區域大小,並非某個View的可視區域大小,所以用窗口中的任意View來執行都是沒有差別的。一般來說可以使用當前窗口的根View來執行這個方法,也就是調用Window對象的getDecorView().getWindowVisibleDisplayFrame()來獲取。在Acitivity和Dialog中可以用getWindow()來得到Window對象,合起來就是這樣的。

Rect rect = new Rect();
getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
  • 1
  • 2

這裏需要注意的是,由於getWindowVisibleDisplayFrame()方法是用來獲取某個窗口的可視區域大小,所以調用getWindowVisibleDisplayFrame()方法的View必須包含在該窗口中,如果是一個孤立的View,或者包含在其他窗口中,是沒有意義的。例如

Rect rect = new Rect();
// 這個new出來的View並沒有add到任何窗口上,所以調用它的getWindowVisibleDisplayFrame()方法是沒有意義的。
new View(this).getWindowVisibleDisplayFrame(rect);
  • 1
  • 2
  • 3

getWindowVisibleDisplayFrame()的執行結果和View對象狀態的關係

雖然getWindowVisibleDisplayFrame()的執行結果和窗口中View的選取沒有關係,但是卻和執行此方法時View的狀態有關。由於此方法是用來獲取窗口的可視區域大小,所以如果調用此方法時,調用的View對象還沒有附着(attach)到任何Window上,那麼執行此方法將不會得到實際的某個窗口的可視區域大小,只有View對象已經attach到Window上之後,調用此方法才能得到真實的窗口的可視區域大小。當調用的View對象還沒有attach到Window時,getWindowVisibleDisplayFrame()方法會估計出一個可能的可視區域大小,這個大小通常是設備的屏幕尺寸(以像素爲單位),由於它並不代表真實的窗口可視區域大小,所以這個數值的意義不大。

由於在Activity/Fragment/Dialog的onCreate()方法中,View對象還沒有attach到Window上,所以在onCreate()方法中執行某個View的getWindowVisibleDisplayFrame()方法,是不會得到當前Window實際的可視區域大小的。

在Activity的onAttachedToWindow()方法中執行getWindowVisibleDisplayFrame(),也是得不到當前Window實際的可視區域大小的。因爲Activity的onAttachedToWindow()方法執行時,表示當前Window被attach到window manager中,Window中的View仍然沒有attach到Window上。

View attach到Window之後,View對象的onAttachedToWindow()方法會被執行,理論上來說,在自定義View的onAttachedToWindow()方法中執行getWindowVisibleDisplayFrame()應該可以得到當前Window實際的可視區域大小,但實際情況卻並非如此。在自定義View的onAttachedToWindow()方法中執行getWindowVisibleDisplayFrame()會概率性的出現不同的結果,有時返回的rect對象大小是0,有時則是設備的屏幕尺寸,但都不是當前Window實際的可視區域大小。具體原因未知,沒有仔細研究。所以,也不要在View對象的onAttachedToWindow()方法中執行getWindowVisibleDisplayFrame()。

要想得到當前Window實際的可視區域大小,可以在Activity/Fragment/Dialog的onWindowFocusChanged()方法中執行getWindowVisibleDisplayFrame()。這時Window中的View對象都已經attach到Window上。

還有一點需要說明的是,getWindowVisibleDisplayFrame()的執行結果和View是否可見沒有關係。View無論是VISIBLE,還是INVISIBLE或者GONE,對getWindowVisibleDisplayFrame()的執行結果沒有影響。當View是INVISIBLE的時候,其onDraw()方法不會被調用,當View是GONE的時候,其onDraw()和onLayout()方法不會被調用。但無論是INVISIBLE或者GONE,onAttachedToWindow()都會被調用,也就是說View會被attach到Window上,所以即使View是INVISIBLE或者GONE的,getWindowVisibleDisplayFrame()也能夠正確的返回。

getWindowVisibleDisplayFrame()的執行結果分析

這裏所說的getWindowVisibleDisplayFrame()執行結果均是指當前Window實際的可視區域大小,對調用的View對象還沒有attach到Window時,getWindowVisibleDisplayFrame()方法估計出可視區域大小的情況不做討論。

getWindowVisibleDisplayFrame()執行結果和以下因素有關

  1. 系統狀態欄

    系統狀態欄會影響getWindowVisibleDisplayFrame()執行結果outRect中的top屬性的值。

    如果窗口是全屏的,也就是設置了flags爲WindowManager.LayoutParams.FLAG_FULLSCREEN,或者android:windowFullscreen設置爲true,則outRect中的top屬性不受狀態欄影響,其值始終爲0。否則,outRect中的top屬性值將會受到系統狀態欄的影響。

    如果窗口的LayoutParams的height設置爲WindowManager.LayoutParams.MATCH_PARENT,則outRect中的top值會等於系統狀態欄的高度,如果窗口的LayoutParams的height設置爲WindowManager.LayoutParams.WRAP_CONTENT或者某個具體的值,則outRect中的top值會等於系統狀態欄和窗口重疊區域的高度,如果沒有重疊,則是0。

    例如,屏幕高度爲1920,窗口高度設置爲1900,窗口居中顯示。這時窗口上下距離屏幕各有10個像素的距離。假如系統狀態欄高度爲60,窗口和狀態欄的重疊區域的高度就是50。因此,getWindowVisibleDisplayFrame()返回的outRect中的top值爲50。

    這裏寫圖片描述

    上面這幾點可以歸結爲一點,outRect中的top值等於系統狀態欄在理論上會對窗口上方所在位置產生的影響。

    如果窗口是全屏的,系統狀態欄將無法影響窗口上方位置,因此,outRect中的top值始終爲0。如果窗口的LayoutParams的height設置爲WindowManager.LayoutParams.MATCH_PARENT,則理論上窗口將到達屏幕最上方的位置,但是由於狀態欄的存在,會壓迫窗口位置到狀態欄下方,因此,outRect中的top值等於系統狀態欄的高度。如果窗口的LayoutParams的height設置爲WindowManager.LayoutParams.WRAP_CONTENT或者某個具體的值,且窗口和狀態欄存在重疊,則這時狀態欄同樣會試圖壓迫窗口位置到狀態欄下方,其位移就是重疊區域的高度,因此outRect中的top值等於重疊區域的高度。需要注意的是,這裏狀態欄對窗口位置的影響並不會實際生效,也就是窗口仍然會和狀態欄重疊,因此狀態欄對窗口位置的影響是一種理論上的,並非一定會生效。

  2. 虛擬鍵盤

    虛擬鍵盤會影響getWindowVisibleDisplayFrame()執行結果outRect中的bottom屬性的值。

    如果虛擬鍵盤是隱藏的,則outRect中的bottom屬性的值將始終等於屏幕高度(實際上還要減去虛擬按鍵欄的高度,這裏先忽略虛擬按鍵)。如果虛擬鍵盤是顯示的,outRect中的bottom屬性的值將等於屏幕高度減去理論上虛擬鍵盤會對窗口位置產生的影響。如果窗口高度是MATCH_PARENT的,則outRect中的bottom屬性的值將等於屏幕高度減去虛擬鍵盤的高度。

    同樣的例子,屏幕高度爲1920,窗口高度設置爲1900,窗口居中顯示。這時窗口上下距離屏幕各有10個像素的距離。假如虛擬鍵盤高度爲600,窗口和虛擬鍵盤的重疊區域的高度就是590。因此,getWindowVisibleDisplayFrame()返回的outRect中的bottom值爲1920 - 590。

  3. 虛擬按鍵欄

    虛擬按鍵欄會影響getWindowVisibleDisplayFrame()執行結果outRect中的bottom屬性的值。

    這裏只考慮軟鍵盤是隱藏的情況,如果軟鍵盤是顯示的,則軟鍵盤和虛擬按鍵欄對outRect中的bottom屬性的值的影響將會疊加。

    如果虛擬按鍵欄是隱藏的,則outRect中的bottom屬性的值將始終等於屏幕高度。如果虛擬按鍵是顯示的,outRect中的bottom屬性的值將等於屏幕高度減去理論上虛擬按鍵會對窗口位置產生的影響。如果窗口高度是MATCH_PARENT的,則outRect中的bottom屬性的值將等於屏幕高度減去虛擬按鍵的高度。

    這裏不再舉例說明。

綜上所述,getWindowVisibleDisplayFrame()執行結果會受到系統狀態欄,系統軟鍵盤,系統虛擬按鍵的影響。

這裏需要注意的是,getWindowVisibleDisplayFrame()的結果並不是該窗口實際的大小(雖然它和窗口的大小有一定關係)。例如一個居中顯示的對話框,它的實際高度只有500px,它和系統狀態欄,系統軟鍵盤,系統虛擬按鍵欄都沒有重疊,那麼getWindowVisibleDisplayFrame()的結果就是設備的尺寸大小,而不是該對話框的實際大小。

此外,雖然方法名字中有一個Visible,但是getWindowVisibleDisplayFrame()的結果並不受該窗口是否在被其他窗口遮擋的影響。即使該窗口已經被切換到後臺,只要該窗口還沒有dettach,getWindowVisibleDisplayFrame()的結果就不會變化。

getWindowVisibleDisplayFrame()的應用

在Android系統中,並沒有提供api來獲取系統狀態欄,系統軟鍵盤和系統虛擬按鍵欄的高度,但在應用中有時會需要獲取這幾個數值。由於getWindowVisibleDisplayFrame()返回結果會受到系統狀態欄,系統軟鍵盤,系統虛擬按鍵的影響。因此,這個api常常被用來獲取系統狀態欄,系統軟鍵盤和系統虛擬按鍵欄的高度。

對系統狀態欄高度,獲取一個非全屏,且窗口的LayoutParams的height設置爲WindowManager.LayoutParams.MATCH_PARENT的窗口可視區域大小,其top值就是狀態欄的高度。

對系統軟鍵盤,獲取一個高度是MATCH_PARENT的窗口在軟鍵盤顯示和隱藏兩種不同狀態下的可視區域大小,將bottom值相減就可以得到軟鍵盤的高度。

對系統系統虛擬按鍵欄,獲取一個高度是MATCH_PARENT的窗口在虛擬按鍵顯示和隱藏兩種不同狀態下的可視區域大小,將bottom值相減就可以得到虛擬按鍵的高度。

getWindowVisibleDisplayFrame()的性能問題

在getWindowVisibleDisplayFrame()方法的註釋中有這樣一段

這裏寫圖片描述

說明getWindowVisibleDisplayFrame()方法是通過IPC方式從window manager中獲取到這個信息的,相對來說它的開銷會比較大,因此不適合放在對性能要求很高的地方調用,例如View繪製的代碼中。

 

/**
 * Listener for applying window insets on a view in a custom way.
 *
 * <p>Apps may choose to implement this interface if they want to apply custom policy
 * to the way that window insets are treated for a view. If an OnApplyWindowInsetsListener
 * is set, its
 * {@link OnApplyWindowInsetsListener#onApplyWindowInsets(View, WindowInsets) onApplyWindowInsets}
 * method will be called instead of the View's own
 * {@link #onApplyWindowInsets(WindowInsets) onApplyWindowInsets} method. The listener
 * may optionally call the parameter View's <code>onApplyWindowInsets</code> method to apply
 * the View's normal behavior as part of its own.</p>
 */
public interface OnApplyWindowInsetsListener {
    /**
     * When {@link View#setOnApplyWindowInsetsListener(View.OnApplyWindowInsetsListener) set}
     * on a View, this listener method will be called instead of the view's own
     * {@link View#onApplyWindowInsets(WindowInsets) onApplyWindowInsets} method.
     *
     * @param v The view applying window insets
     * @param insets The insets to apply
     * @return The insets supplied, minus any insets that were consumed
     */
    public WindowInsets onApplyWindowInsets(View v, WindowInsets insets);
}

當前View註冊監聽,通過WindowInsets的get方法獲取範圍:

/**
 * Returns the left system window inset in pixels.
 *
 * <p>The system window inset represents the area of a full-screen window that is
 * partially or fully obscured by the status bar, navigation bar, IME or other system windows.
 * </p>
 *
 * @return The left system window inset
 */
public int getSystemWindowInsetLeft() {
    return getSystemWindowInsets().left;
}

/**
 * Returns the top system window inset in pixels.
 *
 * <p>The system window inset represents the area of a full-screen window that is
 * partially or fully obscured by the status bar, navigation bar, IME or other system windows.
 * </p>
 *
 * @return The top system window inset
 */
public int getSystemWindowInsetTop() {
    return getSystemWindowInsets().top;
}

/**
 * Returns the right system window inset in pixels.
 *
 * <p>The system window inset represents the area of a full-screen window that is
 * partially or fully obscured by the status bar, navigation bar, IME or other system windows.
 * </p>
 *
 * @return The right system window inset
 */
public int getSystemWindowInsetRight() {
    return getSystemWindowInsets().right;
}

/**
 * Returns the bottom system window inset in pixels.
 *
 * <p>The system window inset represents the area of a full-screen window that is
 * partially or fully obscured by the status bar, navigation bar, IME or other system windows.
 * </p>
 *
 * @return The bottom system window inset
 */
public int getSystemWindowInsetBottom() {
    return getSystemWindowInsets().bottom;
}

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章