一、前期基礎(chǔ)知識儲備
話不多說病瞳,這么多劉海屏手機今年集中爆發(fā),所以盡管劉海屏不好看悲酷,但是還是要適配套菜。
2017年蘋果X開啟了劉海屏?xí)r代,2018年集中爆發(fā)设易,紛紛采取劉海屏這一策略來實現(xiàn)全面屏的概念(看36氪中的新聞逗柴,明年是5G元年,同時三星推出了折疊屏顿肺,未來的手機主流趨勢是否會發(fā)生改變暫不得而知戏溺,但劉海屏不會退出市場,淡出視野這一點是確定的)屠尊,所以Android手機對于劉海屏的適配也是比較重要的旷祸。所謂適配劉海屏,其實就是處理與劉海齊平的手機屏幕部分讼昆,這也是所有劉海屏手機系統(tǒng)自帶的一個可選項:是否顯示劉海屏托享,以華為劉海屏為例,是否顯示劉海屏前后效果如下:
??
從上面的圖中我們可以發(fā)現(xiàn)這幾個重要的適配信息:
①與劉海屏齊平的手機屏幕部分實際上是手機的狀態(tài)欄浸赫;
②顯示劉海屏闰围,劉海部分顯示的狀態(tài)背景色為APP應(yīng)用背景色,狀態(tài)欄文字圖標(biāo)部分變?yōu)楹谏?/p>
③不顯示劉海屏掺炭,則劉海部分顯示的狀態(tài)欄為手機原始狀態(tài)欄,電量標(biāo)志凭戴、事件涧狮、運營商信息都是白字;
所以適配劉海屏的關(guān)鍵在于:
①判斷是否是劉海屏,不是劉海屏就隱藏狀態(tài)欄者冤,是劉海屏則顯示狀態(tài)欄肤视,同時對狀態(tài)欄做出相應(yīng)處理;
②如果是劉海屏涉枫,則顯示的狀態(tài)欄顏色變?yōu)锳PP應(yīng)用本身的背景色邢滑;
③其次狀態(tài)欄中的圖標(biāo)、文字等信息是否需要變色(應(yīng)用為深色背景色時定為白色愿汰,應(yīng)用為淺色背景色時定為黑色)
二困后、上代碼 具體實現(xiàn)
1)判斷是否是劉海屏手機 工具類 judgeNotchUtils
/**
* 判斷是否是劉海屏 寫在Activity基類BaseActivity onCreate()方法中或者單獨設(shè)置
* 國內(nèi)主流手機小米 華為 VIVO OPPO劉海屏判斷
* @return
*/
public static boolean hasNotchScreen(Activity activity){
if (getInt("ro.miui.notch",activity) == 1 || hasNotchInHuawei(activity) || hasNotchInVivo(activity)
|| hasNotchInOppo(activity) || hasNotchInXiaomi(activity)){
return true;
}
return false;
}
/**
* Android P 版本判斷 需要應(yīng)用的CompileSDKVersion設(shè)為28
* 其他劉海屏手機判斷
* @param activity
* @return
*/
public static DisplayCutout isAndroidP(Activity activity){
View decorView = activity.getWindow().getDecorView();
if (decorView != null && android.os.Build.VERSION.SDK_INT >= 28){
WindowInsets windowInsets = decorView.getRootWindowInsets();
if (windowInsets != null)
return windowInsets.getDisplayCutout();
}
return null;
}
/**
* 小米劉海屏判斷
* @return 0 if it is not notch ; return 1 means notch
* @throws IllegalArgumentException if the key exceeds 32 characters
*/
public static int getInt(String key,Activity activity) {
int result = 0;
if (isXiaomi()){
try {
ClassLoader classLoader = activity.getClassLoader();
@SuppressWarnings("rawtypes")
Class SystemProperties = classLoader.loadClass("android.os.SystemProperties");
@SuppressWarnings("rawtypes")
Class[] paramTypes = new Class[2];
paramTypes[0] = String.class;
paramTypes[1] = int.class;
Method getInt = SystemProperties.getMethod("getInt", paramTypes);
Object[] params = new Object[2];
params[0] = new String(key);
params[1] = new Integer(0);
result = (Integer) getInt.invoke(SystemProperties, params);
} catch (Exception e) {
return result;
}
}
return result;
}
public static boolean hasNotchInXiaomi(Context context) {
if (isXiaomi()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
int result = 0;
int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = context.getResources().getDimensionPixelSize(resourceId);
}
if (result > 0) {
return true;
} else {
return false;
}
}
}
return false;
}
/**
* 華為劉海屏判斷
* @return
*/
public static boolean hasNotchInHuawei(Context context) {
boolean hasNotch = false;
try {
ClassLoader cl = context.getClassLoader();
Class HwNotchSizeUtil = cl.loadClass("com.huawei.android.util.HwNotchSizeUtil");
Method hasNotchInScreen = HwNotchSizeUtil.getMethod("hasNotchInScreen");
if(hasNotchInScreen != null) {
hasNotch = (boolean) hasNotchInScreen.invoke(HwNotchSizeUtil);
}
} catch (Exception e) {
e.printStackTrace();
}
return hasNotch;
}
/**
* VIVO劉海屏判斷
* @return
*/
public static boolean hasNotchInVivo(Context context) {
boolean hasNotch = false;
try {
ClassLoader cl = context.getClassLoader();
Class ftFeature = cl.loadClass("android.util.FtFeature");
Method[] methods = ftFeature.getDeclaredMethods();
if (methods != null) {
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
if(method != null) {
if (method.getName().equalsIgnoreCase("isFeatureSupport")) {
hasNotch = (boolean) method.invoke(ftFeature, 0x00000020);
break;
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
hasNotch = false;
}
return hasNotch;
}
/**
* OPPO劉海屏判斷
* @return
*/
public static boolean hasNotchInOppo(Context context) {
return context.getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism");
}
public static boolean isXiaomi() {
return "Xiaomi".equals(Build.MANUFACTURER);
}
使用代碼:
if(judgeNotchUtils.hasNotchScreen(BaseActivity.this)){
// 有劉海屏的處理
// 顯示狀態(tài)欄
// 狀態(tài)欄文字、圖標(biāo)顏色控制
} else {
// 無劉海屏的處理
// 隱藏狀態(tài)欄
hideStatusBar();
}
public void hideStatusBar() {
if (Build.VERSION.SDK_INT < 30) { getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN); getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
} else {
View decorView = getWindow().getDecorView();
int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN;
decorView.setSystemUiVisibility(uiOptions);
}
}
2)狀態(tài)欄文字圖標(biāo)顏色控制 工具類 StatusBarUtils
public class StatusBarUtils {
/**
* 修改狀態(tài)欄為全透明
* @param activity
*/
@TargetApi(19)
public static void transparencyBar(Activity activity){
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = activity.getWindow();
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(Color.TRANSPARENT);
} else
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Window window =activity.getWindow(); window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
}
/**
* 修改狀態(tài)欄顏色衬廷,支持4.4以上版本
* @param activity
* @param colorId
*/
public static void setStatusBarColor(Activity activity,int colorId) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = activity.getWindow();
// window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); window.setStatusBarColor(activity.getResources().getColor(colorId));
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
//使用SystemBarTint庫使4.4版本狀態(tài)欄變色摇予,需要先將狀態(tài)欄設(shè)置為透明
transparencyBar(activity);
SystemBarTintManager tintManager = new SystemBarTintManager(activity);
tintManager.setStatusBarTintEnabled(true);
tintManager.setStatusBarTintResource(colorId);
}
}
/**
*狀態(tài)欄亮色模式,設(shè)置狀態(tài)欄黑色文字吗跋、圖標(biāo)侧戴,
* 適配4.4以上版本MIUIV、Flyme和6.0以上版本其他Android
* @param activity
* @return 1:MIUUI 2:Flyme 3:android6.0
*/
public static int StatusBarLightMode(Activity activity){
int result=0;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if(MIUISetStatusBarLightMode(activity, true)){
result=1;
}else if(FlymeSetStatusBarLightMode(activity.getWindow(), true)){
result=2;
}else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
activity.getWindow().getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN|View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
result=3;
}
}
return result;
}
/**
* 已知系統(tǒng)類型時跌宛,設(shè)置狀態(tài)欄黑色文字酗宋、圖標(biāo)。
* 適配4.4以上版本MIUIV疆拘、Flyme和6.0以上版本其他Android
* @param activity
* @param type 1:MIUUI 2:Flyme 3:android6.0
*/
public static void StatusBarLightMode(Activity activity,int type){
if(type==1){
MIUISetStatusBarLightMode(activity, true);
}else if(type==2){
FlymeSetStatusBarLightMode(activity.getWindow(), true);
}else if(type==3){
activity.getWindow().getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN|View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}
}
/**
* 狀態(tài)欄暗色模式蜕猫,清除MIUI、flyme或6.0以上版本狀態(tài)欄黑色文字入问、圖標(biāo)
*/
public static void StatusBarDarkMode(Activity activity,int type){
if(type==1){
MIUISetStatusBarLightMode(activity, false);
}else if(type==2){
FlymeSetStatusBarLightMode(activity.getWindow(), false);
}else if(type==3){ activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
}
}
/**
* 設(shè)置狀態(tài)欄圖標(biāo)為深色和魅族特定的文字風(fēng)格
* 可以用來判斷是否為Flyme用戶
* @param window 需要設(shè)置的窗口
* @param dark 是否把狀態(tài)欄文字及圖標(biāo)顏色設(shè)置為深色
* @return boolean 成功執(zhí)行返回true
*
*/
public static boolean FlymeSetStatusBarLightMode(Window window, boolean dark) {
boolean result = false;
if (window != null) {
try {
WindowManager.LayoutParams lp = window.getAttributes();
Field darkFlag = WindowManager.LayoutParams.class .getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON");
Field meizuFlags = WindowManager.LayoutParams.class
.getDeclaredField("meizuFlags");
darkFlag.setAccessible(true);
meizuFlags.setAccessible(true);
int bit = darkFlag.getInt(null);
int value = meizuFlags.getInt(lp);
if (dark) {
value |= bit;
} else {
value &= ~bit;
}
meizuFlags.setInt(lp, value);
window.setAttributes(lp);
result = true;
} catch (Exception e) {
}
}
return result;
}
/**
* 需要MIUIV6以上
* @param activity
* @param dark 是否把狀態(tài)欄文字及圖標(biāo)顏色設(shè)置為深色
* @return boolean 成功執(zhí)行返回true
*
*/
public static boolean MIUISetStatusBarLightMode(Activity activity, boolean dark) {
boolean result = false;
Window window=activity.getWindow();
if (window != null) {
Class clazz = window.getClass();
try {
int darkModeFlag = 0;
Class layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams");
Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE");
darkModeFlag = field.getInt(layoutParams);
Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class);
if(dark){
extraFlagField.invoke(window,darkModeFlag,darkModeFlag);//狀態(tài)欄透明且黑色字體
}else{
extraFlagField.invoke(window, 0, darkModeFlag);//清除黑色字體
}
result=true;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
//開發(fā)版 7.7.13 及以后版本采用了系統(tǒng)API丹锹,舊方法無效但不會報錯,所以兩個方式都要加上
if(dark){
activity.getWindow().getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN| View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}else { activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
}
}
}catch (Exception e){
}
}
return result;
}
}
使用時代碼如下: 比如博主的開發(fā)的應(yīng)用是淺色背景色芬失,所以標(biāo)題欄也被設(shè)為淺色楣黍,此時應(yīng)該修改狀態(tài)欄顯色黑色文字圖標(biāo)
if(judgeNotchUtils.hasNotchScreen(BaseActivity.this)){
// 有劉海屏的處理
// 顯示狀態(tài)啦
// 將狀態(tài)欄文字圖標(biāo)設(shè)為黑色
showStatusBar();
StatusBarUtils.StatusBarLightMode(BaseActivity.this)
} else {
// 無劉海屏的處理 隱藏狀態(tài)欄
hideStatusBar();
}
//顯示狀態(tài)欄
public void showStatusBar() {
if (Build.VERSION.SDK_INT < 30) { getWindow().addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
} else {
View decorView = getWindow().getDecorView();
int uiOptions = View.SYSTEM_UI_FLAG_VISIBLE;
decorView.setSystemUiVisibility(uiOptions);
}
}
//隱藏狀態(tài)欄
public void hideStatusBar() {
if (Build.VERSION.SDK_INT < 30) { getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN); getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
} else {
View decorView = getWindow().getDecorView();
int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN;
decorView.setSystemUiVisibility(uiOptions);
}
}
同時將Activity根布局的fitsSystemWindows屬性設(shè)為true(默認(rèn)為false),此時根布局的paddding屬性由系統(tǒng)設(shè)置棱烂,用戶在布局文件中設(shè)置的 padding會被忽略租漂。系統(tǒng)會為該View設(shè)置一個paddingTop,值為statusbar(狀態(tài)欄)的高度颊糜。即此時應(yīng)用的Content不會和系統(tǒng)狀態(tài)欄發(fā)生重疊哩治。
android:fitsSystemWindows="true"
若不設(shè)置此屬性,則Activity內(nèi)容會與系統(tǒng)狀態(tài)欄發(fā)生重疊衬鱼。(╯﹏╰)(當(dāng)時調(diào)了好久)(╯﹏╰)
效果圖如下:
①應(yīng)用頁:狀態(tài)欄文字业筏、圖標(biāo)設(shè)為黑色:
②歡迎頁:狀態(tài)欄文字、圖標(biāo)不改變顏色鸟赫,仍為白色:
以上圖片蒜胖,讀者湊合看一下消别,不好截劉海屏的小劉海,所以后期自己加了形狀表示一下台谢。o(╯□╰)oo(╯□╰)oo(╯□╰)o
③不適配劉海屏?xí)r(直接隱藏狀態(tài)欄寻狂,劉海屏手機使用體驗感稍差)
在此附上各劉海屏手機廠商的劉海屏適配方案(謝謝奧特曼超人博主的分享):
android 關(guān)于google劉海屏的解決方案
最后,更多劉海屏適配文章推薦:
CSDN
Android 劉海屏適配全攻略- Android P 模擬器可模擬劉海屏
簡書
博客園
一大波 Android 劉海屏來襲朋沮,全網(wǎng)最全適配技巧蛇券!
注:博主博客會同步發(fā)布到CSDN,歡迎讀者閱讀