Android 9.0 你不知道的劉海屏適配方案


Android 9.0 你不知道的劉海屏適配方案


前言

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

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

適配場景

  • 如果App使用了沉浸式的狀態欄,或者透明狀態欄,我們自己的佈局延伸到了狀態欄內部,這時候如果我們在劉海處有一個可交互的控件就會被遮擋
  • 使用了全面屏的頁面,比如APP的閃屏界面,圖片查看大圖的頁面,這時候狀態欄不可見,劉海會遮擋一部分地方。
  • 常見劉海屏樣式


    Android 9.0 你不知道的劉海屏適配方案

    “張雨綺”式劉海(Top Center)


    Android 9.0 你不知道的劉海屏適配方案

    “桂綸鎂”式劉海(Top Corner)


    Android 9.0 你不知道的劉海屏適配方案

    “關二爺”式劉海(Bottom)


    Android 9.0 你不知道的劉海屏適配方案

    混合劉海的方式(Top + Bottom)

    劉海模擬

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

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


    Android 9.0 你不知道的劉海屏適配方案

    劉海屏設置頁面

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


    Android 9.0 你不知道的劉海屏適配方案

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


    Android 9.0 你不知道的劉海屏適配方案

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


    Android 9.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。在不編寫任何額外代碼的情況下直接運行該項目,效果如下圖所示。


    Android 9.0 你不知道的劉海屏適配方案

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

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


    Android 9.0 你不知道的劉海屏適配方案

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

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

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

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

    <code>
    <androidx.constraintlayout.widget.constraintlayout> 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" />

    /<button>/<button>/<androidx.constraintlayout.widget.constraintlayout>/<code>

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

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

    <code>


    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)
    }
    }
    }/<code>

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

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


    Android 9.0 你不知道的劉海屏適配方案

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

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

    Android 9.0 你不知道的劉海屏適配方案

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

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

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

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

    <code>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()
    /<code>

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

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


    Android 9.0 你不知道的劉海屏適配方案

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

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


    Android 9.0 你不知道的劉海屏適配方案

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

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

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

    總結

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

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


    分享到:


    相關文章: