13518219792

建站动态

根据您的个性需求进行定制 先人一步 抢占小程序红利时代

Android进阶之沉浸式状态栏原理和使用详解

本文转载自微信公众号「Android开发编程」,作者Android开发编程。转载本文请联系Android开发编程公众号。

前言

沉浸式就是要给用户提供完全沉浸的体验,使用户有一种置身于虚拟世界之中的感觉;

这种体验在各类游戏中被广泛应用,绝大部分的游戏都会在打开后,使得屏幕被完全被游戏占据,让玩家沉浸其中,从体验上沉浸式效果会更好一些;

网上很多人都写过沉浸式原理的文章,讲解的都不是很清晰,让人很费解;

今天我们就来彻底的总结下沉浸式实现和原理;

一、沉浸式概念和为何要用沉浸式

1、沉浸式概念

2、为何要用沉浸式

二、沉浸式原理和兼容

从Android4.4 到现在(Android 7.1),关于沉浸式大概可以分成三个阶段:

1、Android4.4(API 19)- Android 5.0(API 21)

Android在4.4新增了一个重要的属性:FLAG_TRANSLUCENT_STATUS

 
 
 
 
  1. /** 
  2.   * Window flag: request a translucent status bar with minimal system-provided 
  3.   * background protection. 
  4.   * 
  5.   * 

    This flag can be controlled in your theme through the 

  6.   * {@link android.R.attr#windowTranslucentStatus} attribute; this attribute 
  7.   * is automatically set for you in the standard translucent decor themes 
  8.   * such as 
  9.   * {@link android.R.style#Theme_Holo_NoActionBar_TranslucentDecor}, 
  10.   * {@link android.R.style#Theme_Holo_Light_NoActionBar_TranslucentDecor}, 
  11.   * {@link android.R.style#Theme_DeviceDefault_NoActionBar_TranslucentDecor}, and 
  12.   * {@link android.R.style#Theme_DeviceDefault_Light_NoActionBar_TranslucentDecor}.

     
  13.   * 
  14.   * 

    When this flag is enabled for a window, it automatically sets 

  15.   * the system UI visibility flags {@link View#SYSTEM_UI_FLAG_LAYOUT_STABLE} and 
  16.   * {@link View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}.

     
  17.   */ 
  18.  public static final int FLAG_TRANSLUCENT_STATUS = 0x04000000; 

设置状态栏透明,并且变为全屏模式。当这个属性有效的时候,会自动设置 system ui visibility的标志SYSTEM_UI_FLAG_LAYOUT_STABLE和SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 。

通过 FLAG_TRANSLUCENT_STATUS 设置状态栏为透明并且为全屏模式,然后通过添加一个与 StatusBar 一样大小的 View,将 View 的 backgroud 设置为我们想要的颜色,从而实现沉浸式。

①, 设置 FLAG_TRANSLUCENT_STATUS,可以在代码中设置,如下:

 
 
 
 
  1. activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 

或者可以在 theme 设置属性 windowTranslucentStatus,如下:

②.根据有需要,设置一个和 StatusBar 一样大小的占位 View,如果不设置则内容 View 会向上顶一个 StattusBar 的高度。

图片延伸到状态栏只需要设置FLAG_TRANSLUCENT_STATUS就可以

