久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

Android劉海屏適配全方案(華為,、小米,、Vivo、Oppo)

 西北望msm66g9f 2019-03-26

多點(diǎn)在看,,就是真愛(ài),!

作者 :  快樂(lè)丸
轉(zhuǎn)載自 :https://www.jianshu.com/p/8ead0701d8ef

前言

目前市面上的劉海屏和水滴屏手機(jī)越來(lái)越多了,顏值方面是因人而異,有的人覺(jué)得很好看,,也有人覺(jué)得丑爆了,,我個(gè)人覺(jué)得是還可以。但是作為移動(dòng)開(kāi)發(fā)者來(lái)說(shuō),,這并不是一件好事,,越來(lái)越多異形屏手機(jī)的出現(xiàn)意味著我們需要投入大量精力在適配上(就不提之后會(huì)出的折疊屏手機(jī)了)。本文總結(jié)了當(dāng)下主流手機(jī)的劉海屏適配方案,,鑒于目前Android碎片化的情況,,想要覆蓋所有的機(jī)型是不可能的,但是能適配一些是一些,,總比什么都不做要好,。

所謂劉海屏,指的是手機(jī)屏幕正上方由于追求極致邊框而采用的一種手機(jī)解決方案,。因形似劉海兒而得名——來(lái)自百度百科,,水滴屏也是類似,為了簡(jiǎn)單起見(jiàn),,下文就統(tǒng)稱這兩種為劉海屏了,。

什么時(shí)候需要適配

這里先上一張官方的圖

從圖中可以看出,劉海區(qū)域是鑲嵌在狀態(tài)欄內(nèi)部的,,劉海區(qū)域的高度一般是不超過(guò)狀態(tài)欄高度的,。因此,當(dāng)我們的應(yīng)用布局需要占據(jù)狀態(tài)欄來(lái)顯示時(shí),,就需要考慮到劉海區(qū)域是否會(huì)遮擋住頁(yè)面上的控件或者背景,,這就是為什么將狀態(tài)欄區(qū)域稱為危險(xiǎn)區(qū)域。如果應(yīng)用不需要占據(jù)狀態(tài)欄顯示,,全部顯示在安全區(qū)域內(nèi),那么恭喜你,,不需要做任何適配處理,。總結(jié)來(lái)說(shuō),,只有當(dāng)應(yīng)用需要全屏顯示時(shí)才需要進(jìn)行適配,。
全屏顯示無(wú)非就是兩種情況:第一種是我們常說(shuō)的沉浸式狀態(tài)欄,也就是狀態(tài)欄透明,,頁(yè)面的布局延伸到狀態(tài)欄顯示,,這種情況下?tīng)顟B(tài)欄依然可見(jiàn);第二種是類似應(yīng)用的閃屏頁(yè)風(fēng)格,,頁(yè)面全屏顯示,,狀態(tài)欄不可見(jiàn)。這兩種情況下如果不進(jìn)行適配處理都會(huì)產(chǎn)生一些問(wèn)題。
先來(lái)看第一種情況,,沉浸式風(fēng)格,。需要將狀態(tài)欄設(shè)置為透明,需要注意只有在Android 4.4(API Level 19)以上才支持設(shè)置透明狀態(tài)欄,。有兩種設(shè)置方法:
方法一:為Activity設(shè)置style,,添加一個(gè)屬性:

<item name='android:windowTranslucentStatus'>true</item>

方法二:在Activity的onCreate()中為Window添加Flag

public class ImmersiveActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_immersive);
        // 透明狀態(tài)欄
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            getWindow().addFlags(
                    WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
        }
    }
}

頁(yè)面的布局很簡(jiǎn)單,只包含一個(gè)按鈕,,為了明顯,,我為根布局設(shè)置了一個(gè)背景。
activity_immersive.xml

<?xml version='1.0' encoding='utf-8'?>
<LinearLayout xmlns:android='http://schemas./apk/res/android'
    android:id='@+id/ll_root'
    android:layout_width='match_parent'
    android:layout_height='match_parent'
    android:background='@mipmap/bg'
    android:orientation='vertical'>


    <Button
        android:layout_width='150dp'
        android:layout_height='wrap_content'
        android:layout_gravity='center_horizontal' />


