沉浸式顯示效果,這是在 Android 4.4 (API Level 19) 引入的一個概念,平常開發中常常能接觸到,官方介紹 Using Immersive Full-Screen Mode 。html
Android 的 System bars 包含 Status bar 和 Navigation bar,以下圖所示頂部狀態欄是 Status bar,底部虛擬按鍵欄是 Navigation bar。java
一般 System Bars 會和你的應用佈局同時顯示在屏幕上。應用爲了能夠沉浸式顯示內容,能夠經過暫時淡化 System bars 來實現減小分散用戶注意力的體驗,或者經過暫時隱藏 System bars 來實現徹底沉浸式的的體驗。具體可參考這篇文章 管理 System window barsandroid
在平常開發中,咱們主要是處理 Activity 的沉浸效果,在全屏沉浸模式中彈出 Dialog 或 PopupWindow,則會自動退出沉浸模式。退出沉浸式的緣由是由於 Activity 的 Window 焦點被搶走了,Window 中的 DecorView 狀態改變致使了退出。ide
Android 中的 Window 表示一個窗口的概念,Android 中全部的視圖都是經過 Window 來呈現的,不管是 Activity、Dialog 或是 PopupWindow、Toast ,他們的視圖實際上都是附加在 Window 上的。工具
Window 共有三種類型, 分別是應用 Window、子 Window、系統 Window。其中應用類 Window 對應着一個 Activity,子 Window 不能單獨存在,他須要附屬在特定的父 Window 中。好比常見的 Dialog 和 PopupWindow 就是一個子 Window。系統 Window 是須要聲明權限才能建立的 Window,好比 Toast 和一些系統懸浮窗口都是系統的 Window。佈局
咱們能夠經過 Android Studio 的 Layout Inspector 工具查看一個簡單 Activity 的 View Tree 結構。ui
能夠看到不管是系統狀態欄或是虛擬按鍵欄都是附在 DecorView 上,要避免 DecorView 狀態改變而致使沉浸式模式退出。除了設置在 Activity 的 onWindowFocusChanged 方法狀態時從新設置進入沉浸式模式,還須要把 Dialog 和 PopupWindow 彈出時形成的焦點改變也考慮進去。this
下面給出在 Activity 、Dialog 和 PopupWindow 中的沉浸式效果的具體實現。spa
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.xxx);
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
fullScreenImmersive(mDecorView);
}
複製代碼
/** * 全屏顯示,隱藏虛擬按鈕 * @param view */
private void fullScreenImmersive(View view) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
int uiOptions = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_FULLSCREEN;
view.setSystemUiVisibility(uiOptions);
}
}
複製代碼
Dialog 在初始化時會生成新的 Window,先禁止 Dialog Window 獲取焦點,等 Dialog 顯示後對 Dialog Window 的 DecorView 設置 setSystemUiVisibility ,接着再獲取焦點,這樣看起來就沒有退出沉浸模式。code
public class LoadingDialog extends Dialog {
@Override
public void show() {
// Dialog 在初始化時會生成新的 Window,先禁止 Dialog Window 獲取焦點,等 Dialog 顯示後對 Dialog Window 的 DecorView 設置 setSystemUiVisibility ,接着再獲取焦點。 這樣表面上看起來就沒有退出沉浸模式。
// Set the dialog to not focusable (makes navigation ignore us adding the window)
this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
//Show the dialog!
super.show();
//Set the dialog to immersive
fullScreenImmersive(getWindow().getDecorView());
//Clear the not focusable flag from the window
this.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
}
}
複製代碼
而 PopupWindow 並無建立新的 Window,只是將 PopupWindow 的 View 添加到當前的 WindowManager。不過能夠對 PopupWindow 設置 setFocusable。
同理,先在失焦的狀態,彈出 PopupWindow , 再對 PopupWindow 的 DecorView 設置 setSystemUiVisibility ,最後獲取焦點便可。 雖然 PopupWindow 對外沒有暴露出 DecorView ,但只要是 PopupWindow 中的可見 View 都行。
public void showPopupWindow(int x, int y, int width, int height, View anchor){
if (mPopupWindow != null){
mPopupWindow.setFocusable(false);
mPopupWindow.update();
mPopupWindow.showAtLocation(anchor, Gravity.NO_GRAVITY, windowPos[0], windowPos[1]);
fullScreenImmersive(mPopupWindow.getContentView());
mPopupWindow.setFocusable(true);
mPopupWindow.update();
}
}
複製代碼