添加占位View的代码如下:

 
 
 
 
  1. //获取decorView 
  2.         ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView(); 
  3.         int count = decorView.getChildCount(); 
  4.         //判断是否已经添加了statusBarView 
  5.         if (count > 0 && decorView.getChildAt(count - 1) instanceof StatusBarView) { 
  6.             decorView.getChildAt(count - 1).setBackgroundColor(calculateStatusColor(color, statusBarAlpha)); 
  7.         } else { 
  8.             //新建一个和状态栏高宽的view 
  9.             StatusBarView statusView = createStatusBarView(activity, color, statusBarAlpha); 
  10.             decorView.addView(statusView); 
  11.         } 
  12.         ViewGroup rootView = (ViewGroup) ((ViewGroup) activity.findViewById(android.R.id.content)).getChildAt(0); 
  13.         //rootview不会为状态栏留出状态栏空间 
  14.         ViewCompat.setFitsSystemWindows(rootView,true); 
  15.         rootView.setClipToPadding(true); 
  16. private static StatusBarView createStatusBarView(Activity activity, int color, int alpha) { 
  17.         // 绘制一个和状态栏一样高的矩形 
  18.         StatusBarView statusBarView = new StatusBarView(activity); 
  19.         LinearLayout.LayoutParams params = 
  20.                 new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight(activity)); 
  21.         statusBarView.setLayoutParams(params); 
  22.         statusBarView.setBackgroundColor(calculateStatusColor(color, alpha)); 
  23.         return statusBarView; 

2、Android 5.0(API 21)以上版本

Android 5.0 是一个里程碑式的版本,google 加入了一个比较重要的方法 setStatusBarColor (对应属性:android:statusBarColor), 通过这个方法,可以很轻松地实现沉浸式状态栏。方法如下:

 
 
 
 
  1. /** 
  2.      * Sets the color of the status bar to {@code color}. 
  3.      * 
  4.      * For this to take effect, 
  5.      * the window must be drawing the system bar backgrounds with 
  6.      * {@link android.view.WindowManager.LayoutParams#FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS} and 
  7.      * {@link android.view.WindowManager.LayoutParams#FLAG_TRANSLUCENT_STATUS} must not be set. 
  8.      * 
  9.      * If {@code color} is not opaque, consider setting 
  10.      * {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_STABLE} and 
  11.      * {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}. 
  12.      * 

     

  13.      * The transitionName for the view background will be "android:status:background". 
  14.      * 

     
  15.      */ 
  16.     public abstract void setStatusBarColor(@ColorInt int color); 

不过,要想这个方法生效,必须还要配合一个 Flag 一起使用,必须设置 FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,并且不能设置 FLAG_TRANSLUCENT_STATUS (Android 4.4 才用这个)。

设置了 FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS 表明 Window 负责系统 bar 的 background 绘制,绘制透明背景的系统 bar(状态栏和导航栏),然后用 getStatusBarColor() 和 getNavigationBarColor() 的颜色填充相应的区域,实现代码如下:

 
 
 
 
  1. getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); 
  2. //注意要清除 FLAG_TRANSLUCENT_STATUS flag 
  3. getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 
  4. getWindow().setStatusBarColor(getResources().getColor(android.R.color.holo_red_light)); 

也可以直接在 Theme 中使用,在 vlues-v21 文件夹下添加如下主题:

 
 
 
 
  1.  
  2.         false 
  3.         true 
  4.         @android:color/holo_red_light 
  5.  

如果要让图片延申至状态栏,只需设置 windowTranslucentStatus,将 statusBarColor 设置为透明,同时设置 DecorView 的 属性:

 
 
 
 
  1. getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN |  
  2. View.SYSTEM_UI_FLAG_LAYOUT_STABLE); 

3、Android 6.0 +

其实Android6.0以上的实现方式和Android 5.0 +是一样,为什么要将它归为一个单独重要的阶段呢?是因为从Android 6.0(API 23)开始,我们可以改状态栏的绘制模式,可以显示白色或浅黑色的内容和图标;

使用Android6.0 以上版本沉浸式的时候会遇到一个问题,那就是 Android 系统状态栏的字色和图标颜色为白色,当状态栏颜色接近浅色的时候,状态栏上的内容就看不清了;