</LinearLayout>

運(yùn)行之后發(fā)現(xiàn)按鈕會(huì)被劉海區(qū)域所遮擋,,如圖所示:

再說(shuō)第二種情況,,全屏風(fēng)格,狀態(tài)欄不可見(jiàn),。同樣有兩種設(shè)置方法:
方法一:為Activity設(shè)置style,,添加屬性:

<item name='android:windowFullscreen'>true</item>
<!-- 這里為了簡(jiǎn)單,直接從style中指定一個(gè)背景 -->
<item name='android:windowBackground'>@mipmap/bg</item>

方法二:在Activity的OnCreate()中添加代碼:

public class FullScreenActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 全屏顯示
        getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN |
                View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
    }
}

補(bǔ)充說(shuō)明一點(diǎn),,現(xiàn)在的手機(jī)屏幕高寬比例越來(lái)越大,,我們還需要額外做一下適配才能使應(yīng)用在所有手機(jī)上都能全屏顯示,具體方式有兩種:
方式一:在AndroidManifest.xml中配置支持最大高寬比

<meta-data android:name='android.max_aspect'  
    android:value='ratio_float' />

或者

 android:maxAspectRatio='ratio_float' (API LEVEL 26)

說(shuō)明:以上兩種接口可以二選一,,ratio_float = 屏幕高 / 屏幕寬 (如oppo新機(jī)型屏幕分辨率為2280 x 1080,, ratio_float = 2280 / 1080 = 2.11,建議設(shè)置 ratio_float為2.2或者更大)
方式二:在AndroidManifest.xml中配置支持分屏,,注意驗(yàn)證分屏下界面兼容性

android:resizeableActivity='true'  

也可以通過(guò)設(shè)置targetSdkVersion>=24(即Android 7.0),該屬性的值會(huì)默認(rèn)為true,,就不需要在AndroidManifest.xml中配置了。
運(yùn)行之后,,我們發(fā)現(xiàn)狀態(tài)欄的部分留出了一條黑邊,,看上起很奇怪,這顯然不是我們想要的效果,。

如何適配

上文中已經(jīng)展示了劉海屏中全屏顯示帶來(lái)的問(wèn)題,,那么如何去解決呢?

1.沉浸式狀態(tài)欄的適配

其實(shí)沉浸式狀態(tài)欄帶來(lái)的遮擋問(wèn)題與劉海屏無(wú)關(guān),,本質(zhì)上是由于設(shè)置了透明狀態(tài)欄導(dǎo)致布局延伸到了狀態(tài)欄中,,就算是不具有劉海屏,一定程度上也會(huì)造成布局的遮擋,。不過(guò)既然劉海屏是處在狀態(tài)欄當(dāng)中的,,那么我們就把這種情況也包含在劉海屏的適配中。清楚了原因之后,,解決起來(lái)就很簡(jiǎn)單了,,我們只需要讓控件或布局避開(kāi)狀態(tài)欄顯示就可以了,,具體的解決方法有三種。
方法一.利用fitsSystemWindows屬性
當(dāng)我們給最外層View設(shè)置了android:fitsSystemWindows='true'屬性后,,當(dāng)設(shè)置了透明狀態(tài)欄或者透明導(dǎo)航欄后,,就會(huì)自動(dòng)給View添加paddingTop或paddingBottom屬性,這樣就在屏幕上預(yù)留出了狀態(tài)欄的高度,,我們的布局就不會(huì)占用狀態(tài)欄來(lái)顯示了,。
activity_immersive.xml

<?xml version='1.0' encoding='utf-8'?>
<LinearLayout xmlns:android='http://schemas./apk/res/android'
    android:id='@+id/ll_root'
    android:layout_width='match_parent'
    android:layout_height='match_parent'
    android:background='@mipmap/bg'
    android:fitsSystemWindows='true'
    android:orientation='vertical'>


    <Button
        android:layout_width='150dp'
        android:layout_height='wrap_content'
        android:layout_gravity='center_horizontal' />


