Android P 刘海缺口实现原理

一、刘海缺口原理

1. 切换刘海缺口形状入口

显示刘海缺口窗口的开关入口在 设置->开发者选项->模拟“刘海屏”,点击选择不同的刘海形状,会调用OverlayManagerServiceImpl.setEnabledExclusive(),在这个方法中选择启用frameworks/base/packages/overlays中对应的资源应用,这些应用都只包含资源文件,其中定义了R.string.config_mainBuiltInDisplayCutout来描述刘海的形状。

2. 更新DisplayCutout数据供应用适配

上面讲到选择不同的刘海形状,会调用OverlayManagerServiceImpl的相关方法,其中同时也会触发onOverlaysChanged回调来通知资源变化:

DisplayManagerService.LocalService.onOverlayChanged() -> LocalDisplayAdapter.LocalDisplayDevice.requestDisplayModesLocked() -> DisplayManagerService.handleDisplayDeviceChanged() -> DisplayCutout.fromResources()

DisplayCutout.java中更新屏幕边界和安全区域(mBounds、mSafeInsets)的相关数据,提供了public方法来供上层应用获取来进行刘海屏的适配,包括:

  • getBoundingRects():返回Rects的列表,表示显示屏上非功能区域的边界矩形。
  • getSafeInsetXXX():返回安全区域距离屏幕的距离(XXX表示Left,Right等)。

3. 显示刘海缺口

刘海缺口的显示在SystemUIScreenDecorations.java,其中注册了显示屏变化的回调DisplayManager.DisplayListener(),当刘海缺口有变化时,读取实际的刘海形状来进行绘制(DisplayCutoutView)


4. 调整应用窗口

Android P中为应用适配新增了窗口属性windowLayoutInDisplayCutoutMode,有以下三种类型:

  • LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT:默认值,只有当DisplayCutout完全包含在系统状态栏中时,才允许窗口延伸到DisplayCutout区域显示。
  • LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER:该窗口决不允许与DisplayCutout区域重叠。
  • LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES:该窗口始终允许延伸到屏幕短边上的DisplayCutout区域。

该属性的作用,主要涉及以下三处:

  • PhoneWindowManager.java中,计算并调整窗口大小的layoutWindowLw(),在这里会将未适配刘海屏的应用窗口移动到只在安全区域内显示;
  • ViewRootImpl.java中,View Tree遍历流程中设置View的屏幕边距;
  • WindowState.java中,判断是否需要在刘海区域绘制黑条 Letterbox,显示Letterbox的场景包括未适配刘海屏的全屏页面、未适配刘海屏的横屏页面等。

其中,Letterbox的原理可以简单描述为: Letterboxsystem_server进程进行管理,在其中维护一个屏幕尺寸outer和实际窗口尺寸inner,当窗口切换时,也就是由一个WindowState切换到另一个WindowState时,由于对应窗口的实际显示区域不同,outerinner会有变化时,此时Letterbox就需要更新,若outer>inner,则SurfaceControl创建的黑色surface背景就会显示出来,刘海区域矩形就显示为黑条。


二、状态栏原理

1. 状态栏背景色修改

状态栏与导航栏都属于SystemUI的管理范畴,拥有自己独立的窗口,而且这两个窗口的优先级较高,会悬浮在所有窗口之上,其本身是全透明的,而我们看到的背景颜色是因为下层APP在状态栏对应的区域添加了颜色,对应的视图在应用的顶层布局DecorView中。

应用修改状态栏背景色,可以通过两种方式:

  1. 使用getWindow().setStatusBarColor()方法在代码中修改;

  2. style.xml中通过定制 colorPrimaryDark 属性的方式修改。

这两种方式都是通过 DecorView.java 中的updateColorViews() 方法来实现,最终设置的状态栏颜色由 calculateStatusBarColor() 方法计算得到。

例外情况是,适配了刘海屏的全屏应用,会将状态栏整体移除,导致刘海区域显示为透明。

看一下状态栏的属性定义:

public static final ColorViewAttributes STATUS_BAR_COLOR_VIEW_ATTRIBUTES =
        new ColorViewAttributes(
               SYSTEM_UI_FLAG_FULLSCREEN, FLAG_TRANSLUCENT_STATUS,
               Gravity.TOP, Gravity.LEFT, Gravity.RIGHT,
               Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME,
               com.android.internal.R.id.statusBarBackground,
               FLAG_FULLSCREEN);

ColorViewAttributes中定义了窗口是否布局和可见条件的方法isPresent()isVisible()

可以看到,对于设置了窗口属性 SYSTEM_UI_FLAG_FULLSCREENFLAG_FULLSCREEN 的全屏页面,状态栏背景是不显示的。


2. 状态栏图标着色

状态栏图标有lightdark两种模式,如果状态栏背景设为黑色同时图标着色为dark模式(显示黑色),那么会导致图标看不到的问题,因此也需要在隐藏刘海的同时禁止设置图标着色模式为dark。

应用修改状态栏图标颜色有两种方法:

  1. 在代码中使用setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR)方法;
  2. styles.xml 中设置windowLightStatusBar 属性。

例外情况是,Android 8.0原生添加了 壁纸变化自动更新主题 的机制(notifyWallpaperColorsChanged() 相关流程),当由深色壁纸切换到浅色壁纸时,SystemUI会切换到浅色主题,此时状态栏图标颜色会被设置为暗色,反之亦然。



  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值