主頁 > 移動端開發 > 什么?都0202年了,你還不會螢屏適配?

什么?都0202年了,你還不會螢屏適配?

2020-11-21 11:50:22 移動端開發

目錄

寫在前面

一、自定義像素適配

二、百分比布局適配

三、修改系統density適配

四、劉海屏適配

4.1、谷歌官方適配策略

4.2、國內定制ROM適配策略


寫在前面

Android的螢屏適配相信大家都做過,可以說是各有各的招,對于螢屏適配真的是逢考必問,反正我每次面試都被問到了,所以它的重要性也是不言而喻了!

先來思考幾個問題吧:為什么需要做螢屏適配?我們做螢屏適配的目的是什么?做了那么久的Android了,你知道都有哪些常見的螢屏適配的方式嗎?OK,下面來一一解答:

  • 原因:Android設備碎片化,導致app的界面元素在不同螢屏尺寸上顯示不一致
  • 目的:讓布局,布局組件,資源,用戶界面流程,匹配不同螢屏尺寸
  • 螢屏適配常見方式:

1、布局適配

2、圖片資源適配

3、用戶流程匹配

4、限定符適配

5、劉海屏適配

什么?就這?嗯,到這里........................到這里當然不能結束啦,Because we're just getting started!咱們才剛剛開始,

上面我們給出了幾個問題的答案,我們可以對照著去思考一下自己是否都了解過,下面重點要開始今天的內容了,介紹幾種常用的螢屏適配的方案,

一、自定義像素適配

核心思想:自定義View,以一個特定寬度尺寸的設備為參考,在View的加載程序,根據當前設備的實際像素換算出目標像素,再作用在控制元件上,

舉個例子:通常公司的UI設計師一般都是只出一套設計稿,可能是以目前市面上Android設備的主流解析度為參考,或者直接以iOS為參考,假設她給出的設計稿規范為720x1280,如果是運行在1080x1920的設備上,那么此時我們就需要做螢屏適配了,如果有一個按鈕的寬是360px,它運行在720x1280的設備上是占到螢屏寬度的一半,當它運行在1080x1920的設備上時變成了螢屏寬度的1/3,這種情況肯定不是我們想要的,我們需要的是它無論顯示在何種設備上都是占到螢屏寬度的1/2,所以我們就需要進行計算了,計算出來一個相對應的縮放比例,其實也很好計算,即:1080/720x360=540px,所以我們需要在1080的設備上將按鈕的寬度調整為540個像素才能使兩邊顯示的效果一致,

OK,下面我們通過自定義ViewGroup來實作螢屏適配,通過繼承系統控制元件比如LinearLayout、RelativeLayout等控制元件,在onMesure()方法中通過縮放比例來重新計算各個子View的寬高從而實作螢屏的適配,

首先我們新建一個工具類,來獲取螢屏的寬高并且計算出縮放比例:

public class ScreenUtil {

    private static ScreenUtil ScreenUtil;

    //這里是設計稿參考寬高
    private static final float STANDARD_WIDTH = 1080;
    private static final float STANDARD_HEIGHT = 1920;

    //這里是螢屏顯示寬高
    private int mDisplayWidth;
    private int mDisplayHeight;

    private ScreenUtil(Context context){
        //獲取螢屏的寬高
        if(mDisplayWidth == 0 || mDisplayHeight == 0){
            WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
            if (manager != null){
                DisplayMetrics displayMetrics = new DisplayMetrics();
                manager.getDefaultDisplay().getMetrics(displayMetrics);
                if (displayMetrics.widthPixels > displayMetrics.heightPixels){
                    //橫屏
                    mDisplayWidth = displayMetrics.heightPixels;
                    mDisplayHeight = displayMetrics.widthPixels;
                }else{
                    mDisplayWidth = displayMetrics.widthPixels;
                    mDisplayHeight = displayMetrics.heightPixels - getStatusBarHeight(context);
                }
            }
        }

    }

