首頁>數碼>

前言

其實Android 9.0系統已經是去年推出的“老”系統了,這個系統中新增了一個比較重要的特性,就是對劉海屏裝置進行了支援。一直以來我也都有打算針對這個新特性好好地寫一篇文章,但是為什麼直到拖到了Android 10.0系統都發布了才開始寫這篇文章呢?當然,一是因為我這段時間確實比較忙。但是最主要的原因並不是這個,而是因為劉海屏裝置的適配存在一定的特殊性。

我先來帶著大家回顧一下手機螢幕的發展歷史。在之前的很長一段時間裡,絕大多數的手機螢幕使用的都是16:9的比例。當時普遍認為16:9就是最合適的裝置螢幕比例,因為手機上方還要給聽筒攝像頭留足空間,下方還要給Home鍵留足空間。然而,根據我所能查到的最早資料,小米是第一個敢於打破這個限制的手機廠商(不保證一定正確)。在2016年的時候,小米推出了MIX一代手機,將螢幕做到了接近18:9的比例,並首次提出了全面屏的概念。但是要做到真正的全面屏並不是一件容易的事情,像Home鍵之類的實體按鍵還可以用虛擬按鍵來替代,但是前置攝像頭這種就是實打實的硬體感測器了,必須得要佔據一定的空間。因此,小米MIX選擇了將攝像頭做到了螢幕下方,形成了一個比較寬的下巴。

適配場景如果App使用了沉浸式的狀態列,或者透明狀態列,我們自己的佈局延伸到了狀態列內部,這時候如果我們在劉海處有一個可互動的控制元件就會被遮擋使用了全面屏的頁面,比如APP的閃屏介面,圖片檢視大圖的頁面,這時候狀態列不可見,劉海會遮擋一部分地方。常見劉海屏樣式劉海模擬

常見的劉海大概就是以上這幾種吧,但是看到這裡你可能會犯難了,我到哪裡去找這麼多不同種類的劉海屏手機來進行測試呢?不用擔心,只要你手上有任何一部Android 9.0或以上系統的手機,都是可以模擬出各種不同型別的劉海的。當然,即使你手上沒有任何一部手機,也可以正常進行測試,只需要藉助Android官方的模擬器即可。

這裡我建立了一臺Android 10.0系統的模擬器,語言選擇中文簡體,在開發者選項當中,將可以找到劉海屏這個欄目,如下圖所示。

劉海屏設定頁面

第一個預設是無劉海,接下來的每一個選項都可以模擬出一種不同的劉海模式,我們可以把每個選項都點一點,比如說下圖對應的就是“張雨綺”式的劉海。

而下圖對應的是“桂綸鎂”式的劉海。

最後,下圖對應的是混合模式的劉海。

了解了各種不同的劉海模式,以及其對應的模擬方式,這樣我們就將準備工作都完成了,接下來終於可以進入到具體的編碼適配環節了。

思考一下,其實對於劉海屏的適配並不應該是一件複雜的事情,因為我們的目標很簡單,就是不要讓劉海部分遮擋到應用程式,或者影響到應用程式的正常使用即可。

為此,Android 9.0系統中提供了3種layoutInDisplayCutoutMode屬性來允許應用自主決定該如何對劉海屏裝置進行適配。

LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT:這是一種預設的屬性,在不進行明確指定的情況下,系統會自動使用這種屬性。這種屬性允許應用程式的內容在豎屏模式下自動延伸到劉海區域,而在橫屏模式下則不會延伸到劉海區域。

LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES:這種屬性表示,不管手機處於橫屏還是豎屏模式,都會允許應用程式的內容延伸到劉海區域。

LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER:這種屬性表示,永遠不允許應用程式的內容延伸到劉海區域。

程式碼實現

了解了以上內容之後,接下來我們就可以動手進行實現了。首先建立一個Demo專案,並讓Android Studio幫我們自動生成一個空的Activity。在不編寫任何額外程式碼的情況下直接執行該專案,效果如下圖所示。

可以看到,在豎屏模式下應用程式的狀態列部分剛好佔據了手機的劉海區域,並且系統還會根據劉海的高度來自動調整狀態列的高度,這樣應用程式中的內容自然是不會被劉海部分遮擋掉的。

現在如果我們旋轉一下手機,橫屏模式下的效果如下圖所示。

這個時候,手機的劉海區域會整個變成一條大黑邊,應用程式的內容是不允許延伸到這部分割槽域裡的,這樣也不會產生內容被遮擋的情況。

也就是說,即使我們不做任何的適配工作,絕大多數的程式在預設情況下也是可以自動適配劉海屏手機的,並不會產生應用程式無法使用等問題的發生。但是,假如你開發的是一款視訊類應用或者遊戲的話,充分利用螢幕的空間明顯可以帶來更好的使用者體驗,介面上留著一條大黑邊對使用者總歸是不夠友好的。這個時候我們就可以通過指定layoutInDisplayCutoutMode屬性的值,來讓應用程式具備更好的螢幕適配性。

這裡我就使用LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES屬性,並且配合著沉浸式模式的程式碼,來編寫一個全屏的UI介面,以此模擬視訊和遊戲類App的效果。

首先為了防止介面出現一片空白的情況,我對activity_main.xml佈局的內容進行了修改,如下所示:

<?xml version="1.0" encoding="utf-8"?><androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@mipmap/bg" tools:context=".MainActivity"> <Button android:layout_width="150dp" android:layout_height="80dp" android:text="button one" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Button android:layout_width="150dp" android:layout_height="80dp" android:text="button two" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toTopOf="parent" /></androidx.constraintlayout.widget.ConstraintLayout>

這裡給最外層的FrameLayout指定了一個背景圖,隨便使用什麼圖片都可以,我們只是為了便於進行演示。然後新增兩個按鈕,用於模擬操作按鈕被劉海遮擋

接下來修改MainActivity中的程式碼,為其指定LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES屬性。另外,為了讓介面效果更加貼近於視訊應用或遊戲,這裡我將MainActivity調整成了沉浸式模式,程式碼如下所示:

class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) requestWindowFeature(Window.FEATURE_NO_TITLE) window.setFlags( WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN ) setContentView(R.layout.activity_main) window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_FULLSCREEN supportActionBar?.hide() if (Build.VERSION.SDK_INT >= 28) { window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES } } override fun onWindowFocusChanged(hasFocus: Boolean) { super.onWindowFocusChanged(hasFocus) if (hasFocus) { window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) } }}

其實這段程式碼需要我們關心的就是if (Build.VERSION.SDK_INT >= 28)的這個邏輯判斷中的內容,在這裡我們將當前Activity的layoutInDisplayCutoutMode屬性指定成LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES,這樣就可以讓應用程式的內容延伸到劉海區域了。

現在重新執行一下程式,效果如下圖所示。

可以看到,程式進入了全屏沉浸式體驗的效果,並且我們在佈局檔案中設定的背景圖是可以延伸到劉海區域的,這就使得手機螢幕的空間得到了更充分的利用。

現在旋轉一下手機螢幕,效果如下圖所示:

很明顯,這比之前在劉海區域空出一條大黑邊的使用者體驗要好上太多了。

不過,雖然現在我們已經實現了讓應用程式的內容延伸到劉海區域的功能,卻無法保證劉海部分不會影響到應用程式的正常使用。正如上圖所看到的一樣,沉浸式全面屏帶來的問題就是會有一部分操作按鈕被遮擋,如果這是一款遊戲,那可能這款遊戲就完全沒法玩了。

因此,對於任何應用程式或者是遊戲而言,都需要在這方面進行適配,保證自己的可互動控制元件絕對不能被劉海區域遮擋住。

那麼具體應該如何實現這個功能呢?Android在9.0系統中提供了一套專門用於獲取安全顯示區域的API,我們只需要確認出哪些位置是有可能被遮擋到的,然後對可互動控制元件進行相應的位置偏移就可以了,示例程式碼新增到onCreate()方法中,如下所示:

if (Build.VERSION.SDK_INT >= 28) { mRootCl?.setOnApplyWindowInsetsListener { _, insets -> if (insets.displayCutout!=null){ val left = insets.displayCutout?.safeInsetLeft val top = insets.displayCutout?.safeInsetTop val right = insets.displayCutout?.safeInsetRight val bottom = insets.displayCutout?.safeInsetBottom (mOneBtn?.layoutParams as ConstraintLayout.LayoutParams).setMargins(left!!, top!!, right!!, bottom!!) (mTwoBtn?.layoutParams as ConstraintLayout.LayoutParams).setMargins(left, top, right, bottom) } insets.consumeSystemWindowInsets()

這段程式碼並沒有什麼難理解的地方,和剛才所講解的示例程式碼是差不多的。只是在得到上下左右方向上的偏移距離之後,我們通過給按鈕的layout設定margin的方式來讓控制元件在四個方向上進行相應的偏移。如果你是在開發遊戲的話,也可以同樣套用這段程式碼,只是在獲取到相應的偏移距離之後,將這幾個值傳遞給遊戲層邏輯即可,由遊戲層來控制如何對可互動的控制元件進行偏移。

現在來重新執行一下程式碼吧,豎屏模式下的結果如下圖所示。

可以看到,頂部可互動控制元件自動向下偏移了一段距離,剛好可以保證不被劉海區域遮擋到。

那麼再來看一下橫屏模式下的結果吧,如下圖所示。

沒有問題,橫屏模式下側邊可互動控制元件自動向右偏移了一段距離,從而也不會被劉海區域遮擋到了。

不過你會發現,在橫屏模式下,頂部可互動控制元件並沒有處於螢幕中間的位置,這是因為螢幕的左側存在劉海,因此DisplayCutout會告訴我們要向左偏移一定的距離。但是我們並沒有判斷哪些控制元件需要偏移,哪些控制元件不需要偏移,而是直接將所有控制元件都進行偏移,才出現了這種沒有居中對齊的情況。

至於解決辦法其實並沒有什麼簡單的方式,就是增加邏輯判斷即可,在橫屏模式下我們可以斷定頂部可互動控制元件是絕對不可能被劉海遮擋到的,因此只需要對側邊可互動控制元件進行偏移即可,具體的程式碼我就不再進行演示了。

總結

雖然整篇文章我都一直在使用張雨綺”式的劉海來進行演示,但是我們所使用這種適配方案,是可以保證在任何劉海模式下,你的可互動控制元件都不會被遮擋到的,感興趣的話你可以自行切換成其他的劉海模式來進行更多的測試。

好的,關於Android 9.0系統劉海屏的適配就講到這裡,相信你已經可以完全掌握了。

  • 雙11 神舟放大招,11代i7筆電直降1100?
  • 情人節福利來襲!堅果Pro 3最低僅售2399元,多平臺免息同步進行