我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Android透明化和沉浸式状态栏实践及源码分析

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Android透明化和沉浸式状态栏实践及源码分析

本文所提到的透明状态栏其实指的是将顶部的导航栏延伸到状态栏,使之浑然一体(Google官方建议状态栏颜色比导航栏的颜色略深一点),并不代表一定不设置背景色,比如导航栏是白色,则可设置状态栏为白色,视情况而定。

相比于iOS系统,Android系统对于状态栏的设置就显得稍微复杂了一点。Android系统提供了API 19以上对状态栏的设置接口,而直到API 23以上才提供对于icon颜色的设置,还有就是各家厂商(如魅族,小米等)对于状态栏的有自己的定制,对于需要使用浅色背景状态栏的应用,没处理好的话往往导致浅色背景,白色icon ,状态栏不分你我的悲剧。。

(内心os:嗯?右上角那一个绿色的电池,用户一定知道他是状态栏对吧。)

我随即对比了一些主流app,发现在我的魅蓝2(Android 5.1 Flyme 4.5)上竟然都不支持透明状态栏,这对于我这种追求审美的人(其实是视觉提的需求)来说简直不能忍。在我折腾了几天之后,终于解决了这些问题,希望对大家思路有一些帮助。

言归正传,本文主要针对以下几点进行分析:1.是否隐藏状态栏(全屏模式) 2.状态栏的背景色的设置 3.状态栏icon的颜色的设置,而对于透明状态栏设置过程中,可能造成的icon颜色设置成功,而背景颜色设置失败,等等原因造成的浅色底,浅色字或深色底,深色字等错误情况的处理及兜底方案 4.源码实现。

下面我们就以上几点来讨论一下Android中透明状态栏的实现。

1. 全屏模式(沉浸式状态栏)

这种情况其实用得并不多,基本上使用场景在闪屏页展示广告或logo,以及一些阅读类app需要尽可能的利用到屏幕大小,展示更多的内容。

设置方法很简单,分两种,在API > 16时:

theme中定义:将自定义该Activtiy的theme,并在其中添加


<item name="android:windowFullscreen">true</item>

代码中定义:

代码如下:
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)

2. 状态栏的背景色

我们都知道,在Android5.0(即API > 21)时,Google官方提供接口设置对应的状态栏背景色


window.setStatusBarColor(@ColorInt int color);

那我们要想在Android5.0以下设置背景色就真的没有办法了吗?并不是,我们发现在Android4.4以后,出现了windowTranslucentStatus这一特性,所以思路如下:

先设置状态栏的透明属性 给根布局顶部加上一个和状态栏一样大小的矩形View,充当假的状态栏 设置FitsSystemWindows属性为true,此时跟布局会延伸到状态栏,处在顶部位置就是之前设置的view,这样就可以以假乱真了。 

 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
   // 设置状态栏透明
   activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
   // 生成一个状态栏大小的矩形View
   View statusView = createStatusView(activity, color);
   ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
   decorView.addView(statusView);
   // 设置根布局的参数
   ViewGroup rootView = (ViewGroup) ((ViewGroup) activity.findViewById(android.R.id.content)).getChildAt(0);
   rootView.setFitsSystemWindows(true);
   rootView.setClipToPadding(true);
  }

设置矩形色块的代码如下:


 private static View createStatusView(Activity activity, int color) {
  // 获得状态栏高度
  int resourceId = activity.getResources().getIdentifier("status_bar_height", "dimen", "android");
  int statusBarHeight = activity.getResources().getDimensionPixelSize(resourceId);
  // 绘制一个和状态栏一样高的矩形
  View statusView = new View(activity);
  LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
    statusBarHeight);
  statusView.setLayoutParams(params);
  statusView.setBackgroundColor(color);
  return statusView;
 }

效果如下:

3. 状态栏icon的颜色