</LinearLayout>

方法二.根據(jù)狀態(tài)欄高度手動(dòng)設(shè)置paddingTop
這種方法的實(shí)現(xiàn)本質(zhì)上和設(shè)置fitsSystemWindows是一樣的,首先獲取狀態(tài)欄高度,,然后設(shè)置根布局的paddingTop等于狀態(tài)欄高度就可以了,,代碼如下:

public class ImmersiveActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_immersive);
        // 透明狀態(tài)欄
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            getWindow().addFlags(
                    WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
        }
        LinearLayout llRoot = findViewById(R.id.ll_root);
        // 設(shè)置根布局的paddingTop
        llRoot.setPadding(0, getStatusBarHeight(this), 00);
    }

    /**
     * 獲取狀態(tài)欄高度
     *
     * @param context
     * @return
     */

    public int getStatusBarHeight(Context context) {
        int statusBarHeight = 0;
        int resourceId = context.getResources().getIdentifier('status_bar_height''dimen''android');
        if (resourceId > 0) {
            statusBarHeight = context.getResources().getDimensionPixelSize(resourceId);
        }
        return statusBarHeight;
    }
}

方法三.在布局中添加一個(gè)和狀態(tài)欄高度相同的View
和前兩種方法原理類似,同樣是讓屏幕預(yù)留出狀態(tài)欄的高度,,這里在根布局中添加了一個(gè)透明的View,,高度和狀態(tài)欄高度相同。這種方法的好處是可以自定義填充狀態(tài)欄View的背景,,更靈活地實(shí)現(xiàn)我們想要的效果,。

public class ImmersiveActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_immersive);
        // 透明狀態(tài)欄
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            getWindow().addFlags(
                    WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
        }
        LinearLayout llRoot = findViewById(R.id.ll_root);
        View statusBarView = new View(this);
        statusBarView.setBackgroundColor(Color.TRANSPARENT);
        ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                getStatusBarHeight(this));
        // 在根布局中添加一個(gè)狀態(tài)欄高度的View
        llRoot.addView(statusBarView, 0, lp);
    }

    /**
     * 獲取狀態(tài)欄高度
     *
     * @param context
     * @return
     */

    public int getStatusBarHeight(Context context) {
        int statusBarHeight = 0;
        int resourceId = context.getResources().getIdentifier('status_bar_height''dimen''android');
        if (resourceId > 0) {
            statusBarHeight = context.getResources().getDimensionPixelSize(resourceId);
        }
        return statusBarHeight;
    }
}

適配之后成功地將控件避開(kāi)了狀態(tài)欄(危險(xiǎn)區(qū)域),如下圖所示:

2.全屏顯示的適配

對(duì)于全屏顯示的情況,,處理起來(lái)要相對(duì)麻煩一些,,下面重點(diǎn)說(shuō)一下這種情況下的適配方案。

2.1.Android P及以上

谷歌官方從Android P開(kāi)始給開(kāi)發(fā)者提供了劉海屏相關(guān)的API,,可以通過(guò)直接調(diào)用API來(lái)進(jìn)行劉海屏的適配處理,。
通過(guò)DisplayCutout類可以獲得安全區(qū)域的范圍以及劉海區(qū)域(官方的叫法是缺口)的信息,需要注意只有API Level在28及以上才可以調(diào)用,。

/**
 * 獲得劉海區(qū)域信息
 */

