关于沉浸式
大家有没有觉得我这个标题有点欠妥.网上很多人都叫这种适配叫沉浸式适配.实际上就是对StatusBar 和 navigationBar 的存在进行隐藏或者是修改颜色(透明).
我的理解
沉浸式应该是说将虚拟按键和状态栏隐藏掉 达到全屏的效果.让用户达到一种置身于整个屏幕中的感觉。
先了解一下读完这篇文章可以学会什么东西
屏幕延伸状态栏(API19以上)
屏幕延伸导航栏(虚拟按键 API19以上)
获取StatusBar高度
获取NavigationBar高度
设置StatusBar颜色(API19以上)
设置NavigationBar颜色(API21以上)
其实以前一直都有用这类的工具类,但都是别人写下来的,这次整理是因为发现项目对全面屏的虚拟按键不是很适配才整理了一下的。
先给大家上一张原始的效果图
屏幕延伸状态栏(API19以上)
JcUtilScreen.extendStatusBar(this);
整体布局下移
JcUtilScreen.moveViewDown(binding.textview, JcUtilScreen.getStatusBarHeight(this));
屏幕延伸导航栏(虚拟按键 API19以上)
JcUtilScreen.extendNavigationBar(this);
设置NavigationBar颜色(API21以上)
JcUtilScreen.setNavigationBarColor(this, Color.parseColor("#cc7f7f"));
设置StatusBar颜色(API19以上)
JcUtilScreen.setStatusBarColor(this, Color.parseColor("#cc7f7f"));
package com.hjc.screenadaptation;
import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.os.Build;
import android.support.v4.view.ViewCompat;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.Display;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.Window;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* Created by 卖火柴的小女孩 - Jc on 2018/7/31.
*/
public class JcUtilScreen {
public static Context mContext;
/**
* 屏幕密度
*/
public static float mDensity;
/**
* 屏幕宽度
*/
public static float mScreenWidth;
/**
* 屏幕高度
*/
public static float mScreenHeight;
public static void init(Context context) {
mContext = context; //通过构造方法来传入上下文,这个上下文是Aplication的,在Aplication创建的时候,创建了
initScreenSize();//获取屏幕尺寸的方法
}
/**
* 屏幕的尺寸
*/
private static void initScreenSize() {
DisplayMetrics dm = mContext.getResources().getDisplayMetrics();//上下文,通过资源文件获取到getDisplayMetrics方法
mDensity = dm.density; //获取到屏幕的密度
mScreenHeight = dm.heightPixels;//获取到屏幕的高度
mScreenWidth = dm.widthPixels;//获取到屏幕的宽度
}
//延伸Layout至StatusBar - StatusBar透明
public static void extendStatusBar(Activity activity) {
//4.4及以上可以使用沉浸式状态栏
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
//延伸布局至NavigationBar - NavigationBar透明 - 不可单独使用
public static void extendNavigationBar(Activity activity) {
//4.4及以上可以使用沉浸式状态栏
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
//这里有个坑 单单 当Windows设置FLAG_TRANSLUCENT_NAVIGATION
//屏幕的显示内容区域就会延伸到状态栏上,但状态栏区域依旧被状态栏掩盖 -- 颜色是主题颜色 colorPrimary
//解决方案1:布局文件 android:layout_marginTop="" getStatusBarHeight()
//解决方案2:extendStatusBar() + 解决方案1
}
//获取StatusBar的高度
public static int getStatusBarHeight(Context context) {
try {
Class<?> c = Class.forName("com.android.internal.R$dimen");
Object obj = c.newInstance();
Field field = c.getField("status_bar_height");
int x = Integer.parseInt(field.get(obj).toString());
return context.getResources().getDimensionPixelSize(x);
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
//检查设备是否具有NavigationBar - (全面屏返回 Ture)
public static boolean checkDeviceHasNavigationBar(Activity activity) {
boolean hasNavigationBar = false;
Resources rs = activity.getResources();
int id = rs.getIdentifier("config_showNavigationBar", "bool", "android");
if (id > 0) {
hasNavigationBar = rs.getBoolean(id);
}
try {
Class systemPropertiesClass = Class.forName("android.os.SystemProperties");
Method m = systemPropertiesClass.getMethod("get", String.class);
String navBarOverride = (String) m.invoke(systemPropertiesClass, "qemu.hw.mainkeys");
if ("1".equals(navBarOverride)) {
hasNavigationBar = false;
} else if ("0".equals(navBarOverride)) {
hasNavigationBar = true;
}
} catch (Exception e) {
}
return hasNavigationBar;
}
public static int getNavigationBar(Context context) {
return getScreenHeight(context) - getScreenShowHeight(context);
}
//屏幕的全高
public static int getScreenHeight(Context context) {
int dpi = 0;
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = windowManager.getDefaultDisplay();
DisplayMetrics displayMetrics = new DisplayMetrics();
@SuppressWarnings("rawtypes")
Class c;
try {
c = Class.forName("android.view.Display");
@SuppressWarnings("unchecked")
Method method = c.getMethod("getRealMetrics", DisplayMetrics.class);
method.invoke(display, displayMetrics);
dpi = displayMetrics.heightPixels;
} catch (Exception e) {
e.printStackTrace();
}
return dpi;
}
//屏幕显示内容的高
public static int getScreenShowHeight(Context context) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics outMetrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(outMetrics);
return outMetrics.heightPixels;
}
public static void moveViewDown(View view) {
//sdk 4.4: KITKAT
int distance = getStatusBarHeight(view.getContext());
// 父控件
ViewParent parent = view.getParent();
if (parent instanceof LinearLayout) {
// 通过marginTop属性让控件往下偏移一段距离
// 相当于:android:layout_marginTop="24dp"
((LinearLayout.LayoutParams) view.getLayoutParams()).topMargin = distance;
} else if (parent instanceof RelativeLayout) {
((RelativeLayout.LayoutParams) view.getLayoutParams()).topMargin = distance;
} else if (parent instanceof FrameLayout) {
((FrameLayout.LayoutParams) view.getLayoutParams()).topMargin = distance;
}
return;
}
public static void moveViewDown(View view, int downHeight) {
//sdk 4.4: KITKAT
// 父控件
ViewParent parent = view.getParent();
if (parent instanceof LinearLayout) {
// 通过marginTop属性让控件往下偏移一段距离
// 相当于:android:layout_marginTop="24dp"
((LinearLayout.LayoutParams) view.getLayoutParams()).topMargin = downHeight;
} else if (parent instanceof RelativeLayout) {
((RelativeLayout.LayoutParams) view.getLayoutParams()).topMargin = downHeight;
} else if (parent instanceof FrameLayout) {
((FrameLayout.LayoutParams) view.getLayoutParams()).topMargin = downHeight;
}
return;
}
//-- Color 设置
//设置 NavigationBar 颜色 5.0 一下机型不支持
public static void setNavigationBarColor(Activity activity, int navigationBarColor) {
//API21 以上才能设置
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
activity.getWindow().setNavigationBarColor(navigationBarColor);
}
public static void setStatusBarColor(Activity activity, int statusBarColor) {
//设置的extendStatusBar将无效
// [5.0 以上
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setStatusBarColor5_0(activity, statusBarColor);
}
// [4.4 - 5.0}
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
setStatusBarColor4_4_5_0(activity, statusBarColor);
}
}
private static void setStatusBarColor5_0(Activity activity, int statusBarColor) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = activity.getWindow();
//取消状态栏透明
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
//添加Flag把状态栏设为可绘制模式
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
//设置状态栏颜色
window.setStatusBarColor(statusBarColor);
//设置系统状态栏处于可见状态
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
//让view不根据系统窗口来调整自己的布局
ViewGroup mContentView = (ViewGroup) window.findViewById(Window.ID_ANDROID_CONTENT);
View mChildView = mContentView.getChildAt(0);
if (mChildView != null) {
ViewCompat.setFitsSystemWindows(mChildView, false);
ViewCompat.requestApplyInsets(mChildView);
}
}
}
private static View mStatusBarView;
private static void setStatusBarColor4_4_5_0(Activity activity, int statusBarColor) {
//4.4 - 5.0 不支持直接设置statusBarHeight
//思路:全屏 - 添加一个View置顶 高为getStatusBarHeight
Window window = activity.getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
//获取状态栏的高度
int statusBarHeight = getStatusBarHeight(activity);
//将顶部空间的top padding设置为和状态栏一样的高度,以此达到预期的效果
ViewGroup root = (ViewGroup) activity.findViewById(android.R.id.content);
if (mStatusBarView == null) {
mStatusBarView = new View(activity);
mStatusBarView.setBackgroundColor(statusBarColor);
} else {
// 先解除父子控件关系,否则重复把一个控件多次
// 添加到其它父控件中会出错
ViewParent parent = mStatusBarView.getParent();
if (parent != null) {
ViewGroup viewGroup = (ViewGroup) parent;
if (viewGroup != null)
viewGroup.removeView(mStatusBarView);
}
}
ViewGroup.LayoutParams param = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
statusBarHeight);
root.addView(mStatusBarView, param);
return;
}
//-- 单位 转换
/**
* dp转px
*
* @param context
* @param dpVal
* @return
*/
public static int dp2px(Context context, float dpVal) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpVal, context.getResources().getDisplayMetrics());
}
/**
* sp转px
*
* @param context
* @param spVal
* @return
*/
public static int sp2px(Context context, float spVal) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
spVal, context.getResources().getDisplayMetrics());
}
/**
* px转dp
*
* @param context
* @param pxVal
* @return
*/
public static float px2dp(Context context, float pxVal) {
final float scale = context.getResources().getDisplayMetrics().density;
return (pxVal / scale);
}
/**
* px转sp
*
* @param context
* @param pxVal
* @return
*/
public static float px2sp(Context context, float pxVal) {
return (pxVal / context.getResources().getDisplayMetrics().scaledDensity);
}
}
至于我写这个工具类的时候并没有使用到全屏(标题栏悬浮)的效果,所以我就没有写这个方法上去了。
public static void fullScreen(Activity activity) {
activity.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
这个方法呢.跟extendStatusBar差不多。extendStatusBar的状态栏任然显示只不过是透明的而已。
做视频播放的时候可以考虑使用一下这个fullScreen。禁止不动状态栏自动隐藏 , 触摸屏幕 状态栏显示几秒钟。
关于全面屏的NavigationBar
全面屏都没有Home键 (实际触摸按键),所以理论上都支持NavigationBar的,我自己的小米8是支持的。当你打开全面屏模式的时候,去checkDeviceHasNavigationBar() ,他是返回True回来的。
关于适配通过 Settings.Secure.getInt() 去拿到各大厂商不一样的设置值,判断用户手机是否开启了虚拟按键功能。
我个人觉得适配状态栏和导航栏,这个类已经够用了.有补充的小伙伴.欢迎提出.代码有Bug,欢迎找我.