我们通过调研发现,魅族和小米的两个厂商分别对于Flyme及MIUI做了状态栏的定制,所以我们可以在Flyme4.0以上及MIUIV6以上都可以实现icon颜色的替换,结合上述的状态栏背景色的替换,我们就可以对于魅族及小米两款机型适配浅色的导航栏了。

3.1 魅族Flyme4.0+


 
 public static boolean setMeizuStatusBarDarkIcon(Window window, boolean dark) {
  boolean result = false;
  if (window != null) {
   try {
    WindowManager.LayoutParams lp = window.getAttributes();
    Field darkFlag = WindowManager.LayoutParams.class
      .getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON");
    Field meizuFlags = WindowManager.LayoutParams.class
      .getDeclaredField("meizuFlags");
    darkFlag.setAccessible(true);
    meizuFlags.setAccessible(true);
    int bit = darkFlag.getInt(null);
    int value = meizuFlags.getInt(lp);
    if (dark) {
     value |= bit;
    } else {
     value &= ~bit;
    }
    meizuFlags.setInt(lp, value);
    window.setAttributes(lp);
    result = true;
   } catch (Exception e) {
    //错误处理
   }
  }
  return result;
 }

3.2 MIUIV6.0+


 
 public static boolean setMIUIStatusBarLightMode(Window window, boolean dark) {
  boolean result = false;
  if (window != null) {
   Class clazz = window.getClass();
   try {
    int darkModeFlag = 0;
    Class layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams");
    Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE");
    darkModeFlag = field.getInt(layoutParams);
    Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class);
    if(dark){
     extraFlagField.invoke(window,darkModeFlag,darkModeFlag);//状态栏透明且黑色字体
    }else{
     extraFlagField.invoke(window, 0, darkModeFlag);//清除黑色字体
    }
    result=true;
   }catch (Exception e){
    //错误处理
   }
  }
  return result;
 }

效果如下:

4. 设置背景色失败处理方案

4.1 解决思路

说了那么多,有童鞋可能就问了,上述说的代码可能对大多数机型有效,但是万一某些机型设置失败了呢?体验不但没提升,反倒让用户不能忍受了。

好吧,下面我们来看一下应该怎么解决。思路如下:

获取状态栏的背景色,与需要设置的颜色进行比对,如果相等则设置成功。 如果背景色设置失败,针对非Flyme机型,我们回退状态栏icon颜色,还原到最初状态。 上述为什么要强调非Flyme机型,是因为用过Flyme机型的童鞋肯定知道有一个透明状态栏的开关,当开启后,Flyme会对状态栏进行处理,但处理效果并不和我们想要的一样(比如视图顶部为黑色背景,但是却设置了白色状态栏),并且两者都对状态栏进行操作,操作的冲突也有可能引起上述的icon颜色不明显的情况,所以我们自己定义一个ColorDrawable进行替换。

4.2 获取状态栏颜色


 
 public static int getColor(Window window){
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
   return window.getStatusBarColor();
  }else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
   ViewGroup decorView = (ViewGroup) window.getDecorView();
   int count = decorView.getChildCount();
   if (count > 0 && decorView.getChildAt(count - 1) instanceof StatusBarView) {
    int color = Color.TRANSPARENT;
    Drawable background = decorView.getChildAt(count - 1).getBackground();
    if (background instanceof ColorDrawable)
     color = ((ColorDrawable) background).getColor();
    return color;
   }
  }
  return -1;
 }

下面讲一下针对如何屏蔽Flyme机型沉浸式状态栏打开时的影响:


 private static boolean setMeizuStatusColor(Window window, @ColorInt int color, int alpha) {
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
   final View view = window.getDecorView().findViewById(android.R.id.statusBarBackground);
   if (view != null) {
    if (!(view.getBackground() instanceof MeizuColorDrawable)) {
      //替换成自定义ColorDrawable
     view.setBackground(new MeizuColorDrawable());
    }
    if (view.getBackground() instanceof MeizuColorDrawable) {
     MeizuColorDrawable colorDrawable = (MeizuColorDrawable) view.getBackground();
     int finalColor = StatusBarUtil.calculateStatusColor(color, alpha);
     view.setBackgroundColor(finalColor);
     colorDrawable.setColor(finalColor);
     return true;
    }
   }
  }
  return false;
 }