@TargetApi(28)
public void getNotchParams() {
    final View decorView = getWindow().getDecorView();
    if (decorView != null) {
        decorView.post(new Runnable() {
            @Override
            public void run() {
                WindowInsets windowInsets = decorView.getRootWindowInsets();
                if (windowInsets != null) {
                    // 當(dāng)全屏頂部顯示黑邊時(shí),,getDisplayCutout()返回為null
                    DisplayCutout displayCutout = windowInsets.getDisplayCutout();
                    Log.e('TAG''安全區(qū)域距離屏幕左邊的距離 SafeInsetLeft:' + displayCutout.getSafeInsetLeft());
                    Log.e('TAG''安全區(qū)域距離屏幕右部的距離 SafeInsetRight:' + displayCutout.getSafeInsetRight());
                    Log.e('TAG''安全區(qū)域距離屏幕頂部的距離 SafeInsetTop:' + displayCutout.getSafeInsetTop());
                    Log.e('TAG''安全區(qū)域距離屏幕底部的距離 SafeInsetBottom:' + displayCutout.getSafeInsetBottom());
                    // 獲得劉海區(qū)域
                    List<Rect> rects = displayCutout.getBoundingRects();
                    if (rects == null || rects.size() == 0) {
                        Log.e('TAG''不是劉海屏');
                    } else {
                        Log.e('TAG''劉海屏數(shù)量:' + rects.size());
                        for (Rect rect : rects) {
                            Log.e('TAG''劉海屏區(qū)域:' + rect);
                        }
                    }
                }
            }
        });
    }
}

這里我在測(cè)試時(shí)也發(fā)現(xiàn)了一個(gè)問(wèn)題,就是如果是在style中設(shè)置了全屏模式,,在適配之前,,頂部狀態(tài)欄區(qū)域顯示一條黑邊,這時(shí)候調(diào)用getDisplayCutout()獲取DisplayCutout對(duì)象返回的結(jié)果是null,,其實(shí)這也不難理解,,因?yàn)檫@時(shí)候是看不出劉海區(qū)域的,但是這樣會(huì)導(dǎo)致在適配之前無(wú)法通過(guò)DisplayCutout判斷是否存在劉海屏,,只能在適配后才能獲取到劉海區(qū)域信息,因此只能對(duì)于所有設(shè)備都添加適配代碼,。
那么接下來(lái)如何進(jìn)行適配呢,,Android P中增加了一個(gè)窗口布局參數(shù)屬性layoutInDisplayCutoutMode,該屬性有三個(gè)值可以?。?/p>

LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT:默認(rèn)的布局模式,,僅當(dāng)劉海區(qū)域完全包含在狀態(tài)欄之中時(shí),,才允許窗口延伸到劉海區(qū)域顯示,也就是說(shuō),,如果沒(méi)有設(shè)置為全屏顯示模式,,就允許窗口延伸到劉海區(qū)域,否則不允許,。
LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER:永遠(yuǎn)不允許窗口延伸到劉海區(qū)域,。
LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES:始終允許窗口延伸到屏幕短邊上的劉海區(qū)域,窗口永遠(yuǎn)不會(huì)延伸到屏幕長(zhǎng)邊上的劉海區(qū)域,。

還有一個(gè)LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS模式,,目前已經(jīng)被LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES所取代,不允許使用了,,這里就不提了,。
這么看可能還是有些不理解,接下來(lái)我們?cè)谝粋€(gè)全屏顯示的頁(yè)面分別設(shè)置三種布局模式,,看看有什么區(qū)別,。

public class FullScreenActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            WindowManager.LayoutParams lp = getWindow().getAttributes();
            // 僅當(dāng)缺口區(qū)域完全包含在狀態(tài)欄之中時(shí),才允許窗口延伸到劉海區(qū)域顯示
//            lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
            // 永遠(yuǎn)不允許窗口延伸到劉海區(qū)域
//            lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
            // 始終允許窗口延伸到屏幕短邊上的劉海區(qū)域
            lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
            getWindow().setAttributes(lp);
        }
    }
}

三種模式下的顯示效果如下圖所示:

LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT

LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER

LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES

可以看出,,當(dāng)在全屏顯示情況下,,LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT和LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER的效果是一樣的,都是在狀態(tài)欄顯示一條黑邊,,也就是不允許窗口布局延伸到劉海區(qū)域,,而LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES則允許窗口布局延伸到了劉海區(qū)域,這里需要注意是短邊劉海區(qū)域,,不過(guò)一般市面上的手機(jī)劉海區(qū)域都是在短邊上的,,我是沒(méi)見(jiàn)過(guò)劉海長(zhǎng)在“腰”上的,因此利用這個(gè)模式就實(shí)現(xiàn)適配了,。
通過(guò)之前沉浸式狀態(tài)欄的顯示效果可以看出,,LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT在此時(shí)是允許窗口布局延伸到劉海區(qū)域的,因此更證實(shí)了只有在全屏顯示的情況下該模式才不允許窗口布局延伸到劉海區(qū)域,。
適配后效果如下,,現(xiàn)在看起來(lái)就很舒服了:

我這里為了簡(jiǎn)單沒(méi)有添加任何控件,實(shí)際開(kāi)發(fā)中在全屏顯示后我們?nèi)匀恍枰紤]劉海區(qū)域是否會(huì)遮擋顯示的內(nèi)容和控件,,同樣需要避開(kāi)危險(xiǎn)區(qū)域來(lái)顯示,。做法和沉浸式狀態(tài)欄的適配相同,原理同樣是將布局下移,,預(yù)留出狀態(tài)欄的高度,,這里就不一一列舉了。

2.2 Android P以下

目前市面上的劉海屏手機(jī)可以說(shuō)是琳瑯滿目,,各大廠商都在追求極致的屏占比,,推出的新機(jī)型也基本上都有劉海屏,,針對(duì)Android P以下的手機(jī),我們只能依照各個(gè)廠商提供的適配方案來(lái)進(jìn)行適配,。我也查閱了網(wǎng)上的一些適配文章,,主要還是針對(duì)目前主流的手機(jī)品牌,本文總結(jié)了華為,、小米,、Vivo和Oppo的適配方案,其他品牌的手機(jī)之后有時(shí)間的話可能會(huì)再考慮,。

華為適配方案

華為官方提供的適配文檔:華為劉海屏手機(jī)安卓O版本適配指導(dǎo)
文檔中提供了很多劉海屏相關(guān)的方法,,這里就不一一列舉了,著重看一下我們需要用到的方法,。
判斷是否有劉海屏

 /**
 * 判斷是否有劉海屏
 *
 * @param context
 * @return true:有劉海屏,;false:沒(méi)有劉海屏
 */

public static boolean hasNotch(Context context) {
    boolean ret = false;
    try {
        ClassLoader cl = context.getClassLoader();
        Class HwNotchSizeUtil = cl.loadClass('com.huawei.android.util.HwNotchSizeUtil');
        Method get = HwNotchSizeUtil.getMethod('hasNotchInScreen');
        ret = (boolean) get.invoke(HwNotchSizeUtil);
    } catch (ClassNotFoundException e) {
        Log.e('test''hasNotchInScreen ClassNotFoundException');
    } catch (NoSuchMethodException e) {
        Log.e('test''hasNotchInScreen NoSuchMethodException');
    } catch (Exception e) {
        Log.e('test''hasNotchInScreen Exception');
    } finally {
        return ret;
    }
}

應(yīng)用頁(yè)面設(shè)置使用劉海區(qū)顯示
官方提供了兩種適配方案:
方案一.使用新增的meta-data屬性android.notch_support,在應(yīng)用的AndroidManifest.xml中增加meta-data屬性,,此屬性不僅可以針對(duì)Application生效,,也可以對(duì)Activity配置生效。
使用方式如下:

<meta-data android:name='android.notch_support' android:value='true'/>

可以在Application下添加,,意味著該應(yīng)用的所有頁(yè)面,,系統(tǒng)都不會(huì)做豎屏場(chǎng)景的特殊下移或者是橫屏場(chǎng)景的右移特殊處理。

<application
    android:allowBackup='true'
    android:icon='@mipmap/ic_launcher'
    android:label='@string/app_name'
    android:roundIcon='@mipmap/ic_launcher_round'
    android:supportsRtl='true'
    android:theme='@style/AppTheme'>
    <meta-data
        android:name='android.notch_support'
        android:value='true' />
     ...
</application>

