1. 程式人生 > >劉海屏之全面屏適配攻略

劉海屏之全面屏適配攻略

劉海屏之全面屏攻略

前言

由於蘋果公司的“先進設計”導致各大手機廠商紛紛跟風其設計,導致Android的螢幕適配出現新的剛需——劉海屏的適配。為了簡化這些適配操作以及繁瑣的判斷封裝優化出一個工具庫:BangScreenToolsMaster

適配方案及原理

適配流程

AndroidP的適配方式:

AndroidP及以上的方式必須適用於sdk大於等於28的情況下使用。

顯示模式:

Android P中新增了一個佈局引數屬性layoutInDisplayCutoutMode,包含了三種不同的模式

LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT 只有當DisplayCutout完全包含在系統欄中時,才允許視窗延伸到DisplayCutout區域。 否則,窗口布局不與DisplayCutout區域重疊
LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER 不使用劉海屏區域
LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES 該視窗始終允許延伸到螢幕短邊上的DisplayCutout區域。
方法 介面描述
getBoundingRects() 返回Rects的列表,每個Rects都是顯示屏上非功能區域的邊界矩形。
getSafeInsetLeft () 返回安全區域距離螢幕左邊的距離,單位是px。
getSafeInsetRight () 返回安全區域距離螢幕右邊的距離,單位是px。
getSafeInsetTop () 返回安全區域距離螢幕頂部的距離,單位是px。
getSafeInsetBottom() 返回安全區域距離螢幕底部的距離,單位是px。

AndroidP以下的華為適配方案:

使用新增的meta-data屬性android.notch_support。在應用的AndroidManifest.xml中增加meta-data屬性,此屬性不僅可以針對Application生效,也可以對Activity

配置生效。 如下所示:

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

  • Application生效,意味著該應用的所有頁面,系統都不會做豎屏場景的特殊下移或者是橫屏場景的右移特殊處理。
  • Activity生效,意味著可以針對單個頁面進行劉海屏適配,設定了該屬性的Activity系統將不會做特殊處理。

華為的適配使用了反射相關操作程式碼如下:

//判斷是否是華為手機
 public final boolean isHuaWei() {
        String manufacturer = Build.MANUFACTURER;
        if (!TextUtils.isEmpty(manufacturer)){
            if (manufacturer.contains("HUAWEI")) return true;
        }
        return false;
    }
//判斷是否存有劉海屏
@RequiresApi(api = Build.VERSION_CODES.O)
    @Override
    public boolean hasNotBangScreen(Window window) {
        if (isHaveResult) return isBangScreen;
        try {
            ClassLoader huaWeiClassLoader = window.getContext().getClassLoader();
            Class HwNotchSizeUtil = huaWeiClassLoader.loadClass("com.huawei.android.util.HwNotchSizeUtil");
            Method method = HwNotchSizeUtil.getMethod("hasNotchInScreen");
            isHaveResult = true;
            return isBangScreen = (boolean) method.invoke(HwNotchSizeUtil);
        } catch (ClassNotFoundException e) {
            Log.e(TAG, "hasNotchInScreen ClassNotFoundException");
        } catch (NoSuchMethodException e) {
            Log.e(TAG, "hasNotchInScreen NoSuchMethodException");
        } catch (Exception e) {
            Log.e(TAG, "hasNotchInScreen Exception");
        } finally {
            isHaveResult = true;
            return isBangScreen;
        }
    }
//獲取劉海屏尺寸相關資訊
    @RequiresApi(api = Build.VERSION_CODES.O)
    @Override
    public List<Rect> getBangSize(Window window) {
        ArrayList<Rect> result = new ArrayList<Rect>();
        if (window != null) {
            Rect rect = new Rect();
            try {
                Context context = window.getContext();
                if (hwBangSizeUtil == null && context != null) {
                    ClassLoader cl = context.getClassLoader();
                    hwBangSizeUtil = cl.loadClass("com.huawei.android.util.HwNotchSizeUtil");
                }
                if (hwBangSizeUtil == null) {
                    return result;
                }
                Method get = hwBangSizeUtil.getMethod("getNotchSize");
                int[] ret = (int[]) get.invoke(hwBangSizeUtil);
                if (ret == null) {
                    return result;
                } else {
                    Resources resources = context.getResources();
                    if (resources != null) {
                        rect.left = (resources.getDisplayMetrics().widthPixels - ret[0]) / 2;
                        rect.bottom = ret[1];
                        rect.right = rect.left + ret[0];
                        rect.top = 0;
                        result.add(rect);
                    }
                    return result;
                }
            } catch (Exception e) {
                Log.e(TAG, e.getMessage());
                return result;
            }
        } else return result;
    }


AndroidP以下的VIVO適配方案:

vivo在設定--顯示與亮度--第三方應用顯示比例中可以切換是否全屏顯示還是安全區域顯示

相關程式碼如下:

//判斷是不是Vivo手機
public final boolean isVivo() {
        String manufacturer = this.getSystemProperty("ro.vivo.os.name");
        if (!TextUtils.isEmpty(manufacturer)){
            return true;
        }
        return false;
    }
