当前位置: 首页 > news >正文

Android 15强制edge-to-edge全面屏体验

一、背景

Edge-to-edge 全面屏体验并非 Android 15 才有的新功能,早在 Android 15 之前系统就已支持。然而,该功能推出多年来,众多应用程序依旧未针对全面屏体验进行适配。因此,在 Android 15 的更新中,Google 终于决定强推这一功能,力求让所有应用程序都能带来更出色的使用体验。

需要注意的是,在 Android 15 系统下,仅当应用程序将 targetSdkVersion 指定为 35 或更高版本时,系统才会强制启用 edge-to-edge 功能。所以,若开发者不想进行适配,只要不升级 targetSdkVersion 版本即可。

二、什么是 edge-to-edge 全面屏体验

我们的 App 将以边到边的方式显示,窗口会在系统栏后面绘制,从而跨越整个显示屏的宽度和高度。系统栏包括状态栏、标题栏和导航栏。

下面通过一个具体示例,来深入探究 edge-to-edge 全面屏效果。在项目里,当把 targetSdkVersion 指定为 34 时,默认不会强制开启 edge-to-edge 功能。以下是相关代码:

<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/main"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"><ImageViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:adjustViewBounds="true"android:contentDescription="@null"android:scaleType="fitXY"android:src="@drawable/test" /></LinearLayout>
</androidx.core.widget.NestedScrollView>

将该项目运行于 Android 15 设备上,效果如下图所示:

当把 targetSdkVersion 指定为 35 或者更高版本时,App 会自动切换至 edge-to-edge 全面屏效果,如下图所示:

为确保在开启 edge-to-edge 全面屏体验后,导航条不会因背景因素而难以辨认,Android 系统做了相应优化。当在屏幕上进行滚动操作时,导航条的颜色会随之改变。

若手机底部采用的是传统的 Back、Home、Task 三按键导航栏,而非手势导航栏,edge-to-edge 全面屏体验会有所不同。此时,导航栏会呈现半透明效果,默认不透明度为 80%,效果如下:

从这种显示效果能够看出,三按键导航栏在 edge-to-edge 全面屏体验方面存在明显不足,未来很可能会逐渐被 Android 系统边缘化。

随着 edge-to-edge 全面屏体验的普及,一些与状态栏、导航栏颜色设置相关的 API 也逐渐被边缘化。这是因为这些 API 与 edge-to-edge 全面屏体验存在冲突,部分 API 当下已无法使用,部分则不再被推荐使用,例如以下这些 API:

Window#setStatusBarColor
Window#setStatusBarContrastEnforced
Window#setNavigationBarColor
Window#setNavigationBarContrastEnforced

三、如何适配

是否需要针对 edge-to-edge 全面屏进行额外的适配工作,很大程度上取决于应用界面的具体设计。就像前文第二节所举的例子,即便不做任何适配,用户体验依旧良好。然而,换作其他界面,情况可能就大不相同了。

接下来,我们以腾讯 QMUI_Android 的主界面为例,看看它在 edge-to-edge 全面屏体验下的实际效果,结果如下:

可以看到,这次的显示效果并不理想。在主界面,底部的 tab 栏陷入了导航栏区域,这会对 tab 按钮的操作产生干扰。而在其他页面,页面内容延伸到了状态栏区域,使得页面内容与状态栏相互重叠,严重影响了内容的可读性。

这这些问题正是 edge-to-edge 全面屏体验可能带来的典型状况,同时也是我们在开发过程中需要进行适配优化的重点方向。

2.1 启用无边框显示

前面说过,edge-to-edge 全面屏体验其实并不是全新的功能,在 Android 15 之前也是支持的,Android 15 只是将这个功能强制开启了而已。要在 Android 15 之前的设备上启用 edge-to-edge 全面屏体验,只需要额外两步就可以完成。

第一步,在项目的build.gradle文件中添加如下库的依赖:

dependencies {// Java language implementationimplementation 'androidx.activity:activity:$activity_version'// Kotlinimplementation 'androidx.activity:activity-ktx:$activity_version'
}

第二步,在Activity的onCreate函数中添加如下代码:

class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {enableEdgeToEdge()super.onCreate(savedInstanceState)...}
}

enableEdgeToEdge 应在 setContentView 之前调用此方法,默认情况下,enableEdgeToEdge 会使系统栏透明,但在三按钮导航模式下,状态栏会获得半透明的遮罩。系统图标和遮罩的颜色会根据系统的浅色或深色主题进行调整。

2.2 系统条

适配的代码其实还是比较简单的,主要就是借助 ViewCompat.setOnApplyWindowInsetsListener 这个函数,来对某些指定的 View 进行偏移,保证其不会被系统的状态栏或导航栏遮挡住就可以了。

