GitHub源码:TransparentStatusbar app
认识透明状态栏 从Android4.4开始引入了透明状态栏的新特性.Android系统状态栏,右边为透明状态栏.   
正常显示状态栏的图标/文字   
状态栏的背景是透明的,能透出应用的背景色.而不像之前一样是默认的黑色不可编辑. 
 
透明状态栏Api及特性 从Android 4.4(v19)开始,透明状态栏特性变化很频繁,直到Android 6.0(v23)才真正完善稳定.  Api或特性.
Version/level 
Features 
Description 
 
 
4.4/v19 
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS 
状态栏是渐变色的半透明 
 
4.4_Watch/v20 
OnApplyWindowInsetsListener 
能够区分多个Inset事件与Rect信息(PS.系统状态栏属于插入区Inset的一种) 
 
5.0/v21 
WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS 
允许自定义状态栏背景色了,但无法控制状态栏上的文字/图标颜色 
 
6.0/v23 
View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR 
状态栏上的图标/文字颜色的亮色模式,即颜色是暗色 
 
设置透明状态栏 根据多个版本间的Api及特性,Java代码如下:  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // Activity.java // onCreate(Bundle bundle)                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {                 // Android 5.0                    int visibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {                     // Android 6.0                        // 亮色模式,避免系统状态栏的图标不可见                        // visibility |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;                    }                    window.getDecorView().setSystemUiVisibility(visibility);                    window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);                 // 自定义状态栏背景色                    window.setStatusBarColor(Color.TRANSPARENT);                } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {                 // Android 4.4                    window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);                } 
注意:  要设置透明状态栏的Activity其theme须是NoTitleBar.  
 
1 2 3 4 5 6 7 8 9 10 // AndroidManifest.xml         <activity android:name="MyActivity"                    android:theme="@android:style/Theme.NoTitleBar"               />       // Or / 或            // MyActivity.java     public void onCreate(Bundle bundle) {           super.onCreate(bundle);           requestWindowFeature(Window.FEATURE_NO_TITLE);       } 
代码执行后,界面显示效果如下图:Activity的contentView顶上去 占据了原来属于系统状态栏的区域.Back Title虽然仍在Titlebar区域垂直居中,但视觉效果上受状态栏图标的影响,却不是垂直居中的效果.      
所以接下来第二步就是: 以何种方式处理消失的系统状态栏区域?
处理消失的系统状态栏区域 处理方式可以有:    
就让activity的contentView顶上去吧,不需要修改.
 
调整Titlebar的高度.  
可以通过设置paddingTop/layout_height. 
对于不同的Android版本,可以通过版本适配文件,在values/dimens.xml和values-v19/dimens.xml分别定义具体数值. 
调整paddingTop,有可能导致Titlebar中的内容不会再垂直居中. 
不适应无Titlebar的activity.    
 
 
android:fitsSystemWindows && OnApplyWindowInsetsListener
fitsSystemWindows标签可以直接对View添加paddingXXX  从activity的布局嵌套结构中只对第一个 设置fitsSystemWidnows的View有效,无法设置设置到多个View 
方式单一,对于指定的界面实现简单,但要应用于整个App超多Activity的layout.xml,不够灵活. 
不够灵活还表现在:有些activity的复杂效果可能会有多个View同时或分场合占据系统状态栏的空间,需要留出额外的修改接口.             
 
在activity的contentView的顶部再addView直接填充原来状态栏的区域.    
OnApplyWindowInsetsListener可以回调给开发者当前WindowInset的区域类型与区域宽高Rect信息   
和fitsSystemWindows一样,多个View设置该监听但也只有最外层的view会被调用执行.    
 
 
在实践中,本人采用了1 2 5这三种方式配合使用.   
fitsSystemWindows 我并不想用该属性.
1 2 3 4 5 6 View.java   public WindowInsets dispatchApplyWindowInsets(WindowInsets insets)   private boolean fitSystemWindowsInt(Rect insets)      // 这个方法是真正为View添加paddingXXX的地方   protected void internalSetPadding(int left, int top, int right, int bottom) 
调试系统源码的一个方法:Android自带模拟器Debug,断点跟进执行过程.要注意,模拟器的apk level要和compileSdkVersion及buildToolsVersion相对应.  
Activity中的接口设计 接口设计的原则:  
对正常的业务布局xml的编写没有强制要求fitsSystemWindows   
不影响正常的业务Activity的java代码编写 
提供灵活的处理方式 
 