//獲取手機是否是劉海屏手機
  @RequiresApi(api = Build.VERSION_CODES.O)
    @Override
    public boolean hasNotBangScreen(Window window) {
        if (window == null)
            return false;
        if (vivo == null) {
            ClassLoader vivoLoader = window.getContext().getClassLoader();
            try {
                vivo = vivoLoader.loadClass("android.util.FtFeature");
                vivoMethod = vivo.getMethod("isFeatureSupport", Integer.TYPE);
                return (boolean) vivoMethod.invoke(vivo, 0x00000020);
            } catch (ClassNotFoundException e) {
                logError(e);
                return false;
            } catch (NoSuchMethodException e) {
                logError(e);
                return false;
            } catch (IllegalAccessException e) {
                logError(e);
                return false;
            } catch (InvocationTargetException e) {
                logError(e);
                return false;
            }
        } else {
            if (vivoMethod == null) {
                try {
                    vivoMethod = vivo.getMethod("isFeatureSupport", Integer.TYPE);
                } catch (NoSuchMethodException e) {
                    logError(e);
                    return false;
                }
                try {
                    return (boolean) vivoMethod.invoke(vivo, 0x00000020);
                } catch (IllegalAccessException e) {
                    logError(e);
                    return false;
                } catch (InvocationTargetException e) {
                    logError(e);
                    return false;
                }
            }
        }
        return false;
    }
//獲取劉海屏尺寸資訊

    @RequiresApi(api = Build.VERSION_CODES.O)
    @Override
    public List<Rect> getBangSize(Window window) {
        List<Rect> result = new ArrayList<>();
        if (window == null) return result;
        Rect rect = new Rect();
        DisplayMetrics displayMetrics = window.getContext().getResources().getDisplayMetrics();
        int notchWidth = (int) TypedValue.applyDimension(1, 100.0F, displayMetrics);
        int notchHeight = (int) TypedValue.applyDimension(1, 27.0F, displayMetrics);
        rect.left = (displayMetrics.widthPixels - notchWidth) / 2;
        rect.right = rect.left + notchWidth;
        rect.top = 0;
        rect.bottom = notchHeight;
        result.add(rect);
        return result;
    }

AndroidP以下的小米適配方案:

//判斷是否是小米
  public final boolean isMiui() {
        String manufacturer = getSystemProperty("ro.miui.ui.version.name");
        if (!TextUtils.isEmpty(manufacturer)){
            return true;
        }
        return false;
    }
//判斷是否是劉海屏
@RequiresApi(api = Build.VERSION_CODES.O)
    @Override
    public boolean hasNotBangScreen(Window window) {
        return "1".equals(SystemProperties.getSingle().get("ro.miui.notch"));
    }
//由於小米的狀態列高度略大於或等於劉海屏高度故這樣封裝

    @RequiresApi(api = Build.VERSION_CODES.O)
    @Override
    public List<Rect> getBangSize(Window window) {
        List<Rect> result = new ArrayList<>();
        if (window == null) return result;
        Context context = window.getContext();
        Resources resources = context.getResources();
        Rect rect = new Rect();
        if (resources != null) {
            rect.left = 0;
            rect.bottom = BangScreenTools.getBangScreenTools().getStatusBarHeight(context);
            rect.right = resources.getDisplayMetrics().widthPixels;
            rect.top = 0;
            result.add(rect);
        }
        return result;
    }

AndroidP以下的oppo適配方案:

//判斷是否是oppo   
 public final boolean isOppo() {
        String manufacturer = getSystemProperty("ro.product.brand");
        if (!TextUtils.isEmpty(manufacturer)){
            return true;
        }
        return false;
    }
//判斷是否是劉海屏
    @RequiresApi(api = Build.VERSION_CODES.O)
    @Override
    public boolean hasNotBangScreen(Window window) {
        if (window == null) return false;
        return window.getContext().getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism");
    }
    //目前Oppo劉海屏機型尺寸規格都是統一的,顯示屏寬度為1080px,高度為2280px,劉海區域寬度為324px, 高度為80px
    @RequiresApi(api = Build.VERSION_CODES.O)
    @Override
    public List<Rect> getBangSize(Window window) {
        List<Rect> result = new ArrayList<>();
        if (window == null) return result;
        DisplayMetrics displayMetrics = window.getContext().getResources().getDisplayMetrics();
        Rect rect = new Rect();
        int width = 324;
        int height = 80;
        rect.left = (displayMetrics.widthPixels - width) / 2;
        rect.right = rect.left + width;
        rect.bottom = height;
        rect.top = 0;
        result.add(rect);
        return result;
    }

make the statusbar was transparent

//used onCreat method
    BangScreenTools.getBangScreenTools().transparentStatusCutout(window, this)
//used onWindowFocusChanged method
    BangScreenTools.getBangScreenTools().windowChangeTransparentStatusCutout(window)

make the layout extend statusbar

//used onCreat method
    BangScreenTools.getBangScreenTools().extendStatusCutout(window, this)
//used onWindowFocusChanged method
    BangScreenTools.getBangScreenTools().windowChangeExtendStatusCutout(window)

make the layout was fullscreen

//used onCreat method
    BangScreenTools.getBangScreenTools().fullscreen(window, this)
//used onWindowFocusChanged method,this void is make fullscreen is worked.
    BangScreenTools.getBangScreenTools().windowChangeFullscreen(window)

make the layout not use bangscreen

//used onCreat method
    BangScreenTools.getBangScreenTools().blockDisplayCutout(window)

這是AndroidP相關Api的介紹接下來展示下封裝後代碼執行的效果