    //獲取狀態欄的高度
    public int getStatusBarHeight(Context context){
        int resID = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
        if (resID > 0){
            return context.getResources().getDimensionPixelSize(resID);
        }
        return 0;
    }

    public static ScreenUtil getInstance(Context context){
        if (ScreenUtil == null){
            ScreenUtil = new ScreenUtil(context.getApplicationContext());
        }
        return ScreenUtil;
    }

    //獲取水平方向的縮放比例
    public float getHorizontalScale(){
        return mDisplayWidth / STANDARD_WIDTH;
    }

    //獲取垂直方向的縮放比例
    public float getVerticalScale(){
        return mDisplayHeight / STANDARD_HEIGHT;
    }

}

接著我們來實作自定義的ViewGroup,這里繼承自系統的控制元件,在onMesure()方法中重新計算各個子控制元件的大小:

public class PixelLinearLayout extends LinearLayout {

    //防止二次測量
    private boolean flag;

    public PixelLinearLayout(Context context) {
        super(context);
    }

    public PixelLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public PixelLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (!flag) {
            float scaleX = ScreenUtil.getInstance(getContext()).getHorizontalScale();
            float scaleY = ScreenUtil.getInstance(getContext()).getVerticalScale();

            int count = getChildCount();
            for (int i = 0; i < count; i++) {
                View child = getChildAt(i);
                LayoutParams params = (LayoutParams) child.getLayoutParams();
                params.width = (int) (params.width * scaleX);
                params.height = (int) (params.height * scaleY);
                params.leftMargin = (int) (params.leftMargin * scaleX);
                params.rightMargin = (int) (params.rightMargin * scaleX);
                params.topMargin = (int) (params.topMargin * scaleY);
                params.bottomMargin = (int) (params.bottomMargin * scaleY);
            }
            flag = true;
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}

這里為了防止二次測量,所以加了一個flag標志位,讓計算的代碼只執行一次,這里我們是繼承自線性布局的,在實際使用程序中你可以根據需求去進行擴展,比如再實作RelativeLayout、FrameLayout等控制元件,

由于咱們的設計稿也就是標準寬高是1080x1920,所以我們可以在布局檔案中添加控制元件,在不同的設備上運行來看效果,注意:布局檔案中控制元件的大小填入的單位需要使用px,因為我們獲取的螢屏尺寸單位是以像素為單位的:

<?xml version="1.0" encoding="utf-8"?>
<com.jarchie.androidui.screenadapter.pixel.PixelLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:layout_width="540px"
        android:layout_height="200px"
        android:layout_marginTop="30dp"
        android:background="@color/colorAccent"
        android:gravity="center"
        android:text="自定義像素適配"
        android:textColor="#fff" />

</com.jarchie.androidui.screenadapter.pixel.PixelLinearLayout>

由于手里沒那么多真機,所以就簡單的使用模擬器來進行測驗了,這里我分別啟動了1080x1920和720x1280的兩個模擬器:

在1080x1920和720x1280的兩臺設備上的效果如下圖所示,都是占了螢屏寬度的一半:

二、百分比布局適配

核心思想:以父容器尺寸作為參考,在View的加載程序,根據當前父容器實際尺寸換算出目標尺寸,再作用在View上,

使用百分比布局適配,我們無需知道設計稿的尺寸,我們只需要知道控制元件占用螢屏寬高的比例即可,

首先需要在build.gradle檔案中添加百分比布局的依賴:

//谷歌百分比布局
implementation 'com.android.support:percent:28.0.0'

接著在布局檔案中來使用百分比布局,在其中放置一個TextView寬高我們設定各占螢屏的一半,注意這里需要添加命名空間,百分比布局的屬性都需要通過app:xxx來設定,如果你不使用命名空間,那么它就和普通的布局沒有任何區別,它只是對普通的布局進行了擴充:

<?xml version="1.0" encoding="utf-8"?>
<android.support.percent.PercentRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/colorAccent"
        android:gravity="center"
        android:text="百分比布局適配"
        android:textColor="#fff"
        app:layout_widthPercent="50%"
        app:layout_heightPercent="50%"/>

</android.support.percent.PercentRelativeLayout>

效果如下:

這個就是谷歌官方推出的一個百分比布局適配的庫,現在使用好像是會劃橫線告訴你它已經過時了,不過依然不影響你使用,如果你確實看著不爽,那么你可以自己實作這樣一套百分比布局適配的解決方案,其實還是自定義ViewGroup,

這里簡單說一下思路,具體的代碼就不寫了,大家可以參考谷歌官方的百分比布局來寫:

  • 新建PercentLayout繼承自系統控制元件RelativeLayout,對系統控制元件進行擴展
  • 在values檔案夾下新建attrs.xml檔案,撰寫自定義屬性,就是類似于谷歌官方的app:layout_widthPercent這種形式的
  • 在PercentLayout類中新建LayoutParams這個布局屬性內部類,繼承自RelativeLayout.LayoutParams這個類,保證原有屬性可用,然后決議上面自定義的屬性
  • 在onMesure()方法中進行布局屬性的獲取以及寬高的二次計算

三、修改系統density適配

核心思想:修改density、scaleDensity、densityDpi值,直接更改系統內部對于目標尺寸而言的像素密度,

關于density、scaleDensity、densityDpi這些概念以及它們之間的關系,我找了兩篇文章給大家做參考:

《Android:sp與dp(densityDpi與scaledDensity)》

《dpi 、 dip 、解析度、螢屏尺寸、px、density 關系以及換算》

接下來我們來看看在代碼中究竟該如何實作呢?

首先,來寫一個工具類,我們給出一個參考設備的寬度,以及螢屏密度和字體縮放比例,然后獲取到當前螢屏的顯示資訊,來計算出目標值density、scaleDensity和densityDpi,并將目標值替換原有的螢屏顯示資訊:

public class Density {

    private static final float  WIDTH = 360;//參考設備的寬,單位是dp 360 / 2 = 180

    private static float appDensity;//表示螢屏密度
    private static float appScaleDensity; //字體縮放比例,默認appDensity

    public static void setDensity(final Application application, Activity activity){
        //獲取當前app的螢屏顯示資訊
        DisplayMetrics displayMetrics = application.getResources().getDisplayMetrics();
        if (appDensity == 0){
            //初始化賦值操作
            appDensity = displayMetrics.density;
            appScaleDensity = displayMetrics.scaledDensity;

            //添加字體變化監聽回呼
            application.registerComponentCallbacks(new ComponentCallbacks() {
                @Override
                public void onConfigurationChanged(Configuration newConfig) {
                    //字體發生更改,重新對scaleDensity進行賦值
                    if (newConfig != null && newConfig.fontScale > 0){
                        appScaleDensity = application.getResources().getDisplayMetrics().scaledDensity;
                    }
                }

                @Override
                public void onLowMemory() {

                }
            });
        }

        //計算目標值density, scaleDensity, densityDpi
        float targetDensity = displayMetrics.widthPixels / WIDTH; // 1080 / 360 = 3.0
        float targetScaleDensity = targetDensity * (appScaleDensity / appDensity);
        int targetDensityDpi = (int) (targetDensity * 160);

        //替換Activity的density, scaleDensity, densityDpi
        DisplayMetrics dm = activity.getResources().getDisplayMetrics();
        dm.density = targetDensity;
        dm.scaledDensity = targetScaleDensity;
        dm.densityDpi = targetDensityDpi;
    }
}

上面的代碼中我們還添加了系統設定中字體變化前后的一個監聽,

OK,一個工具類就可以搞定啦,實際專案中使用時,由于我們的頁面肯定不止一個,所以通常情況下你可以使用如下兩種方式來進行替換:

①、全域Application

自定義Application類,在onCreate()方法中監聽Activity創建的回呼,同時別忘記在Manifest.xml清單檔案中設定我們自己的Application類:

public class App extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                Density.setDensity(App.this, activity);
            }

            @Override
            public void onActivityStarted(Activity activity) {

            }

            @Override
            public void onActivityResumed(Activity activity) {

            }

            @Override
            public void onActivityPaused(Activity activity) {

            }

            @Override
            public void onActivityStopped(Activity activity) {

            }

            @Override
            public void onActivitySaveInstanceState(Activity activity, Bundle outState) {

            }

            @Override
            public void onActivityDestroyed(Activity activity) {

            }
        });
    }
}

②、定義基類BaseActivity

定義BaseActivity在該類中統一處理,注意:設定的代碼必須寫在setContentView之前:

public class BaseActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Density.setDensity(getApplication(),this);
    }
}

public class MainActivity extends BaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_screen_adapter_layout);
    }
}

接著,我們來做個測驗,由于我們設定的參考設備的寬是360dp,所以假設我們想讓控制元件占用螢屏的一半,可以直接在布局檔案中設定寬度為180dp即可:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/text1"
        android:layout_width="180dp"
        android:layout_height="180dp"
        android:text="左上"
        android:gravity="center"
        android:textColor="#fff"
        android:textSize="18sp"
        android:background="@color/colorAccent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:layout_width="180dp"
        android:layout_height="180dp"
        android:text="右下"
        android:gravity="center"
        android:textColor="#fff"
        android:textSize="18sp"
        android:background="@color/colorAccent"
        app:layout_constraintLeft_toRightOf="@id/text1"
        app:layout_constraintTop_toBottomOf="@id/text1" />

</android.support.constraint.ConstraintLayout>

運行的效果如下:

實際專案中推薦使用這種方案進行螢屏適配,使用方式簡單,侵入性低,可以適配三方庫和系統控制元件,并且也不會有性能損耗,其實這也就是今日頭條適配方案,相信大家也都聽說過這個方案了,當時出來的時候火了一段時間,我特地去把原文翻出來🤲雙手奉上:

位元組跳動技術團隊:《一種極低成本的Android螢屏適配方式》

我這里還為大家準備了另一種類似的解決方案,推薦閱讀:《Android 螢屏適配終結者》

四、劉海屏適配

4.1、谷歌官方適配策略

Android官方9.0劉海屏適配策略

  1. 如果非全屏模式(有狀態欄),則app不受劉海屏的影響,劉海屏的高就是狀態欄的高
  2. 如果全屏模式,app未適配劉海屏,系統會對界面做特殊處理,豎屏向下移動,橫屏向右移動

針對上面說的這兩種情況,我們通過一個實際的專案來看下效果:

首先將模擬器的開發者選項打開,在開發者模式下找到模擬具有凹口的顯示屏,選擇長型顯示屏凹口,這樣就把我們的模擬器調整為了具有劉海屏的顯示幕:

然后準備一張圖片,然后撰寫布局檔案,我們讓圖片橫向豎向都充滿螢屏:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_centerInParent="true"
        android:scaleType="fitXY"
        android:src="@drawable/zly" />
</RelativeLayout>

然后在Activity代碼中什么都不做,直接啟動這個頁面,第一種情況運行的結果如下圖所示,圖片在下方填充,上方狀態欄和劉海屏有重疊,這種效果也就是默認會出現的效果:

第二種情況是全屏模式,我們先來考慮第一種應用場景:app某個詳情頁面的某張圖片點擊查看大圖,一般都是跳轉到新的頁面讓它全屏展示,這種情況該如何做?

首先我們需要將螢屏設定為全屏:

//設定全屏,注意這幾行代碼需要在setContentView之前執行
requestWindowFeature(Window.FEATURE_NO_TITLE);
Window window = getWindow();
window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);

setContentView(R.layout.activity_screen_adapter_layout);

效果如下圖所示:

在螢屏上方出現了一條黑邊,這是系統為它做的默認的處理,表示如果當前界面為全屏模式,它會將內容區域向下移動,上面這條黑邊就會空出來,此時我們就需要對螢屏進行適配了,我們需要將內容區域延伸進劉海區域,

接著來看谷歌官方9.0對它的處理,主要用到的是WindowManager.LayoutParams類里面的LayoutInDisplayCutoutMode這個值,它是Android9.0之后對劉海屏支持的屬性,具體都有哪些值我們來看一下:

//讓內容區域延伸進劉海
WindowManager.LayoutParams params = window.getAttributes();
/**
 * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT 全屏模式,內容下移,非全屏不受影響
 * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES 允許內容延伸進劉海區
 * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER 不允許內容延伸進劉海區
 */
params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
window.setAttributes(params);

我們設定這個值允許內容延伸進劉海,結果如下圖所示:

這里上面狀態欄變成了白色,有人會說了你這內容區還是在下方啊,也沒見它延伸到劉海中啊?事實上它已經延伸進去了,這個白色是因為頂層布局的背景是白色的,所以我們還需要再做一步操作,將當前頁面設定為沉浸式模式:

//設定成沉浸式
int flags = View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
int visibility = window.getDecorView().getSystemUiVisibility();
visibility |= flags; //追加沉浸式設定
window.getDecorView().setSystemUiVisibility(visibility);

設定完成之后運行的效果就是我們想要的了:

在實際專案開發中,對于官方的Android9.0的適配,我們首先應該考慮它是否有劉海屏,所以為了邏輯的完整性以及代碼的健壯性,我們再來寫一個判斷是否有劉海的方法:

private boolean hasDisplayCutout(Window window) {
        DisplayCutout displayCutout;
        View rootView = window.getDecorView();
        WindowInsets insets = rootView.getRootWindowInsets();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && insets != null){
            displayCutout = insets.getDisplayCutout();
            if (displayCutout != null){
                if (displayCutout.getBoundingRects() != null && displayCutout.getBoundingRects().size() > 0 && displayCutout.getSafeInsetTop() > 0){
                    return true;
                }
            }
        }
        return false; 
    }

OK,這樣一種場景的劉海屏適配我們就搞定啦!

別急著走,此時如果我們把xml布局中的圖片給換掉,我們換成一個文本,讓它居中顯示,請問會出現什么問題?

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/content">

    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="50dp"
        android:layout_centerHorizontal="true"
        android:background="@color/colorAccent"
        android:gravity="center"
        android:text="Hello Android!"
        android:textColor="#fff" />
</RelativeLayout>

沒錯,它就變成了這個樣子:

這種情況很明顯,我們的TextView被劉海區域給擋住了,所以此時就不能只是簡單的把內容區域延伸進劉海了,解決方案一般有兩種:

①、UI在出設計稿的時候盡量規避這種情況,不要把內容放到這個區域,這種方案以實際公司業務為準,這里不多說了;

②、通過代碼把內容向下移動,獲取劉海屏的高度,讓內容下移對應的高度,一般情況下,劉海的高度等于狀態欄的高度,所以我們可以直接取狀態欄的高度,

對于②我們來實際操作一下,首先獲取劉海的高度:

    //通常情況下,劉海的高就是狀態欄的高
    public int heightForDisplayCutout(){
        int resID = getResources().getIdentifier("status_bar_height", "dimen", "android");
        if (resID > 0){
            return getResources().getDimensionPixelSize(resID);
        }
        return 96;
    }

然后將內容下移對應的高度:

//將內容區域下移劉海的高度
TextView text= findViewById(R.id.text);
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) text.getLayoutParams();
layoutParams.topMargin = heightForDisplayCutout();
text.setLayoutParams(layoutParams);

或者通過父容器去設定它的Padding:

RelativeLayout layout = findViewById(R.id.content);
layout.setPadding(layout.getPaddingLeft(), heightForDisplayCutout(), layout.getPaddingRight(), layout.getPaddingBottom());

OK,上面這些就是對官方劉海屏的一個適配的程序了,我們再來總結一下操作步驟:

  • 判斷手機是否有劉海
  • 設定是否讓內容區域延伸進劉海
  • 設定控制元件是否避開劉海區域
  • 獲取劉海的高度

4.2、國內定制ROM適配策略

