1. 程式人生 > >Android 系統(49)---Android獲取視窗可視區域大小: getWindowVisibleDisplayFrame()

Android 系統(49)---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);
  • 這裡需要注意的是,由於getWindowVisibleDisplayFrame()方法是用來獲取某個視窗的可視區域大小,所以呼叫getWindowVisibleDisplayFrame()方法的View必須包含在該視窗中,如果是一個孤立的View,或者包含在其他視窗中,是沒有意義的。例如
Rect rect = new Rect();
// 這個new出來的View並沒有add到任何視窗上,所以呼叫它的getWindowVisibleDisplayFrame()方法是沒有意義的。
new View(this).getWindowVisibleDisplayFrame(rect);
  • 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()方法的註釋中有這樣一段

這裡寫圖片描述