Android 6.0 新添加了一个属性来解决这个问题,属性是 SYSTEM_UI_FLAG_LIGHT_STATUS_BAR,可以设置状态栏字色和图标浅黑色。

 
 
 
 
  1. /** 
  2.      * Flag for {@link #setSystemUiVisibility(int)}: Requests the status bar to draw in a mode that 
  3.      * is compatible with light status bar backgrounds. 
  4.      * 
  5.      * 

    For this to take effect, the window must request 

  6.      * {@link android.view.WindowManager.LayoutParams#FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS 
  7.      *         FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS} but not 
  8.      * {@link android.view.WindowManager.LayoutParams#FLAG_TRANSLUCENT_STATUS 
  9.      *         FLAG_TRANSLUCENT_STATUS}. 
  10.      * 
  11.      * @see android.R.attr#windowLightStatusBar 
  12.      */ 
  13.     public static final int SYSTEM_UI_FLAG_LIGHT_STATUS_BAR = 0x00002000; 

不过要想这个属性生效的前提是要先设置了FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS flag ,同时清除了FLAG_TRANSLUCENT_STATUS flag 才会生效。

(1)状态栏字体白色

 
 
 
 
  1. getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);//字体默认白色 
  2. getWindow().setStatusBarColor(android.R.color.transparent);//透明背景 

(2)状态栏字体黑色

 
 
 
 
  1. getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN|View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);//黑色字体 
  2. getWindow().setStatusBarColor(android.R.color.transparent);//透明背景 

三、实际沉浸式开发中的难点分析

1、沉浸式中常用的flag总结

①. View.SYSTEM_UI_FLAG_FULLSCREEN:Activity全屏显示,且状态栏被隐藏覆盖掉

②.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN:Activity全屏显示,但状态栏不会被隐藏覆盖,状态栏依然可见,

③. View.SYSTEM_UI_FLAG_LAYOUT_STABLE

使用了SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN和SYSTEM_UI_FLAG_LAYOUT_STABLE,注意两个Flag必须要结合在一起使用,表示会让应用的主体内容占用系统状态栏的空间

④. View.SYSTEM_UI_FLAG_HIDE_NAVIGATION:隐藏虚拟按键(导航栏)。有些手机会用虚拟按键来代替物理按键。

⑤. View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION:隐藏导航栏 效果同View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN

⑥. 有的手机默认全屏显示,有时需要强制不显示全屏就用以下flag

不全屏显示

 
 
 
 
  1. getWindow().addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); 

全屏显示

 
 
 
 
  1. getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); 

2、状态栏字体颜色适配

 
 
 
 
  1. /*** 
  2.     * 状态栏字体适配方案 
  3.     * @param activity 
  4.     * @param dark 
  5.     */ 
  6.    public static void darkMode(Activity activity, boolean dark) { 
  7.        try { 
  8.            if (isFlyme4Later()) { 
  9.                //魅族 
  10.                darkModeForFlyme4(activity.getWindow(), dark); 
  11.            } else if (isMIUI6Later()) { 
  12.                //小米 
  13.                darkModeForMIUI6(activity.getWindow(), dark); 
  14.            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 
  15.                //其他通用方案 
  16.                darkModeForM(activity.getWindow(), dark); 
  17.            } 
  18.        } catch (Exception e) { 
  19.        } 
  20.    } 
  21.  /*** 
  22.     * 状态栏字体适配方案 
  23.     * @param activity 
  24.     * @param dark 
  25.     */ 
  26.    public static void darkMode(Activity activity, boolean dark) { 
  27.        try { 
  28.            if (isFlyme4Later()) { 
  29.                //魅族 
  30.                darkModeForFlyme4(activity.getWindow(), dark); 
  31.            } else if (isMIUI6Later()) { 
  32.                //小米 
  33.                darkModeForMIUI6(activity.getWindow(), dark); 
  34.            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 
  35.                //其他通用方案 
  36.                darkModeForM(activity.getWindow(), dark); 
  37.            } 
  38.        } catch (Exception e) { 
  39.        } 
  40.    } 
  41.   /** 
  42.     * 判断是否Flyme4以上 
  43.     */ 
  44.    public static boolean isFlyme4Later() { 
  45.        return Build.FINGERPRINT.contains("Flyme_OS_4") 
  46.                || Build.VERSION.INCREMENTAL.contains("Flyme_OS_4") 
  47.                || Pattern.compile("Flyme OS [4|5]", Pattern.CASE_INSENSITIVE).matcher(Build.DISPLAY).find(); 
  48.    } 
  49.  /** 
  50.     * 判断是否为MIUI6以上 
  51.     */ 
  52.    @SuppressLint("PrivateApi") 
  53.    public static boolean isMIUI6Later() { 
  54.        try { 
  55.            Class clz = Class.forName("android.os.SystemProperties"); 
  56.            Method mtd = clz.getMethod("get", String.class); 
  57.            String val = (String) mtd.invoke(null, "ro.miui.ui.version.name"); 
  58.            assert val != null; 
  59.            val = val.replaceAll("[vV]", ""); 
  60.            int version = Integer.parseInt(val); 
  61.            return version >= 6; 
  62.        } catch (Exception e) { 
  63.            return false; 
  64.        } 
  65.    } 
  66.   /** 
  67.     * android 6.0设置字体颜色 
  68.     */ 
  69.    @RequiresApi(Build.VERSION_CODES.M) 
  70.    private static void darkModeForM(Window window, boolean dark) { 
  71.        window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 
  72.        window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); 
  73.        window.setStatusBarColor(Color.TRANSPARENT); 
  74.        int systemUiVisibility = window.getDecorView().getSystemUiVisibility(); 
  75.        if (dark) { 
  76.            systemUiVisibility |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; 
  77.        } else { 
  78.            systemUiVisibility &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; 
  79.        } 
  80.        window.getDecorView().setSystemUiVisibility(systemUiVisibility); 
  81.    } 