也可以針對(duì)指定的Activity添加,,意味著可以針對(duì)單個(gè)頁(yè)面進(jìn)行劉海屏適配,,設(shè)置了該屬性的Activity系統(tǒng)將不會(huì)做特殊處理。

<!-- 全屏顯示頁(yè)面 -->
<activity
    android:name='.ui.FullScreenActivity'
    android:screenOrientation='portrait'
    android:theme='@style/FullScreenTheme'>

    <meta-data
        android:name='android.notch_support'
        android:value='true' />

</activity>

方案二.使用給window添加新增的FLAG_NOTCH_SUPPORT
代碼如下:

/**
 * 設(shè)置應(yīng)用窗口在劉海屏手機(jī)使用劉海區(qū)
 * <p>
 * 通過(guò)添加窗口FLAG的方式設(shè)置頁(yè)面使用劉海區(qū)顯示
 *
 * @param window 應(yīng)用頁(yè)面window對(duì)象
 */

public static void setFullScreenWindowLayoutInDisplayCutout(Window window) {
    if (window == null) {
        return;
    }
    WindowManager.LayoutParams layoutParams = window.getAttributes();
    try {
        Class layoutParamsExCls = Class.forName('com.huawei.android.view.LayoutParamsEx');
        Constructor con = layoutParamsExCls.getConstructor(WindowManager.LayoutParams.class);
        Object layoutParamsExObj = con.newInstance(layoutParams);
        Method method = layoutParamsExCls.getMethod('addHwFlags'int.class);
        method.invoke(layoutParamsExObj, FLAG_NOTCH_SUPPORT);
    } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InstantiationException
            | InvocationTargetException e) {
        Log.e('test''hw add notch screen flag api error');
    } catch (Exception e) {
        Log.e('test''other Exception');
    }
}

官方提供的所有方法我已經(jīng)放到了工具類HwNotchUtils里,,可以根據(jù)需求來(lái)使用,。

小米適配方案

小米官方提供的適配文檔:https://dev.mi.com/console/doc/detail?pId=1293

我們同樣看一下關(guān)鍵方法。
判斷是否有劉海屏

/**
 * 判斷是否有劉海屏
 *
 * @param context
 * @return true:有劉海屏,;false:沒(méi)有劉海屏
 */

public static boolean hasNotch(Context context) {
    boolean ret = false;
    try {
        ClassLoader cl = context.getClassLoader();
        Class SystemProperties = cl.loadClass('android.os.SystemProperties');
        Method get = SystemProperties.getMethod('getInt', String.class, int.class);
        ret = (Integer) get.invoke(SystemProperties, 'ro.miui.notch'0) == 1;
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        return ret;
    }
}

應(yīng)用頁(yè)面設(shè)置使用劉海區(qū)顯示
小米提供的適配方案同樣有兩種(meta-data和Flag),,使用方法和華為類似。
方案一.Application級(jí)別的控制接口
在 Application 下增加一個(gè) meta-data,,用以聲明該應(yīng)用窗口是否可以延伸到狀態(tài)欄,。

<meta-data
    android:name='notch.config'
    android:value='portrait|landscape'/>

其中,value的值可以是以下四種

'none' 橫豎屏都不繪制耳朵區(qū)

'portrait' 豎屏繪制到耳朵區(qū)

'landscape' 橫屏繪制到耳朵區(qū)

'portrait|landscape' 橫豎屏都繪制到耳朵區(qū)

這里的耳朵區(qū)指的就是劉海區(qū)兩側(cè)的狀態(tài)欄區(qū)域

雖然官方文檔上說(shuō)的是Application級(jí)別的,,但是我覺(jué)得也可以針對(duì)某一個(gè)Activity來(lái)配置,,不過(guò)由于手頭上的手機(jī)條件不滿足,我并沒(méi)有驗(yàn)證,,如果有小伙伴測(cè)試過(guò)的話可以反饋一下,,我再修正一下這里的說(shuō)法。
方案二.Window級(jí)別的控制接口
通過(guò)給Window添加Flag也可以實(shí)現(xiàn)將窗口布局延伸到狀態(tài)欄中顯示,。

