android屏幕适配的五种方式_讲一讲Android 9.0系统的新特性,对刘海屏设备进行适配...
其實Android 9.0系統(tǒng)已經(jīng)是去年推出的“老”系統(tǒng)了,這個系統(tǒng)中新增了一個比較重要的特性,就是對劉海屏設備進行了支持。一直以來我也都有打算針對這個新特性好好地寫一篇文章,但是為什么直到拖到了Android 10.0系統(tǒng)都發(fā)布了才開始寫這篇文章呢?當然,一是因為我這段時間確實比較忙,今年幾乎絕大部分的業(yè)余時間都放到寫新書上了。但是最主要的原因并不是這個,而是因為劉海屏設備的適配存在一定的特殊性。
我先來帶著大家回顧一下手機屏幕的發(fā)展歷史。在之前的很長一段時間里,絕大多數(shù)的手機屏幕使用的都是16:9的比例。當時普遍認為16:9就是最合適的設備屏幕比例,因為手機上方還要給聽筒攝像頭留足空間,下方還要給Home鍵留足空間。然而,根據(jù)我所能查到的最早資料,小米是第一個敢于打破這個限制的手機廠商(不保證一定正確)。在2016年的時候,小米推出了MIX一代手機,將屏幕做到了接近18:9的比例,并首次提出了全面屏的概念。但是要做到真正的全面屏并不是一件容易的事情,像Home鍵之類的實體按鍵還可以用虛擬按鍵來替代,但是前置攝像頭這種就是實打?qū)嵉挠布鞲衅髁?#xff0c;必須得要占據(jù)一定的空間。因此,小米MIX選擇了將攝像頭做到了屏幕下方,形成了一個比較寬的下巴。
這里我并不想評價小米MIX手機是不是一個成功的產(chǎn)品,或者是否具備劃時代的意義。但是至少從那個時候開始,絕大多數(shù)的手機廠商都開始想要突破16:9的屏幕比例限制了,其中也包括蘋果。
2017年,蘋果推出了iPhone產(chǎn)品的10周年紀念手機——iPhone X,庫克聲稱,它將會引導未來10年的手機發(fā)展方向。iPhone X最主要的變化就是其首次在iPhone設備上應用了全面屏的概念,將屏幕做到了19.5:9的比例,取消了使用10年之久的Home按鍵,改為使用FaceId來進行身份認證。
而FaceId是需要通過攝像頭進行面部識別來認證身份的,因此蘋果同樣無法繞過的一個問題就是,前置攝像頭應該放在哪里?
和小米MIX的做法不同,iPhone X采用了將屏幕切出一個凹口的方式來放置前置攝像頭以及其他傳感器硬件,因此形成了一種非標準矩形屏幕。由于切出的凹口很像人的劉海,又被大家戲稱為劉海屏。
不過,蘋果向來是手機行業(yè)的風向標,iPhone X推出之后,大家應該能猜想到接下來會發(fā)生什么事情吧?沒錯,就是一大堆的國產(chǎn)手機廠商也立即跟風去生產(chǎn)劉海屏的Android手機。但是注意,當時的Android 8.0系統(tǒng)還不支持劉海屏設備。那該怎么辦呢?這個時候就只能八仙過海,各顯神通了。每個手機廠商為了不落后于他人,都通過修改ROM的方式制訂了一套自己的API標準,有的是在AndroidManifest里面配置屬性的,有的是通過反射來進行賦值的,有的是讀取system feature的,總之什么方式的都有。
后來2018年,Google推出了Android 9.0系統(tǒng),官方正式對劉海屏設備進行了支持。但是剛推出的新系統(tǒng)普及率很低,市場上大部分都還是各個廠商自己定制的8.0系統(tǒng)版的劉海屏手機。如果那個時候來寫本篇文章,一定會有許多讀者問,華為的劉海屏手機怎么適配?OPPO的劉海屏手機又該怎么適配?VIVO的呢?最終就只能是各種亂七八糟的適配滿天飛,而我是很不愿意去寫這樣一篇文章的。
但是現(xiàn)在不同了,Android 9.0系統(tǒng)發(fā)布一年之后,以前的劉海屏手機基本上都已經(jīng)完成了9.0系統(tǒng)的升級。也就是說,現(xiàn)在我們可以不用再去考慮那些不同手機廠商之間的適配問題了,只需要按照Android官方提供的標準API來進行劉海屏設備的適配即可。因此,我也終于決定要好好寫一篇關(guān)于Android 9.0系統(tǒng)劉海屏設備適配的文章。
不過,iPhone手機劉海的位置和大小都是固定的,至少目前來說是固定的,而Android手機的劉海卻可能以不同的形式出現(xiàn)在不同的位置,因此不要想著可以用硬編碼的方式來簡單進行適配。
下面我們就來探討一下,Android手機通常可能會有哪幾種劉海形式吧(以下圖片創(chuàng)意來自于我參加的一場Google開發(fā)者會議,并非是我所原創(chuàng))。
首先,最常見和最普遍,同時可能也是最受歡迎的,就是“張雨綺”式劉海(Top Center)。
由于iPhone X使用的就是這種形式的劉海,多數(shù)Android手機廠商也跟風學習,因此這種劉海的市場占有率極高。
不過,有些手機也可能會使用“桂綸鎂”式劉海(Top Corner)。
這種劉海的特點是只會占用屏幕的一個角落空間,手機的屏占比將會更大。
當然,也有一些手機可能會使用“關(guān)二爺”式劉海(Bottom)。
這種劉海和“張雨綺”式劉海其實就是相對的,只是一個劉海在上,一個劉海在下而已。
最后,手機廠商也可能會選擇使用混合劉海的方式(Top + Bottom)。
常見的劉海大概就是以上這幾種吧,但是看到這里你可能會犯難了,我到哪里去找這么多不同種類的劉海屏手機來進行測試呢?不用擔心,只要你手上有任何一部Android 9.0或以上系統(tǒng)的手機,都是可以模擬出各種不同類型的劉海的。當然,即使你手上沒有任何一部手機,也可以正常進行測試,只需要借助Android官方的模擬器即可。
這里我創(chuàng)建了一臺Android 10.0系統(tǒng)的模擬器,在開發(fā)者選項當中,將可以找到Display cutout這個欄目,如下圖所示。
點擊該欄目,會彈出一個如下圖所示的菜單。
這里的第一個選項表示無劉海模式,接下來的每一個選項都可以模擬出一種不同的劉海模式,我們可以把每個選項都點一點,比如說下圖對應的就是“張雨綺”式的劉海。
而下圖對應的是“桂綸鎂”式的劉海。
最后,下圖對應的是混合模式的劉海。
了解了各種不同的劉海模式,以及其對應的模擬方式,這樣我們就將準備工作都完成了,接下來終于可以進入到具體的編碼適配環(huán)節(jié)了。
思考一下,其實對于劉海屏的適配并不應該是一件復雜的事情,因為我們的目標很簡單,就是不要讓劉海部分遮擋到應用程序,或者影響到應用程序的正常使用即可。
為此,Android 9.0系統(tǒng)中提供了3種layoutInDisplayCutoutMode屬性來允許應用自主決定該如何對劉海屏設備進行適配。
LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
這是一種默認的屬性,在不進行明確指定的情況下,系統(tǒng)會自動使用這種屬性。這種屬性允許應用程序的內(nèi)容在豎屏模式下自動延伸到劉海區(qū)域,而在橫屏模式下則不會延伸到劉海區(qū)域。
LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
這種屬性表示,不管手機處于橫屏還是豎屏模式,都會允許應用程序的內(nèi)容延伸到劉海區(qū)域。
LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
這種屬性表示,永遠不允許應用程序的內(nèi)容延伸到劉海區(qū)域。
那么我們具體應該如何對layoutInDisplayCutoutMode屬性的值進行指定呢?主要有兩種方式,在主題xml文件中進行指定,以及在代碼中動態(tài)進行指定。
如果選擇在主題xml文件中進行指定,需要先創(chuàng)建一個values-v28文件夾,并在其中創(chuàng)建一個styles.xml文件,然后在指定Activity的主題下加入如下配置即可:
<style?name="ActivityTheme"?parent="Theme.AppCompat.Light.DarkActionBar">
????<item?name="android:windowLayoutInDisplayCutoutMode">
????????shortEdges?
????item>
style>
如果選擇在代碼中進行指定,只需要在Activity中加入如下代碼即可:
if?(Build.VERSION.SDK_INT?>=?28)?{????WindowManager.LayoutParams?params?=?getWindow().getAttributes();????params.layoutInDisplayCutoutMode?=?WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;????getWindow().setAttributes(params);}
了解了以上內(nèi)容之后,接下來我們就可以動手進行實現(xiàn)了。首先創(chuàng)建一個CutoutTest項目,并讓Android Studio幫我們自動生成一個空的Activity。在不編寫任何額外代碼的情況下直接運行該項目,效果如下圖所示。
可以看到,在豎屏模式下應用程序的狀態(tài)欄部分剛好占據(jù)了手機的劉海區(qū)域,并且系統(tǒng)還會根據(jù)劉海的高度來自動調(diào)整狀態(tài)欄的高度,這樣應用程序中的內(nèi)容自然是不會被劉海部分遮擋到的。
現(xiàn)在如果我們旋轉(zhuǎn)一下手機,橫屏模式下的效果如下圖所示。
這個時候,手機的劉海區(qū)域會整個變成一條大黑邊,應用程序的內(nèi)容是不允許延伸到這部分區(qū)域里的,這樣也不會產(chǎn)生內(nèi)容被遮擋的情況。
也就是說,即使我們不做任何的適配工作,絕大多數(shù)的程序在默認情況下也是可以自動適配劉海屏手機的,并不會產(chǎn)生應用程序無法使用等問題的發(fā)生。但是,假如你開發(fā)的是一款視頻類應用或者游戲的話,充分利用屏幕的空間明顯可以帶來更好的用戶體驗,界面上留著一條大黑邊對用戶總歸是不夠友好的。這個時候我們就可以通過指定layoutInDisplayCutoutMode屬性的值,來讓應用程序具備更好的屏幕適配性。
這里我就使用LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES屬性,并且配合著沉浸式模式的代碼,來編寫一個全屏的UI界面,以此模擬視頻和游戲類App的效果。
首先為了防止界面出現(xiàn)一片空白的情況,我對activity_main.xml布局的內(nèi)容進行了修改,如下所示:
<FrameLayout?xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/rootLayout"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@drawable/bg">FrameLayout>
這里給最外層的FrameLayout指定了一個背景圖,隨便使用什么圖片都可以,我們只是為了便于進行演示。
接下來修改MainActivity中的代碼,為其指定LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES屬性。另外,為了讓界面效果更加貼近于視頻應用或游戲,這里我將MainActivity調(diào)整成了沉浸式模式,代碼如下所示:
public?class?MainActivity?extends?AppCompatActivity?{????@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.activity_main);????????View?decorView?=?getWindow().getDecorView();????????int?option?=?View.SYSTEM_UI_FLAG_HIDE_NAVIGATION?|?View.SYSTEM_UI_FLAG_FULLSCREEN;????????decorView.setSystemUiVisibility(option);????????ActionBar?actionBar?=?getSupportActionBar();????????if?(actionBar?!=?null)?{????????????actionBar.hide();????????}????????if?(Build.VERSION.SDK_INT?>=?28)?{????????????WindowManager.LayoutParams?params?=?getWindow().getAttributes();????????????params.layoutInDisplayCutoutMode?=?WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;????????????getWindow().setAttributes(params);????????}????}????@Override????public?void?onWindowFocusChanged(boolean?hasFocus)?{????????super.onWindowFocusChanged(hasFocus);????????if?(hasFocus?&&?Build.VERSION.SDK_INT?>=?19)?{????????????View?decorView?=?getWindow().getDecorView();????????????decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE????????????????????????????|?View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION????????????????????????????|?View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN????????????????????????????|?View.SYSTEM_UI_FLAG_HIDE_NAVIGATION????????????????????????????|?View.SYSTEM_UI_FLAG_FULLSCREEN????????????????????????????|?View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);????????}????}}
其實這段代碼需要我們關(guān)心的就是if (Build.VERSION.SDK_INT >= 28)的這個邏輯判斷中的內(nèi)容,在這里我們將當前Activity的layoutInDisplayCutoutMode屬性指定成LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES,這樣就可以讓應用程序的內(nèi)容延伸到劉海區(qū)域了。其他部分的代碼都是用于設置沉浸式模式的,關(guān)于這部分內(nèi)容我就不展開進行介紹了,更多沉浸式模式的講解,可以參考我之前的這篇文章 Android沉浸式狀態(tài)欄完全解析 。
現(xiàn)在重新運行一下程序,效果如下圖所示。
可以看到,程序進入了全屏沉浸式體驗的效果,并且我們在布局文件中設置的背景圖是可以延伸到劉海區(qū)域的,這就使得手機屏幕的空間得到了更充分的利用。
現(xiàn)在旋轉(zhuǎn)一下手機屏幕,效果如下圖所示:
很明顯,這比之前在劉海區(qū)域空出一條大黑邊的用戶體驗要好上太多了。
不過,雖然現(xiàn)在我們已經(jīng)實現(xiàn)了讓應用程序的內(nèi)容延伸到劉海區(qū)域的功能,卻無法保證劉海部分不會影響到應用程序的正常使用。什么意思呢?假設你正在玩一款游戲,劉海區(qū)域遮擋掉了一小塊游戲背景地圖,這可能并不會造成什么大的影響,你還是可以正常進行游戲。但如果劉海區(qū)域剛好遮擋掉的是一個攻擊按鈕,那么這款游戲你就完全無法玩了。因此,對于任何應用程序或者是游戲而言,都需要在這方面進行適配,保證自己的可交互控件絕對不能被劉海區(qū)域遮擋住。
那么具體應該如何實現(xiàn)這個功能呢?Android在9.0系統(tǒng)中提供了一套專門用于獲取安全顯示區(qū)域的API,我們只需要確認出哪些位置是有可能被遮擋到的,然后對可交互控件進行相應的位置偏移就可以了,示例代碼如下所示:
if?(Build.VERSION.SDK_INT?>=?28)?{????rootLayout.setOnApplyWindowInsetsListener(new?View.OnApplyWindowInsetsListener()?{????????@Override????????public?WindowInsets?onApplyWindowInsets(View?view,?WindowInsets?windowInsets)?{????????????DisplayCutout?displayCutout?=?windowInsets.getDisplayCutout();????????????if?(displayCutout?!=?null)?{????????????????int?left?=?displayCutout.getSafeInsetLeft();????????????????int?top?=?displayCutout.getSafeInsetTop();????????????????int?right?=?displayCutout.getSafeInsetRight();????????????????int?bottom?=?displayCutout.getSafeInsetBottom();????????????}????????????return?windowInsets.consumeSystemWindowInsets();????????}????});}
可以看到,這里先在最外層布局上調(diào)用了setOnApplyWindowInsetsListener()方法,然后當發(fā)生了onApplyWindowInsets()回調(diào)時,就立即獲取DisplayCutout對象,接著從這個對象中分別取出上下左右所應當偏移的距離即可。
下面我們還是通過具體的Demo來體驗一下實際的效果。首先在activity_main.xml看加入兩個按鈕,一個在頂部,一個在側(cè)邊,如下所示:
<FrameLayout?xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/rootLayout"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@drawable/bg">????<Buttonandroid:id="@+id/topButton"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:text="頂部可交互控件"/>????<Buttonandroid:id="@+id/sideButton"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_vertical"android:text="側(cè)邊可交互控件"/>FrameLayout>
那么如果現(xiàn)在直接運行程序的話會是什么樣的結(jié)果呢?我只能說,這一定不是你想要的,如下圖所示。
可以看到,頂部可交互控件被手機劉海遮擋掉了大半,這種用戶體驗是非常差的。
而橫屏情況下的結(jié)果也好不到哪兒去,如下圖所示。
這次變成了側(cè)邊可交互控件被劉海遮擋了,所以這兩種情況我們都必須要進行適配。
好在適配的方法并不復雜,使用前面介紹的那段示例代碼即可輕松完成適配工作。現(xiàn)在修改MainActivity中的代碼,如下所示:
public?class?MainActivity?extends?AppCompatActivity?{????@Override????protected?void?onCreate(Bundle?savedInstanceState)?{????????……????????final?FrameLayout?rootLayout?=?findViewById(R.id.rootLayout);????????final?Button?topButton?=?findViewById(R.id.topButton);????????final?Button?sideButton?=?findViewById(R.id.sideButton);????????if?(Build.VERSION.SDK_INT?>=?28)?{????????????rootLayout.setOnApplyWindowInsetsListener(new?View.OnApplyWindowInsetsListener()?{????????????????@Override????????????????public?WindowInsets?onApplyWindowInsets(View?view,?WindowInsets?windowInsets)?{????????????????????DisplayCutout?displayCutout?=?windowInsets.getDisplayCutout();????????????????????if?(displayCutout?!=?null)?{????????????????????????int?left?=?displayCutout.getSafeInsetLeft();????????????????????????int?top?=?displayCutout.getSafeInsetTop();????????????????????????int?right?=?displayCutout.getSafeInsetRight();????????????????????????int?bottom?=?displayCutout.getSafeInsetBottom();????????????????????????FrameLayout.LayoutParams?topParams?=?(FrameLayout.LayoutParams)?topButton.getLayoutParams();????????????????????????topParams.setMargins(left,?top,?right,?bottom);????????????????????????FrameLayout.LayoutParams?sideParams?=?(FrameLayout.LayoutParams)?sideButton.getLayoutParams();????????????????????????sideParams.setMargins(left,?top,?right,?bottom);????????????????????}????????????????????return?windowInsets.consumeSystemWindowInsets();????????????????}????????????});????????}????}????……}
這段代碼并沒有什么難理解的地方,和剛才所講解的示例代碼是差不多的。只是在得到上下左右方向上的偏移距離之后,我們通過給按鈕的layout設置margin的方式來讓控件在四個方向上進行相應的偏移。如果你是在開發(fā)游戲的話,也可以同樣套用這段代碼,只是在獲取到相應的偏移距離之后,將這幾個值傳遞給游戲?qū)舆壿嫾纯?#xff0c;由游戲?qū)觼砜刂迫绾螌山换サ目丶M行偏移。
現(xiàn)在來重新運行一下代碼吧,豎屏模式下的結(jié)果如下圖所示。
可以看到,頂部可交互控件自動向下偏移了一段距離,剛好可以保證不被劉海區(qū)域遮擋到。
那么再來看一下橫屏模式下的結(jié)果吧,如下圖所示。
沒有問題,橫屏模式下側(cè)邊可交互控件自動向右偏移了一段距離,從而也不會被劉海區(qū)域遮擋到了。
不過你會發(fā)現(xiàn),在橫屏模式下,頂部可交互控件并沒有處于屏幕中間的位置,這是因為屏幕的左側(cè)存在劉海,因此DisplayCutout會告訴我們要向左偏移一定的距離。但是我們并沒有判斷哪些控件需要偏移,哪些控件不需要偏移,而是直接將所有控件都進行偏移,才出現(xiàn)了這種沒有居中對齊的情況。
至于解決辦法其實并沒有什么簡單的方式,就是增加邏輯判斷即可,在橫屏模式下我們可以斷定頂部可交互控件是絕對不可能被劉海遮擋到的,因此只需要對側(cè)邊可交互控件進行偏移即可,具體的代碼我就不再進行演示了。
最后,雖然整篇文章我都一直在使用張雨綺”式的劉海來進行演示,但是我們所使用這種適配方案,是可以保證在任何劉海模式下,你的可交互控件都不會被遮擋到的,感興趣的話你可以自行切換成其他的劉海模式來進行更多的測試。
好的,關(guān)于Android 9.0系統(tǒng)劉海屏的適配就講到這里,相信你已經(jīng)可以完全掌握了。
?推薦↓↓↓?
長
按
關(guān)
注
?【16個技術(shù)公眾號】都在這里!
涵蓋:程序員大咖、源碼共讀、程序員共讀、數(shù)據(jù)結(jié)構(gòu)與算法、黑客技術(shù)和網(wǎng)絡安全、大數(shù)據(jù)科技、編程前端、Java、Python、Web編程開發(fā)、Android、iOS開發(fā)、Linux、數(shù)據(jù)庫研發(fā)、幽默程序員等。
萬水千山總是情,點個 “在看” 行不行總結(jié)
以上是生活随笔為你收集整理的android屏幕适配的五种方式_讲一讲Android 9.0系统的新特性,对刘海屏设备进行适配...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: “鸿蒙智行”来袭
- 下一篇: 小鹏汽车:P7、G9 车型 10 月销量