4.3 兜底方案

上面提到了一些失败判断途径,那么如果还是对一些机型无效呢?我的建议是可以在本地预埋一个开关,万一出问题,服务器可以对相应机型的沉浸式开关关掉即可,或者采用hotfix等方式来进行修复。

5. 源码分析

看过上面针对魅族和小米设置icon的源代码我们知道,共同点都是通过反射来进行调用相应的方法,或者对于相应的状态栏视图进行设置颜色。我们来看看源码,在Window中有一个抽象方法是setStatusBarColor而在PhoneWindow中我们找到了该方法的实现


 @Override
 public void setStatusBarColor(int color) {
  mStatusBarColor = color;
  mForcedStatusBarColor = true;
  if (mDecor != null) {
   mDecor.updateColorViews(null, false );
  }
 }

mDecor.updateColorViews()


  private WindowInsets updateColorViews(WindowInsets insets, boolean animate) {
  ...
     updateColorViewInt(mNavigationColorViewState, sysUiVisibility, mNavigationBarColor,
      navBarSize, navBarToRightEdge, 0 ,
      animate && !disallowAnimate);
    boolean statusBarNeedsRightInset = navBarToRightEdge
      && mNavigationColorViewState.present;
    int statusBarRightInset = statusBarNeedsRightInset ? mLastRightInset : 0;
    updateColorViewInt(mStatusColorViewState, sysUiVisibility, mStatusBarColor,
      mLastTopInset, false , statusBarRightInset,
      animate && !disallowAnimate);
  ...
}

那么mNavigationColorViewState和mStatusColorViewState又是什么呢?别着急,我们继续往下看


  private final ColorViewState mStatusColorViewState = new ColorViewState(
    SYSTEM_UI_FLAG_FULLSCREEN, FLAG_TRANSLUCENT_STATUS,
    Gravity.TOP,
    Gravity.LEFT,
    STATUS_BAR_BACKGROUND_TRANSITION_NAME,
    com.android.internal.R.id.statusBarBackground,
    FLAG_FULLSCREEN);
  private final ColorViewState mNavigationColorViewState = new ColorViewState(
    SYSTEM_UI_FLAG_HIDE_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION,
    Gravity.BOTTOM,
    Gravity.RIGHT,
    NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME,
    com.android.internal.R.id.navigationBarBackground,
    0 );

mNavigationColorViewState和mStatusColorViewState这两个都是ColorViewState定义好的ColorViewState,主要存储了关于statusbar的一些视图信息。包括id,translucentFlag,verticalGravity等等。

接着看updateColorViewInt()方法,这个方法是对一个View设置颜色的,自然这个View也就是指的状态栏的View,下面来看一下实现:


private void updateColorViewInt(final ColorViewState state, int sysUiVis, int color,
    int size, boolean verticalBar, int rightMargin, boolean animate) {
    state.present = size > 0 && (sysUiVis & state.systemUiHideFlag) == 0
     && (getAttributes().flags & state.hideWindowFlag) == 0
     && (getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
   boolean show = state.present
     && (color & Color.BLACK) != 0
     && (getAttributes().flags & state.translucentFlag) == 0;
   boolean visibilityChanged = false;
   View view = state.view;
   int resolvedHeight = verticalBar ? LayoutParams.MATCH_PARENT : size;
   int resolvedWidth = verticalBar ? size : LayoutParams.MATCH_PARENT;
   int resolvedGravity = verticalBar ? state.horizontalGravity : state.verticalGravity;
    //对传入的ColorViewState的view做非空判断
   if (view == null) {
    if (show) {
     //新建一个view
     state.view = view = new View(mContext);
     //设置背景色
     view.setBackgroundColor(color);
     view.setTransitionName(state.transitionName);
     view.setId(state.id);
     visibilityChanged = true;
     //有人可能会感觉奇怪,为什么show为true的时候,这里反倒要设置INVISIBLE。从后面代码可以知道,这里无非是想通过动画来做一个渐显操作,而不是立马显示出来。
     view.setVisibility(INVISIBLE);
     state.targetVisibility = VISIBLE;
     LayoutParams lp = new LayoutParams(resolvedWidth, resolvedHeight,
       resolvedGravity);
     lp.rightMargin = rightMargin;
     addView(view, lp);
     updateColorViewTranslations();
    }
   } else {
    int vis = show ? VISIBLE : INVISIBLE;
    visibilityChanged = state.targetVisibility != vis;
    state.targetVisibility = vis;
    if (show) {
     LayoutParams lp = (LayoutParams) view.getLayoutParams();
     if (lp.height != resolvedHeight || lp.width != resolvedWidth
       || lp.gravity != resolvedGravity || lp.rightMargin != rightMargin) {
      lp.height = resolvedHeight;
      lp.width = resolvedWidth;
      lp.gravity = resolvedGravity;
      lp.rightMargin = rightMargin;
      view.setLayoutParams(lp);
     }
     //设置背景色
     view.setBackgroundColor(color);
    }
   }
   //改变状态栏visibility属性时加入动画
   if (visibilityChanged) {
    view.animate().cancel();
    if (animate) {
     if (show) {
      //显示时先设置为全透明
      if (view.getVisibility() != VISIBLE) {
       view.setVisibility(VISIBLE);
       view.setAlpha(0.0f);
      }
      //关于透明度的动画插值器
      view.animate().alpha(1.0f).setInterpolator(mShowInterpolator).
        setDuration(mBarEnterExitDuration);
     } else {
      view.animate().alpha(0.0f).setInterpolator(mHideInterpolator)
        .setDuration(mBarEnterExitDuration)
        .withEndAction(new Runnable() {
         @Override
         public void run() {
          state.view.setAlpha(1.0f);
          state.view.setVisibility(INVISIBLE);
         }
        });
     }
    } else {
     view.setAlpha(1.0f);
     view.setVisibility(show ? VISIBLE : INVISIBLE);
    }
   }
  }

6. 总结

话说了这么多,其实就是想让大家知道为什么很多app放弃在较低版本的Android系统对状态栏进行视觉修改,尤其是浅色导航栏的应用。

您可能感兴趣的文章:Android 实现沉浸式状态栏的方法Android之沉浸式状态栏的实现方法、状态栏透明Android App仿QQ制作Material Design风格沉浸式状态栏Android沉浸式状态栏实现另外两种Android沉浸式状态栏实现思路Android 高仿QQ 沉浸式状态栏详解Android中的沉浸式状态栏效果实例Android 沉浸式状态栏与隐藏导航栏实例详解Android沉浸式状态栏实现示例Android沉浸式状态栏设计的实例代码


免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Android透明化和沉浸式状态栏实践及源码分析

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Android透明化和沉浸式状态栏实践及源码分析

本文所提到的透明状态栏其实指的是将顶部的导航栏延伸到状态栏,使之浑然一体(Google官方建议状态栏颜色比导航栏的颜色略深一点),并不代表一定不设置背景色,比如导航栏是白色,则可设置状态栏为白色,视情况而定。 相比于iOS系统,Androi
2022-06-06

Android之沉浸式状态栏的实现方法、状态栏透明

现在越来越多的软件都开始使用沉浸式状态栏了,下面总结一下沉浸式状态栏的两种使用方法 注意!沉浸式状态栏只支持安卓4.4及以上的版本 状态栏:4.4上是渐变色,5.0上是完全透明,本文模拟器为4.4演示 效果图: 注意!两种方法的区别: 第一
2022-06-06

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录