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

📅 2026/8/15 22:05:36
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 方案选型与考量获取这些信息主要有以下几种途径各有优劣DisplayMetrics与WindowManager 用于获取与屏幕密度相关的物理尺寸和缩放后的尺寸是获取屏幕和窗口宽高的主要入口。资源系统 (resources.getDimensionPixelSize) 获取系统预定义的维度值如状态栏和导航栏的标准高度这是最推荐的方式。视图布局系统 (View.getLocationOnScreen,View.getWindowVisibleDisplayFrame) 通过视图在窗口中的位置和可见区域来计算系统UI的高度非常直观但可能需要在视图布局完成后才能获取准确值。反射 (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 } }代码解析与注意事项上下文 (Context) 的选择getStatusBarHeight和getNavigationBarHeight通常使用Application Context即可因为它们获取的是系统资源。而getWindowSize必须使用Activity的Window对象因为窗口尺寸是每个Activity独立的。getScreenRealSize的兼容性 在 Android R (API 30) 及以上推荐使用新的WindowMetricsAPI它提供了更准确、包含所有显示切口的边界信息。旧 APIDisplay.getRealSize()在某些有刘海或挖孔的设备上可能返回包含“不可显示”区域的尺寸。导航栏高度的复杂性 这是最容易出错的地方。navigation_bar_height这个资源在设备没有虚拟导航栏如全面屏手势或处于横屏模式导航栏可能移到侧面时可能为0。因此工具类中getNavigationBarHeight方法返回0不一定代表错误可能只是真实情况的反映。更精确的判断需要结合hasNavigationBar方法和具体的窗口可见区域计算。布局完成的时机 通过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.findViewByIdViewGroup(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.findViewByIdViewGroup(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内包含AppBarLayout和RecyclerView你需要精确计算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.findViewByIdToolbar(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.findViewByIdTabLayout(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.findViewByIdRecyclerView(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获取的窗口高度可能已经排除了手势指示条的区域不这里有个关键点在全面屏手势下系统通常希望应用内容延伸到最底部手势指示条是叠加在内容之上的。解决方案 使用WindowInsetsAPIAndroid 11 (R) 及以上推荐。// 在View通常是根布局上设置监听 ViewCompat.setOnApplyWindowInsetsListener(rootView) { view, insets - val systemBars insets.getInsets(WindowInsetsCompat.Type.systemBars()) val navigationBarHeight systemBars.bottom // 这里获取的就是系统栏包括手势区的底部插入距离 // 为需要避开手势区的视图设置底部padding或margin val contentView view.findViewByIdView(R.id.content_area) contentView.setPadding(0, 0, 0, navigationBarHeight) // 返回处理后的insets WindowInsetsCompat.CONSUMED }对于 Android R 以下可以通过检查View.getRootWindowInsets()来获取类似信息但API有所不同。更通用的兼容性做法是在布局文件中为根视图或底部关键视图设置android:fitsSystemWindowstrue让系统自动处理但这样会失去一些自定义控制力。5.2 问题二横屏模式下尺寸获取异常现象 在横屏时状态栏和导航栏的高度值可能发生变化或者通过getWindowSize计算出的可用区域不对。根因分析状态栏 在横屏时状态栏可能不显示沉浸式应用或高度变为0某些设备。导航栏 在横屏时虚拟导航栏可能移动到屏幕侧边通常是右侧此时navigation_bar_height资源可能返回的是宽度即导航栏的厚度或者返回0。getWindowSize获取的窗口高度在横屏下对应的是屏幕的宽度因为设备旋转了。解决方案区分方向获取资源 Android 为横竖屏提供了不同的资源限定符。系统定义的navigation_bar_height和status_bar_height可能有land横屏版本。但依赖这个并不完全可靠。使用WindowInsets 这是最准确的方法。WindowInsets提供的left,top,right,bottom值始终是当前方向下系统UI在视图四边的“插入”距离。横屏时导航栏在右侧那么systemBars.right的值就是导航栏的宽度高度。动态计算 在横屏下如果需要知道导航栏是否在底部可以结合屏幕真实尺寸、窗口尺寸和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 问题三getWindowSize在onCreate中返回0或错误值现象 在Activity的onCreate或onResume方法中调用getWindowSize返回的宽高可能是0或者是不包含导航栏的旧值例如在隐藏导航栏后。根因分析 视图的测量和布局 (measurelayout) 过程与Activity的生命周期并不同步。在onCreate时DecorView可能尚未被窗口管理器分配最终的尺寸。同样系统UI如导航栏的显示/隐藏是异步发生的。解决方案延迟获取或监听布局完成事件。方案A使用View.postoverride 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使用OnGlobalLayoutListeneroverride 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 返回的是整个显示面板的缓冲区大小可能包含位于刘海或摄像头下方的像素。这些像素虽然物理存在但系统默认不会将内容绘制到那里。解决方案使用 Android R 的WindowMetrics 如工具类所示这是官方推荐的正确方式其bounds已经考虑了所有显示切口Display Cutout。使用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 之间的区域 // 不完全是系统默认已经处理。你需要关注的是全屏模式下如何避开切口。 }布局属性 在主题中设置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 findViewByIdConstraintLayout(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.findViewByIdFloatingActionButton(R.id.fab) fab.translationY (-systemBars.bottom).toFloat() // 例如FAB上移避开导航栏 // 4. 返回消费掉的insets WindowInsetsCompat.CONSUMED }优势准确性 直接由系统提供插入距离无需计算横竖屏、异形屏、动态隐藏/显示系统栏等情况都能完美处理。性能 避免了多次测量和布局计算。未来兼容性 是官方主推的现代化API。迁移建议 对于新项目应直接基于WindowInsets进行设计。对于老项目在修改与系统UI区域相关的布局时逐步采用新的WindowInsets方案替代旧的getXXXHeight计算方式特别是在处理全屏、沉浸式场景时。