/*劉海屏全屏顯示FLAG*/
public static final int FLAG_NOTCH_SUPPORT = 0x00000100// 開(kāi)啟配置
public static final int FLAG_NOTCH_PORTRAIT = 0x00000200// 豎屏配置
public static final int FLAG_NOTCH_HORIZONTAL = 0x00000400// 橫屏配置

/**
 * 設(shè)置應(yīng)用窗口在劉海屏手機(jī)使用劉海區(qū)
 * <p>
 * 通過(guò)添加窗口FLAG的方式設(shè)置頁(yè)面使用劉海區(qū)顯示
 *
 * @param window 應(yīng)用頁(yè)面window對(duì)象
 */

public static void setFullScreenWindowLayoutInDisplayCutout(Window window) {
    // 豎屏繪制到耳朵區(qū)
    int flag = FLAG_NOTCH_SUPPORT | FLAG_NOTCH_PORTRAIT;
    try {
        Method method = Window.class.getMethod('addExtraFlags',
                int.class);
        method.invoke(window, flag);
    } catch (Exception e) {
        Log.e('test''addExtraFlags not found.');
    }
}

官方提供的所有方法我已經(jīng)放到了工具類XiaomiNotchUtils里,,可以根據(jù)需求來(lái)使用。
這里說(shuō)一下我的測(cè)試情況,,我是用小米8測(cè)試的,,系統(tǒng)版本已經(jīng)升到了Android P,利用小米官方提供的適配方法沒(méi)有效果,,只能用谷歌官方針對(duì)Android P的適配方案,,這一點(diǎn)小米的官方文檔也提到了。

至于Android P以下版本的小米手機(jī),,我并沒(méi)有測(cè)試,,如果有哪位大佬測(cè)試過(guò)了發(fā)現(xiàn)有問(wèn)題可以反饋一下。

Vivo,、Oppo適配方案

Vivo官方提供的適配文檔:Vivo全面屏應(yīng)用適配指南
Oppo官方提供的適配文檔:Oppo凹形屏適配指南
這里把Vivo和Oppo放在一起說(shuō),,官方提供的資料不像華為和小米那么詳細(xì),只是提供了判斷是否有劉海屏的方法,。
Vivo判斷是否有劉海屏

public static final int VIVO_NOTCH = 0x00000020// 是否有劉海
public static final int VIVO_FILLET = 0x00000008// 是否有圓角

/**
 * 判斷是否有劉海屏
 *
 * @param context
 * @return true:有劉海屏,;false:沒(méi)有劉海屏
 */

public static boolean hasNotch(Context context) {
    boolean ret = false;
    try {
        ClassLoader classLoader = context.getClassLoader();
        Class FtFeature = classLoader.loadClass('android.util.FtFeature');
        Method method = FtFeature.getMethod('isFeatureSupport'int.class);
        ret = (boolean) method.invoke(FtFeature, VIVO_NOTCH);
    } catch (ClassNotFoundException e) {
        Log.e('Notch''hasNotchAtVivo ClassNotFoundException');
    } catch (NoSuchMethodException e) {
        Log.e('Notch''hasNotchAtVivo NoSuchMethodException');
    } catch (Exception e) {
        Log.e('Notch''hasNotchAtVivo Exception');
    } finally {
        return ret;
    }
}

Oppo判斷是否有劉海屏

/**
 * 判斷是否有劉海屏
 *
 * @param context
 * @return true:有劉海屏;false:沒(méi)有劉海屏
 */

public static boolean hasNotch(Context context) {
    return context.getPackageManager().hasSystemFeature('com.oppo.feature.screen.heteromorphism');
}

