Android 11异形屏适配实战:从DisplayCutout到沉浸式布局

发布时间:2026/8/6 7:01:41
Android 11异形屏适配实战:从DisplayCutout到沉浸式布局 1. 项目概述当屏幕不再是矩形在移动设备形态日新月异的今天我们早已习惯了各种形态的屏幕。从最初的“刘海屏”到“水滴屏”再到“挖孔屏”、“瀑布屏”乃至如今折叠屏的内外屏差异一个核心的挑战始终横亘在应用开发者面前如何让应用内容完美适配这些非标准、非矩形的显示区域这不仅仅是美观问题更直接关系到核心功能是否可用、交互是否顺畅。Android 11 系统层面对“异形屏”适配提供的“裁剪系统显示区域”能力正是解决这一系列痛点的官方方案。它不再是让应用去猜测哪里能点、哪里能看而是系统主动告知应用“嘿这块区域被摄像头或圆角占用了你的内容最好别放这儿。”简单来说这个项目就是深入挖掘 Android 11 引入的WindowInsets.getDisplayCutout()API 及相关布局特性的实战应用。它适合所有需要在 Android 11 及以上系统版本中追求极致全面屏体验和稳定交互的移动应用开发者。无论你是处理一个突兀的刘海还是应对复杂的多挖孔屏幕理解并正确使用系统显示区域裁剪都能让你的应用从“勉强能用”提升到“浑然一体”的体验层级。接下来我将结合多年踩坑经验带你从设计思路到代码细节彻底掌握这套适配方法论。2. 核心设计思路与方案选型在 Android 中处理异形屏历史上经历过几个阶段早期应用对刘海视而不见导致内容被遮挡后来出现了各种厂商私有的适配方案需要针对不同品牌手机做特殊处理维护成本极高直到 Android 9Pie引入了统一的 DisplayCutout 概念并在 Android 11R中极大地增强了其能力和易用性才真正提供了官方的、标准化的解决方案。2.1 理解“系统显示区域”与“可绘制区域”这是整个适配逻辑的基石必须首先厘清。系统显示区域指的是整个物理屏幕的像素范围包括被摄像头、传感器、圆角等硬件元素占据的不可绘制部分。Display.getRealSize()获取的通常就是这个区域。可绘制区域也称为“安全区域”或“内容区域”是系统保证应用可以安全绘制内容而不会被硬件遮挡的矩形区域。在默认情况下系统会自动将你的应用窗口限制在这个区域内。Android 11 的“裁剪系统显示区域”本质是允许应用主动选择如何与系统显示区域进行交互。具体表现为几种模式默认模式LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT对于未做适配的老应用系统行为取决于版本和手机厂商。在 Android 11 上通常会让内容流入刘海两侧但顶部状态栏高度会增加以避开刘海保证状态栏图标和文字不被遮挡。这是一种“保守但安全”的兼容策略。始终不占用刘海区域模式LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER这是最传统的“黑边”模式。系统会严格将你的窗口限制在安全区域内刘海两侧和下方会出现黑边。除非有特殊兼容性要求如某些全屏游戏否则现代应用一般不采用此模式因为它浪费了宝贵的屏幕空间。短边流入模式LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES这是目前绝大多数沉浸式应用如视频、阅读、游戏推荐使用的模式。它的逻辑是允许内容流入屏幕的“短边”所在的刘海/挖孔区域。对于常见的顶部刘海屏手机“短边”就是屏幕的左右两侧横屏时的上下两侧。在此模式下当手机竖屏时内容可以延伸到顶部刘海的两侧但不会延伸到刘海下方当手机横屏时内容可以延伸到左侧或右侧的刘海/挖孔区域取决于哪边是短边。这既充分利用了屏幕又避免了核心交互元素如标题栏按钮被遮挡的风险。始终允许流入模式LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS允许内容流入所有边的刘海区域。这需要非常精细的布局控制否则极易导致内容被遮挡。通常仅用于需要极致沉浸感的场景如全屏相册、绘图应用并且需要开发者手动处理布局避开切割区域。方案选型背后的考量为什么SHORT_EDGES成为主流核心在于平衡“沉浸感”和“可用性”。NEVER太保守ALWAYS太激进且风险高。SHORT_EDGES巧妙地利用了手机屏幕长宽比的物理特性在横竖屏切换时自动选择风险较小的边进行扩展将适配的复杂度从开发者转移给了系统规则是一种优雅的折中。对于大多数内容消费型应用直接选择SHORT_EDGES就能获得很好的效果。2.2 从“全屏”到“适配沉浸式状态栏”的思维转变很多开发者一提到异形屏就想到FLAG_FULLSCREEN隐藏状态栏和导航栏。但这只是第一步而且是相对简单的一步。真正的难点在于隐藏系统栏后如何防止你的交互按钮或关键文本落入刘海的“陷阱”。Android 11 的适配核心思想是先通过WindowInsetsController或SYSTEM_UI_FLAG_FULLSCREEN等标志进入沉浸模式再通过WindowInsetsAPI 获取系统“裁剪区域”即DisplayCutout的精确信息最后在你的布局中主动避开这些区域。这要求我们的布局代码从“静态适配固定间距”转变为“动态响应系统插入区域”。传统的android:fitsSystemWindows”true”属性虽然简单但其行为在不同版本和主题下不一致且无法精细控制。在 Android 11 的适配体系中我们更推荐使用WindowInsetsCompat来自 AndroidX Core 库来以向后兼容的方式监听和处理插入区域的变化。3. 核心细节解析与实操要点3.1 关键 API 深度剖析WindowInsets.getDisplayCutout()这是获取裁剪区域信息的入口。它返回一个DisplayCutout对象。关键要检查其是否为null为null意味着当前设备没有异形区域或当前窗口模式不包含任何切割区域信息。DisplayCutout对象它提供了几种获取安全区域边界的方法getSafeInsetLeft(),getSafeInsetTop(),getSafeInsetRight(),getSafeInsetBottom()这四个方法返回的是系统建议你从窗口的对应边向内缩进的距离以避免内容被遮挡。注意在LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES模式下只有当内容流入的那条短边对应的 safeInset 值才可能是非零的。例如竖屏顶部刘海getSafeInsetTop()会返回一个值表示从窗口顶部向下偏移这个距离才是安全区域的上边界。getBoundingRects()这是更强大和精细的工具。它返回一个ListRect列表中的每一个Rect都精确描述了一个不可绘制区域的屏幕坐标范围。对于“挖孔屏”这里可能只有一个矩形对于“长条刘海”可能是一个横跨顶部的矩形对于“角部刘海”可能是两个矩形。通过遍历这个列表你可以知道屏幕上每一个“洞”的具体位置和大小从而实现像素级避让。重要提示getSafeInsetXXX()返回的是“距离”而getBoundingRects()返回的是“区域”。前者用于快速设置边距后者用于复杂场景的碰撞检测。在大多数 UI 布局场景下使用safeInset设置padding就足够了。但在游戏、自定义绘制或特殊控件布局时boundingRects必不可少。设置窗口布局模式这是告诉系统你的窗口希望如何与切割区域交互。Activity 级别在onCreate中通过Window对象设置。if (Build.VERSION.SDK_INT Build.VERSION_CODES.P) { val params window.attributes params.layoutInDisplayCutoutMode WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES window.attributes params }Window 级别也可以通过WindowInsetsController进行更动态的控制但设置布局模式通常还是在窗口属性中完成。3.2 使用 AndroidX Core 库进行兼容性处理Google 强烈推荐使用androidx.core:core-ktx或androidx.core:core库中的WindowInsetsCompatAPI。它抹平了 Android 不同版本从 API 20 开始之间WindowInsets行为的差异并提供了一致、易用的监听器。核心类WindowInsetsCompat它提供了getInsets(int typeMask)方法来获取特定类型的插入区域。对于异形屏我们主要关心Type.displayCutout()或Type.systemBars()。更重要的是它允许我们通过View.setOnApplyWindowInsetsListener来监听插入区域的变化。一个经典的适配流程是在 Activity 的onCreate中设置窗口模式为LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES。在布局的根视图通常是ConstraintLayout或FrameLayout上设置OnApplyWindowInsetsListener。在监听器中获取DisplayCutoutCompat对象并提取出安全插入距离safe insets。将这些安全距离应用为根视图的padding从而使其所有子视图自动被限制在安全区域内。同时消费掉这些插入信息防止它们继续向下传递造成重复处理。// 使用 AndroidX Core KT ViewCompat.setOnApplyWindowInsetsListener(rootView) { view, insets - val cutout insets.getInsets(WindowInsetsCompat.Type.displayCutout()) // 应用padding避开切割区域 view.updatePadding( left cutout.left, top cutout.top, right cutout.right, bottom cutout.bottom ) // 消费掉displayCutout类型的insets保证子View收到的是处理后的信息 WindowInsetsCompat.CONSUMED }4. 实操过程与核心环节实现让我们通过一个完整的案例来实现一个适配异形屏的视频播放器 Activity。4.1 环境与依赖准备首先确保你的build.gradle文件中包含了 AndroidX Core 库。dependencies { implementation androidx.core:core-ktx:1.12.0 // 请使用最新稳定版 // 其他依赖... }并将你的 App 主题设置为透明状态栏为沉浸式体验打下基础。在res/values/themes.xml中style nameTheme.MyApp parentTheme.Material3.DayNight.NoActionBar !-- 设置状态栏透明 -- item nameandroid:windowTranslucentStatustrue/item item nameandroid:windowTranslucentNavigationfalse/item !-- 或者使用 windowLightStatusBar 等控制状态栏图标颜色 -- /style4.2 布局文件设计我们设计一个简单的布局顶部是返回按钮和标题需要避开刘海中间是视频播放器可以全屏延伸底部是控制栏需要避开底部导航栏如果存在。activity_video_player.xml:?xml version1.0 encodingutf-8? androidx.constraintlayout.widget.ConstraintLayout xmlns:androidhttp://schemas.android.com/apk/res/android xmlns:apphttp://schemas.android.com/apk/res-auto android:idid/root_container android:layout_widthmatch_parent android:layout_heightmatch_parent android:backgroundcolor/black !-- 顶部应用栏需要位于安全区域 -- com.google.android.material.appbar.MaterialToolbar android:idid/top_app_bar android:layout_width0dp android:layout_height?attr/actionBarSize android:backgroundcolor/semi_transparent_black app:layout_constraintEnd_toEndOfparent app:layout_constraintStart_toStartOfparent app:layout_constraintTop_toTopOfparent / !-- 视频播放器SurfaceView/TextureView可以充满整个父容器 -- androidx.constraintlayout.widget.ConstraintLayout android:idid/video_container android:layout_width0dp android:layout_height0dp app:layout_constraintBottom_toTopOfid/bottom_control_bar app:layout_constraintEnd_toEndOfparent app:layout_constraintStart_toStartOfparent app:layout_constraintTop_toBottomOfid/top_app_bar !-- 这里放置实际的VideoView或ExoPlayer的SurfaceView -- /androidx.constraintlayout.widget.ConstraintLayout !-- 底部控制栏需要位于安全区域 -- LinearLayout android:idid/bottom_control_bar android:layout_width0dp android:layout_heightwrap_content android:orientationhorizontal android:backgroundcolor/semi_transparent_black android:padding16dp app:layout_constraintBottom_toBottomOfparent app:layout_constraintEnd_toEndOfparent app:layout_constraintStart_toStartOfparent !-- 播放、暂停、进度条等控件 -- /LinearLayout /androidx.constraintlayout.widget.ConstraintLayout这个布局的关键在于我们将需要避开不安全区域的控件顶部栏和底部栏与可以全屏扩展的视频容器分离开。4.3 Activity 代码实现现在在VideoPlayerActivity.kt中实现核心逻辑import android.os.Build import android.os.Bundle import android.view.View import android.view.WindowManager import androidx.appcompat.app.AppCompatActivity import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.updatePadding import com.yourpackage.databinding.ActivityVideoPlayerBinding class VideoPlayerActivity : AppCompatActivity() { private lateinit var binding: ActivityVideoPlayerBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding ActivityVideoPlayerBinding.inflate(layoutInflater) setContentView(binding.root) // 步骤1: 设置全屏和异形屏布局模式 setupWindowFlags() // 步骤2: 设置WindowInsets监听动态调整布局 setupWindowInsetsListener() // 步骤3: 初始化播放器等业务逻辑 initVideoPlayer() } private fun setupWindowFlags() { // 隐藏状态栏和导航栏实现沉浸式 window.decorView.systemUiVisibility ( View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY ) // 关键设置窗口允许内容流入短边异形区域 (Android P) if (Build.VERSION.SDK_INT Build.VERSION_CODES.P) { val params window.attributes params.layoutInDisplayCutoutMode WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES window.attributes params } } private fun setupWindowInsetsListener() { // 在根布局上设置监听器 ViewCompat.setOnApplyWindowInsetsListener(binding.rootContainer) { view, insets - // 获取系统栏状态栏导航栏的插入区域 val systemBars insets.getInsets(WindowInsetsCompat.Type.systemBars()) // 获取异形屏裁剪区域的插入区域 val displayCutout insets.getInsets(WindowInsetsCompat.Type.displayCutout()) // 计算最终的安全边距。 // 顶部取系统状态栏和异形区域两者的最大值确保顶部控件完全可见。 // 底部主要考虑系统导航栏的高度。 // 左右考虑异形区域如侧边瀑布屏的曲边和系统栏。 val leftInset maxOf(systemBars.left, displayCutout.left) val topInset maxOf(systemBars.top, displayCutout.top) val rightInset maxOf(systemBars.right, displayCutout.right) val bottomInset maxOf(systemBars.bottom, displayCutout.bottom) // 将计算出的安全边距应用到根布局的padding上。 // 这样所有直接子Viewtop_app_bar, video_container, bottom_control_bar的布局边界都会自动内缩避开不安全区域。 view.updatePadding(leftInset, topInset, rightInset, bottomInset) // 同时我们需要手动调整顶部AppBar和底部ControlBar的额外内边距。 // 因为根容器已经有了padding但AppBar和ControlBar的约束是贴父容器边的它们自身还需要额外的padding来让内容如按钮文字不紧贴屏幕边缘。 binding.topAppBar.updatePadding(top topInset) binding.bottomControlBar.updatePadding(bottom bottomInset) // 返回消费后的Insets表示我们已经处理了这些插入区域子View无需再处理。 WindowInsetsCompat.CONSUMED } } private fun initVideoPlayer() { // 初始化你的视频播放器如ExoPlayer将视频画面设置到 video_container 中。 // 注意视频播放器视图SurfaceView应设置为 match_parent让它填满 video_container。 // 由于 video_container 已经被根布局的padding限制在了安全区域内视频画面会自动避开刘海。 // 但为了极致的沉浸感你可能希望视频内容能延伸到刘海区域。 // 这时可以单独对 video_container 不应用顶部padding或者使用 getBoundingRects() 进行更精细的控制。 // 本例中video_container 的上下约束分别依赖于 topAppBar 和 bottomControlBar因此它自然位于安全区域中部。 } // 处理用户点击重新进入沉浸模式 override fun onWindowFocusChanged(hasFocus: Boolean) { super.onWindowFocusChanged(hasFocus) if (hasFocus) { hideSystemUI() } } private fun hideSystemUI() { window.decorView.systemUiVisibility ( View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY ) } }代码逻辑解读setupWindowFlags首先进入沉浸式全屏模式并设置窗口允许内容流入短边异形区。这是告诉系统我们的意图。setupWindowInsetsListener这是适配的核心。我们监听系统插入区域的变化。我们同时获取systemBars和displayCutout的插入值。因为即使隐藏了系统栏它们的“占位空间”信息仍然存在且异形区域是独立于系统栏的。取两者最大值作为最终的安全边距是为了应对最坏情况确保覆盖所有可能的遮挡。将安全边距设置为根视图的padding这是一个高效的方法能让所有子视图的布局边界自动内缩。我们额外调整了topAppBar和bottomControlBar的padding这是为了确保工具栏内的图标和文字不会紧贴屏幕物理边缘保持良好的触摸和视觉体验。返回CONSUMED表示我们已经处理了这些插入信息防止子视图重复处理。视频播放器容器 (video_container) 的上下边界依赖于已经避开不安全区域的topAppBar和bottomControlBar因此它自然被限制在垂直方向的安全区域内而水平方向由于根布局的左右padding也得以保证。视频内容在这个容器内可以安全播放。4.4 处理横屏场景横屏是异形屏适配的另一个重点。当设备横屏时“短边”可能变成屏幕的顶部和底部对于有刘海或挖孔的手机。我们的代码已经具备了适应性因为WindowInsets会在屏幕旋转时自动更新OnApplyWindowInsetsListener会被再次调用计算出的topInset和bottomInset会相应变化。但是在横屏全屏游戏或视频播放器中我们可能希望视频内容能充满整个屏幕包括刘海区域。这时你需要更精细的策略仅对视频播放视图禁用安全区域限制在setupWindowInsetsListener中不给video_container设置来自根布局的padding限制或者单独为其设置不同的padding例如只保留左右padding以避开侧边曲面但不保留顶部padding。使用getBoundingRects()进行碰撞检测如果你有需要绝对避开刘海的UI元素如暂停按钮、字幕可以获取DisplayCutout的boundingRects在布局或绘制时动态计算这些元素的位置确保它们不会与Rect列表中的任何区域重叠。ViewCompat.setOnApplyWindowInsetsListener(binding.rootContainer) { view, insets - val cutoutCompat insets.getDisplayCutout() cutoutCompat?.let { cutout - val boundingRects cutout.boundingRects // 遍历 boundingRects判断你的关键控件位置是否与其相交 for (rect in boundingRects) { if (isViewOverlapWithRect(myCriticalButton, rect)) { // 调整 myCriticalButton 的位置 adjustViewPosition(myCriticalButton, rect) } } } // ... 其他处理 WindowInsetsCompat.CONSUMED }5. 常见问题与排查技巧实录在实际开发和测试中你会遇到各种各样的问题。以下是我总结的典型问题及其解决方案。5.1 问题一设置了SHORT_EDGES模式但内容仍然没有流入刘海区域可能原因1主题或样式冲突。检查你的 Activity 主题是否设置了android:windowDrawsSystemBarBackgrounds”false”或某些强制全屏的属性与系统窗口管理器冲突。尝试使用一个干净的、继承自Theme.MaterialComponents.Light.NoActionBar的主题进行测试。可能原因2窗口标志位设置顺序。确保先设置layoutInDisplayCutoutMode然后再执行setContentView。有些窗口属性需要在视图附加到窗口之前设置。排查技巧在onCreate中设置完窗口属性后添加一个延迟任务打印当前窗口的layoutInDisplayCutoutMode值确认设置生效。binding.rootContainer.post { if (Build.VERSION.SDK_INT Build.VERSION_CODES.P) { Log.d(CutoutDebug, Current cutout mode: ${window.attributes.layoutInDisplayCutoutMode}) } }5.2 问题二在横屏时底部导航栏区域遮挡了控制按钮可能原因在横屏时系统导航栏可能出现在屏幕侧边手势导航区域。我们的代码中bottomInset可能来自displayCutout为0而不是systemBars。如果systemBars.bottom在横屏时不为0取决于手势导航的宽度而我们又将其应用到了底部控制栏的paddingBottom就会导致控制栏内容过于靠上。解决方案区分对待。对于底部控制栏我们可能只希望避开明确的导航栏而不是一个固定的bottomInset。可以修改setupWindowInsetsListener中的逻辑val systemBars insets.getInsets(WindowInsetsCompat.Type.systemBars()) val displayCutout insets.getInsets(WindowInsetsCompat.Type.displayCutout()) val navigationBars insets.getInsets(WindowInsetsCompat.Type.navigationBars()) // 单独获取导航栏 // 根布局padding仍然使用最大范围保证安全 val leftInset maxOf(systemBars.left, displayCutout.left) val topInset maxOf(systemBars.top, displayCutout.top) val rightInset maxOf(systemBars.right, displayCutout.right) // 底部安全区域主要考虑异形切割导航栏由底部控件单独处理 val bottomSafeInset displayCutout.bottom view.updatePadding(leftInset, topInset, rightInset, bottomSafeInset) // 底部控制栏的paddingBottom只考虑导航栏高度 binding.bottomControlBar.updatePadding(bottom navigationBars.bottom)5.3 问题三getSafeInsetTop()返回0但刘海明明存在可能原因当前窗口的layoutInDisplayCutoutMode可能是NEVER或者内容没有流入到顶部例如在横屏SHORT_EDGES模式下顶部可能不是短边。safeInset值只对当前模式下内容会流入的那条边有意义。排查技巧不要依赖safeInset判断是否有刘海。始终使用getDisplayCutout() ! null来判断。获取切割区域的详细信息应使用getBoundingRects()。val cutout ViewCompat.getRootWindowInsets(view)?.getDisplayCutout() if (cutout ! null) { Log.d(CutoutDebug, Cutout exists. Bounding Rects: ${cutout.boundingRects}) // 即使 safeInsetTop 为0 boundingRects 也可能包含顶部的矩形 }5.4 问题四在 WebView 或第三方库渲染的内容区域适配异常可能原因WebView 或某些原生视图如地图SDK有自己独立的视图层级和窗口处理逻辑可能不直接响应根视图的padding或WindowInsets。解决方案对于 WebView可以尝试在onPageStarted或onPageFinished后通过 JavaScript 接口向网页内容传递安全区域信息让前端CSS通过env(safe-area-inset-top)等变量进行适配。这需要前后端协作。对于第三方视图查阅其官方文档看是否提供了设置padding或处理WindowInsets的接口。如果不行可能需要将该视图放置在一个设置了正确padding的容器中并确保该容器的背景色与视图融合。终极方案如果库完全不配合可以考虑在显示该视图时临时将窗口模式改为LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER为其提供一个安全的矩形绘制环境退出时再改回来。但这会影响体验的一致性。5.5 问题速查表问题现象可能原因排查步骤与解决方案内容被刘海遮挡1. 未设置窗口模式。2. 模式设置为NEVER。3. 根视图未消费和处理WindowInsets。1. 确认onCreate中设置了LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES。2. 检查根视图是否设置了OnApplyWindowInsetsListener并应用了padding。3. 使用布局检查工具查看视图边界是否越过了安全区域。状态栏/导航栏隐藏后出现空白根视图的padding应用了systemBars的插入值但系统栏隐藏后这些值未更新。确保在OnApplyWindowInsetsListener中使用的insets对象是最新的。使用WindowInsetsCompat.CONSUMED正确消费。检查是否在onResume等生命周期中错误地重置了padding。横竖屏切换后布局错乱OnApplyWindowInsetsListener可能未在配置变更后重新生效或padding计算逻辑未考虑方向。确保监听器设置在根视图上且能响应配置变更。在计算padding时考虑区分top/bottom与left/right在横竖屏下的不同含义。使用maxOf(systemBars, displayCutout)是通用做法。特定机型如华为、小米适配无效厂商早期自定义实现与 Android 标准 API 存在差异。1. 优先使用WindowInsetsCompat它已包含部分厂商兼容逻辑。2. 测试时在开发者选项中打开“模拟具有显示屏凹口的屏幕”进行初步验证。3. 如有必要针对特定机型通过Build.MANUFACTURER判断做细微调整但尽量依赖标准API。最后的实操心得异形屏适配不是一个“设置即完成”的动作而是一个需要贯穿于 UI 设计、布局编写和代码逻辑的持续过程。最稳健的方法是采用“防御式布局”始终假设屏幕边缘可能存在遮挡通过动态padding和margin来保证核心内容和操作区域的安全。WindowInsetsCompatAPI 是你最好的工具它提供的动态信息流能让你的应用灵活优雅地应对千变万化的屏幕形态。在发布前务必在多种具有不同异形屏设计的真机上进行测试包括竖屏、横屏以及全屏沉浸模式下的切换才能确保万无一失。