在第二节的例子中,若要避免图片被状态栏和导航栏遮挡,只需对代码进行如下修改:

class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)insets}}
}

由于我们不希望图片延伸到状态栏和导航栏区域,所以调用 WindowInsetsCompat.Type.systemBars 来获取所有系统条(包含状态栏和导航栏)的 Insets。借助这个 Insets,我们能够获取状态栏和导航栏的高度,然后为 NestedScrollView 设置内边距(padding),这样就能确保图片内容不会进入状态栏和导航栏。

添加这段代码后,重新运行程序,便可得到较为理想的显示效果,具体效果如下:

除了 WindowInsetsCompat.Type.systemBars,还有多种其他类型的 Insets 可供选择:

  • 若希望某个 View 不进入状态栏区域,可使用 WindowInsetsCompat.Type.statusBars。

  • 若希望某个 View 不进入导航栏区域,可使用 WindowInsetsCompat.Type.navigationBars。

  • 若希望某个 View 不进入 Cutout 区域,可使用 WindowInsetsCompat.Type.displayCutout。

Cutout 这一概念是在 Android 9 系统中引入的。当时,手机市场刚兴起刘海屏,为了适配可能出现的各种不同样式的刘海设计,Google 推出了 Cutout API。不过,后来手机厂商并未设计出各种奇形怪状的刘海,大多选择将刘海区域整合到状态栏中。因此,如今 displayCutout 这个 API 的实际效果与 statusBars 已无太大差异。

2.3 应用圆角

在 Android 设备的屏幕设计中,圆角屏幕逐渐成为一种流行趋势。从 Android 12(API 级别 31)开始,系统提供了 RoundedCorner 和 WindowInsets.getRoundedCorner 相关 API,利用这些 API 可以获取设备屏幕圆角的半径和中心点。其主要目的在于避免应用的界面元素在圆角屏幕上被截断,从而保证应用在不同屏幕形状的设备上都能有良好的显示效果。

当在应用中实现这些 API 时,无需担心对非圆角屏幕设备产生影响,因为这些 API 仅会在支持圆角屏幕的设备上生效,对于非圆角屏幕设备不会有任何额外的处理。

以下示例代码展示了如何依据 RoundedCorner 提供的信息来设置视图的外边距,从而避免界面元素被截断。这里以获取屏幕左上角的圆角信息为例:

class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)enableEdgeToEdge()setContentView(R.layout.activity_main)val main = findViewById<View>(R.id.main)ViewCompat.setOnApplyWindowInsetsListener(main) { _, insets ->if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {val rootWindowInsets = main.rootWindowInsets// 获取屏幕左上角的圆角信息  val topLeft = rootWindowInsets?.getRoundedCorner(RoundedCorner.POSITION_TOP_LEFT)Log.d("lx-test", " topLeft: $topLeft")// 根据topLeft的信息设置外边距}insets}}
}

运行日志如下:

在获取到屏幕圆角信息后,后续我们可进一步根据 RoundedCorner 对界面进行外边距的设置,以此有效避免界面元素被屏幕圆角截断,提升应用在圆角屏幕设备上的视觉体验。

2.4 手势导航

自 Android 10 起,Google 引入了手势导航功能。在此模式下,手机屏幕的左右两侧可用于触发 Back 键操作,屏幕底部则用于触发 Home 键操作,其触发区域如下面的图示中的橙色部分所示:

这意味着,若我们设计的应用界面在这些区域也存在相似的手势操作,就会引发手势冲突问题,导致用户操作无法正常执行。

如同处理系统栏内边距问题一样,我们可以借助 WindowInsetsCompat.Type.systemGestures 来获取橙色区域的 Insets。随后,通过设置内边距(padding)的方式,让存在事件冲突的 View 避开这个区域,从而避免与系统手势内边距重叠,确保用户操作的流畅性。

2.5 Material 组件

许多基于 View 的 Android Material 组件 (com.google.android.material)具备自动处理边衬区的能力,像 BottomAppBar、BottomNavigationView、NavigationRailView 以及 NavigationView这些组件都能自行处理边衬区的相关问题。

然而,AppBarLayout 它不会自动处理内边距。可以添加 android:fitsSystemWindows="true" 以处理顶部边衬区。

所以,当项目中使用了 Material 组件时,开发者需要依据具体的应用场景,有针对性地对这些组件进行适配操作,以确保界面在不同的系统环境下都能有良好的显示和交互效果。

2.6 沉浸模式

在某些场景下,将内容以全屏模式呈现,能为用户带来绝佳体验,使其更具身临其境之感。为了在沉浸模式下隐藏系统栏,可以借助 WindowInsetsController 和 WindowInsetsControllerCompat 库来实现。示例代码如下:

val windowInsetsController = WindowCompat.getInsetsController(window, window.decorView)
// 隐藏系统栏
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
// 显示系统栏
windowInsetsController.show(WindowInsetsCompat.Type.systemBars())

当采用无边框设计时,可能需要手动调整系统栏图标颜色,确保其与应用背景形成鲜明对比,以提升视觉辨识度。例如,若要创建浅色状态栏图标,可按以下步骤操作:

WindowCompat.getInsetsController(window, window.decorView).isAppearanceLightStatusBars = false

四、Compose中如何适配

下面是将第二节的代码转换为 Jetpack Compose 实现的内容。转换后的代码如下:

class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)enableEdgeToEdge()setContent {EdgeToEdgeTheme {MainPage()}}}
}@Composable
fun MainPage() {Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())) {Image(painter = painterResource(id = R.drawable.test),contentDescription = null,contentScale = ContentScale.FillWidth,modifier = Modifier.fillMaxWidth())}
}

