沉浸模式 | 手勢導航連載 (四)
- 2020 年 1 月 3 日
- 筆記
作者 / Chris Banes, Android 開發者關係團隊工程師
本文是手勢導航連載的第四篇文章,如果您希望了解其他手勢導航的話題,請查看本系列的其他文。
本文我們將為大家介紹的是手勢交互和衝突在全屏應用 (系統欄也被隱藏) 下的情況和注意事項。讓我們給大家講講流程圖右側的兩種情況。

△ 請點擊圖片放大查看 右側的兩個解決方案都是 Android 平台為應用提供的沉浸模式 (immersive mode)。那問題來了: 什麼是沉浸模式?
什麼是沉浸模式?
沉浸模式是一種讓內容全屏呈現的方式,用來隱藏系統欄,從而確保應用擁有最大的屏幕空間。此外,它還提供了防誤操作的功能 (比如意外使用手勢離開應用),特別適合在遊戲中採用。
沉浸模式分為兩種:
- 非粘性沉浸模式: 用戶可以通過在系統欄上滑動來退出沉浸模式。
- 粘性沉浸模式: 用戶可以通過在系統欄上滑動來暫時退出沉浸模式。在經過一小段時間後 (只有幾秒) 會重新自動回到沉浸模式。
這兩種模式都有兩種狀態:
- 系統欄隱藏: 在此狀態下,返回主屏幕手勢和後退手勢均被禁用。用戶必須首先從邊緣向內側滑動才能讓系統欄顯示。
- 系統欄顯示: 在此狀態下,返回主屏幕手勢和後退手勢可以正常工作。
現在,我們已經了解了沉浸模式的基礎知識,下面介紹這兩種不同模式的細節。
非粘性沉浸模式
大家在上面的流程圖中可能已經看到,非粘性 (non-sticky) 沉浸模式非常適合需要全屏顯示但不需要在屏幕邊緣附近使用精確滑動手勢的 UI。常見的例子包括全屏視頻播放和照片瀏覽等。
就手勢導航而言,非粘性沉浸模式與其在早期版本的 Android 上的工作方式一致。在此模式下,無論系統欄是否可見,每個邊緣能排除的區域高度仍舊限制為 200dp。
如果您的應用正在使用非粘性沉浸模式,我們建議您回顧一下前文,避免在屏幕邊緣出現的視圖與系統手勢出現衝突。
粘性沉浸模式
粘性 (sticky) 沉浸模式適合那些強烈需要使用整個屏幕,並要求在整個屏幕區域內進行觸摸輸入的 UI。常見的例子是繪圖應用,以及使用滑動操作的遊戲。
我們來看一下運行在 Android 10 上,且使用手勢導航的 Markers 繪圖應用:

如上圖所示,一旦用戶開始在屏幕邊緣附近滑動 (繪製),就會觸發後退手勢,這會打斷用戶當前的操作。
使用粘性沉浸模式的應用會有很強的交互性,因此手勢區域排除 API 的限制會被移除,但僅限於系統欄隱藏的時候。這意味着應用可以根據需要完全佔用屏幕左 / 右邊緣。
但是,在系統欄可見時,系統則會忽略所有排除的手勢區域,讓用戶可以返回,而不會受到來自應用的干擾。在粘性沉浸模式下,系統欄僅在短時間內可見,因此不會影響應用的正常交互。
屏幕底部的主屏手勢區域依舊會正常存在,是無法排除的 "強制" 手勢區域。處於粘性沉浸模式的應用可能會佔用兩個垂直邊緣的整個長度,因此屏幕底部的主手勢區域可能是用戶呼出系統欄並退出應用的唯一方法。
接下來我們來看一下繪圖應用的改進版本,整個垂直邊緣都被應用佔用:

可以看到,用戶現在可以在屏幕邊緣附近自由繪製,後退手勢不會再干擾他們。如果用戶想要退出應用,則可以從屏幕底部向上滑動呼出系統欄,進行後退或返回主屏的操作。
在實現方面,此處使用的代碼大體沿用自第三篇文章中的 "使用手勢區域排除 API" 部分,不同之處在於,我們希望視圖能夠知道它自身是否處於沉浸模式之中:
private val exclusionRects = ArrayList<Rect>() override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { super.onLayout(changed, left, top, right, bottom) if (changed && Build.VERSION.SDK_INT >= 29) { updateGestureExclusionRects() } } override fun onWindowSystemUiVisibilityChanged(visibility: Int) { super.onWindowSystemUiVisibilityChanged(visibility) // Update our gesture exclusions rects if we』re // running on Android 10+ if (Build.VERSION.SDK_INT >= 29) { updateGestureExclusionRects() } } private fun updateGestureExclusionRects() { // If the navigation bar is hidden, let's exclude any vertical edges so // that the user can draw freely if ((windowSystemUiVisibility and SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0) { // Root window insets are null, which happens if this is called // before we're attached and laid out. Ignore the call for now. val rootWindowInsets = rootWindowInsets ?: return val gestureInsets = rootWindowInsets.systemGestureInsets exclusionRects.clear() // Add an exclusion rect for the left gesture edge exclusionRects += Rect(0, 0, gestureInsets.left, height) // Add an exclusion rect for the right gesture edge exclusionRects += Rect(width - gestureInsets.right, 0, width, height) systemGestureExclusionRects = exclusionRects } else { // If the navigation bar is showing, we don't want to exclude any edges. systemGestureExclusionRects = emptyList() } }
您可以在 GitHub 上閱讀 Makers 應用更新的完整代碼。
- 在 Android 10 上使用手勢區域排除 API github.com/chrisbanes/…
總結對比: 非粘性與粘性
呼,一口氣看到這裡可能有點記不住。這裡我為大家了提供一張表格,它總結出了非粘性和粘性沉浸模式之間的差異。

△ 請點擊圖片放大查看
繼續深入
如何處理手勢交互中的衝突就講到這裡。我也希望您已經對手勢交互有了更深的理解,並將這些理解完美落實到應用的開發與更新中去。