类图如下:
BaseActivity 是App中所有Activity的父类.Activity相关,所以对应的接口声明都放在BaseActivity中.Activity的透明状态栏功能是开启的. 
1 2 `onCreate` → `setContentView` → `isFixTransparentStatusbar`                                             └──true→ `fixTransparentStatusbar` 
具体代码实现为:   
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 // WhateverActivity.java       @Override       protected void onCreate(Bundle savedInstanceState) {           super.onCreate(savedInstanceState);           setContentView(R.layout.activity_main);       }   // BaseActivity.java       @Override       public void setContentView(View view) {           rootView = view;           super.setContentView(view);              if (isFixTransparentStatusBar()) {               Window window = getWindow();               if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {                   int visibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;                   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {                       // 亮色模式,避免系统状态栏的图标不可见                     visibility |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;                 }                   window.getDecorView().setSystemUiVisibility(visibility);                   window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);                   window.setStatusBarColor(Color.TRANSPARENT);                      fixTransparentStatusBar(view);                   // 最后fix一下状态栏背景白色与系统的文字图标白色的问题                   fixTransparentStatusBarWhiteTextColor(view, viewStatusbarBackground);               } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {   //                WindowManager.LayoutParams localLayoutParams = window.getAttributes();   //                localLayoutParams.flags = (WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS | localLayoutParams.flags);                   window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);                     fixTransparentStatusBar(view);                  // 最后fix一下状态栏背景白色与系统的文字图标白色的问题                   fixTransparentStatusBarWhiteTextColor(view, viewStatusbarBackground);               } else {                   setStatusbarBackgroundGone();               }           } else {                setStatusbarBackgroundGone();           }       } 
注意  : 由于透明状态栏是执行Window.addFlags()实现的,该方法又调用了Window.setFlags().Api文档,发现推荐先执行setContentView后执行Window.setFlags().
 
