news 2026/8/15 22:05:36

Android屏幕尺寸获取全解析:从基础概念到实战适配方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Android屏幕尺寸获取全解析:从基础概念到实战适配方案

1. 项目概述:为什么获取屏幕尺寸信息是Android开发的必修课

在Android应用开发中,获取屏幕的宽高、状态栏高度以及底部导航栏高度,听起来像是一个基础得不能再基础的操作。但恰恰是这个基础操作,是构建适配性良好、用户体验一致的UI界面的基石。我见过太多因为尺寸计算错误导致的UI错乱问题:一个全屏弹窗底部被导航栏遮挡,一个沉浸式状态栏的标题栏布局错位,或者在不同屏幕比例的设备上界面拉伸变形。这些问题追根溯源,往往是对系统提供的各种尺寸概念理解不清,或者获取方法使用不当。

这个项目标题“Android 获取屏幕宽高、状态栏高度、底部导航栏高度信息”,其核心价值在于为开发者提供一套准确、可靠且兼容性强的尺寸信息获取方案。它不仅仅是调用几个API那么简单,更涉及到对Android窗口系统、视图层级以及不同系统版本差异的深刻理解。无论是刚入行的新手,还是有一定经验的开发者,系统地掌握这部分知识,都能让你在解决UI适配问题时更加得心应手,避免很多“坑”。

2. 核心概念解析与方案设计思路

在动手写代码之前,我们必须先厘清几个关键概念。Android屏幕上的尺寸信息并非单一来源,它们分布在不同的上下文和计算维度中。

2.1 关键尺寸定义与来源

屏幕宽高 (Screen Width/Height): 通常指整个物理显示面板的原始分辨率,例如 1080x2340 像素。这是设备的硬件属性。

应用窗口宽高 (Window/DecorView Width/Height): 这是你的应用实际可用的绘制区域。它等于屏幕尺寸减去系统UI(如状态栏、导航栏)占据的空间。在非全屏模式下,窗口大小小于屏幕大小。

状态栏高度 (Status Bar Height): 屏幕顶部显示时间、电量、信号等系统信息的区域高度。这是一个系统资源维度。

导航栏高度 (Navigation Bar Height): 屏幕底部带有虚拟按键(返回、主页、多任务)的区域高度。在具有实体按键的设备上,此高度可能为0。它同样是一个系统资源维度。

内容区域高度 (Content Height): 通常指ContentView(即你通过setContentView设置的布局)的可用高度,它等于窗口高度减去ActionBar/Toolbar(如果存在)的高度。

我们的目标,就是准确地从系统中提取出这些值。设计思路遵循一个原则:优先使用系统提供的资源ID和标准API,其次再考虑通过计算反射等“黑科技”,以保证最大的兼容性和稳定性。

2.2 方案选型与考量

获取这些信息主要有以下几种途径,各有优劣:

  1. DisplayMetricsWindowManager: 用于获取与屏幕密度相关的物理尺寸和缩放后的尺寸,是获取屏幕和窗口宽高的主要入口。
  2. 资源系统 (resources.getDimensionPixelSize): 获取系统预定义的维度值,如状态栏和导航栏的标准高度,这是最推荐的方式。
  3. 视图布局系统 (View.getLocationOnScreen,View.getWindowVisibleDisplayFrame): 通过视图在窗口中的位置和可见区域来计算系统UI的高度,非常直观,但可能需要在视图布局完成后才能获取准确值。
  4. 反射 (Reflection): 用于访问系统内部未公开的API或字段,例如某些旧版本系统中获取导航栏高度的方法。这是最后的手段,因为存在兼容性风险和未来系统版本变更导致失效的可能。

在实际项目中,我通常会采用一种组合策略:对于状态栏和导航栏高度,优先尝试通过资源ID获取;如果失败(返回0),再通过视图计算法作为后备方案;仅在极端特殊情况下,才谨慎考虑使用反射。对于屏幕和窗口宽高,则直接使用标准API。

3. 核心工具类实现与逐行解析

下面,我将分享一个经过大量项目验证的ScreenUtils工具类。它不仅提供了获取各种高度的方法,还包含了重要的上下文处理逻辑和兼容性处理。

import android.content.Context import android.graphics.Point import android.graphics.Rect import android.os.Build import android.util.DisplayMetrics import android.view.View import android.view.Window import android.view.WindowManager object ScreenUtils { /** * 获取屏幕的原始物理分辨率(像素)。 * 注意:此尺寸包含所有系统装饰(状态栏、导航栏)。 * * @param context 上下文 * @return Point对象,x为宽度,y为高度 */ fun getScreenRealSize(context: Context): Point { val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager val point = Point() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { // Android 11 (R) 及以上,使用新的API val windowMetrics = windowManager.currentWindowMetrics val bounds = windowMetrics.bounds point.x = bounds.width() point.y = bounds.height() } else { // 传统方式,但注意:在有些有刘海屏或挖孔屏的设备上, // `getRealSize` 返回的可能是包含“不可用区域”的尺寸。 val display = windowManager.defaultDisplay display.getRealSize(point) } return point } /** * 获取应用窗口的尺寸(像素)。 * 即屏幕尺寸减去系统UI(状态栏、导航栏)占用的区域。 * 这是你的UI布局的实际可用区域。 * * @param window 当前Activity的Window对象 * @return Point对象,x为宽度,y为高度 */ fun getWindowSize(window: Window): Point { val point = Point() val decorView = window.decorView decorView.getWindowVisibleDisplayFrame(Rect()).let { rect -> point.x = rect.width() point.y = rect.height() } return point } /** * 获取状态栏高度(像素)。 * 优先通过系统资源获取,这是最稳定可靠的方式。 * * @param context 上下文 * @return 状态栏高度,单位px */ fun getStatusBarHeight(context: Context): Int { var result = 0 val resourceId = context.resources.getIdentifier( "status_bar_height", "dimen", "android" ) if (resourceId > 0) { result = context.resources.getDimensionPixelSize(resourceId) } // 如果通过资源没取到(理论上不会),提供一个常见值的兜底 if (result == 0) { result = (24 * context.resources.displayMetrics.density).toInt() // 近似24dp } return result } /** * 获取底部导航栏高度(像素)。 * 逻辑相对复杂,需要处理有无导航栏、横竖屏等情况。 * * @param context 上下文 * @return 导航栏高度,单位px */ fun getNavigationBarHeight(context: Context): Int { var result = 0 // 方法1:尝试通过系统资源获取(最推荐) val resourceId = context.resources.getIdentifier( "navigation_bar_height", "dimen", "android" ) if (resourceId > 0) { result = context.resources.getDimensionPixelSize(resourceId) } // 方法2:如果资源获取为0,可能设备没有虚拟导航栏(如使用手势或实体键), // 或者在某些横屏模式下导航栏高度资源值为0。 // 此时可以通过计算屏幕真实高度与窗口可用高度之差来推断。 if (result == 0) { // 注意:此计算需要在Activity的Window已经附加视图后进行,且需要区分横竖屏逻辑。 // 这里提供一个通用思路,实际使用可能需要结合具体Activity。 // val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager // val realSize = getScreenRealSize(context) // val windowSize = getWindowSize(/*需要传入Window对象*/) // // 简单判断:如果屏幕真实高度大于窗口高度,差值可能是导航栏高度(需排除状态栏) // val diff = realSize.y - windowSize.y // if (diff > 0) { // // 需要进一步判断这个diff是导航栏还是其他系统装饰,通常与状态栏高度比较 // val statusBarHeight = getStatusBarHeight(context) // if (diff != statusBarHeight) { // result = diff // } // } // 由于计算法依赖具体Window且逻辑复杂,通常不作为工具类默认返回值。 // 我们这里保守地返回0,表示未检测到或无需导航栏。 } return result } /** * 获取ActionBar/Toolbar的高度(像素)。 * 需要传入具体的View对象。 * * @param toolbarView 工具栏视图 * @return 工具栏高度,单位px */ fun getActionBarHeight(toolbarView: View): Int { return if (toolbarView.isLaidOut) { toolbarView.height } else { toolbarView.post { toolbarView.height } // 如果未布局,异步获取 0 // 临时返回0,实际使用应等待回调 } } /** * 判断当前是否显示导航栏(虚拟按键栏)。 * 这是一个粗略判断,基于检查导航栏高度资源是否存在且大于0。 * * @param context 上下文 * @return true 表示可能有虚拟导航栏,false 表示可能没有(手势或实体键) */ fun hasNavigationBar(context: Context): Boolean { val resourceId = context.resources.getIdentifier( "config_showNavigationBar", "bool", "android" ) val hasBar = if (resourceId > 0) { context.resources.getBoolean(resourceId) } else { // 如果配置不存在,回退到检查导航栏高度 getNavigationBarHeight(context) > 0 } return hasBar } }

代码解析与注意事项:

  1. 上下文 (Context) 的选择getStatusBarHeightgetNavigationBarHeight通常使用Application Context即可,因为它们获取的是系统资源。而getWindowSize必须使用ActivityWindow对象,因为窗口尺寸是每个Activity独立的。
  2. getScreenRealSize的兼容性: 在 Android R (API 30) 及以上,推荐使用新的WindowMetricsAPI,它提供了更准确、包含所有显示切口的边界信息。旧 APIDisplay.getRealSize()在某些有刘海或挖孔的设备上可能返回包含“不可显示”区域的尺寸。
  3. 导航栏高度的复杂性: 这是最容易出错的地方。navigation_bar_height这个资源在设备没有虚拟导航栏(如全面屏手势)或处于横屏模式(导航栏可能移到侧面)时,可能为0。因此,工具类中getNavigationBarHeight方法返回0不一定代表错误,可能只是真实情况的反映。更精确的判断需要结合hasNavigationBar方法和具体的窗口可见区域计算。
  4. 布局完成的时机: 通过View获取高度(如getActionBarHeight),必须确保视图已经完成测量和布局 (isLaidOut为 true)。否则获取到的高度是0。常见的做法是在onWindowFocusChanged或视图的post回调中获取。

4. 实战应用场景与适配技巧

掌握了获取尺寸的方法,关键在于如何在真实场景中应用。下面结合几个典型场景,讲解如何运用这些数据。

4.1 场景一:实现沉浸式状态栏

沉浸式状态栏(或称透明状态栏)是让应用内容延伸到状态栏后面,并通过设置状态栏文字图标颜色来保证可读性的效果。

// 在Activity的onCreate中,setContentView之后调用 fun setImmerseStatusBar(activity: Activity, isLightStatusBar: Boolean = false) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { val window = activity.window // 1. 设置全屏布局,内容延伸到状态栏下 window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE) // 2. 设置状态栏透明 window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) window.statusBarColor = Color.TRANSPARENT // 3. 设置状态栏文字图标颜色(浅色或深色) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { var visibility = window.decorView.systemUiVisibility visibility = if (isLightStatusBar) { visibility or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR } else { visibility and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv() } window.decorView.systemUiVisibility = visibility } } // 4. 关键步骤:为根布局设置顶部Padding,防止内容与状态栏重叠 val rootView = activity.findViewById<ViewGroup>(android.R.id.content).getChildAt(0) rootView?.apply { setPadding( paddingLeft, ScreenUtils.getStatusBarHeight(activity), // 使用工具类获取高度 paddingRight, paddingBottom ) } }

注意: 在 Android 11 (R) 及以后,systemUiVisibility已被标记为废弃,推荐使用WindowInsetsController。上述代码需要做兼容性处理。

4.2 场景二:全屏弹窗或底部抽屉适配导航栏

当需要展示一个全屏的Dialog或者从底部弹出的抽屉(BottomSheet)时,必须考虑导航栏的遮挡问题。

// 假设有一个全屏的DialogFragment class FullScreenDialogFragment : DialogFragment() { override fun onStart() { super.onStart() dialog?.window?.let { window -> // 设置Dialog为全屏 window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) // 获取屏幕真实高度和窗口高度 val realSize = ScreenUtils.getScreenRealSize(requireContext()) val windowSize = ScreenUtils.getWindowSize(window) // 计算差值,这通常是状态栏+导航栏的高度 val systemUiHeight = realSize.y - windowSize.y // 方法A:如果希望Dialog内容在导航栏之上(即被导航栏遮挡一部分) // 可以设置一个底部Margin,值为导航栏高度,让出空间。 val contentView = window.decorView.findViewById<ViewGroup>(android.R.id.content) contentView?.apply { (layoutParams as? ViewGroup.MarginLayoutParams)?.bottomMargin = ScreenUtils.getNavigationBarHeight(requireContext()) } // 方法B:如果希望Dialog完全覆盖导航栏(真正的全屏),需要设置一些系统UI标志 // 但这可能会与系统手势冲突,需谨慎使用。 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES } window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) } } }

4.3 场景三:精确计算列表或滚动视图的可用高度

在复杂的布局中,例如一个CoordinatorLayout内包含AppBarLayoutRecyclerView,你需要精确计算RecyclerView应该设置的高度,使其刚好填满剩余空间,不产生多余的滚动或空白。

// 在Activity/Fragment的onCreate或onViewCreated中 fun calculateListHeight(activity: Activity, appBarLayout: AppBarLayout): Int { // 1. 获取屏幕可用窗口高度 val windowHeight = ScreenUtils.getWindowSize(activity.window).y // 2. 获取状态栏高度(如果状态栏透明且布局延伸,则可能为0或已包含在Padding中) val statusBarHeight = ScreenUtils.getStatusBarHeight(activity) // 3. 获取ActionBar/Toolbar的实际高度(需要等待布局完成) val toolbar = activity.findViewById<Toolbar>(R.id.toolbar) var actionBarHeight = 0 toolbar?.let { if (it.isLaidOut) { actionBarHeight = it.height } else { it.post { actionBarHeight = it.height /* 更新UI */ } } } // 4. 获取其他固定高度组件,如TabLayout val tabLayoutHeight = activity.findViewById<TabLayout>(R.id.tabs)?.height ?: 0 // 5. 计算RecyclerView的预期高度 // 假设布局结构:状态栏(已延伸) + Toolbar + Tabs + RecyclerView // 如果使用了沉浸式状态栏并为根布局设置了top padding,则windowHeight已经减去了状态栏占用的视觉空间? // 这里容易混淆。更稳妥的方式是:在onGlobalLayout回调中,计算AppBarLayout的底部位置。 appBarLayout.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { override fun onGlobalLayout() { appBarLayout.viewTreeObserver.removeOnGlobalLayoutListener(this) val appBarBottom = appBarLayout.bottom // AppBarLayout在屏幕中的底部坐标 val recyclerView = activity.findViewById<RecyclerView>(R.id.recyclerView) recyclerView?.layoutParams?.height = windowHeight - appBarBottom recyclerView?.requestLayout() } }) return 0 // 实际高度在监听器中设置 }

这个场景的关键在于理解坐标系windowHeight是窗口的像素高度,而appBarLayout.bottom是该视图在其父容器(屏幕坐标系)中的底部Y坐标。两者相减,正好是RecyclerView可用的垂直空间。这种方法比手动累加各个组件的高度更可靠,因为它自动处理了边距 (margin)、内边距 (padding) 和布局权重 (weight) 等影响因素。

5. 常见疑难问题与深度排查指南

在实际开发中,你肯定会遇到一些“诡异”的尺寸问题。下面我整理了一份问题排查清单和解决方案。

5.1 问题一:getNavigationBarHeight在全面屏手势设备上返回0,但底部仍有手势指示条区域

现象: 在启用全面屏手势(隐藏虚拟按键栏)的设备上,通过资源ID获取的导航栏高度为0。但屏幕底部有一条细长的“手势指示条”区域,应用内容如果铺满,会被这条指示条遮挡一部分。

根因分析: 从 Android 10 (Q) 开始,系统引入了全新的手势导航。传统的navigation_bar_height资源确实代表虚拟按键栏的高度。当启用手势后,这个区域被手势指示条取代,其高度定义在不同的系统资源中,并且这个区域默认被视为“可交互区域”的一部分,而不是系统装饰栏。因此,getWindowSize获取的窗口高度可能已经排除了手势指示条的区域?不,这里有个关键点:在全面屏手势下,系统通常希望应用内容延伸到最底部,手势指示条是叠加在内容之上的。

解决方案: 使用WindowInsetsAPI(Android 11 (R) 及以上推荐)。

// 在View(通常是根布局)上设置监听 ViewCompat.setOnApplyWindowInsetsListener(rootView) { view, insets -> val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) val navigationBarHeight = systemBars.bottom // 这里获取的就是系统栏(包括手势区)的底部插入距离 // 为需要避开手势区的视图设置底部padding或margin val contentView = view.findViewById<View>(R.id.content_area) contentView.setPadding(0, 0, 0, navigationBarHeight) // 返回处理后的insets WindowInsetsCompat.CONSUMED }

对于 Android R 以下,可以通过检查View.getRootWindowInsets()来获取类似信息,但API有所不同。更通用的兼容性做法是,在布局文件中为根视图或底部关键视图设置android:fitsSystemWindows="true",让系统自动处理,但这样会失去一些自定义控制力。

5.2 问题二:横屏模式下尺寸获取异常

现象: 在横屏时,状态栏和导航栏的高度值可能发生变化,或者通过getWindowSize计算出的可用区域不对。

根因分析

  • 状态栏: 在横屏时,状态栏可能不显示(沉浸式应用)或高度变为0(某些设备)。
  • 导航栏: 在横屏时,虚拟导航栏可能移动到屏幕侧边(通常是右侧),此时navigation_bar_height资源可能返回的是宽度(即导航栏的厚度),或者返回0。getWindowSize获取的窗口高度,在横屏下对应的是屏幕的宽度(因为设备旋转了)。

解决方案

  1. 区分方向获取资源: Android 为横竖屏提供了不同的资源限定符。系统定义的navigation_bar_heightstatus_bar_height可能有land(横屏)版本。但依赖这个并不完全可靠。
  2. 使用WindowInsets: 这是最准确的方法。WindowInsets提供的left,top,right,bottom值始终是当前方向下,系统UI在视图四边的“插入”距离。横屏时,导航栏在右侧,那么systemBars.right的值就是导航栏的宽度(高度)。
  3. 动态计算: 在横屏下,如果需要知道导航栏是否在底部,可以结合屏幕真实尺寸、窗口尺寸和WindowInsets综合判断。
fun getNavigationBarSize(context: Context, window: Window): Point { val point = Point(0, 0) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { val windowMetrics = windowManager.currentWindowMetrics val windowInsets = windowMetrics.windowInsets val insets = windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.systemBars()) // insets.left, .top, .right, .bottom 分别对应四边的系统栏占用 point.x = insets.left + insets.right // 横屏时,导航栏可能在左右侧 point.y = insets.top + insets.bottom // 竖屏时,导航栏在底部 } else { // 兼容旧版本:通过计算差值 val realSize = getScreenRealSize(context) val windowSize = getWindowSize(window) val statusBarHeight = getStatusBarHeight(context) // 差值可能分布在底部或右侧 val diffX = realSize.x - windowSize.x val diffY = realSize.y - windowSize.y // 简单的启发式判断:如果竖屏,diffY大且不等于状态栏高度,则是导航栏高度。 // 如果横屏,diffX大,则可能是导航栏宽度。这很粗略。 val isPortrait = context.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT if (isPortrait) { point.y = if (diffY > statusBarHeight) diffY else 0 } else { point.x = diffX // 假设横屏下宽度差就是导航栏宽度 } } return point }

5.3 问题三:getWindowSizeonCreate中返回0或错误值

现象: 在ActivityonCreateonResume方法中调用getWindowSize,返回的宽高可能是0,或者是不包含导航栏的旧值(例如在隐藏导航栏后)。

根因分析: 视图的测量和布局 (measure&layout) 过程与Activity的生命周期并不同步。在onCreate时,DecorView可能尚未被窗口管理器分配最终的尺寸。同样,系统UI(如导航栏)的显示/隐藏是异步发生的。

解决方案延迟获取或监听布局完成事件

  • 方案A:使用View.post

    override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val rootView = window.decorView rootView.post { // 此时视图已完成初次布局 val windowSize = ScreenUtils.getWindowSize(window) val realSize = ScreenUtils.getScreenRealSize(this) Log.d("SizeInfo", "Window: $windowSize, Real: $realSize") } }
  • 方案B:使用OnGlobalLayoutListener

    override fun onWindowFocusChanged(hasFocus: Boolean) { super.onWindowFocusChanged(hasFocus) if (hasFocus) { // 窗口获得焦点,尺寸通常已稳定 val windowSize = ScreenUtils.getWindowSize(window) // ... 使用尺寸信息 } }

    onWindowFocusChanged是一个非常好的时机,不仅表示布局完成,还表示窗口的焦点状态(这与系统UI的显示隐藏相关)。

  • 方案C:使用WindowInsets(Android R+)WindowInsets的监听是动态的,当系统栏变化时会实时回调,从根本上解决了时机问题。

5.4 问题四:异形屏(刘海屏、挖孔屏)下的适配

现象: 在刘海屏或挖孔屏设备上,getScreenRealSize返回的尺寸可能包含不可显示的“缺口”区域,导致计算错误。

根因分析: 旧版Display.getRealSize()API 返回的是整个显示面板的缓冲区大小,可能包含位于刘海或摄像头下方的像素。这些像素虽然物理存在,但系统默认不会将内容绘制到那里。

解决方案

  1. 使用 Android R+ 的WindowMetrics: 如工具类所示,这是官方推荐的正确方式,其bounds已经考虑了所有显示切口(Display Cutout)。
  2. 使用getSafeInsetAPI (Android P+): 对于旧版本,可以获取DisplayCutout对象来获取安全区域。
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { val decorView = window.decorView val cutout = decorView.rootWindowInsets?.displayCutout val safeInsetTop = cutout?.safeInsetTop ?: 0 // 安全区域上边距 val safeInsetBottom = cutout?.safeInsetBottom ?: 0 // 安全区域下边距 // 你的内容区域应避免进入 safeInsetTop 和 safeInsetBottom 之间的区域? // 不完全是,系统默认已经处理。你需要关注的是全屏模式下,如何避开切口。 }
  3. 布局属性: 在主题中设置android:windowLayoutInDisplayCutoutMode,或代码中设置layoutInDisplayCutoutMode,控制内容如何与切口区域交互。通常设置为LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES允许内容延伸到短边切口,但你需要确保关键交互元素和文字避开该区域。

6. 高级话题:WindowInsets的现代适配方案

从 Android 11 (R) 开始,Google 强烈推荐使用WindowInsetsAPI 来处理所有与系统UI区域相关的布局。它提供了一个统一、声明式且更准确的模型。

核心思想: 不要自己去计算系统栏占了多少像素,而是告诉系统:“请给我一块避开系统栏的区域”,或者“我知道系统栏在这里,我会自己处理内容与它的重叠”。

基本用法示例:

// 在 Activity 的 onCreate 中 WindowCompat.setDecorFitsSystemWindows(window, false) // 1. 关闭默认适配,自己控制 val rootView = findViewById<ConstraintLayout>(R.id.root) ViewCompat.setOnApplyWindowInsetsListener(rootView) { view, insets -> val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) val ime = insets.getInsets(WindowInsetsCompat.Type.ime()) // 输入法 // 2. 应用内边距,为系统栏和输入法留出空间 view.setPadding( systemBars.left, systemBars.top, systemBars.right, systemBars.bottom + ime.bottom // 底部需要同时考虑导航栏和输入法 ) // 3. 更新其他子视图的布局参数 val fab = view.findViewById<FloatingActionButton>(R.id.fab) fab.translationY = (-systemBars.bottom).toFloat() // 例如,FAB上移避开导航栏 // 4. 返回消费掉的insets WindowInsetsCompat.CONSUMED }

优势:

  • 准确性: 直接由系统提供插入距离,无需计算,横竖屏、异形屏、动态隐藏/显示系统栏等情况都能完美处理。
  • 性能: 避免了多次测量和布局计算。
  • 未来兼容性: 是官方主推的现代化API。

迁移建议: 对于新项目,应直接基于WindowInsets进行设计。对于老项目,在修改与系统UI区域相关的布局时,逐步采用新的WindowInsets方案替代旧的getXXXHeight计算方式,特别是在处理全屏、沉浸式场景时。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/15 21:52:28

UniApp跨平台文件下载全攻略:从Blob原理到多端兼容实现

1. 从需求到方案&#xff1a;为什么文件下载在混合开发中是个“坑”做前端和移动端开发&#xff0c;尤其是用uniapp这类跨平台框架&#xff0c;文件下载这个功能点&#xff0c;乍一看很简单&#xff0c;不就是发个请求把数据存下来吗&#xff1f;但真上手做&#xff0c;尤其是在…

作者头像 李华
网站建设 2026/8/15 21:51:24

VScode 安装opencode

文章目录安装vscode管网下载 [VScode官网](https://code.visualstudio.com/)在VScode中使用anaconda 环境安装 Node.js安装opencodenpm install -g opencode-ai在VScode中使用opencode安装vscode 管网下载 VScode官网 下载安装 下载后一路安装就可以了 安装完后&#xff0c;这…

作者头像 李华
网站建设 2026/8/15 21:49:54

输入法卡顿、候选词错乱?系统化排查与优化指南

这次我们来看一个关于输入法体验的观察。如果你经常在电脑上打字&#xff0c;可能会遇到一些看似微小但实际影响效率的输入法行为&#xff0c;比如候选词排序不合理、中英文切换不跟手、或者在某些特定软件里出现兼容性问题。这篇文章不讨论某个具体的输入法产品&#xff0c;而…

作者头像 李华
网站建设 2026/8/15 21:47:05

零基础转行SAP MM顾问:3-6个月学习路径与求职指南

如果你正在考虑从供应链、仓储或采购岗位转行&#xff0c;SAP MM&#xff08;物料管理&#xff09;模块实施顾问是一个极具吸引力的选择。这个岗位不仅薪资可观&#xff0c;而且职业路径清晰&#xff0c;市场需求稳定。本文不绕弯子&#xff0c;直接告诉你&#xff1a;零基础转…

作者头像 李华
网站建设 2026/8/15 21:46:29

机器学习特征工程:相关性分析消除冗余特征实战指南

1. 项目缘起&#xff1a;为什么我们总在“杀”特征&#xff1f;做机器学习项目&#xff0c;尤其是数据挖掘和建模&#xff0c;你肯定遇到过这种情况&#xff1a;辛辛苦苦从业务里扒拉出几百个特征&#xff0c;满怀信心地扔进模型&#xff0c;结果训练时间长得离谱&#xff0c;模…

作者头像 李华
网站建设 2026/8/15 21:41:07

2026年前端技术选型:Vue与React的长期价值与团队适配分析

最近和几个团队负责人聊天&#xff0c;话题又绕回了那个经典问题&#xff1a;“新项目&#xff0c;选 Vue 还是 React&#xff1f;” 有意思的是&#xff0c;这次讨论的背景不是“现在”&#xff0c;而是“2026年”。当我把时间线拉到两年后&#xff0c;大家争论的焦点不再是“…

作者头像 李华