1.全屏PopupWindow
大客户中全屏的PopupWindow,通常是用于一些简单信息的展示、引导和选择等,常见的有以下几种:
(1)引导PopupWindow
商详页“加常用”至清单引导
全部菜品&购物车页的切换门店引导
(2)优惠券
(3)添加商品输入键盘
(4)商详大图浏览
(5)结算页图片弹屏广告
(6)购物车页商品需分开结算弹窗
……
目前大客户中的绝大多数全屏PopupWindow只要在构造函数中添加如下一行代码即可实现沉浸式状态栏,如下:
this.setClippingEnabled(false);
注:少数全屏PopupWindow在调用showAtLocation(View parent, int gravity, int x, int y)时,第一个参数view传入了嵌套太深的位置不对的子view会导致无法实现沉浸式状态栏。
虚拟导航栏与全屏PopupWindow显示重叠问题解决方案
设置了this.setClippingEnabled(false);后,在底部有虚拟导航栏的手机上会发生重叠,这时候可以有如下解决方案:
//方案一 使全屏PopupWindow底部撑出一个虚拟导航栏的高度的内边距
PopupWindowUtil.getNavigationBarHeight(context);//获取底部虚拟导航栏高度
if(PopupWindowUtil.hasNavBar(context)){
mMenuView.setPadding(mMenuView.getPaddingLeft(),
mMenuView.getPaddingTop(),
mMenuView.getPaddingRight(),
mMenuView.getPaddingBottom()+PopupWindowUtil.getNavigationBarHeight(context)
);
}
//方案二 重写showAtLocation设置gravity、x、y
@Override
public void showAtLocation(View parent, int gravity, int x, int y) {
super.showAtLocation(parent, Gravity.TOP, x, y - PopupWindowUtil.getNavigationBarHeight(parent.getContext()));
}
2.DialogFragment
全屏DialogFragment如果setStyle(int style, int theme),写theme时,选择带_Fullscreen的theme,如下:
setStyle(DialogFragment.STYLE_NO_TITLE,android.R.style.Theme_Translucent_NoTitleBar_Fullscreen);
3.Activity
3.1普通Activity
在这里普通Activity指,Activity里的主体内容不是Fragment且状态栏颜色不需要频繁变化的Activity。这种Activity,只需要在基类或onCreate()中调用状态栏设置工具,如下:
StatusBarUtil.StatusBarLightMode(this,R.color.color_FFFFFF,R.color.color_FFFFFF, false);
上面的代码是将状态栏设置为白色,在实际使用时应根据需要设置不同颜色。考虑到现在大客户和商城中大部分UI设计,状态栏为白色,所以可以在基类的onCreate()中调用上面的代码,这样不少页面就无需再单独设置。事实上五六米就是这么干的,基类中调用StatusBarLightMode,将状态栏颜色默认设为白色。
StatusBarLightMode方法的源码如下:
public static void StatusBarLightMode(Activity activity, int colorId, int statusBarColorUnderM, boolean isImmersive) {
int buildSdkInt = Build.VERSION.SDK_INT;
if (buildSdkInt >= Build.VERSION_CODES.LOLLIPOP) {
if (isImmersive) {
int uiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
if (buildSdkInt >= Build.VERSION_CODES.M) {
uiVisibility |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
activity.getWindow().setStatusBarColor(ContextCompat.getColor(activity, colorId));
} else {
activity.getWindow().setStatusBarColor(activity.getResources().getColor(statusBarColorUnderM));
}
activity.getWindow().getDecorView().setSystemUiVisibility(uiVisibility);
} else {
if (buildSdkInt >= Build.VERSION_CODES.M) {
activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
activity.getWindow().setStatusBarColor(ContextCompat.getColor(activity, colorId));
} else {
activity.getWindow().setStatusBarColor(ContextCompat.getColor(activity, statusBarColorUnderM));
}
}
}
}
从源码可以看出,StatusBarLightMode方法仅对Android5.0及以上系统设置有效,不影响Android5.0之下的状态栏设置。从StatusBarLightMode源码,还可以看出,StatusBarLightMode方法的第2个参数指的是设置6.0以上系统的状态栏颜色,第3个参数专指5.0系统的状态栏颜色。
3.2特殊Activity
主体显示内容为Fragment或需要频繁变动状态栏的Activity,可以在onCreate()中调用状态栏设置工具,设置如下:
StatusBarUtil.StatusBarLightMode(this,R.color.color_00000000,R.color.color_00000000, true);
思路是将状态栏设置为透明(R.color.color_00000000),使用沉浸式占位来占出状态栏的高度。其中的关键是沉浸式占位,而沉浸式占位也有两个常见思路,一是在代码里获取状态栏高度,设置顶部第一个可见view的距离顶部的内边距增加一个状态栏的高度,常见Java代码如下:
//沉浸式占位(方式:内边距占位)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
int statusBarHeight = StatusBarUtil.getStatusBarHeight(getContext());
homePageTitleSearch.setPadding(
homePageTitleSearch.getPaddingLeft(),
homePageTitleSearch.getPaddingTop() + statusBarHeight,
homePageTitleSearch.getPaddingRight(),
homePageTitleSearch.getPaddingBottom()
);
}
另一个常见的占位方式是,新增一个占位View,放在原顶部可见view之前,即放在顶部位置,并在布局中将其可见行性设置为GONE。在代码中,判断当前是不是Android5.0及以上系统,如果是则将占位View高度设置为状态栏高度并显示出来。在实际应用中,例如,购物车,其顶部titleBar会随着滑动自动隐藏,这时如果使用第一种内边距占位,待到购物车顶部titleBar隐藏时状态栏信息则会与购物车显示信息重叠。这时可以考虑第二种占位方式,使用单独的占位View,常见Kotlin代码如下:
//沉浸式占位(方式:单独的占位View)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
viewHeaderPlaceholder.visibility = View.VISIBLE
val layoutParams = viewHeaderPlaceholder.layoutParams
val statusBarHeight = StatusBarUtil.getStatusBarHeight(viewHeaderPlaceholder.context)
layoutParams.height = statusBarHeight
viewHeaderPlaceholder.layoutParams = layoutParams
}
注意:
xml中使用android:fitsSystemWindows="true"和下面这个冲突
StatusBarUtil.StatusBarLightMode(this,R.color.color_00000000,R.color.color_00000000, true);
后记
H5页面的状态栏设置:目前Jsbridge里暂无状态栏设置的方法,另外假设Jsbridge实现了状态栏调用方法,而H5开发人员不去主动调用,可以考虑当native导航栏被调用,这时可以考虑当发现H5没有调设置状态栏方法时,代码里根据导航栏颜色主动去设置状态栏颜色,最大限度保持沉浸式体验。
优化点:当前状态栏工具还有优化的空间,可以考虑后期优化优化。另外一些方法或style的应用也会或多或少影响沉浸式状态栏的显示效果,可以进一步考虑更细致化的优化,最大限度保持沉浸式体验。
package com.meicai.common.utils;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.graphics.Color;
import android.os.Build;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import com.meicai.commonlib.R;
import com.readystatesoftware.systembartint.SystemBarTintManager;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* 状态栏设置
*/
public class StatusBarUtil {
/**
* 修改状态栏为全透明
*
* @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);
}
}
/**
* 修改状态栏颜色,支持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版本状态栏变色,需要先将状态栏设置为透明
transparencyBar(activity);
SystemBarTintManager tintManager = new SystemBarTintManager(activity);
tintManager.setStatusBarTintEnabled(true);
tintManager.setStatusBarTintResource(colorId);
}
}
/**
* 状态栏亮色模式,设置状态栏黑色文字、图标,
* 适配4.4以上版本MIUIV、Flyme和6.0以上版本其他Android
*
* @param activity
* @return 1:MIUUI 2:Flyme 3:android6.0
*/
public static void StatusBarLightMode(Activity activity, int colorId, int statusBarColorUnderM) {
StatusBarLightMode(activity, colorId, statusBarColorUnderM, false);
}
/**
* 状态栏亮色模式,设置状态栏黑色文字、图标,
* 适配4.4以上版本MIUIV、Flyme和6.0以上版本其他Android
*
* @param activity
* @return 1:MIUUI 2:Flyme 3:android6.0
*/
public static void StatusBarLightMode(Activity activity, int colorId, int statusBarColorUnderM, boolean isImmersive) {
int buildSdkInt = Build.VERSION.SDK_INT;
if (buildSdkInt >= Build.VERSION_CODES.LOLLIPOP) {
if (isImmersive) {
int uiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
if (buildSdkInt >= Build.VERSION_CODES.M) {//6.0
uiVisibility |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
activity.getWindow().setStatusBarColor(ContextCompat.getColor(activity, colorId));
} else if (buildSdkInt >= Build.VERSION_CODES.LOLLIPOP) {//5.0
activity.getWindow().setStatusBarColor(ContextCompat.getColor(activity,
R.color.dark_transparent));
} else {
activity.getWindow().setStatusBarColor(activity.getResources().getColor(statusBarColorUnderM));
}
activity.getWindow().getDecorView().setSystemUiVisibility(uiVisibility);
} else {
if (buildSdkInt >= Build.VERSION_CODES.M) {//6.0
activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
activity.getWindow().setStatusBarColor(ContextCompat.getColor(activity, colorId));
} else if (buildSdkInt >= Build.VERSION_CODES.LOLLIPOP) {//5.0
activity.getWindow().setStatusBarColor(ContextCompat.getColor(activity,
R.color.dark_transparent));
} else {
activity.getWindow().setStatusBarColor(ContextCompat.getColor(activity, statusBarColorUnderM));
}
}
}
}
/**
* 已知系统类型时,设置状态栏黑色文字、图标。
* 适配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);
}
}
/**
* 状态栏暗色模式,清除MIUI、flyme或6.0以上版本状态栏黑色文字、图标
*/
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);
}
}
/**
* 设置状态栏图标为深色和魅族特定的文字风格
* 可以用来判断是否为Flyme用户
*
* @param window 需要设置的窗口
* @param dark 是否把状态栏文字及图标颜色设置为深色
* @return boolean 成功执行返回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 是否把状态栏文字及图标颜色设置为深色
* @return boolean 成功执行返回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);//状态栏透明且黑色字体
} else {
extraFlagField.invoke(window, 0, darkModeFlag);//清除黑色字体
}
result = true;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
//开发版 7.7.13 及以后版本采用了系统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;
}
/**
* 将sp值转换为px值,保证文字大小不变
*
* @param spValue (DisplayMetrics类中属性scaledDensity)
* @return
*/
public static int sp2px(Context context, float spValue) {
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (spValue * fontScale + 0.5f);
}
public static int getStatusBarHeight(@NonNull Context context) {
int result = 0;
int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = context.getResources().getDimensionPixelSize(resourceId);
}
return result;
}
public static void setImmersiveStatusBar(Context context, View view) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
transparencyBar((Activity) context);
int statusBarHeight = StatusBarUtil.getStatusBarHeight(context);
ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
layoutParams.height += statusBarHeight;
view.setLayoutParams(layoutParams);
view.setPadding(view.getPaddingLeft(), view.getPaddingTop() + statusBarHeight, view.getPaddingRight(), view.getPaddingBottom());
}
}
/**
* Set the status bar's light mode.
*
* @param activity The activity.
* @param isLightMode True to set status bar light mode, false otherwise.
*/
public static void setStatusBarLightMode(@NonNull final Activity activity,
final boolean isLightMode) {
setStatusBarLightMode(activity.getWindow(), isLightMode);
}
/**
* Set the status bar's light mode.
*
* @param window The window.
* @param isLightMode True to set status bar light mode, false otherwise.
*/
public static void setStatusBarLightMode(@NonNull final Window window,
final boolean isLightMode) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
View decorView = window.getDecorView();
int vis = decorView.getSystemUiVisibility();
if (isLightMode) {
vis |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
} else {
vis &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
}
decorView.setSystemUiVisibility(vis);
}
}
}