沉浸式顯示效果,這是在 Android 4.4 (API Level 19) 引入的一個(gè)概念,日常開發(fā)中經(jīng)常能接觸到朴上,官方介紹 Using Immersive Full-Screen Mode 秦踪。
Android 的 System bars 包含 Status bar 和 Navigation bar,如下圖所示頂部狀態(tài)欄是 Status bar爬范,底部虛擬按鍵欄是 Navigation bar父腕。
通常 System Bars 會(huì)和你的應(yīng)用布局同時(shí)顯示在屏幕上。應(yīng)用為了可以沉浸式顯示內(nèi)容青瀑,可以通過暫時(shí)淡化 System bars 來實(shí)現(xiàn)減少分散用戶注意力的體驗(yàn)璧亮,或者通過暫時(shí)隱藏 System bars 來實(shí)現(xiàn)完全沉浸式的的體驗(yàn)。具體可參考這篇文章 管理 System window bars
在日常開發(fā)中斥难,我們主要是處理 Activity 的沉浸效果枝嘶,在全屏沉浸模式中彈出 Dialog 或 PopupWindow,則會(huì)自動(dòng)退出沉浸模式哑诊。退出沉浸式的原因是因?yàn)?Activity 的 Window 焦點(diǎn)被搶走了群扶,Window 中的 DecorView 狀態(tài)改變導(dǎo)致了退出。
Android 中的 Window 表示一個(gè)窗口的概念,Android 中所有的視圖都是通過 Window 來呈現(xiàn)的竞阐,不論是 Activity缴饭、Dialog 或是 PopupWindow、Toast 骆莹,他們的視圖實(shí)際上都是附加在 Window 上的颗搂。
Window 共有三種類型, 分別是應(yīng)用 Window、子 Window幕垦、系統(tǒng) Window丢氢。其中應(yīng)用類 Window 對應(yīng)著一個(gè) Activity,子 Window 不能單獨(dú)存在先改,他需要附屬在特定的父 Window 中疚察。比如常見的 Dialog 和 PopupWindow 就是一個(gè)子 Window。系統(tǒng) Window 是需要聲明權(quán)限才能創(chuàng)建的 Window盏道,比如 Toast 和一些系統(tǒng)懸浮窗口都是系統(tǒng)的 Window稍浆。
我們可以通過 Android Studio 的 Layout Inspector 工具查看一個(gè)簡單 Activity 的 View Tree 結(jié)構(gòu)。
可以看到不論是系統(tǒng)狀態(tài)欄或是虛擬按鍵欄都是附在 DecorView 上猜嘱,要避免 DecorView 狀態(tài)改變而導(dǎo)致沉浸式模式退出衅枫。除了設(shè)置在 Activity 的 onWindowFocusChanged 方法狀態(tài)時(shí)重新設(shè)置進(jìn)入沉浸式模式,還需要把 Dialog 和 PopupWindow 彈出時(shí)造成的焦點(diǎn)改變也考慮進(jìn)去朗伶。
下面給出在 Activity 弦撩、Dialog 和 PopupWindow 中的沉浸式效果的具體實(shí)現(xiàn)。
在 Activity 中實(shí)現(xiàn)
@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 中實(shí)現(xiàn)
Dialog 在初始化時(shí)會(huì)生成新的 Window益楼,先禁止 Dialog Window 獲取焦點(diǎn),等 Dialog 顯示后對 Dialog Window 的 DecorView 設(shè)置 setSystemUiVisibility 点晴,接著再獲取焦點(diǎn)感凤,這樣看起來就沒有退出沉浸模式。
public class LoadingDialog extends Dialog {
@Override
public void show() {
// Dialog 在初始化時(shí)會(huì)生成新的 Window粒督,先禁止 Dialog Window 獲取焦點(diǎn)陪竿,等 Dialog 顯示后對 Dialog Window 的 DecorView 設(shè)置 setSystemUiVisibility ,接著再獲取焦點(diǎn)屠橄。 這樣表面上看起來就沒有退出沉浸模式族跛。
// 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 中實(shí)現(xiàn)
而 PopupWindow 并沒有創(chuàng)建新的 Window,只是將 PopupWindow 的 View 添加到當(dāng)前的 WindowManager锐墙。不過可以對 PopupWindow 設(shè)置 setFocusable礁哄。
同理,先在失焦的狀態(tài)溪北,彈出 PopupWindow 桐绒, 再對 PopupWindow 的 DecorView 設(shè)置 setSystemUiVisibility 夺脾,最后獲取焦點(diǎn)即可。 雖然 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();
}
}