最近在App上需要对状态栏进行相关的设置,在网上看了些文章,像郭神的沉浸式那篇博客对我启发蛮大,但是对状态栏的设置,包括隐藏,透明,颜色设置等,并没有比较系统的概念,实现方式不止一种,有操作Window属性的方式,也有操作decorView.setSystemUiVisibility方法来控制系统UI的方式。但是这些方式有什么区别,以及如何具体操作,所以写这篇博客总结学习一下。
首先要说的一点就是Android系统到了4.4以后才提供沉浸式体验的支持。当设置透明效果后,4.4以下无效果,4.4~5.0全透明,5.0以上半透明;Android沉浸式模式的本质就是全屏化。下面就围绕这个展开学习。
根据Android的设计建议,ActionBar是不应该独立于状态栏而单独显示的,因此状态栏如果隐藏了,我们同时也需要隐藏ActionBar。所以先来了解下隐藏系统标题栏的方法。
这里提供三种方式:
1、调用ActionBar的hide()方法将ActionBar也进行隐藏。
ActionBar actionBar = getSupportActionBar();
actionBar.hide();
2、调用requestWindowFeature(Window.FEATURE_NO_TITLE);(推荐)
注意:supportRequestWindowFeature(Window.FEATURE_NO_TITLE);来实现,不过记得要放在setContentView()方法前面;
3、在主题中设置
< item name="windowNoTitle">true< /item >
注意:”android:windowNoTitle”和“windowNoTitle”两个属性的区别,我使用了V7兼容包主题,有两个所以使用的是后者,这两个属性的区别,因为涉及到内容较多,所以单独再写了一篇博客。
这里提供三种方式:
1、主题方式
可以用在< appliction >节点下的主题,也可以根据项目需求放在相应的< Activity>节点下的主题上;这种方式兼顾到Android本身状态栏对于不同版本的适配(例如4.4~5.0全透明,5.0以上半透明),会根据不同版本来适配。
<item name="android:windowTranslucentStatus">trueitem>
<item name="android:windowTranslucentNavigation">trueitem>
<item name="android:statusBarColor">@android:color/transparentitem>
2、通过系统提供的标志位设置(推荐)
这种方式和方式一同样会根据Android本身状态栏对于不同版本来适配
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);//设置透明状态栏
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);//设置透明导航栏
}
3、通过给系统窗口设置颜色值
这种方式可以避免Android本身状态栏对于不同版本的适配,只要用这种方式设置,5.0以上也可以实现全透明。但前提要求是Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP,只能适用于5.0以上的系统。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getWindow().setStatusBarColor(Color.TRANSPARENT);//将状态栏设置成透明色
getWindow().setNavigationBarColor(Color.TRANSPARENT);//将导航栏设置为透明色
}
这种需求一般容易出现在顶部直接就是图片的情况,如果是标题栏的话,直接设置透明会让文本内容置于状态栏下面,不利于一些点击事件的响应。
效果如下:
代码的话直接按上面透明方式设置就可以了
开发中大部分需求是实现状态栏和顶部的控件是同一个颜色,同时,控件内容也不和状态栏重复。要实现这样的效果关键是在xml中给顶部控件添加上两个属性就可以了,这两个属性在我博客Android中XML属性中都有详细的文章。这里就不多赘述了。
android:fitsSystemWindows="true"
android:clipToPadding="true"
布局代码:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/llt_root"
android:orientation="vertical">
<TextView
android:fitsSystemWindows="true"
android:clipToPadding="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorAccent"
android:gravity="center"
android:text="App标题栏"
android:textSize="30sp"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#0CE3EE"
android:gravity="center"
android:text="App内容部分"
android:textSize="30sp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#4188F8"
android:gravity="center"
android:text="App导航栏"
android:textSize="30sp"/>
LinearLayout>
然后设置好状态栏透明:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
if (Build.VERSION.SDK_INT >= 21) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
getWindow().setStatusBarColor(Color.TRANSPARENT);//防止5.x以后半透明影响效果,使用这种透明方式
}
}
界面默认情况下是全屏的,状态栏和导航栏都不会显示。而当我们需要用到状态栏或导航栏时,只需要在屏幕顶部向下拉,状态栏和导航栏就会显示出来,此时界面上任何元素的显示或大小都不会受影响。过一段时间后如果没有任何操作,状态栏和导航栏又会自动隐藏起来,重新回到全屏状态。
效果如图:
这里附上一张郭霖博客中的图片,该博客地址在总结中给出
当你确定要使用沉浸式模式,那么只需要重写Activity的onWindowFocusChanged()方法,然后加入如下逻辑即可:
//沉浸式体验
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus && Build.VERSION.SDK_INT >= 19) {
View decorView = getWindow().getDecorView();
decorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);//设置透明状态栏
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);//设置透明导航栏
}
将以上概念整理成工具类,直接拷贝使用就可以了。
public class BaseApp extends Application {
public static float mDensity;
private static Context context;
@Override
public void onCreate() {
super.onCreate();
context = this;
initScreenSize();
}
private static void initScreenSize() {
DisplayMetrics dm = context.getResources().getDisplayMetrics();
mDensity = dm.density;
}
//=============沉侵式==(begin)=================
private static View mStatusBarView;
/** 设置全屏沉侵式效果 */
public static void setNoStatusBarFullMode(Activity activity) {
// sdk 4.4
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
Window window = activity.getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
if (mStatusBarView != null) {
ViewGroup root = (ViewGroup) activity.findViewById(android.R.id.content);
root.removeView(mStatusBarView);
}
return;
}
// sdk 5.x
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = activity.getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
window.setStatusBarColor(Color.TRANSPARENT);
return;
}
}
/** 设置控件的paddingTop, 使它不被StatusBar覆盖 */
public static void setStatusBarPadding(View view) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
int marginTop = getStatusBarHeight(view.getContext());
view.setPadding(view.getPaddingLeft(), marginTop,
view.getPaddingRight(), view.getPaddingBottom());
return;
}
}
public static void setStatusBarColor(Activity activity, int statusColor) {
// sdk 4.4
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
ViewGroup root = (ViewGroup) activity.findViewById(android.R.id.content);
if (mStatusBarView == null) {
//为了适配一些特殊机型的状态栏颜色无法改变,同时高度和系统原生的高度区别,所以这里重新创建一个View用于覆盖状态栏来实现效果
mStatusBarView = new View(activity);
mStatusBarView.setBackgroundColor(statusColor);
} 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,
getStatusBarHeight(activity));
root.addView(mStatusBarView, param);
}
return;
}
// sdk 5.x
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
activity.getWindow().setStatusBarColor(statusColor);
return;
}
}
/**
* 通过反射的方式获取状态栏高度,
* 一般为24dp,有些可能较特殊,所以需要反射动态获取
*/
private static int getStatusBarHeight(Context context) {
try {
Class> clazz = Class.forName("com.android.internal.R$dimen");
Object obj = clazz.newInstance();
Field field = clazz.getField("status_bar_height");
int id = Integer.parseInt(field.get(obj).toString());
return context.getResources().getDimensionPixelSize(id);
} catch (Exception e) {
e.printStackTrace();
System.out.println("-------无法获取到状态栏高度");
}
return dp2px(24);
}
public static int dp2px(int dp) {
return (int) (dp * mDensity);
}
//=============沉侵式==(end)=================
这里要说的是,其实真正的沉浸式体验除了像游戏或者视频软件这类特殊的应用,大多数的应用程序都是用不到沉浸式模式的。大部分情况下,只是要求状态栏能和所在页面顶部View的颜色保持一致,且不覆盖该View的内容。
也许在一些手机上还会遇到适配不成功的问题,因为国内手机生产商都会对Android手机系统做一定的定制,当然这也是Android碎片化的一个影响嘛。但是这不是我们做技术的借口,遇到难题还是要有去解决的激情。当然如果博文中有什么错误的地方,希望能及时留言帮我指出,这样才能不断的进步嘛。
附上:郭霖 Android状态栏微技巧,带你真正理解沉浸式模式