Android 透明状态栏

weijh 8年前
   <p>最近业务上看到一个设计图挺好看,所以研究了一下透明状态栏,注意不是沉浸式状态栏,在参考了网上的一些资料后,整理出了这篇博客.</p>    <p>Github Demo 链接: <a href="/misc/goto?guid=4959670105455063852" rel="external">StatusBarCompat</a></p>    <h2>参考文章:</h2>    <ol>     <li> <p><a href="http://www.jianshu.com/p/140be70b84cd?utm_source=tuicool&utm_medium=referral" rel="external">由沉浸式状态栏引发的血案</a></p> </li>     <li> <p><a href="/misc/goto?guid=4958986147220235828" rel="external">Translucent System Bar 的最佳实践</a></p> </li>     <li> <p><a href="/misc/goto?guid=4959670216781749623" rel="external">该使用 fitsSystemWindows 了!</a></p> </li>    </ol>    <p>首先强调,对于状态栏的处理有两种不同的方式, 这里从<a href="/misc/goto?guid=4958986147220235828" rel="external">Translucent System Bar 的最佳实践</a>直接盗了两张图做对比~.</p>    <table style="width:899px">     <thead>      <tr>       <th style="text-align:left; vertical-align:middle">全屏( ContentView 可以进入状态栏)</th>       <th style="text-align:left; vertical-align:middle">非全屏 ( ContentView 与状态栏分离, 状态栏直接着色)</th>      </tr>     </thead>     <tbody>      <tr>       <td style="text-align:left; vertical-align:middle"><a href="https://simg.open-open.com/show/a3c1de102b08dbd73756af83b27e78b4.png" rel="group"><img alt="" src="https://simg.open-open.com/show/a3c1de102b08dbd73756af83b27e78b4.png" style="margin:0px"></a></td>       <td style="text-align:left; vertical-align:middle"><a href="https://simg.open-open.com/show/443feb3c1d9aa37f8f8f8e492fc1b633.png" rel="group"><img alt="" src="https://simg.open-open.com/show/443feb3c1d9aa37f8f8f8e492fc1b633.png" style="margin:0px"></a></td>      </tr>     </tbody>    </table>    <p>先定义几个名词:</p>    <ol>     <li> <p>全屏模式: 左边图所示.</p> </li>     <li> <p>着色模式: 右边图所示.</p> </li>     <li> <p>ContentView: activity.findViewById(Window.ID_ANDROID_CONTENT) 获取的 View , 即 setContentView 方法所设置的 View, 实质为 FrameLayout.</p> </li>     <li> <p>ContentParent: ContentView 的 parent , 实质为 LinearLayout.</p> </li>     <li> <p>ChildView: ContentView 的第一个子 View ,即布局文件中的 layout .</p> </li>    </ol>    <p>再介绍一下相关的函数:</p>    <ol>     <li> <p>fitsSystemWindows, 该属性可以设置是否为系统 View 预留出空间, 当设置为 true 时,会预留出状态栏的空间.</p> </li>     <li> <p>ContentView, 实质为 ContentFrameLayout, 但是重写了 dispatchFitSystemWindows 方法, 所以对其设置 fitsSystemWindows 无效.</p> </li>     <li> <p>ContentParent, 实质为 FitWindowsLinearLayout, 里面第一个 View 是 ViewStubCompat, 如果主题没有设置 title ,它就不会 inflate .第二个 View 就是 ContentView.</p> </li>    </ol>    <h2>5.0以上的处理:</h2>    <p>自5.0引入 Material Design ,状态栏对开发者更加直接,可以直接调用 setStatusBarColor 来设置状态栏的颜色.</p>    <p><strong>全屏模式:</strong></p>    <pre>  <code class="language-java">Window window = activity.getWindow();  //设置透明状态栏,这样才能让 ContentView 向上  window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);     //需要设置这个 flag 才能调用 setStatusBarColor 来设置状态栏颜色  window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);   //设置状态栏颜色  window.setStatusBarColor(statusColor);    ViewGroup mContentView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);  View mChildView = mContentView.getChildAt(0);  if (mChildView != null) {      //注意不是设置 ContentView 的 FitsSystemWindows, 而是设置 ContentView 的第一个子 View . 使其不为系统 View 预留空间.      ViewCompat.setFitsSystemWindows(mChildView, false);  }</code></pre>    <p><strong>着色模式:</strong></p>    <pre>  <code class="language-java">Window window = activity.getWindow();  //取消设置透明状态栏,使 ContentView 内容不再覆盖状态栏  window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);     //需要设置这个 flag 才能调用 setStatusBarColor 来设置状态栏颜色  window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);   //设置状态栏颜色  window.setStatusBarColor(statusColor);    ViewGroup mContentView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);  View mChildView = mContentView.getChildAt(0);  if (mChildView != null) {      //注意不是设置 ContentView 的 FitsSystemWindows, 而是设置 ContentView 的第一个子 View . 预留出系统 View 的空间.      ViewCompat.setFitsSystemWindows(mChildView, true);  }</code></pre>    <h2>4.4-5.0的处理:</h2>    <p>4.4-5.0因为没有直接的 API 可以调用,需要自己兼容处理,网上的解决方法基本都是创建一下高度为状态栏的 View ,通过设置这个 View 的背景色来模拟状态栏. 这里我尝试了三种方法来兼容处理.</p>    <p>方法1: 向 ContentView 添加假 View , 设置 ChildView 的 marginTop 属性来模拟 fitsSystemWindows .</p>    <p>全屏模式:</p>    <pre>  <code class="language-java">Window window = activity.getWindow();  ViewGroup mContentView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);    //首先使 ChildView 不预留空间  View mChildView = mContentView.getChildAt(0);  if (mChildView != null) {      ViewCompat.setFitsSystemWindows(mChildView, false);  }    int statusBarHeight = getStatusBarHeight(activity);  //需要设置这个 flag 才能设置状态栏  window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);  //避免多次调用该方法时,多次移除了 View  if (mChildView != null && mChildView.getLayoutParams() != null && mChildView.getLayoutParams().height == statusBarHeight) {      //移除假的 View.      mContentView.removeView(mChildView);      mChildView = mContentView.getChildAt(0);  }  if (mChildView != null) {      FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mChildView.getLayoutParams();      //清除 ChildView 的 marginTop 属性      if (lp != null && lp.topMargin >= statusBarHeight) {          lp.topMargin -= statusBarHeight;          mChildView.setLayoutParams(lp);      }  }</code></pre>    <p>着色模式:</p>    <pre>  <code class="language-java">Window window = activity.getWindow();  ViewGroup mContentView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);    //First translucent status bar.  window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);  int statusBarHeight = getStatusBarHeight(activity);    View mChildView = mContentView.getChildAt(0);  if (mChildView != null) {      FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mChildView.getLayoutParams();      //如果已经为 ChildView 设置过了 marginTop, 再次调用时直接跳过      if (lp != null && lp.topMargin < statusBarHeight && lp.height != statusBarHeight) {          //不预留系统空间          ViewCompat.setFitsSystemWindows(mChildView, false);           lp.topMargin += statusBarHeight;          mChildView.setLayoutParams(lp);      }  }    View statusBarView = mContentView.getChildAt(0);  if (statusBarView != null && statusBarView.getLayoutParams() != null && statusBarView.getLayoutParams().height == statusBarHeight) {      //避免重复调用时多次添加 View      statusBarView.setBackgroundColor(statusColor);      return;  }  statusBarView = new View(activity);  ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight);  statusBarView.setBackgroundColor(statusColor);  //向 ContentView 中添加假 View  mContentView.addView(statusBarView, 0, lp);</code></pre>    <p><strong>方法2: 向 ContentParent 添加假 View ,设置 ContentView 和 ChildView 的 fitsSystemWindows.</strong></p>    <p>全屏模式:</p>    <pre>  <code class="language-java">Window window = activity.getWindow();  window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);    ViewGroup mContentView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);  ViewGroup mContentParent = (ViewGroup) mContentView.getParent();    View statusBarView = mContentParent.getChildAt(0);  if (statusBarView != null && statusBarView.getLayoutParams() != null && statusBarView.getLayoutParams().height == getStatusBarHeight(activity)) {      //移除假的 View      mContentParent.removeView(statusBarView);  }  //ContentView 不预留空间  if (mContentParent.getChildAt(0) != null) {      ViewCompat.setFitsSystemWindows(mContentParent.getChildAt(0), false);  }    //ChildView 不预留空间  View mChildView = mContentView.getChildAt(0);  if (mChildView != null) {      ViewCompat.setFitsSystemWindows(mChildView, false);  }</code></pre>    <p>着色模式(会有一条黑线,无法解决):</p>    <pre>  <code class="language-java">Window window = activity.getWindow();  window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);    ViewGroup mContentView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);  ViewGroup mContentParent = (ViewGroup) mContentView.getParent();    View statusBarView = mContentParent.getChildAt(0);  if (statusBarView != null && statusBarView.getLayoutParams() != null && statusBarView.getLayoutParams().height == getStatusBarHeight(activity)) {      //避免重复调用时多次添加 View      statusBarView.setBackgroundColor(statusColor);      return;  }    //创建一个假的 View, 并添加到 ContentParent  statusBarView = new View(activity);  ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,          getStatusBarHeight(activity));  statusBarView.setBackgroundColor(statusColor);  mContentParent.addView(statusBarView, 0, lp);    //ChildView 不需要预留系统空间  View mChildView = mContentView.getChildAt(0);  if (mChildView != null) {      ViewCompat.setFitsSystemWindows(mChildView, false);  }</code></pre>    <p><strong>方法3:向 ContentView 添加假 View , 设置 ChildView 的 fitsSystemWindows.</strong></p>    <p>全屏模式:</p>    <pre>  <code class="language-java">Window window = activity.getWindow();  window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);    ViewGroup mContentView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);  View statusBarView = mContentView.getChildAt(0);  //移除假的 View  if (statusBarView != null && statusBarView.getLayoutParams() != null && statusBarView.getLayoutParams().height == getStatusBarHeight(activity)) {      mContentView.removeView(statusBarView);  }  //不预留空间  if (mContentView.getChildAt(0) != null) {      ViewCompat.setFitsSystemWindows(mContentView.getChildAt(0), false);  }</code></pre>    <p>着色模式:</p>    <pre>  <code class="language-java">Window window = activity.getWindow();  window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);    ViewGroup mContentView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);  int statusBarHeight = getStatusBarHeight(activity);    View mTopView = mContentView.getChildAt(0);  if (mTopView != null && mTopView.getLayoutParams() != null && mTopView.getLayoutParams().height == statusBarHeight) {      //避免重复添加 View      mTopView.setBackgroundColor(statusColor);      return;  }  //使 ChildView 预留空间  if (mTopView != null) {      ViewCompat.setFitsSystemWindows(mTopView, true);  }    //添加假 View  mTopView = new View(activity);  ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight);  mTopView.setBackgroundColor(statusColor);  mContentView.addView(mTopView, 0, lp);</code></pre>    <p> </p>    <p>其实<strong>全屏模式在三种模式下实现都是一样的</strong>,主要是<strong>着色模式</strong>实现不同.</p>    <p>对比一下三种<strong>着色模式</strong>实现的方式:</p>    <table style="width:899px">     <thead>      <tr>       <th style="text-align:left; vertical-align:middle"> </th>       <th style="text-align:left; vertical-align:middle">方法1</th>       <th style="text-align:left; vertical-align:middle">方法2</th>       <th style="text-align:left; vertical-align:middle">方法3</th>      </tr>     </thead>     <tbody>      <tr>       <td style="text-align:left; vertical-align:middle">原理</td>       <td style="text-align:left; vertical-align:middle">向 ContentView 中添加假 View, 然后利用 ChildView 的 marginTop 属性来模拟 fitsSystemWindows ,主要是通过修改 marginTop 的值可以在全屏模式和着色模式之间切换.</td>       <td style="text-align:left; vertical-align:middle">因为 ParentView 的实质是一个 LinearLayout , 可以再其顶部添加 View .</td>       <td style="text-align:left; vertical-align:middle">向 ContentView 中添加假 View, 然后利用ChildView 的 fitsSystemWindows 属性来控制位置, 但是实现缺陷就是不能随时切换两种模式.</td>      </tr>      <tr>       <td style="text-align:left; vertical-align:middle">缺陷</td>       <td style="text-align:left; vertical-align:middle">改变了 ChildView 的 marginTop 值</td>       <td style="text-align:left; vertical-align:middle">着色模式下,会像<a href="http://www.jianshu.com/p/140be70b84cd?utm_source=tuicool&utm_medium=referral" rel="external">由沉浸式状态栏引发的血案</a>中一样出现一条黑线</td>       <td style="text-align:left; vertical-align:middle">不能在不重启 Activity 的情况下切换模式.</td>      </tr>      <tr>       <td style="text-align:left; vertical-align:middle">对应 Github demo 中代码</td>       <td style="text-align:left; vertical-align:middle">StatusBarCompat类</td>       <td style="text-align:left; vertical-align:middle">StatusBarCompat1类</td>       <td style="text-align:left; vertical-align:middle">StatusBarCompat2 类</td>      </tr>     </tbody>    </table>    <h2>总结</h2>    <ul style="list-style-type:square">     <li> <p>StatusBarCompat2 主要问题不能切换.</p> </li>     <li> <p>StatusBarCompat1 在4.4上会有一条黑线, 如果可以解决我觉得这是最靠谱的解决方法.</p> </li>     <li> <p>StatusBarCompat 类算是我最后给出的解决方案吧, 目前使用效果比较完善.推荐使用</p>      <ul style="list-style-type:square">       <li> <p>用户可以随时在同一个 Activity 中切换不同的状态栏模式.</p> </li>       <li> <p>就算子 View 重写了 dispatchFitSystemWindows 也不会有影响.</p> </li>      </ul> </li>    </ul>    <p> </p>    <p> </p>    <p>来源:<a href="http://niorgai.github.io/2016/03/20/Android-transulcent-status-bar/?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io" rel="start">Jianqiu's blog</a></p>    <ul>     <li> </li>    </ul>    <p> </p>    <p> </p>