若不希望图片延伸到状态栏和导航栏区域,可借助 Modifier 的 systemBarsPadding 函数,对指定的可组合项进行偏移,从而避免其被系统的状态栏或导航栏遮挡。修改后的代码如下:

@Composable
fun MainPage() {Column(modifier = Modifier.fillMaxSize().systemBarsPadding() .verticalScroll(rememberScrollState())) {// ...}
}

systemBarsPadding 是 Compose 内置的专门用于处理 Insets 问题的函数。此外,Compose 还提供了许多其他实用的函数:

  • statusBarsPadding:可防止 Compose 控件的内容绘制到状态栏区域。

  • navigationBarsPadding:能避免 Compose 控件的内容绘制到导航栏区域。

  • displayCutoutPadding:可保护 Compose 控件的内容不进入 Cutout 区域。

  • safeDrawingPadding:该函数可确保 Compose 控件的内容不会绘制到任何系统 UI 区域,涵盖状态栏、导航栏、刘海区域等,是最常用的 Insets 处理函数之一。

  • safeGesturesPadding:能避免与系统手势发生冲突。

  • safeContentPadding:它是 safeDrawingPadding 和 safeGesturesPadding 的结合,可保证界面和手势都不会与系统 UI 发生冲突或覆盖。

除了上述这些常用函数外,Compose 还提供了众多用于解决其他场景问题的 Insets 函数,例如曲面屏手机、输入法弹出等场景。由于函数数量较多,这里不再逐一介绍。若你想深入了解,可以参考官方文档

https://developer.android.com/reference/kotlin/androidx/compose/ui/Modifier

另外,如果你使用了一些 Compose Material 3 的控件,像 TopAppBar、BottomAppBar、NavigationBar 等,它们会自动处理 Insets 问题,无需手动进行适配。

相关文章:

  • docker部署ruoyi-vue-pro前后端详细笔记
  • Linux:权限相关问题
  • 一款支持多线程的批量任务均衡器
  • AI日报 - 2024年04月22日
  • 实验四-用户和权限管理
  • Uniapp:view容器(容器布局)
  • 微硕WSP4407A MOS管在智能晾衣架中的应用与市场分析
  • 时序逻辑入门指南:LTL、CTL与PTL的概念介绍与应用场景
  • Flowable7.x学习笔记(十)分页查询已部署 BPMN XML 流程
  • 【Python】Python如何在字符串中添加变量
  • leetcode 647. Palindromic Substrings
  • 6N60-ASEMI机器人功率器件专用6N60
  • 《P3029 [USACO11NOV] Cow Lineup S》
  • 使用Mybaitis-plus提供的各种的免写SQL的Wrapper的使用方式
  • VLAN虚拟局域网
  • llama-webui docker实现界面部署
  • BEVDet4D: Exploit Temporal Cues in Multi-camera 3D Object Detection
  • QT 的.pro 转 vsproject 工程
  • 从多个Excel批量筛查数据后合并到一起
  • 方案精读:2024 华为数字政府智慧政务一网统管解决方案【附全文阅读】
  • 广西三江通报“网约车司机加价”:对网约车平台进行约谈
  • 规模再创新高,超百款新车首发!上海车展明日开幕
  • 美方因涉港问题对中国官员滥施非法单边制裁,外交部:强烈谴责,对等反制
  • 全国登记在册民营企业超过5700万户
  • 云南昭通一公园发现毒饵,多只宠物狗疑中毒致死
  • 东北三省,十年少了一个“哈尔滨”