zoukankan      html  css  js  c++  java
  • 安卓高级9 沉浸式布局 获取状态栏高度等

    Android 沉浸式状态栏、状态栏一体化、透明状态栏、仿iOS透明状态栏 

    http://blog.csdn.NET/jdsjlzx/article/details/50437779

    注:状态栏的字体颜色位白色, 如果状态栏背景为白色,上面的博客已经解决了。


    原文出处:http://niorgai.github.io/2016/03/20/Android-transulcent-status-bar/ 

    最近业务上看到一个设计图挺好看,所以研究了一下透明状态栏,注意不是沉浸式状态栏,在参考了网上的一些资料后,整理出了这篇博客.

    Github Demo 链接: StatusBarCompat

    参考文章:
    1. 由沉浸式状态栏引发的血案

    2. Translucent System Bar 的最佳实践

    3. 该使用 fitsSystemWindows 了!


    首先强调,对于状态栏的处理有两种不同的方式, 这里从Translucent System Bar 的最佳实践直接盗了两张图做对比~.

    全屏( ContentView 可以进入状态栏)非全屏 ( ContentView 与状态栏分离, 状态栏直接着色)

    先定义几个名词:

    1. 全屏模式: 左边图所示.

    2. 着色模式: 右边图所示.

    3. ContentView: activity.findViewById(Window.ID_ANDROID_CONTENT) 获取的 View , 即 setContentView 方法所设置的 View的父布局,FrameLayout.

    4. ContentParent: ContentView 的 parent , 实质为 LinearLayout.

    5. ChildView: ContentView 的第一个子 View ,即布局文件中的 layout .

    再介绍一下相关的函数:

    1. fitsSystemWindows, 该属性可以设置是否为系统 View 预留出空间, 当设置为 true 时,会预留出状态栏的空间.

    2. ContentView, 实质为 ContentFrameLayout, 但是重写了 dispatchFitSystemWindows 方法, 所以对其设置 fitsSystemWindows 无效.

    3. ContentParent, 实质为 FitWindowsLinearLayout, 里面第一个 View 是 ViewStubCompat, 如果主题没有设置 title ,它就不会 inflate .第二个 View 就是 ContentView.

    5.0以上的处理:

    自5.0引入 Material Design ,状态栏对开发者更加直接,可以直接调用 setStatusBarColor 来设置状态栏的颜色.

    全屏模式:

    1. Window window = activity.getWindow();  
    2. //设置透明状态栏,这样才能让 ContentView 向上  
    3. window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);   
    4.   
    5. //需要设置这个 flag 才能调用 setStatusBarColor 来设置状态栏颜色  
    6. window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);   
    7. //设置状态栏颜色  
    8. window.setStatusBarColor(statusColor);  
    9.   
    10. ViewGroup mContentView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);  
    11. View mChildView = mContentView.getChildAt(0);  
    12. if (mChildView != null) {  
    13.     //注意不是设置 ContentView 的 FitsSystemWindows, 而是设置 ContentView 的第一个子 View . 使其不为系统 View 预留空间.  
    14.     ViewCompat.setFitsSystemWindows(mChildView, false);  
    15. }  

    着色模式:

    1. Window window = activity.getWindow();  
    2. //取消设置透明状态栏,使 ContentView 内容不再覆盖状态栏  
    3. window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);   
    4.   
    5. //需要设置这个 flag 才能调用 setStatusBarColor 来设置状态栏颜色  
    6. window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);   
    7. //设置状态栏颜色  
    8. window.setStatusBarColor(statusColor);  
    9.   
    10. ViewGroup mContentView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);  
    11. View mChildView = mContentView.getChildAt(0);  
    12. if (mChildView != null) {  
    13.     //注意不是设置 ContentView 的 FitsSystemWindows, 而是设置 ContentView 的第一个子 View . 预留出系统 View 的空间.  
    14.     ViewCompat.setFitsSystemWindows(mChildView, true);  
    15. }  

    4.4-5.0的处理:

    4.4-5.0因为没有直接的 API 可以调用,需要自己兼容处理,网上的解决方法基本都是创建一下高度为状态栏的 View ,通过设置这个 View 的背景色来模拟状态栏. 这里我尝试了三种方法来兼容处理.

    方法1: 向 ContentView 添加假 View , 设置 ChildView 的 marginTop 属性来模拟 fitsSystemWindows .

    全屏模式:

    1. Window window = activity.getWindow();  
    2. ViewGroup mContentView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);  
    3.   
    4. //首先使 ChildView 不预留空间  
    5. View mChildView = mContentView.getChildAt(0);  
    6. if (mChildView != null) {  
    7.     ViewCompat.setFitsSystemWindows(mChildView, false);  
    8. }  
    9.   
    10. int statusBarHeight = getStatusBarHeight(activity);  
    11. //需要设置这个 flag 才能设置状态栏  
    12. window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);  
    13. //避免多次调用该方法时,多次移除了 View  
    14. if (mChildView != null && mChildView.getLayoutParams() != null && mChildView.getLayoutParams().height == statusBarHeight) {  
    15.     //移除假的 View.  
    16.     mContentView.removeView(mChildView);  
    17.     mChildView = mContentView.getChildAt(0);  
    18. }  
    19. if (mChildView != null) {  
    20.     FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mChildView.getLayoutParams();  
    21.     //清除 ChildView 的 marginTop 属性  
    22.     if (lp != null && lp.topMargin >= statusBarHeight) {  
    23.         lp.topMargin -= statusBarHeight;  
    24.         mChildView.setLayoutParams(lp);  
    25.     }  
    26. }  

    着色模式:

    1. Window window = activity.getWindow();  
    2. ViewGroup mContentView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);  
    3.   
    4. //First translucent status bar.  
    5. window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);  
    6. int statusBarHeight = getStatusBarHeight(activity);  
    7.   
    8. View mChildView = mContentView.getChildAt(0);  
    9. if (mChildView != null) {  
    10.     FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mChildView.getLayoutParams();  
    11.     //如果已经为 ChildView 设置过了 marginTop, 再次调用时直接跳过  
    12.     if (lp != null && lp.topMargin < statusBarHeight && lp.height != statusBarHeight) {  
    13.         //不预留系统空间  
    14.         ViewCompat.setFitsSystemWindows(mChildView, false);   
    15.         lp.topMargin += statusBarHeight;  
    16.         mChildView.setLayoutParams(lp);  
    17.     }  
    18. }  
    19.   
    20. View statusBarView = mContentView.getChildAt(0);  
    21. if (statusBarView != null && statusBarView.getLayoutParams() != null && statusBarView.getLayoutParams().height == statusBarHeight) {  
    22.     //避免重复调用时多次添加 View  
    23.     statusBarView.setBackgroundColor(statusColor);  
    24.     return;  
    25. }  
    26. statusBarView = new View(activity);  
    27. ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight);  
    28. statusBarView.setBackgroundColor(statusColor);  
    29. //向 ContentView 中添加假 View  
    30. mContentView.addView(statusBarView, 0, lp);  

    方法2: 向 ContentParent 添加假 View ,设置 ContentView 和 ChildView 的 fitsSystemWindows.

    全屏模式:

    1. Window window = activity.getWindow();  
    2. window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);  
    3.   
    4. ViewGroup mContentView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);  
    5. ViewGroup mContentParent = (ViewGroup) mContentView.getParent();  
    6.   
    7. View statusBarView = mContentParent.getChildAt(0);  
    8. if (statusBarView != null && statusBarView.getLayoutParams() != null && statusBarView.getLayoutParams().height == getStatusBarHeight(activity)) {  
    9.     //移除假的 View  
    10.     mContentParent.removeView(statusBarView);  
    11. }  
    12. //ContentView 不预留空间  
    13. if (mContentParent.getChildAt(0) != null) {  
    14.     ViewCompat.setFitsSystemWindows(mContentParent.getChildAt(0), false);  
    15. }  
    16.   
    17. //ChildView 不预留空间  
    18. View mChildView = mContentView.getChildAt(0);  
    19. if (mChildView != null) {  
    20.     ViewCompat.setFitsSystemWindows(mChildView, false);  
    21. }  

    着色模式(会有一条黑线,无法解决):

    1. Window window = activity.getWindow();  
    2. window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);  
    3.   
    4. ViewGroup mContentView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);  
    5. ViewGroup mContentParent = (ViewGroup) mContentView.getParent();  
    6.   
    7. View statusBarView = mContentParent.getChildAt(0);  
    8. if (statusBarView != null && statusBarView.getLayoutParams() != null && statusBarView.getLayoutParams().height == getStatusBarHeight(activity)) {  
    9.     //避免重复调用时多次添加 View  
    10.     statusBarView.setBackgroundColor(statusColor);  
    11.     return;  
    12. }  
    13.   
    14. //创建一个假的 View, 并添加到 ContentParent  
    15. statusBarView = new View(activity);  
    16. ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,  
    17.         getStatusBarHeight(activity));  
    18. statusBarView.setBackgroundColor(statusColor);  
    19. mContentParent.addView(statusBarView, 0, lp);  
    20.   
    21. //ChildView 不需要预留系统空间  
    22. View mChildView = mContentView.getChildAt(0);  
    23. if (mChildView != null) {  
    24.     ViewCompat.setFitsSystemWindows(mChildView, false);  
    25. }  

    方法3:向 ContentView 添加假 View , 设置 ChildView 的 fitsSystemWindows.

    全屏模式:

    1. Window window = activity.getWindow();  
    2. window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);  
    3.   
    4. ViewGroup mContentView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);  
    5. View statusBarView = mContentView.getChildAt(0);  
    6. //移除假的 View  
    7. if (statusBarView != null && statusBarView.getLayoutParams() != null && statusBarView.getLayoutParams().height == getStatusBarHeight(activity)) {  
    8.     mContentView.removeView(statusBarView);  
    9. }  
    10. //不预留空间  
    11. if (mContentView.getChildAt(0) != null) {  
    12.     ViewCompat.setFitsSystemWindows(mContentView.getChildAt(0), false);  
    13. }  

    着色模式:

    1. Window window = activity.getWindow();  
    2. window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);  
    3.   
    4. ViewGroup mContentView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);  
    5. int statusBarHeight = getStatusBarHeight(activity);  
    6.   
    7. View mTopView = mContentView.getChildAt(0);  
    8. if (mTopView != null && mTopView.getLayoutParams() != null && mTopView.getLayoutParams().height == statusBarHeight) {  
    9.     //避免重复添加 View  
    10.     mTopView.setBackgroundColor(statusColor);  
    11.     return;  
    12. }  
    13. //使 ChildView 预留空间  
    14. if (mTopView != null) {  
    15.     ViewCompat.setFitsSystemWindows(mTopView, true);  
    16. }  
    17.   
    18. //添加假 View  
    19. mTopView = new View(activity);  
    20. ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight);  
    21. mTopView.setBackgroundColor(statusColor);  
    22. mContentView.addView(mTopView, 0, lp);  

    其实全屏模式在三种模式下实现都是一样的,主要是着色模式实现不同.

    对比一下三种着色模式实现的方式:

     方法1方法2方法3
    原理向 ContentView 中添加假 View, 然后利用 ChildView 的 marginTop 属性来模拟 fitsSystemWindows ,主要是通过修改 marginTop 的值可以在全屏模式和着色模式之间切换.因为 ParentView 的实质是一个 LinearLayout , 可以再其顶部添加 View .向 ContentView 中添加假 View, 然后利用ChildView 的 fitsSystemWindows 属性来控制位置, 但是实现缺陷就是不能随时切换两种模式.
    缺陷改变了 ChildView 的 marginTop 值着色模式下,会像由沉浸式状态栏引发的血案中一样出现一条黑线不能在不重启 Activity 的情况下切换模式.
    对应 Github demo 中代码StatusBarCompat类StatusBarCompat1类StatusBarCompat2 类

    总结

    • StatusBarCompat2 主要问题不能切换.

    • StatusBarCompat1 在4.4上会有一条黑线, 如果可以解决我觉得这是最靠谱的解决方法.

    • StatusBarCompat 类算是我最后给出的解决方案吧, 目前使用效果比较完善.推荐使用

      • 用户可以随时在同一个 Activity 中切换不同的状态栏模式.

      • 就算子 View 重写了 dispatchFitSystemWindows 也不会有影响.


    推荐干货集中营Android客户端,实现了沉浸式状态栏,无缝换肤,带3D感觉的侧滑菜单

    主页及侧滑页截图


    gank1.png

    福利及换肤截图


    gank2.png

    下载地址:干货集中营
    github:GanK

  • 相关阅读:
    540 Single Element in a Sorted Array 有序数组中的单一元素
    539 Minimum Time Difference 最小时间差
    538 Convert BST to Greater Tree 把二叉搜索树转换为累加树
    537 Complex Number Multiplication 复数乘法
    535 Encode and Decode TinyURL 编码和解码精简URL地址
    532 K-diff Pairs in an Array 数组中差为K的数对
    530 Minimum Absolute Difference in BST 二叉搜索树的最小绝对差
    529 Minesweeper 扫雷游戏
    526 Beautiful Arrangement
    1.5 函数
  • 原文地址:https://www.cnblogs.com/muyuge/p/6152125.html
Copyright © 2011-2022 走看看