至于全屏顯示的適配方案,,通過(guò)閱讀官方文檔和網(wǎng)上的其他適配文章,,我個(gè)人總結(jié)一下就是這兩種品牌的手機(jī)在設(shè)置全屏顯示時(shí)都無(wú)需做任何處理(前提是適配了全面屏,上文中提到過(guò)如何配置),,也就是不會(huì)產(chǎn)生黑邊,,我們只需要避免布局中的內(nèi)容或控件不被劉海區(qū)域所遮擋就可以了。具體的做法和沉浸式狀態(tài)欄的適配相同,,基本原理還是將窗口布局下移,,預(yù)留出狀態(tài)欄的高度。
注:由于手頭沒(méi)有這兩種廠商的手機(jī),,因此并沒(méi)有驗(yàn)證,,這一點(diǎn)確實(shí)是我做得不夠嚴(yán)謹(jǐn),有好心的大佬驗(yàn)證之后歡迎指正,。
其實(shí)我本來(lái)也想列出魅族的適配方案的,,但是實(shí)在是沒(méi)找到官方文檔。,。,。如果有知道的大佬可以提供一下,我后面會(huì)把適配方案補(bǔ)上,。
適配時(shí)的基本邏輯就是先判斷手機(jī)的品牌,,這里我利用了一個(gè)開(kāi)源工具類項(xiàng)目AndroidUtilCode,提供了一個(gè)獲取手機(jī)Rom信息的工具類RomUtils,,用起來(lái)很方便,,然后判斷是否是劉海屏,,針對(duì)劉海屏手機(jī)添加適配代碼。完整的適配代碼如下所示:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
    // Android P利用官方提供的API適配
    WindowManager.LayoutParams lp = getWindow().getAttributes();
    // 始終允許窗口延伸到屏幕短邊上的缺口區(qū)域
    lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
    getWindow().setAttributes(lp);
else {
    // Android P以下根據(jù)手機(jī)廠商的適配方案進(jìn)行適配
    if (RomUtils.isHuawei() && HwNotchUtils.hasNotch(this)) {
        HwNotchUtils.setFullScreenWindowLayoutInDisplayCutout(getWindow());
    } else if (RomUtils.isXiaomi() && XiaomiNotchUtils.hasNotch(this)) {
        XiaomiNotchUtils.setFullScreenWindowLayoutInDisplayCutout(getWindow());
    }
}

總結(jié)

雖然文中介紹了很多適配的內(nèi)容,,但其實(shí)在開(kāi)發(fā)中需要我們適配劉海屏的情況并不多,,只有兩種情況需要我們進(jìn)行考慮:
1.沉浸式狀態(tài)欄,窗口布局延伸到了狀態(tài)欄中,,是否會(huì)遮擋必要的內(nèi)容或控件(處在危險(xiǎn)區(qū)域),。適配方案就是將窗口布局下移,預(yù)留出狀態(tài)欄的空間,。
2.全屏顯示模式,,不做適配的話狀態(tài)欄會(huì)呈現(xiàn)一條黑邊。適配方案是首先判斷系統(tǒng)版本,,是Android P及以上就按照官方的API來(lái)適配,,否則根據(jù)手機(jī)廠商的適配方案進(jìn)行適配。鑒于目前市面上Android P還沒(méi)有普及,,為了帶來(lái)更好的用戶體驗(yàn),,我們還是需要多花一些精力來(lái)適配各個(gè)手機(jī)廠商的劉海屏手機(jī)。
最后提示一下,,本文只列出了四個(gè)當(dāng)下主流手機(jī)廠商的適配方案,,我自己驗(yàn)證過(guò)的只有華為和小米(只驗(yàn)證了Android P)的方案,對(duì)于Vivo和Oppo的一些結(jié)論我可能說(shuō)得不對(duì),,歡迎大家指正,。當(dāng)然,如果大家還需要其他廠商的適配方案,,也歡迎提出,,我會(huì)盡力補(bǔ)上。
相關(guān)的代碼和工具類我已經(jīng)上傳到了github,,可以下載Demo來(lái)查看,,大家一起交流

https://github.com/StephenZKCurry/NotchAdaptedTest

----------  END  ----------

分享大前端、Java,、跨平臺(tái)等技術(shù),,

關(guān)注職業(yè)發(fā)展和行業(yè)動(dòng)態(tài)。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購(gòu)買等信息,,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào),。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多