3、fitsSystemWindows理解和用法

我们只需要把内容延伸至状态栏和导航栏,然后给根布局设置图片背景,若需要内容不出现在状态栏和导航栏区域则再添加android:fitsSystemWindows="true"既可

 
 
 
 
  1. /** 
  2.      * 获取状态栏高度 
  3.      */ 
  4.     public static int getStatusBarHeight(Context context) { 
  5.         int result = 24; 
  6.         int resId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); 
  7.         if (resId > 0) { 
  8.             result = context.getResources().getDimensionPixelSize(resId); 
  9.         } else { 
  10.             result = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 
  11.                     result, Resources.getSystem().getDisplayMetrics()); 
  12.         } 
  13.         return result; 
  14.     } 

四、沉浸式轮子方案

其实网上有很多成熟的沉浸式方案,我们也没有必要封装,主要是要了解其中的知识点,遇到问题好排查问题

网上的轮子StatusBarUtil

有以下的功能:

1、设置状态栏颜色

 
 
 
 
  1. StatusBarUtil.setColor(Activity activity, int color) 

设置状态栏半透明

2、StatusBarUtil.setTranslucent(Activity activity, int statusBarAlpha)

设置状态栏全透明

 
 
 
 
  1. StatusBarUtil.setTransparent(Activity activity) 

3、为包含 DrawerLayout 的界面设置状态栏颜色(也可以设置半透明和全透明)

 
 
 
 
  1. StatusBarUtil.setColorForDrawerLayout(Activity activity, DrawerLayout drawerLayout, int color) 

4、为使用 ImageView 作为头部的界面设置状态栏透明

 
 
 
 
  1. StatusBarUtil.setTranslucentForImageView(Activity activity, int statusBarAlpha, View needOffsetView) 

5、在 Fragment 中使用

6、通过传入 statusBarAlpha 参数,可以改变状态栏的透明度值,默认值是112。

总结:

这次知识点总结,希望可以给还没有使用沉浸式的同学一些帮助。如果你已经使用过沉浸式状态栏,可以对各个版本实现的原理有一个更深的了解。


文章标题:Android进阶之沉浸式状态栏原理和使用详解
URL网址:http://cdbrznjsb.com/article/dhjohgo.html

其他资讯

让你的专属顾问为你服务