在實際專案開發程序中,我們需要適配的手機大多都是國內的一些手機廠商他們的產品,比如:華為、小米、OPPO、VIVO...等等,由于國內的手機系統一般都是各家廠商定制的ROM,所以適配這些手機我們需要去針對性的適配,下面為大家整理了國內四大廠商的官方檔案地址,大家可以對照著參考官方檔案去做適配:

  • 華為(好像打不開了):https://devcenter-test.huawei.com/consumer/cn/devservice/doc/50114
  • 花粉俱樂部:https://club.huawei.com/thread-15620158-1-1.html
  • 小米:https://dev.mi.com/console/doc/detail?pId=1293
  • oppo:https://open.oppomobile.com/service/message/detail?id=61876
  • vivo:https://dev.vivo.com.cn/documentCenter/doc/103

這里給大家提供了一個工具類,方便呼叫:

public class Utils {

    /****************************************華為手機***********************************************/
    /**
     * 是否劉海
     * @param context
     * @return
     */
    public static boolean hasNotchInScreen(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");
        }
        return ret;
    }

    /**
     * 獲取劉海尺寸:width、height,int[0]值為劉海寬度 int[1]值為劉海高度,
     * @param context
     * @return
     */
    public static int[] getNotchSize(Context context) {
        int[] ret = new int[]{0, 0};
        try {
            ClassLoader cl = context.getClassLoader();
            Class HwNotchSizeUtil = cl.loadClass("com.huawei.android.util.HwNotchSizeUtil");
            Method get = HwNotchSizeUtil.getMethod("getNotchSize");
            ret = (int[]) get.invoke(HwNotchSizeUtil);
        } catch (ClassNotFoundException e) {
            Log.e("test", "getNotchSize ClassNotFoundException");
        } catch (NoSuchMethodException e) {
            Log.e("test", "getNotchSize NoSuchMethodException");
        } catch (Exception e) {
            Log.e("test", "getNotchSize Exception");
        }
        return ret;
    }

    /**
     * 設定使用劉海區域
     * @param window
     */
    public static void setFullScreenWindowLayoutInDisplayCutout(Window window) {
        if (window == null) {
            return;
        }

        try {
            WindowManager.LayoutParams layoutParams = window.getAttributes();
            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 (Exception e) {
            Log.e("test", "other Exception");
        }
    }

    /*劉海屏全屏顯示FLAG*/
    public static final int FLAG_NOTCH_SUPPORT = 0x00010000;

    /**
     * 設定應用視窗在華為劉海屏手機不使用劉海
     *
     * @param window 應用頁面window物件
     */
    public static void setNotFullScreenWindowLayoutInDisplayCutout(Window window) {
        if (window == null) {
            return;
        }
        try {
            WindowManager.LayoutParams layoutParams = window.getAttributes();
            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("clearHwFlags", int.class);
            method.invoke(layoutParamsExObj, FLAG_NOTCH_SUPPORT);
        } catch (Exception e) {
            Log.e("test", "hw clear notch screen flag api error");
        }
    }

    /****************************************OPPO手機***********************************************/
    /**
     * 判斷該 OPPO 手機是否為劉海屏手機
     * @param context
     * @return
     */
    public static boolean hasNotchInOppo(Context context) {
        return context.getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism");
    }

    /**
     * 劉海高度和狀態欄的高度是一致的
     * @param context
     * @return
     */
    public static int getStatusBarHeight(Context context) {
        int resId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
        if (resId > 0){
            return context.getResources().getDimensionPixelSize(resId);
        }
        return 0;
    }

    /****************************************VIVO手機***********************************************/
    /**
     * Vivo判斷是否有劉海, VIVO的劉海高度小于等于狀態欄高度
     */
    public static final int VIVO_NOTCH = 0x00000020;//是否有劉海
    public static final int VIVO_FILLET = 0x00000008;//是否有圓角

    public static boolean hasNotchAtVivo(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;
        }
    }
}

OK,到了這里今天的內容就說的差不多了,咱們下期再會!

祝:作業順利!

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/225875.html

標籤:其他

上一篇:[OpenGL ES] 自定義GLSurfaceView

下一篇:Android知識總結

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more