Android再探全面屏適配示例詳解
前言
簡單來說,以前是做app的,然后轉(zhuǎn)去做了終端幾年,現(xiàn)在又做回了app,然后就涉及到了全面屏的適配,但是很多年前做的適配也不記得了,所以來重新再探究一遍。
以前做終端的時(shí)候,適配?我不知道什么叫適配,就一個(gè)機(jī)型,想怎么玩就怎么玩,自己就是爹?,F(xiàn)在做應(yīng)用,不好意思,手機(jī)廠商才是大爹,我們都是孫子。
我簡單的回顧了一下,其實(shí)全面屏的適配一開始是因?yàn)閯⒑F敛砰_始這條路線,然后就出現(xiàn)一大堆奇奇怪怪的東西。幸好谷歌也是做人,在28之后就提出一套規(guī)范。
Android P前后
對于Android P,其實(shí)也就android 8.0和android 9.0兩個(gè)版本,因?yàn)槭菑腶ndroid 8.0開始流行的,各做各的,然后在9.0的時(shí)候google給出了一套規(guī)范。
對于Android 9.0也就是28,google推出了DisplayCutout,它統(tǒng)一了android凹凸屏的處理,使用起來也很方便。
WindowManager.LayoutParams wlp = getWindow().getAttributes(); wlp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; getWindow().setAttributes(wlp);
給WindowManager.LayoutParams設(shè)置layoutInDisplayCutoutMode就行,是不是很簡單。
它有幾個(gè)參數(shù)可供選擇
(1)LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT:默認(rèn)值,一般效果和LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER相同。
(2)LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES:內(nèi)容顯示到凹凸屏區(qū)域。
(3)LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER:內(nèi)容不會顯示到凹凸屏區(qū)域。
對于Android 28以下的適配
這個(gè)比較麻煩,因?yàn)樵?8以下是沒有l(wèi)ayoutInDisplayCutoutMode的,所以要單獨(dú)去調(diào),網(wǎng)上也有很多說如何去對不同的廠商去做適配,但其實(shí)這東西還是要調(diào)的。哪怕你是相同的機(jī)型,不同的系統(tǒng)版本都可能會產(chǎn)生不同的效果,沒錯(cuò),就是這么恐怖。基本都是只能做if-else單獨(dú)對不同的機(jī)型做適配。要么就是讓28以下的統(tǒng)一不做全面屏的效果,比如說把內(nèi)容顯示到凹凸屏區(qū)域,你就判斷在28的時(shí)候不做這種操作,但一般不是你說的算,多多少少還是需要做適配,只能具體情況具體調(diào)試。
對不同的場景做適配
你覺得你說你就對28做適配,28以下就不管了,我就設(shè)置layoutInDisplayCutoutMode一行代碼就行??墒虑槟挠羞@么簡單。
系統(tǒng)的Bar主要分為3種,一種是在屏幕上方的狀態(tài)欄,一種是在屏幕底端的導(dǎo)航欄,還是一直是仿IOS的底部橫條代替導(dǎo)航欄,這在和導(dǎo)航欄一起分析但會有些許不同。
而這個(gè)過程中又會區(qū)分為橫屏和豎屏的情況,多少也會又些許差異,當(dāng)然我也沒辦法把全部特殊的常見列舉出來。不同的手機(jī)廠商之間也會存在有不同的情況,還有上面說的android28前后,這里主要是對android28之后進(jìn)行分析。
狀態(tài)欄
假如要實(shí)現(xiàn)全屏顯示的效果,我們要如何去對狀態(tài)欄做適配。
為了方便調(diào)試,我把window的顏色設(shè)置為橙色,把布局的顏色設(shè)置成綠色
<style name="TestTheme" parent="@android:style/Theme.Material.Light.NoActionBar.Fullscreen" > <item name="android:windowBackground">@android:color/holo_orange_light</item> </style>
<activity android:name=".TestActivity" android:configChanges="orientation|screenSize|keyboardHidden|smallestScreenSize|screenSize|screenLayout" android:theme="@style/TestTheme" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#336655" > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="用于參考位置" /> </FrameLayout>
然后我們直接運(yùn)行看看效果
上面狀態(tài)欄的顏色其實(shí)是我的桌面背景,這里看不太清楚,我放一張我桌面的背景上來
能看到,內(nèi)容是不會顯示在狀態(tài)欄的空間的。根據(jù)上面提到的,我們用layoutInDisplayCutoutMode試試
public class TestActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { WindowManager.LayoutParams wlp = getWindow().getAttributes(); wlp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; getWindow().setAttributes(wlp); } } }
看看效果
能看到狀態(tài)欄顯示橙色,這說明window的內(nèi)容已經(jīng)能覆蓋到狀態(tài)欄了,但是顯示的內(nèi)容還是沒上去。
難道這個(gè)屬性不行?當(dāng)然不是。這個(gè)不知道怎么解釋好,你可以簡單理解成窗口是已經(jīng)能顯示到狀態(tài)欄的區(qū)域了,但是view因?yàn)槟承┠J(rèn)的配置導(dǎo)致距離頂部有一定的間距。
相信大家多多少少聽過沉浸模式,我之前寫的軟鍵盤沖突也有涉及到一點(diǎn)這塊,view有一個(gè)方法SystemUiVisibility,它可以設(shè)置一些屬性,其實(shí)這是一個(gè)int值的flags,你把它想象成window的flags就好理解多了。
通過setSystemUiVisibility方法,我們對View做一些配置,就按網(wǎng)上寫的,沉浸模式
public class TestActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { WindowManager.LayoutParams wlp = getWindow().getAttributes(); wlp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; getWindow().setAttributes(wlp); getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); } } }
看看效果
可以看到,內(nèi)容就正常顯示到上面了。我覺得講得已經(jīng)挺詳細(xì)了,好,那提兩個(gè)問題:如果我不設(shè)layoutInDisplayCutoutMode,只設(shè)View的SystemUiVisibility,能不能顯示到狀態(tài)欄的區(qū)域?第二個(gè),我設(shè)了layoutInDisplayCutoutMode,是不是這個(gè)window里面的所有view都必須設(shè)置SystemUiVisibility才能把內(nèi)容顯示到狀態(tài)欄區(qū)域?
這個(gè)是實(shí)現(xiàn)一個(gè)把內(nèi)容顯示到狀態(tài)欄的效果,還有其它效果比如設(shè)置狀態(tài)欄顏色或者什么的,這里就不講了,但要注意,如果你在設(shè)置某些效果的過程中,沒效果,可以考慮一下是不是手機(jī)廠商造成的,多試幾個(gè)廠商。
對了,還有橫屏的情況,橫屏的情況下狀態(tài)欄還是在頂部,但是劉海區(qū)域(凹凸區(qū)域)在側(cè)邊,layoutInDisplayCutoutMode是對側(cè)邊的凹凸區(qū)域生效。所以要知道它不是針對狀態(tài)欄的,是針對凹凸區(qū)域的。
導(dǎo)航欄和底部橫條
這個(gè)就比狀態(tài)欄麻煩一些。這里就主要以橫屏的情況去講解。橫屏的情況下導(dǎo)航欄一共有3種顯示情況,例如小米的橫條,就是顯示在底部,其它手機(jī)的導(dǎo)航欄就是顯示在側(cè)邊,還有一種是側(cè)邊的情況下,不管你怎么轉(zhuǎn)屏,都會固定顯示在右邊。
導(dǎo)航欄沒有凹凸區(qū)域,所以不需要用到windoow lp的layoutInDisplayCutoutMode,我們一般都能直接通過view的SystemUiVisibility方法去配置實(shí)現(xiàn)它的一個(gè)想要的效果。
可以先看看SystemUiVisibility能設(shè)置的一些常用的flags
public static final int SYSTEM_UI_FLAG_VISIBLE = 0; // 系統(tǒng)UI(狀態(tài)欄導(dǎo)航欄)顯示 public static final int SYSTEM_UI_FLAG_LOW_PROFILE = 0x00000001; // 低調(diào)模式(就是類似于變暗等效果) public static final int SYSTEM_UI_FLAG_HIDE_NAVIGATION = 0x00000002; // 隱藏導(dǎo)航欄和橫條 public static final int SYSTEM_UI_FLAG_FULLSCREEN = 0x00000004; // 全屏模式,系統(tǒng)UI會被隱藏 public static final int SYSTEM_UI_FLAG_LAYOUT_STABLE = 0x00000100; // 穩(wěn)定布局 public static final int SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION = 0x00000200; // 對窗口生效SYSTEM_UI_FLAG_HIDE_NAVIGATION public static final int SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN = 0x00000400; // 對窗口生效SYSTEM_UI_FLAG_FULLSCREEN public static final int SYSTEM_UI_FLAG_IMMERSIVE = 0x00000800; // 讓SYSTEM_UI_FLAG_HIDE_NAVIGATION失效 public static final int SYSTEM_UI_FLAG_IMMERSIVE_STICKY = 0x00001000; // 同上相反
關(guān)于SYSTEM_UI_FLAG_LAYOUT_STABLE穩(wěn)定布局,官方有一段注釋是這么說的,如果指定SYSTEM_UI_FLAG_LAYOUT_FULLSEEN和SYSTEM_UI-FLAG_LAYOUT _HIDE_NAVIGATION,則可以使用穩(wěn)定的布局轉(zhuǎn)換到SYSTEM_UI_FLAG_FULLSCREEN和SYSTEM _UI_FLAG-HIDE_NAVIGATION。(請注意,應(yīng)避免單獨(dú)使用SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION。)如果已將窗口標(biāo)志W(wǎng)indowManager.LayoutParams.FLAG_FULLSCREEN設(shè)置為隱藏狀態(tài)欄(而不是使用SYSTEM.UI_FLAG_FULLSCREEN),則隱藏的狀態(tài)欄將被視為“穩(wěn)定”狀態(tài)。
這什么意思呢?意思就是這個(gè)屬性不是死的,它會受其它東西影響到最終的顯示效果。說人話就是你用它的話實(shí)現(xiàn)的效果就很靈活,但同樣會出BUG的概率也更高。
關(guān)于SYSTEM_UI_FLAG_IMMERSIVE讓SYSTEM_UI_FLAG_HIDE_NAVIGATION失效,官方有一段注釋是這么說的:由于此標(biāo)志是SYSTEM_UI_FLAG_HIDE_NAVIGATION的修飾符,因此僅當(dāng)與該標(biāo)志結(jié)合使用時(shí)才具有效果。它所表現(xiàn)出來的效果是SYSTEM_UI_FLAG_HIDE_NAVIGATION會隱藏導(dǎo)航欄,但當(dāng)你手動(dòng)拉出導(dǎo)航欄之后,就不會再隱藏了。
但這也涉及到不同的手機(jī)廠商可能會出現(xiàn)不同的效果。比如小米,默認(rèn)的顯示會是這樣
可以看出,橫條是顯示在底部,側(cè)邊沒有導(dǎo)航欄,然后我們一個(gè)一個(gè)flags來調(diào)看看效果。首先SYSTEM_UI_FLAG_LAYOUT_STABLE這種情況我是不調(diào)了,很難去模擬出來。SYSTEM_UI_FLAG_VISIBLE和SYSTEM_UI_FLAG_LOW_PROFILE效果在這里顯示的應(yīng)該會和默認(rèn)情況顯示的一樣。
這里我會模擬兩種場景來說,第一種是純activity的window的view設(shè)置的效果,第二種是window.addview的view設(shè)置的效果(我后續(xù)會稱之為兩層window),它們的效果會有些許差異
1. SYSTEM_UI_FLAG_HIDE_NAVIGATION
先看看SYSTEM_UI_FLAG_HIDE_NAVIGATION,假如我設(shè)置了SYSTEM_UI_FLAG_HIDE_NAVIGATION
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
可以看到,底部橫條已經(jīng)不顯示了。這里可以多說一句,你們猜猜SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION對view管不管用,可以試試
只設(shè)置它能明顯看出底部的橫條還是存在的。\但是當(dāng)用window.addview之后再配置SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION的話,可以看看效果。
代碼是這樣的
public class TestActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); addWindow(); } private void addWindow() { WindowManager wm = getWindowManager(); WindowManager.LayoutParams wlp = new WindowManager.LayoutParams(); wlp.width = ViewGroup.LayoutParams.WRAP_CONTENT; wlp.height = ViewGroup.LayoutParams.WRAP_CONTENT; wlp.gravity = Gravity.START | Gravity.TOP; TextView textView = new TextView(this); textView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); textView.setText("上一層"); textView.setBackgroundResource(R.color.blue); wm.addView(textView, wlp); } }
看看效果,看到差異沒有,我們之前設(shè)置的話,它的橫條還在,并且是黑色的,現(xiàn)在有一個(gè)window然后我們設(shè)置之后發(fā)現(xiàn)橫條還在,但背景變成透明的了。
2. SYSTEM_UI_FLAG_FULLSCREEN
看看單獨(dú)設(shè)置SYSTEM_UI_FLAG_FULLSCREEN的效果
看得到效果也是一樣的。說明SYSTEM_UI_FLAG_FULLSCREEN是不會隱藏底部導(dǎo)航欄的。
但這個(gè)屬性是不是就沒用了,并不是,假如我使用兩層window的的情況,不設(shè)置這個(gè)參數(shù)的話,可以看到效果會是這樣的:狀態(tài)欄拉下來不會停留,會快速的縮回去
但如果我設(shè)置了,代碼寫成這樣
public class TestActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); addWindow(); } private void addWindow() { WindowManager wm = getWindowManager(); WindowManager.LayoutParams wlp = new WindowManager.LayoutParams(); wlp.width = ViewGroup.LayoutParams.WRAP_CONTENT; wlp.height = ViewGroup.LayoutParams.WRAP_CONTENT; wlp.gravity = Gravity.START | Gravity.TOP; TextView textView = new TextView(this); textView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); textView.setText("上一層"); textView.setBackgroundResource(R.color.blue); wm.addView(textView, wlp); textView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN); } }
能看到效果狀態(tài)欄下拉后就能正常的短暫停留才消失
3. SYSTEM_UI_FLAG_IMMERSIVE_STICKY
前面說了SYSTEM_UI_FLAG_IMMERSIVE_STICKY是配合SYSTEM_UI_FLAG_HIDE_NAVIGATION的,所以我們這里測試的時(shí)候,寫SYSTEM_UI_FLAG_HIDE_NAVIGATION。
直接是單層window去設(shè)置的話是看不出明顯效果的,比如代碼這樣
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); // addWindow(); }
我把第二層給注釋掉,然后給第一層設(shè)置SYSTEM_UI_FLAG_HIDE_NAVIGATION,不管設(shè)不設(shè)置SYSTEM_UI_FLAG_IMMERSIVE_STICKY,它在我這臺手機(jī),表現(xiàn)都一樣。我強(qiáng)調(diào)“我這臺手機(jī)”,是因?yàn)榭赡軙煌瑱C(jī)型有不同的表現(xiàn)。
然后我們用兩層去做看看效果,代碼這樣
public class TestActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); addWindow(); } private void addWindow() { WindowManager wm = getWindowManager(); WindowManager.LayoutParams wlp = new WindowManager.LayoutParams(); wlp.width = ViewGroup.LayoutParams.WRAP_CONTENT; wlp.height = ViewGroup.LayoutParams.WRAP_CONTENT; wlp.gravity = Gravity.START | Gravity.TOP; TextView textView = new TextView(this); textView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); textView.setText("上一層"); textView.setBackgroundResource(R.color.blue); wm.addView(textView, wlp); textView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); } }
不設(shè)置SYSTEM_UI_FLAG_IMMERSIVE_STICKY的情況下,我這里沒辦法錄gif(因?yàn)辄c(diǎn)了就會影響),它的效果是,一開始底部橫條是隱藏的,但是當(dāng)點(diǎn)擊之后會顯示并且顯示之后就不再隱藏。
設(shè)置之后
public class TestActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); addWindow(); } private void addWindow() { WindowManager wm = getWindowManager(); WindowManager.LayoutParams wlp = new WindowManager.LayoutParams(); wlp.width = ViewGroup.LayoutParams.WRAP_CONTENT; wlp.height = ViewGroup.LayoutParams.WRAP_CONTENT; wlp.gravity = Gravity.START | Gravity.TOP; TextView textView = new TextView(this); textView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); textView.setText("上一層"); textView.setBackgroundResource(R.color.blue); wm.addView(textView, wlp); textView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); } }
可以看看效果
看到底部橫條會變透明,然后拉出來一段時(shí)間之后會自己隱藏起來。PS:看到這里狀態(tài)欄縮回去很快,是因?yàn)闆]設(shè)置SYSTEM_UI_FLAG_FULLSCREEN
SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN的效果是什么的?
這里我就補(bǔ)貼代碼直接讓你們看看現(xiàn)象吧,如果不設(shè)置的話,表現(xiàn)是這樣的
可以看出我切出去之后,再回來,第二層的window會被位移(或者說擠壓)再恢復(fù)。但如果設(shè)置的話就不會有這種問題。
總結(jié)
做個(gè)簡單的總結(jié),這里主要是單獨(dú)拆開詳細(xì)講了狀態(tài)欄和導(dǎo)航欄的一些適配場景,首先是想讓大家先有個(gè)概念,重點(diǎn)是想說,即便google在android28中統(tǒng)一了規(guī)范,但還是會出現(xiàn)不同的廠商甚至不同的型號,不同的系統(tǒng)版本,所體現(xiàn)出的效果不同。
我這里也總結(jié)了一些手機(jī)的表現(xiàn),因?yàn)檫@里寫得確實(shí)有點(diǎn)長,所以就之后單獨(dú)再些一篇出來總結(jié)。
以上就是Android再探全面屏適配示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Android全面屏適配的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
android實(shí)現(xiàn)程序自動(dòng)升級到安裝示例分享(下載android程序安裝包)
這篇文章主要介紹了android實(shí)現(xiàn)下載android程序安裝包自動(dòng)升級的示例,大家參考使用吧2014-01-01Flutter實(shí)現(xiàn)網(wǎng)絡(luò)請求的方法示例
這篇文章主要介紹了Flutter實(shí)現(xiàn)網(wǎng)絡(luò)請求的方法示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-03-03Android kotlin RecyclerView遍歷json實(shí)現(xiàn)列表數(shù)據(jù)的案例
這篇文章主要介紹了Android kotlin RecyclerView遍歷json實(shí)現(xiàn)列表數(shù)據(jù)的案例,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2024-08-08Android 登錄頁面的實(shí)現(xiàn)代碼(密碼顯示隱藏、EditText 圖標(biāo)切換、限制輸入長度)
這篇文章主要介紹了Android 登錄頁面的實(shí)現(xiàn)代碼(密碼顯示隱藏、EditText 圖標(biāo)切換、限制輸入長度),本文通過兩種方法給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-08-08Android實(shí)現(xiàn)簡潔的APP登錄界面
這篇文章主要為大家詳細(xì)介紹了Android簡潔登錄界面的編寫代碼,實(shí)現(xiàn)簡單的登錄,用戶名密碼驗(yàn)證功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04Kotlin ViewModelProvider.Factory的使用實(shí)例詳解
這篇文章主要介紹了Kotlin ViewModelProvider.Factory的使用,在我們使用 ViewModel 的時(shí)候,我們會發(fā)現(xiàn),有的時(shí)候我們需要用到 ViewModelFactory,有的時(shí)候不需要2023-02-02