TitlebarActivity 是通用的包含Titlebar的Activity.setContentView(),实现自动添加Titlebar这个通用组件,当然不需要Titlebar时也可以使用setContentViewNoTitlebar().Titlebar前先添加上通用的viewStatusbarBackground.setContentView()的实现为:   
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Override public void setContentView(View view) {       contentView = view;           LinearLayout linearLayout = new LinearLayout(this);       linearLayout.setOrientation(LinearLayout.VERTICAL);          LayoutInflater.from(this).inflate(R.layout.transparent_status_bar_bg_view, linearLayout, true);       viewStatusbarBackground = linearLayout.findViewById(R.id.status_bar_background);          LayoutInflater.from(this).inflate(R.layout.titlebar_original, linearLayout, true);       viewTitlebar = linearLayout.findViewById(R.id.titlebar_layout);          initTitlebarIDs(viewTitlebar);          linearLayout.addView(contentView, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));          super.setContentView(linearLayout);   } 
其中,为了便于定位到Titlebar及viewStatusbarBackground,这两个组件的id都被预先定义在attrs.xml中.  
1 2 3 4 5 // values/attrs.xml <resources>     <item name="status_bar_background" type="id"/>     <item name="titlebar_layout" type="id"/> </resources> 
Fragment中的接口设计 有的Activity的显示主体是Fragment,接口设计的观点为不应干扰Fragment正常的onCreateView()的实现流程.Fragment的contentView呢?Api发现了Fragment::onViewCreated(View view)这个方法,该方法会在onCreateView()返回后,立即执行,且方法参数为onCreateView()所返回的View.  
Java代码实现如下:  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class BaseFragment extends Fragment {     @Override     public void onViewCreated(View view, Bundle savedInstanceState) {         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT                 && isFixTransparentStatusBar()) {             fixTransparentStatusBar(view);         }         super.onViewCreated(view, savedInstanceState);     }     /**      * 是否需要改变status bar背景色,对于某些机型手机(如oppo)无法改变状态栏字体颜色,      * 会被当前状态栏挡住字体颜色,因此修改透明状态栏背景色      * @return true: 调用fixTransparentStatusBar()      */     protected boolean isFixTransparentStatusBar(){         return false;     }     /**      * @param view {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}中返回的view.      * */     protected void fixTransparentStatusBar(View view) {     } } 
通过fixTransparentStatusBar(),即可以调整Fragment的界面显示,无论是往状态栏区域添加一个填充View或根据id再调整宽高或padding都是可以的.  
白色Titlebar的处理 Android 6.0及以上可以使用亮色模式.Titlebar如果是白色的,或者说App的主题是白色的,则会出现状态栏的白色文字和图标被淹没在Titlebar中无法阅读.如下图:layer-list来设置分层背景,不必新增额外的View填充系统状态栏区域.TestBasic/res/drawable/title_layout_white3.xml:   
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android">     <item>         <shape android:shape="rectangle">             <solid android:color="@android:color/black" />         </shape>     </item>     <item android:bottom="1dp">         <shape android:shape="rectangle">             <solid                 android:color="@android:color/white" />         </shape>     </item>     <!-- 48dp为标题栏高度 -->     <item android:bottom="48dp">         <shape xmlns:android="http://schemas.android.com/apk/res/android"             android:shape="rectangle">             <gradient                 android:startColor="@android:color/white"                 android:centerColor="@color/middleColor"                 android:endColor="@android:color/darker_gray"                 android:angle="90"                 />         </shape>     </item> </layer-list> 
第一个item为黑色背景,效果为Titlebar底下的黑色分隔线.   
第二个item为常规的Titlebar背景.   
第三个item为状态栏的过滤渐变背景色.    
 
最张效果见下图:
React-Native的处理 React-Native是js代码,怎么办?React是表象,Native是实质。一样处理掉。React-Native最root的组件界面是ReactRootView,可以在显示的Activity里布局使用LinearLayout,orientation为VERTICAL,TransparentStatusBar及ReactRootView一并添加为子View,设置该LinearLayout为Activity的contentView即可。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class ElnReactBaseActivity extends BaseActivity {     private ReactInstanceManager mReactInstanceManager;        private ReactRootView mReactRootView;     @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);            LinearLayout linearLayout = new LinearLayout(this);         linearLayout.setOrientation(LinearLayout.VERTICAL);         viewStatusbarBackground = LayoutInflater.from(this).inflate(R.layout.transparent_status_bar_bg_view, linearLayout, false);         linearLayout.addView(viewStatusbarBackground);            mReactRootView = new ReactRootView(this);         LinearLayout.LayoutParams layParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);         linearLayout.addView(mReactRootView, layParams);            mReactInstanceManager = ReactHelper.getInstance().getReactManager();         Bundle bundle = getExtra();         mReactRootView.startReactApplication(mReactInstanceManager, "ELearning", bundle);              setContentView(linearLayout);     } } 
小米 与 魅族 与 (莫名其妙的)华为 小米 与 魅族都能通过自各的反射方法实现状态栏的亮色模式,解决白色Titlebar的问题.
小米状态栏变色 魅族状态栏变色 
上述的代码也整合进了GitHub中的工程TitlebarBelowTransparentStatusBar.   
至于华为,额…大部分华为机子都是好机,但华为荣耀6 Plus(PE-TL10,EMUI3.1,Android 5.1.1) 明明是Android 5.1,但使用5.1的代码无效,得使用4.4的实现方式.    
腾讯优测UTest 一个方便使用的App远程测试平台,机型多,Android版本齐全.
About Sodino