Android window属性全解析

Android Theme XML 属性如何影响 PhoneWindow:从 TypedArray 到 LayoutParams 的完整链路

每天写布局、配主题,但你有没有想过------android:windowIsTranslucent="true" 这行 XML 到底是怎么一路传递,最终影响了 Window 的像素格式?为什么 statusBarColor 有时候怎么设都不生效?FLAG_LAYOUT_IN_SCREEN 到底是干什么的?本文将带你深入 Android Framework 源码,逐属性、逐 Flag 拆解这条完整链路。


目录

  1. [架构概览:Theme → PhoneWindow 的数据流](#架构概览:Theme → PhoneWindow 的数据流 "#1-%E6%9E%B6%E6%9E%84%E6%A6%82%E8%A7%88theme--phonewindow-%E7%9A%84%E6%95%B0%E6%8D%AE%E6%B5%81")
  2. 属性的定义:R.styleable.Window
  3. [PhoneWindow 读取属性的两个入口](#PhoneWindow 读取属性的两个入口 "#3-phonewindow-%E8%AF%BB%E5%8F%96%E5%B1%9E%E6%80%A7%E7%9A%84%E4%B8%A4%E4%B8%AA%E5%85%A5%E5%8F%A3")
  4. [深入理解 WindowManager.LayoutParams 的核心 Flags](#深入理解 WindowManager.LayoutParams 的核心 Flags "#4-%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3-windowmanagerlayoutparams-%E7%9A%84%E6%A0%B8%E5%BF%83-flags")
    • [4.1 什么是 Flags?](#4.1 什么是 Flags? "#41-%E4%BB%80%E4%B9%88%E6%98%AF-flags")
    • [4.2 布局相关 Flags(Layout Flags)](#4.2 布局相关 Flags(Layout Flags) "#42-%E5%B8%83%E5%B1%80%E7%9B%B8%E5%85%B3-flagslayout-flags")
    • [4.3 触摸与焦点 Flags(Input Flags)](#4.3 触摸与焦点 Flags(Input Flags) "#43-%E8%A7%A6%E6%91%B8%E4%B8%8E%E7%84%A6%E7%82%B9-flagsinput-flags")
    • [4.4 系统栏相关 Flags(System Bar Flags)](#4.4 系统栏相关 Flags(System Bar Flags) "#44-%E7%B3%BB%E7%BB%9F%E6%A0%8F%E7%9B%B8%E5%85%B3-flags%E7%B3%BB%E7%BB%9F-bar-flags")
    • [4.5 安全与显示 Flags(Security & Display Flags)](#4.5 安全与显示 Flags(Security & Display Flags) "#45-%E5%AE%89%E5%85%A8%E4%B8%8E%E6%98%BE%E7%A4%BA-flags%E5%AE%89%E5%85%A8%E4%B8%8E%E6%98%BE%E7%A4%BA-flags")
    • [4.6 锁屏相关 Flags(Keyguard Flags)](#4.6 锁屏相关 Flags(Keyguard Flags) "#46-%E9%94%81%E5%B1%8F%E7%9B%B8%E5%85%B3-flags%E9%94%81%E5%B1%8F%E7%9B%B8%E5%85%B3-flags")
    • [4.7 Flags 组合使用矩阵](#4.7 Flags 组合使用矩阵 "#47-flags-%E7%BB%84%E5%90%88%E4%BD%BF%E7%94%A8%E7%9F%A9%E9%98%B5")
  5. [Theme 属性分类详解](#Theme 属性分类详解 "#5-theme-%E5%B1%9E%E6%80%A7%E5%88%86%E7%B1%BB%E8%AF%A6%E8%A7%A3")
    • [5.1 窗口 Flags 类](#5.1 窗口 Flags 类 "#51-%E7%AA%97%E5%8F%A3-flags-%E7%B1%BB")
    • [5.2 窗口 Type 类](#5.2 窗口 Type 类 "#52-%E7%AA%97%E5%8F%A3-type-%E7%B1%BB")
    • [5.3 尺寸与布局类](#5.3 尺寸与布局类 "#53-%E5%B0%BA%E5%AF%B8%E4%B8%8E%E5%B8%83%E5%B1%80%E7%B1%BB")
    • [5.4 背景与样式类](#5.4 背景与样式类 "#54-%E8%83%8C%E6%99%AF%E4%B8%8E%E6%A0%B7%E5%BC%8F%E7%B1%BB")
    • [5.5 系统状态栏/导航栏类](#5.5 系统状态栏/导航栏类 "#55-%E7%B3%BB%E7%BB%9F%E7%8A%B6%E6%80%81%E6%A0%8F%E5%AF%BC%E8%88%AA%E6%A0%8F%E7%B1%BB")
    • [5.6 软键盘(SoftInput)类](#5.6 软键盘(SoftInput)类 "#56-%E8%BD%AF%E9%94%AE%E7%9B%98softinput%E7%B1%BB")
    • [5.7 转场动画类](#5.7 转场动画类 "#57-%E8%BD%AC%E5%9C%BA%E5%8A%A8%E7%94%BB%E7%B1%BB")
    • [5.8 像素格式与绘制类](#5.8 像素格式与绘制类 "#58-%E5%83%8F%E7%B4%A0%E6%A0%BC%E5%BC%8F%E4%B8%8E%E7%BB%98%E5%88%B6%E7%B1%BB")
    • [5.9 FitSystemWindows 与 Edge-to-Edge 类](#5.9 FitSystemWindows 与 Edge-to-Edge 类 "#59-fitsystemwindows-%E4%B8%8E-edge-to-edge-%E7%B1%BB")
    • [5.10 其他行为属性](#5.10 其他行为属性 "#510-%E5%85%B6%E4%BB%96%E8%A1%8C%E4%B8%BA%E5%B1%9E%E6%80%A7")
  6. 完整映射表速查
  7. 实战场景
    • [7.1 全屏 Activity](#7.1 全屏 Activity "#71-%E5%85%A8%E5%B1%8F-activity")
    • [7.2 透明背景 Activity / Dialog 样式 Activity](#7.2 透明背景 Activity / Dialog 样式 Activity "#72-%E9%80%8F%E6%98%8E%E8%83%8C%E6%99%AF-activity--dialog-%E6%A0%B7%E5%BC%8F-activity")
    • [7.3 自定义状态栏颜色(Material 风格)](#7.3 自定义状态栏颜色(Material 风格) "#73-%E8%87%AA%E5%AE%9A%E4%B9%89%E7%8A%B6%E6%80%81%E6%A0%8F%E9%A2%9C%E8%89%B2material-%E9%A3%8E%E6%A0%BC")
    • [7.4 图片查看器(沉浸式)](#7.4 图片查看器(沉浸式) "#74-%E5%9B%BE%E7%89%87%E6%9F%A5%E7%9C%8B%E5%99%A8%E6%B2%89%E6%B5%B8%E5%BC%8F")
    • [7.5 锁屏上显示窗口](#7.5 锁屏上显示窗口 "#75-%E9%94%81%E5%B1%8F%E4%B8%8A%E6%98%BE%E7%A4%BA%E7%AA%97%E5%8F%A3")
    • [7.6 禁止截屏的敏感页面](#7.6 禁止截屏的敏感页面 "#76-%E7%A6%81%E6%AD%A2%E6%88%AA%E5%B1%8F%E7%9A%84%E6%95%8F%E6%84%9F%E9%A1%B5%E9%9D%A2")
    • [7.7 软键盘适配表单页面](#7.7 软键盘适配表单页面 "#77-%E8%BD%AF%E9%94%AE%E7%9B%98%E9%80%82%E9%85%8D%E8%A1%A8%E5%8D%95%E9%A1%B5%E9%9D%A2")
  8. 最佳实践与常见陷阱
  9. 总结

1. 架构概览:Theme → PhoneWindow 的数据流

先上一张整体数据流图,建立心智模型:

scss 复制代码
AndroidManifest.xml 中指定的 Theme / setTheme()
        │
        ▼
   Resources.Theme (持有所有属性的键值对)
        │
        │  context.obtainStyledAttributes(R.styleable.Window)
        ▼
   TypedArray (内存中的属性值集合,按索引访问)
        │
        ├──▶ PhoneWindow() 构造函数  ──▶  读取约 15 个属性
        │                                ├── mIsFloating
        │                                ├── 部分 flags (windowNoDisplay, windowSecure...)
        │                                └── 初始 LayoutParams 设置
        │
        └──▶ PhoneWindow.generateLayout()  ──▶  读取约 80+ 个属性(核心分发)
                  │
                  ├──▶ requestFeature()       ──▶ FEATURE_NO_TITLE, FEATURE_ACTION_BAR 等
                  ├──▶ addFlags() / setFlags()──▶ WindowManager.LayoutParams.flags
                  ├──▶ setBackgroundDrawable()──▶ DecorView 背景
                  ├──▶ setStatusBarColor()    ──▶ 通过 InsetsController 控制 SystemUI
                  ├──▶ setNavigationBarColor()──▶ 同上
                  ├──▶ setSoftInputMode()     ──▶ WindowManager.LayoutParams.softInputMode
                  ├──▶ setElevation()         ──▶ WindowManager.LayoutParams.elevation
                  ├──▶ getAttributes()        ──▶ 直接修改 WindowManager.LayoutParams 字段:
                  │       ├── width / height / minWidth / minHeight
                  │       ├── format (OPAQUE / TRANSLUCENT)
                  │       ├── windowAnimations
                  │       ├── layoutInDisplayCutoutMode
                  │       └── ...
                  │
                  └──▶ 构建 DecorView 布局 ──▶ 决定 mContentParent 的结构
                              │
                              ▼
                    最终通过 WindowManager.addView()
                    将 LayoutParams 传递给 WMS (WindowManagerService)
                              │
                              ▼
                    WMS 根据 LayoutParams 决定:
                    - Z-order(层级顺序)
                    - 窗口尺寸与位置
                    - Surface 像素格式
                    - 输入事件分发策略
                    - 系统栏行为
                    - 转场动画

关键类说明:

角色 所在进程
Window (android.view.Window) 抽象基类,定义 flags、type、softInputMode、features 等抽象方法 App 进程
PhoneWindow (com.android.internal.policy.PhoneWindow) 唯一的 Window 实现(系统进程外),所有 Activity/Dialog 的 Window 都是它 App 进程
WindowManager.LayoutParams 描述 Window 的布局参数的结构体,约 80+ 字段 App 进程创建,IPC 传递
WindowManagerService (WMS) 系统服务,管理所有窗口的 Z-order、尺寸、输入事件分发 system_server 进程
SurfaceFlinger 系统服务,消费 WMS 创建的 Surface,做 GPU 合成 surfaceflinger 进程

关键认知:Theme 属性 → PhoneWindow → LayoutParams → WMS 这条链路中,真正的"消费者"是 WMS。你在 Theme 里设置的属性,本质是在向 WMS 描述"我希望我的窗口在系统中如何表现",而 PhoneWindow 是中间的翻译官。


2. 属性的定义:R.styleable.Window

所有 Window 相关的 theme 属性定义在 AOSP 的 frameworks/base/core/res/res/values/attrs.xml 中,属于 <declare-styleable name="Window">。截至 Android 14 (API 34),这个 styleable 包含约 120+ 个属性

xml 复制代码
<!-- frameworks/base/core/res/res/values/attrs.xml -->
<declare-styleable name="Window">
    <!-- Flags 类 -->
    <attr name="windowNoDisplay" format="boolean" />
    <attr name="windowDisablePreview" format="boolean" />
    <attr name="windowEnableSplitTouch" format="boolean" />
    <attr name="windowShowWallpaper" format="boolean" />
    <attr name="windowBlurBehind" format="boolean" />
    <attr name="windowSecure" format="boolean" />
    <attr name="windowNotTouchable" format="boolean" />

    <!-- 系统栏类 -->
    <attr name="windowTranslucentStatus" format="boolean" />
    <attr name="windowTranslucentNavigation" format="boolean" />
    <attr name="windowDrawsSystemBarBackgrounds" format="boolean" />
    <attr name="statusBarColor" format="color" />
    <attr name="navigationBarColor" format="color" />
    <attr name="windowLightStatusBar" format="boolean" />
    <attr name="windowLightNavigationBar" format="boolean" />

    <!-- 尺寸类 -->
    <attr name="windowFixedWidth" format="dimension" />
    <attr name="windowFixedHeight" format="dimension" />
    <attr name="windowMinWidthMajor" format="dimension" />
    <attr name="windowMinWidthMinor" format="dimension" />
    <attr name="windowElevation" format="dimension" />

    <!-- 样式/背景类 -->
    <attr name="windowBackground" format="reference|color" />
    <attr name="windowIsTranslucent" format="boolean" />
    <attr name="windowIsFloating" format="boolean" />
    <attr name="windowNoTitle" format="boolean" />
    <attr name="windowFullscreen" format="boolean" />
    <attr name="windowTitleSize" format="dimension" />

    <!-- 软键盘 -->
    <attr name="windowSoftInputMode" />

    <!-- 转场动画 -->
    <attr name="windowActivityTransitions" format="boolean" />
    <attr name="windowEnterTransition" format="reference" />
    <attr name="windowExitTransition" format="reference" />
    <!-- ...还有 100+ 个 -->
</declare-styleable>

这些属性被 Android 系统的预置 Theme(Theme.Material3.*Theme.Material.*Theme.Holo.* 等)设置了默认值,应用层通过自定义 Theme 覆写。

默认值的来源链 (以 Theme.Material3.DayNight.NoActionBar 为例):

yaml 复制代码
Theme.Material3.DayNight.NoActionBar
  └─ parent: Theme.Material3.DayNight
       └─ parent: Theme.Material3.Light
            └─ parent: Theme.Material3 (或 Theme.Material)
                 └─ parent: Theme.Overlay.Material3 (部分属性)
                      └─ parent: Platform (系统内置,android: 前缀的属性在这里)

在你的 Theme 中设置的属性,会逐级向上覆盖父 Theme 的同名属性。


3. PhoneWindow 读取属性的两个入口

PhoneWindow 在两个时机读取 Window styleable:

入口一:构造函数

java 复制代码
// frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
public PhoneWindow(Context context, Window preservedWindow,
                   ActivityConfigCallback activityConfigCallback) {
    super(context);
    mLayoutInflater = LayoutInflater.from(context);

    // ★ 第一次读取 Window styleable
    TypedArray a = context.obtainStyledAttributes(
        null,                                    // 不传 AttributeSet(不从 XML layout 读)
        R.styleable.Window,                      // 目标 styleable
        0,                                       // defStyleAttr=0,不设中间层
        R.style.PhoneWindow);                    // defStyleRes:兜底默认样式

关键参数解读:

  • set = null:不从 XML layout 节点读取属性,只从 Theme 读取(这正是我们关心的)
  • defStyleAttr = 0:没有中间层默认 attr。如果传了(比如 R.attr.windowStyle),系统会先从 Theme 中取这个 attr 指向的 style 作为默认值
  • defStyleRes = R.style.PhoneWindow:如果 Theme 完全没设置某属性,回退到 PhoneWindow 这个 style 取值

构造阶段消费的属性(约 15 个):

属性 消费方式
windowNoDisplay 直接 setFlags
windowDisablePreview 直接 addFlags
windowEnableSplitTouch 直接 addFlags
windowShowWallpaper 直接 addFlags
windowBlurBehind 直接 addFlags
windowSecure 直接 addFlags + 设置输入特性
windowIsFloating 存入 mIsFloating,延后到 generateLayout 使用
windowIsTranslucent 存入标记,延后到 generateLayout 设置 format
windowSwipeToDismiss 设置 dismiss 监听器
windowBackgroundBlurRadius 存入标记

入口二:generateLayout()

java 复制代码
// PhoneWindow.java
protected ViewGroup generateLayout(DecorView decor) {
    // ★ 第二次读取 Window styleable
    TypedArray a = getWindowStyle();

    // ... 约 80+ 个属性的读取和应用 ...
}

generateLayout()核心分发函数,大部分属性在这里被消费。为什么分两次?

因为在 generateLayout() 之前,以下 API 可能已被调用:

java 复制代码
// Activity.onCreate() 中很常见的代码:
getWindow().requestFeature(Window.FEATURE_NO_TITLE);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
getWindow().setSoftInputMode(...);

generateLayout() 的策略是:如果某个状态已经被代码显式设置了,就不再被 Theme 覆盖;如果没被设置,就用 Theme 的值作为默认值。

实现方式是"标记位"模式:

java 复制代码
// Window.java 基类中的标记
protected boolean mHasSoftInputMode = false;   // 代码调过 setSoftInputMode()?
protected boolean mIsFloating = false;          // Theme 或代码设了浮动窗口?

// generateLayout() 中:
if (!mHasSoftInputMode) {
    // 代码没设过 → 从 Theme 读取默认值
    final int softInputMode = a.getInt(R.styleable.Window_windowSoftInputMode, 0);
    if (softInputMode != 0) {
        getAttributes().softInputMode = softInputMode;
    }
}

4. 深入理解 WindowManager.LayoutParams 的核心 Flags

在具体拆解每个 Theme 属性之前,我们必须先彻底理解它们最终写入的目标------LayoutParams.flags不理解 flags 的含义,就无法理解这些 Theme 属性到底做了什么。

4.1 什么是 Flags?

WindowManager.LayoutParams.flags 是一个 int 类型的位掩码(bitmask)。每个 bit 代表一个独立的行为开关。WMS 在收到 addView() 请求时读取这些 flags,决定窗口的布局方式、输入事件分发策略、系统栏行为等。

目前定义了约 40+ 个 flags,按功能可以分为几类:

arduino 复制代码
flags 字段 (32-bit int)
├── bit 0-7:   布局相关 (LAYOUT_*)
├── bit 8-15:  输入/触摸相关 (NOT_TOUCHABLE, NOT_FOCUSABLE, WATCH_OUTSIDE_TOUCH...)
├── bit 16-23: 系统栏相关 (FULLSCREEN, TRANSLUCENT_*, DRAWS_SYSTEM_BAR_BACKGROUNDS...)
├── bit 24-27: 显示/安全 (SECURE, SHOW_WALLPAPER, DIM_BEHIND...)
└── bit 28-31: 锁屏/屏幕 (SHOW_WHEN_LOCKED, KEEP_SCREEN_ON, TURN_SCREEN_ON...)

4.2 布局相关 Flags(Layout Flags)

这些 flags 控制窗口内容的布局边界------你的 View 能画到屏幕的哪个区域

FLAG_LAYOUT_IN_SCREEN (0x00000100)

作用:允许窗口内容延伸到整个屏幕(包括状态栏和导航栏后面的区域)。

WMS 的效果

  • 正常窗口的 content 区域被限制在 (0, statusBarHeight, screenWidth, screenHeight - navBarHeight) 范围内
  • 设置此 flag 后,content 区域变为 (0, 0, screenWidth, screenHeight)------即全屏幕
  • 但不改变状态栏/导航栏的可见性------系统栏仍然是可见的,只是窗口内容可以画到它们后面
  • WMS 仍然会通过 WindowInsets 告知应用系统栏的位置,由应用自行决定是否避开

使用场景

  • 需要在状态栏后面绘制背景(Material Design 的 AppBar 延伸到状态栏后面的效果)
  • 配合 FLAG_TRANSLUCENT_STATUS 使用,让内容延伸到透明的状态栏后面
  • 全屏视频播放器,视频画面覆盖整个屏幕但保留状态栏可见

FLAG_LAYOUT_NO_LIMITS 的区别

FLAG_LAYOUT_IN_SCREEN FLAG_LAYOUT_NO_LIMITS
布局边界 屏幕物理边界内 允许超出屏幕(用于 overscan 等场景)
状态栏 可延伸到状态栏后面 可延伸到状态栏后面
超出屏幕 不允许 允许
常用程度 ⭐⭐⭐⭐⭐ ⭐(已废弃)
FLAG_LAYOUT_NO_LIMITS (0x00000200)

作用:允许窗口布局超出屏幕物理边界。

WMS 的效果

  • 窗口的 content 区域不再被限制在屏幕边界内
  • 主要用于旧的 overscan 机制(CRT 电视时代的过扫描概念,Android 早期用于处理不同 TV 的过扫描差异)
  • API 18-30 期间配合 FLAG_LAYOUT_IN_OVERSCAN 使用,现已基本废弃

FLAG_LAYOUT_IN_SCREEN 的关系

  • FLAG_LAYOUT_IN_SCREEN 通常会被 WMS 自动附加FLAG_LAYOUT_NO_LIMITS 上,因为超出屏幕必然要先允许覆盖全屏
  • windowClipToOutline=true 场景下也会隐式设置此 flag
FLAG_LAYOUT_IN_OVERSCAN (0x00080000)

作用:允许窗口内容延伸到屏幕的 overscan 区域(已废弃,API 30+ 无效)。

这是 Android TV 时代的遗留物。老式电视机有 5-10% 的画面区域不可见(过扫描),Android 通过 overscan insets 来处理。现代显示器(LCD/OLED)没有过扫描概念,所以 API 30 起已废弃。

FLAG_LAYOUT_ATTACHED_IN_DECOR (0x40000000)

作用:内部使用的标志,表示 LayoutParams 已被附加到 DecorView。

这是一个平台内部标志 ,不应由应用设置。当 WindowManager.addView() 被调用时自动添加,用于防止同一个 LayoutParams 被复用。


4.3 触摸与焦点 Flags(Input Flags)

这些 flags 控制窗口是否接收/如何处理输入事件。

FLAG_NOT_TOUCHABLE (0x00000010)

作用:窗口不接收任何触摸事件,所有触摸事件直接穿透到下层窗口。

WMS 的效果

  • 在 WMS 的输入事件分发中,该窗口被标记为 "untouchable"
  • InputDispatcher 跳过该窗口,将触摸事件发给 Z-order 中下一个可触摸的窗口
  • 即使窗口可见,用户也无法与之交互

使用场景

  • 悬浮窗 + 穿透:如悬浮歌词、悬浮球在不可交互状态时
  • Loading 遮罩 :显示一个全屏的半透明遮罩阻止用户操作(需要设置该 flag 配合 FLAG_NOT_FOCUSABLE
  • Starting Window(启动窗口):系统在真正 Activity 启动前显示的预览窗口,不应接收触摸
  • Toast / 短暂提示:不应拦截触摸事件

FLAG_NOT_FOCUSABLE 的组合关系

FLAG_NOT_TOUCHABLE FLAG_NOT_FOCUSABLE 效果
窗口完全透明于人机交互------不可触摸、不可获焦
窗口不可触摸但可以获焦(少见,通常无实际意义)
窗口可触摸,但不能成为输入焦点窗口(常用------如输入法窗口上面的工具栏)
正常窗口,可触摸、可获焦
FLAG_NOT_FOCUSABLE (0x00000008)

作用:窗口不能成为输入焦点窗口(即键盘事件的目标窗口)。

WMS 的效果

  • 该窗口不会接收 KeyEvent(按键事件)
  • InputDispatcher 不会将焦点设给该窗口
  • 如果同时设置了 FLAG_NOT_TOUCHABLE,相当于该窗口对输入系统完全不可见

使用场景

  • 系统级悬浮窗(如 Chat Heads、悬浮球):可见、可点击但不能抢键盘焦点
  • 输入法窗口:IME 自身不应该是焦点窗口,但它上面的 EditText 所在的窗口是
  • Toast / 短暂提示:不需要焦点
  • 窗口管理器中的非交互式 Layer:如壁纸窗口

注意 :如果窗口需要接收 KeyEvent(如返回键、音量键等),必须取消此 flag。

FLAG_WATCH_OUTSIDE_TOUCH (0x00040000)

作用 :当用户触摸窗口外部 (在窗口边界之外)时,WMS 向该窗口发送 ACTION_OUTSIDE 事件。

WMS 的效果

  • 正常情况下,窗口外部的触摸事件不会发送给该窗口(只发给被触摸到的下层窗口)
  • 设置该 flag 后,WMS 会将外部触摸事件打包成一个特殊的 MotionEvent.ACTION_OUTSIDE 发给该窗口
  • 该 flag 必须配合 FLAG_NOT_TOUCH_MODAL 使用

使用场景

  • Dialog 点击外部关闭 :这是 windowCloseOnTouchOutside 的底层实现机制
  • PopupWindow 点击外部关闭
  • 下拉菜单 / 上下文菜单:点击菜单外部区域关闭菜单
FLAG_NOT_TOUCH_MODAL (0x00000020)

作用:允许触摸事件穿透到该窗口后面的窗口(即 WMS 不会因为该窗口存在而阻止下层窗口接收触摸)。

WMS 的效果

  • 正常情况下(不设此 flag),即使窗口 B 在窗口 A 上面且窗口 B 较小,窗口 A 中未被遮挡的区域也不能接收触摸------因为窗口 B 是 "touch modal" 的
  • "touch modal" = 该窗口的 "触摸区域" 是整个屏幕,即使窗口本身很小
  • 设置该 flag 后,窗口 B 外的触摸事件会传给下层窗口 A

使用场景

  • 系统级悬浮窗:悬浮球在屏幕上,但下面的 App 仍然可以正常操作
  • 输入法窗口:键盘弹出时,键盘上面的内容区域仍可滑动
  • Side Bar / Edge Panel:侧边栏出现时,其余区域仍可操作

典型的触摸行为矩阵

css 复制代码
┌──────────────────────────┐
│  Window A (全屏 App)      │
│                          │
│     ┌──────────┐         │
│     │ Window B │         │  ← 一个 300dp×200dp 的 Dialog
│     └──────────┘         │
│                          │
│  ★ 用户点击这个区域 ★     │  ← Dialog 外部、App 内部
└──────────────────────────┘

不设 FLAG_NOT_TOUCH_MODAL:事件发给 Window B(ACTION_OUTSIDE),App A 收不到
设置 FLAG_NOT_TOUCH_MODAL:事件发给 Window A(正常处理),同时给 B 发 ACTION_OUTSIDE
FLAG_SPLIT_TOUCH (0x00800000)

作用:允许多个手指的触摸事件分别发送给不同的窗口。

WMS 的效果

  • 如果没有此 flag,多点触摸的第二根手指不会触发新的窗口命中测试
  • 设置后,每个手指的 DOWN 事件都独立进行窗口命中,可以分发到不同的窗口

使用场景

  • 多窗口 / 分屏模式:用户可以同时用两根手指操作两个窗口
  • 系统任务栏 + 应用:一根手指滑动任务栏,另一根手指操作应用
  • 游戏手柄映射窗口:虚拟按键和游戏画面需要同时响应不同的触摸

4.4 系统栏相关 Flags(System Bar Flags)

这些 flags 是日常开发中最常用的,控制状态栏和导航栏的外观。

FLAG_FULLSCREEN (0x00000400)

作用:隐藏系统状态栏和导航栏,窗口占据全部屏幕空间。

WMS 的效果

  • WMS 通知 SystemUI 隐藏状态栏和导航栏
  • 屏幕只显示该窗口的内容(完全的"全屏模式")
  • FLAG_LAYOUT_IN_SCREEN 的区别:后者只改变布局边界但不隐藏系统栏

使用场景

  • 视频播放器全屏模式
  • 游戏(几乎所有手游都是全屏的)
  • 图片浏览器全屏
  • 演示 / 幻灯片模式

FLAG_FORCE_NOT_FULLSCREEN 的关系

java 复制代码
// Window.java 中定义 FULLSCREEN:
// 设置此 flag 实际上 = 设置 FLAG_FULLSCREEN + 清除 FLAG_FORCE_NOT_FULLSCREEN
public static final int FLAG_FULLSCREEN = 0x00000400;
// FLAG_FORCE_NOT_FULLSCREEN 是内部标志,表示"有东西强制非全屏",
// 系统用来平衡多个全屏请求
FLAG_TRANSLUCENT_STATUS (0x04000000)

作用:状态栏变为半透明/透明,窗口内容可以延伸到状态栏后面。

WMS 的效果

  • SystemUI 绘制状态栏时使用半透明混合(而非完全不透明)
  • WMS 隐式添加 FLAG_LAYOUT_IN_SCREEN + FLAG_LAYOUT_NO_LIMITS,使窗口布局延伸到状态栏后面
  • 状态栏仍占据空间,但下方的窗口内容透过它可见
  • 此 flag 自动暗示 SYSTEM_UI_FLAG_LAYOUT_STABLESYSTEM_UI_FLAG_LAYOUT_FULLSCREEN

重要 :单独设置此 flag 只会让状态栏半透明 (约 40% 不透明度的黑色 scrim)。如果你想要完全自定义状态栏颜色,必须同时设置:

markdown 复制代码
FLAG_TRANSLUCENT_STATUS     --- 状态栏透明
  +
FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS  --- 由窗口负责绘制状态栏背景色
  +
statusBarColor              --- 你自定义的颜色

三者缺一不可。

使用场景

  • Material Design 彩色状态栏:让状态栏颜色与 ActionBar 统一
  • 图片延伸到状态栏:如个人主页的头部大图
  • 地图 / 相机等全屏应用:地图内容充满整个屏幕,状态栏浮在地图上方
  • Edge-to-Edge 设计:Android 15+ 的默认行为

历史变更

  • API 19 (KitKat):引入 FLAG_TRANSLUCENT_STATUS,但 window 可获得半透明的渐变状态栏
  • API 21 (Lollipop):引入 FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDSstatusBarColor,实现真正的自定义颜色
  • API 23 (Marshmallow):引入 SYSTEM_UI_FLAG_LIGHT_STATUS_BAR(Android 30+ 改用 WindowInsetsController),浅色状态栏图标
  • API 35 (Android 15):强制 Edge-to-Edge(自动设置这些 flags),除非显式 opt-out
FLAG_TRANSLUCENT_NAVIGATION (0x08000000)

作用 :与 FLAG_TRANSLUCENT_STATUS 完全对称,作用于导航栏。

所有关于状态栏的讨论同样适用于导航栏。唯一需要注意的是:导航栏通常是屏幕底部(手势导航条区域),在全面屏设备上可能非常窄(甚至与内容重叠)。

FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS (0x80000000)

作用:声明"系统栏的背景由本窗口负责绘制",WMS 不再绘制默认的黑色半透明 scrim。

WMS 的效果

  • 只有此 flag 存在时,statusBarColornavigationBarColor 才会生效
  • WMS 在绘制系统栏时采用该窗口指定的颜色(通过 WindowInsetsController 传递)
  • 此 flag 必须 配合 FLAG_TRANSLUCENT_STATUS / FLAG_TRANSLUCENT_NAVIGATION 使用

底层原理

bash 复制代码
没有 FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS 时:
┌────────────────────────────┐
│  statusBarColor (不生效)     │
├────────────────────────────┤  ← 这里还是黑色 scrim(WMS 自行绘制)
│                            │
│  窗口内容                    │
│                            │
└────────────────────────────┘

有 FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS 时:
┌────────────────────────────┐
│  statusBarColor = #2196F3  │  ← 这里是你设的颜色
├────────────────────────────┤
│                            │
│  窗口内容                    │
│                            │
└────────────────────────────┘
FLAG_LAYOUT_HIDE_NAV (deprecated)

此 flag 是旧式全屏 API 的残留,功能已被 FLAG_TRANSLUCENT_STATUS / FLAG_TRANSLUCENT_NAVIGATION 替代。


4.5 安全与显示 Flags(Security & Display Flags)

FLAG_SECURE (0x00002000)

作用:窗口内容被视为"安全内容"------禁止截屏、录屏,且禁止在非安全显示器上显示。

WMS 的效果

  • WMS 标记该窗口所在的 Surface 为 "secure"
  • SurfaceFlinger 拒绝在截图/录屏中包含该 Surface 的像素
  • 如果设备连接了非安全的虚拟显示器(如录屏软件创建的虚拟屏),该窗口的内容不渲染到虚拟显示器,在录屏中显示为黑屏
  • 注意:这不影响物理屏幕上的显示------用户在设备上仍然能看到正常内容

使用场景

  • 银行 App / 支付页面
  • 密码输入 / 设置页面
  • 企业内部敏感信息页面
  • DRM 保护的视频播放(如 Netflix 的截屏保护)

限制

  • Android 截图 API 被阻止(MediaProjection 的截图中该窗口为黑色)
  • ADB screencap / screenrecord 中该窗口为黑色
  • 第三方录屏 App 中该窗口为黑色
  • 系统截图(电源键 + 音量键)中该窗口为黑色
  • 无法阻止物理拍照(用另一部手机拍屏幕)
FLAG_SHOW_WALLPAPER (0x00100000)

作用:窗口内容置于壁纸之上,壁纸作为窗口的背景(而非桌面背景)。

WMS 的效果

  • WMS 将该窗口的 Z-order 调整到壁纸窗口之上、其他窗口之下
  • 壁纸作为该窗口的"背景层"
  • 必须配合 windowIsTranslucent 或半透明背景使用,否则看不到壁纸

使用场景

  • 半透明主题的 App:如天气 App 的主界面透过半透明面板看到壁纸
  • Launcher:桌面本身
  • 壁纸选择器 / 预览
  • 锁屏界面
FLAG_DIM_BEHIND (0x00000002)

作用:使该窗口后面的所有窗口变暗。

WMS 的效果

  • WMS 在该窗口后面插入一个半透明黑色 Layer
  • 变暗的程度由 LayoutParams.dimAmount 控制(范围 0.0~1.0,默认 0.0 = 不变暗)
  • 用于实现"模态对话框"的遮罩效果

使用场景

  • AlertDialog:系统默认 AlertDialog 使用此 flag(dimAmount = 0.5)实现背景变暗效果
  • BottomSheet:半透明遮罩
  • Loading 弹窗:遮罩 + 加载动画

注意dimAmount 的值在 Theme 中通过 android:backgroundDimAmount 属性设置:

xml 复制代码
<item name="android:backgroundDimAmount">0.6</item>
FLAG_BLUR_BEHIND (0x00000004)

作用:使该窗口后面的所有窗口内容模糊化。(API 31+ / Android 12+)

使用场景

  • 毛玻璃效果:iOS 风格的模糊背景
  • 通知面板 / 控制中心:下拉面板后面的模糊
  • Dialog 背景模糊

性能注意 :实时模糊是 GPU 密集操作,在低端设备上可能导致掉帧。通常配合 windowBackgroundBlurRadius 控制模糊半径。


4.6 锁屏相关 Flags(Keyguard Flags)

FLAG_SHOW_WHEN_LOCKED (0x00080000)

作用:窗口可以显示在锁屏(Keyguard)之上。

使用场景

  • 来电界面:锁屏时接听电话的 UI
  • 闹钟:闹铃响时的关闭/贪睡界面
  • 音乐播放器锁屏控件
  • 紧急拨号器

安全注意:此 flag 不会绕过锁屏安全------用户仍然需要解锁才能访问 App 的其他部分。

FLAG_DISMISS_KEYGUARD (0x00400000)

作用:当窗口被显示时,自动解除普通锁屏(非安全锁屏------即无密码/图案的滑动解锁)。

使用场景

  • 来电界面:自动解除滑动锁屏,让用户直接看到接听按钮
  • 闹钟:闹铃时解除锁屏

注意:如果用户设置了密码/图案/PIN/生物识别锁屏,此 flag 无效------需要用户主动解锁。

FLAG_TURN_SCREEN_ON (0x00200000)

作用:当窗口被添加到 WMS 时,自动点亮屏幕。

使用场景

  • 来电界面:手机在口袋里,来电时自动亮屏
  • 闹钟:到时间自动亮屏
  • 视频通话请求:收到请求时自动亮屏
FLAG_KEEP_SCREEN_ON (0x00000080)

作用:只要该窗口可见,屏幕不自动熄灭。

使用场景

  • 视频播放器
  • 游戏
  • 地图导航
  • 阅读器(可选)
  • 相机

等效代码

java 复制代码
// 在 Activity 的 View 中设置(不需要 Window flag 权限):
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

// 或者在 layout XML 中:
<View android:keepScreenOn="true" />
FLAG_ALLOW_LOCK_WHILE_SCREEN_ON (0x00000001)

作用 :即使屏幕亮着,也允许设备进入锁屏状态(与 FLAG_KEEP_SCREEN_ON 相反但互不冲突)。

使用场景:极少直接使用。通常由系统管理。


4.7 Flags 组合使用矩阵

以下是一些常见场景的 flags 组合,方便你快速查阅。

全屏沉浸式(视频/游戏)
复制代码
FLAG_FULLSCREEN                          --- 隐藏所有系统栏
FLAG_KEEP_SCREEN_ON                      --- 不自动灭屏
FLAG_LAYOUT_IN_SCREEN                    --- 布局充满屏幕(可选,视需求)
Material Design 自定义状态栏颜色
bash 复制代码
FLAG_TRANSLUCENT_STATUS                  --- 状态栏透明
FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS        --- 窗口绘制状态栏背景
+ statusBarColor = #2196F3               --- 自定义颜色
透明/半透明 Activity(Dialog 样式)
ini 复制代码
FLAG_TRANSLUCENT_STATUS                  --- 状态栏透明(可选)
+ LP.format = PixelFormat.TRANSLUCENT    --- Surface 支持 alpha
+ windowBackground = transparent         --- DecorView 背景透明
悬浮窗(System Alert Window)
ini 复制代码
FLAG_NOT_FOCUSABLE                       --- 不抢键盘焦点
FLAG_NOT_TOUCH_MODAL                     --- 允许触摸穿透
FLAG_WATCH_OUTSIDE_TOUCH                 --- 监听外部触摸(可选)
FLAG_SPLIT_TOUCH                         --- 多点触摸(可选)
+ type = TYPE_APPLICATION_OVERLAY        --- Z-order 在上层
锁屏上显示(来电/闹钟)
复制代码
FLAG_SHOW_WHEN_LOCKED                    --- 显示在锁屏上方
FLAG_DISMISS_KEYGUARD                    --- 解除滑动锁屏
FLAG_TURN_SCREEN_ON                      --- 自动亮屏
FLAG_KEEP_SCREEN_ON                      --- 不自动灭屏(来电场景)
敏感内容保护(银行 App)
arduino 复制代码
FLAG_SECURE                              --- 禁止截屏/录屏
// 不需要其他特殊 flag,单独使用即可

5. Theme 属性分类详解

有了上面的 flags 知识储备,现在逐个分析每个 Theme XML 属性的 Java 消费路径就非常清晰了。

5.1 窗口 Flags 类

这些属性直接映射到 LayoutParams flags。

android:windowNoDisplay
xml 复制代码
<item name="android:windowNoDisplay">true</item>

PhoneWindow 源码消费

java 复制代码
// PhoneWindow 构造函数
if (a.getBoolean(R.styleable.Window_windowNoDisplay, false)) {
    setFlags(FLAG_NOT_TOUCHABLE | FLAG_NOT_FOCUSABLE,
             FLAG_NOT_TOUCHABLE | FLAG_NOT_FOCUSABLE);
    // 额外设置输入特性,使窗口完全不参与输入系统
    final WindowManager.LayoutParams params = getAttributes();
    params.inputFeatures |= INPUT_FEATURE_NO_INPUT_CHANNEL;
}

LayoutParams 影响LP.flags |= FLAG_NOT_TOUCHABLE | FLAG_NOT_FOCUSABLE,同时禁用 InputChannel。

使用场景

  • 后台 Service 持有的 Window(如不需要 UI 的后台任务)
  • 纯逻辑 Activity(如 URL Scheme 中转页,finish 前不显示任何 UI)
  • 极少直接在 Theme 中使用,通常在特定业务场景中由代码设置
android:windowDisablePreview
xml 复制代码
<item name="android:windowDisablePreview">true</item>

PhoneWindow 源码消费

java 复制代码
// PhoneWindow 构造函数
if (a.getBoolean(R.styleable.Window_windowDisablePreview, false)) {
    addFlags(FLAG_DISABLE_PREVIEW);
}

LayoutParams 影响LP.flags |= FLAG_DISABLE_PREVIEW

什么是 Starting Window(启动预览)?

当用户点击图标启动一个冷启动(进程不存在)的 Activity 时,在真正的 Activity 渲染出第一帧之前,AMS 会指示 WMS 显示一个"Starting Window"。Starting Window 根据 Theme 中的 windowBackgroundstatusBarColor 生成一个简单的纯色/渐变窗口,让用户感知到"应用正在启动"。

css 复制代码
冷启动时序:
[点击图标] → [进程创建] → [Application.onCreate] → [Activity.onCreate]
                                                          │
                                    在此时机,屏幕显示 Starting Window(如果没被 Disable)
                                                          │
                                    [setContentView] → [首帧渲染]
                                                          │
                                    此时 Starting Window 被移除,显示真正的 UI

使用场景

  • 自定义 Splash Screen:应用有自己的 SplashActivity(带品牌 Logo 动画),不希望系统再插入一个默认预览窗口
  • 透明背景 Activity:Starting Window 默认是纯色的,在半透明 Activity 下会先看到一个纯色闪烁再变透明,体验很差
  • Android 12+ (API 31) 引入了新的 SplashScreen API(SplashScreen 类),推荐使用它替代自定义 Splash Activity。此时系统会自动处理 FLAG_DISABLE_PREVIEW
android:windowEnableSplitTouch
xml 复制代码
<item name="android:windowEnableSplitTouch">true</item>

PhoneWindow 源码消费

java 复制代码
// PhoneWindow 构造函数
if (a.getBoolean(R.styleable.Window_windowEnableSplitTouch, false)) {
    addFlags(FLAG_SPLIT_TOUCH);
}

LayoutParams 影响LP.flags |= FLAG_SPLIT_TOUCH

使用场景

  • 分屏 / 多窗口:需要多个窗口同时响应不同的手指触摸
  • 游戏:双摇杆控制需要独立处理两个手指
  • 支持分屏的应用:Android 7.0+ 的分屏模式需要此 flag 才能正确分发多点触摸
android:windowShowWallpaper
xml 复制代码
<item name="android:windowShowWallpaper">true</item>

PhoneWindow 源码消费

java 复制代码
// PhoneWindow 构造函数
if (a.getBoolean(R.styleable.Window_windowShowWallpaper, false)) {
    addFlags(FLAG_SHOW_WALLPAPER);
}

LayoutParams 影响LP.flags |= FLAG_SHOW_WALLPAPER

使用场景

  • Launcher / 桌面:在壁纸之上显示图标和 Widget
  • 半透明主题:天气 App 的半透明面板让壁纸透出
  • 锁屏界面:在壁纸上叠加锁屏控件
  • 壁纸选择器:预览壁纸效果

注意:必须配合透明或半透明背景使用,否则不透明的窗口背景会完全遮挡壁纸:

xml 复制代码
<item name="android:windowShowWallpaper">true</item>
<item name="android:windowBackground">#80000000</item>  <!-- 半透明背景 -->
android:windowBlurBehind
xml 复制代码
<item name="android:windowBlurBehind">true</item>

LayoutParams 影响LP.flags |= FLAG_BLUR_BEHIND(API 31+)

使用场景

  • 毛玻璃效果 Dialog:iOS 风格的对话框
  • 通知中心 / 控制中心:下拉面板的背景模糊
  • 半透明 Activity:内容后面的模糊背景

实现原理 :WMS 请求 SurfaceFlinger 对下层窗口的 Surface 做 GPU 模糊渲染,再将结果作为本窗口的背景。性能开销较大,建议通过 windowBackgroundBlurRadius 控制模糊半径以平衡效果与性能:

xml 复制代码
<item name="android:windowBlurBehind">true</item>
<item name="android:windowBackgroundBlurRadius">25dp</item>
android:windowSecure
xml 复制代码
<item name="android:windowSecure">true</item>

PhoneWindow 源码消费

java 复制代码
// PhoneWindow 构造函数
if (a.getBoolean(R.styleable.Window_windowSecure, false)) {
    addFlags(FLAG_SECURE);
    // 同时在 InputChannel 层面标记 secure
    params.inputFeatures |= INPUT_FEATURE_NO_INPUT_CHANNEL;
}

LayoutParams 影响LP.flags |= FLAG_SECURE

使用场景

  • 银行 / 金融 App 所有页面
  • 密码输入页面
  • 支付确认页面
  • 企业 VPN / 内部工具
  • 医学 / 健康隐私数据页面
  • 加密货币钱包

代码中动态启用(更灵活,在特定页面才启用):

java 复制代码
// 在敏感页面的 onResume 中启用
@Override
protected void onResume() {
    super.onResume();
    getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
}

// 在离开敏感页面时关闭(可选)
@Override
protected void onPause() {
    super.onPause();
    getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE);
}

坑点

  • SurfaceView / TextureView 中的内容同样被保护(整个 Surface 都被标记 secure)
  • 系统截图快捷键(电源+音量)也会触发黑屏
  • Android 15 引入的私有空间(Private Space)可能会与此 flag 有交互
android:windowNotTouchable
xml 复制代码
<item name="android:windowNotTouchable">true</item>

LayoutParams 影响LP.flags |= FLAG_NOT_TOUCHABLE(在 generateLayout() 中处理)

使用场景

  • 纯展示页面(如广告展示页、信息大屏):不需要用户交互
  • 悬浮 Loading/进度指示器 :展示加载状态但不阻止用户操作(需配合 FLAG_NOT_FOCUSABLE
  • AOD(常亮显示)或屏保

5.2 窗口 Type 类

android:windowIsFloating
xml 复制代码
<item name="android:windowIsFloating">true</item>

PhoneWindow 源码消费

java 复制代码
// 构造函数中保存标记
mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);

// generateLayout() 中使用
if (mIsFloating) {
    LayoutParams lp = getAttributes();
    lp.width = LayoutParams.WRAP_CONTENT;   // Dialog 风格:宽度自适应内容
    lp.height = LayoutParams.WRAP_CONTENT;  // 高度自适应内容
    lp.gravity = Gravity.CENTER;             // 居中显示
    // 额外:如果窗口有边框/阴影设置,会在 DecorView 层面处理
}

LayoutParams 影响

  • LP.width = WRAP_CONTENT
  • LP.height = WRAP_CONTENT
  • LP.gravity = CENTER

更深远的影响mIsFloating 还会影响 DecorView 的布局选择。PhoneWindow 根据不同 Feature 和 mIsFloating 的组合选择不同的 DecorView 布局文件:

Feature 组合 DecorView 布局
标准 Activity(无特殊 Feature) R.layout.screen_simple
标准 Activity + FEATURE_NO_TITLE R.layout.screen_simple(无标题区域)
mIsFloating + FEATURE_NO_TITLE R.layout.dialog_custom_title 或简化布局
mIsFloating + ActionBar R.layout.screen_toolbar

使用场景

  • Dialog 样式 Activity :在 AndroidManifest.xml 中设置 android:theme="@style/Theme.AppCompat.Dialog",其父 Theme 中就设置了 windowIsFloating=true
  • 浮动工具栏:类似悬浮窗的小窗口
  • 画中画(PiP)相关 :PiP 窗口本质上就是 mIsFloating=true 的特例

完整的 Dialog 样式 Theme(Android 系统内置的 Dialog Theme 大体上是这样的):

xml 复制代码
<style name="Theme.AppCompat.Dialog" parent="Theme.AppCompat">
    <item name="android:windowIsFloating">true</item>
    <item name="android:windowBackground">@android:color/transparent</item>
    <item name="android:windowNoTitle">true</item>
    <item name="android:windowCloseOnTouchOutside">true</item>
</style>

5.3 尺寸与布局类

android:windowFixedWidth / android:windowFixedHeight
xml 复制代码
<item name="android:windowFixedWidth">320dp</item>
<item name="android:windowFixedHeight">480dp</item>

PhoneWindow 源码消费

java 复制代码
// generateLayout()
if (a.hasValue(R.styleable.Window_windowFixedWidth)) {
    lp.width = a.getDimensionPixelSize(
        R.styleable.Window_windowFixedWidth, lp.width);
}
if (a.hasValue(R.styleable.Window_windowFixedHeight)) {
    lp.height = a.getDimensionPixelSize(
        R.styleable.Window_windowFixedHeight, lp.height);
}

LayoutParams 影响 :覆写 LP.width / LP.height 为固定 dp 值。

使用场景

  • 特殊尺寸的 Activity:如电视 App 的遥控器提示面板(不需要全屏)
  • 自定义 Dialog:固定宽度的对话框
  • 多窗口 / 自由窗口模式(Freeform):指定窗口的初始尺寸

windowIsFloating 的配合

xml 复制代码
<item name="android:windowIsFloating">true</item>  <!-- WRAP_CONTENT 兜底 -->
<item name="android:windowFixedWidth">360dp</item> <!-- 覆写为固定宽度 -->
android:windowFixedWidthMajor / android:windowFixedWidthMinor

这两个属性配合横竖屏使用:

  • Major = 长边(landscape 下是宽度,portrait 下是高度)
  • Minor = 短边(landscape 下是高度,portrait 下是宽度)

示例:在平板上,竖屏时窗口宽度设为 360dp,横屏时设为 600dp:

xml 复制代码
<item name="android:windowFixedWidthMajor">600dp</item>  <!-- 横屏时生效 -->
<item name="android:windowFixedWidthMinor">360dp</item>  <!-- 竖屏时生效 -->

使用场景

  • 平板 / 折叠屏适配:不同屏幕方向下窗口尺寸不同
  • 多窗口 / Freeform 模式:在大屏设备上指定窗口初始大小
android:windowMinWidthMajor / android:windowMinWidthMinor

最小宽度/高度约束,与固定尺寸不同,它们是下限而非精确值。

java 复制代码
lp.minWidth = a.getDimensionPixelSize(
    R.styleable.Window_windowMinWidthMajor, lp.minWidth);

LayoutParams 影响LP.minWidth / LP.minHeight

使用场景

  • 防止窗口过小:在多窗口模式下,窗口可能被缩得很小,设置 minWidth 可以防止内容被挤压到不可用
  • 响应式设计:窗口可以选择更大的尺寸,但不能小于某个阈值
android:windowElevation
xml 复制代码
<item name="android:windowElevation">4dp</item>

PhoneWindow 源码消费

java 复制代码
final float elevation = a.getDimension(R.styleable.Window_windowElevation, 0);
if (elevation != 0 && lp.setElevation(elevation)) {
    // elevation 发生了变化,触发 relayout
}

LayoutParams 影响LP.elevation

使用场景

  • Activity 间层级:后启动的 Activity 默认在更高的 Z 层,设置 elevation 可以微调
  • Dialog 相对于 Activity 的 Z 轴偏移:确保 Dialog 的阴影正确投射在下层 Activity 上
  • Material Design 阴影:配合背景的 elevation 阴影效果

注意elevation 影响的是 Z-order 和阴影投射,不是窗口在屏幕上的 x/y 位置。

android:windowClipToOutline
xml 复制代码
<item name="android:windowClipToOutline">true</item>
java 复制代码
final boolean clipToOutline = a.getBoolean(
    R.styleable.Window_windowClipToOutline, false);
if (clipToOutline) {
    lp.flags |= FLAG_LAYOUT_NO_LIMITS;
}

使用场景

  • 圆角窗口 :配合 windowBackground 的圆角 shape,将窗口裁剪为圆角矩形
  • 自定义形状窗口:如圆形头像悬浮窗

为什么 clipToOutline 会设置 FLAG_LAYOUT_NO_LIMITS?

因为裁剪到 outline 意味着窗口的可见区域由 outline path 决定,layout 可以超出屏幕(反正会被裁剪掉)。这个 flag 确保内容可以渲染到 outline 内的所有区域,即使部分在屏幕外。


5.4 背景与样式类

android:windowBackground
xml 复制代码
<!-- 纯色 -->
<item name="android:windowBackground">@color/window_bg</item>
<!-- Drawable -->
<item name="android:windowBackground">@drawable/my_bg</item>
<!-- 透明 -->
<item name="android:windowBackground">@android:color/transparent</item>

PhoneWindow 源码消费

java 复制代码
// generateLayout()
final Drawable background;
if (a.hasValue(R.styleable.Window_windowBackground)) {
    // 优先作为 Drawable 读取
    background = a.getDrawable(R.styleable.Window_windowBackground);
} else {
    // 其次作为 Color 读取
    int color = a.getColor(R.styleable.Window_windowBackground, 0xFF000000);
    background = new ColorDrawable(color);
}
// 设置给 DecorView
mDecor.setWindowBackground(background);

LayoutParams 影响不直接影响 LP,但有两个重要间接影响:

  1. Starting Window(启动窗口)的颜色 :AMS 读取 windowBackground 作为启动窗口的背景色(在 API 31 之前的实现中)
  2. 窗口是否为 "半透明" 的判定:系统会根据背景的 alpha 来判断窗口是否需要特殊处理

使用场景

  • 应用主色调背景:避免每个 Activity 的 root view 都要设置背景(系统级默认背景)
  • Splash Screen :启动窗口的背景色(API 31 后由 windowSplashScreenBackground 替代)
  • 防止启动白屏/黑屏 :这是 windowBackground 最实用的场景之一:
xml 复制代码
<!-- 在 Theme 中设置与你的启动页相同的背景 -->
<style name="AppTheme" parent="Theme.Material3.DayNight.NoActionBar">
    <item name="android:windowBackground">@color/splash_bg</item>
</style>

这样在 Activity 渲染出第一帧之前,用户看到的就是 splash_bg 颜色,而不是刺眼的白屏。

android:windowBackgroundFallback
xml 复制代码
<item name="android:windowBackgroundFallback">@color/fallback_bg</item>

作用 :当同时满足以下条件时,使用此背景替代 windowBackground

  1. windowBackground 未设置或为 null
  2. windowActivityTransitions 为 true

原因 :Transition 框架需要一个背景来做转场动画的渐变/淡出。如果 windowBackground 为空,转场动画没有"底"可画,此时 fallback 登场。

使用场景:通常不需要手动设置,系统有合理的默认值。只有在精细化控制转场体验时才需要。

android:windowContentOverlay
xml 复制代码
<item name="android:windowContentOverlay">@drawable/content_shadow</item>

作用 :在 mContentParent(即 android.R.id.content)上方绘制一个遮罩层。

历史意义:在 Holo 时代,ActionBar 和 content 之间有一个阴影分隔线,这个属性就是用来设置那个阴影的。Material Design 已不再使用。

使用场景 :几乎不再使用。如果你想要 content 区域的阴影/边框效果,建议用 elevation 或 View 自身的 background 实现。

android:windowTitleSize
xml 复制代码
<item name="android:windowTitleSize">24sp</item>

→ 设置传统 Title Bar 的文字大小。在 ActionBar / Toolbar 时代基本废弃。

android:windowNoTitle
xml 复制代码
<item name="android:windowNoTitle">true</item>
java 复制代码
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
    requestFeature(FEATURE_NO_TITLE);
}

→ 调用 requestFeature(FEATURE_NO_TITLE),移除传统标题栏,精简 DecorView 布局。

使用场景

  • 所有使用 Toolbar 的 App :老式 TitleBar 与 Toolbar 冲突,必须先 requestFeature(FEATURE_NO_TITLE) 才能正常使用 Toolbar
  • NoActionBar 主题Theme.Material3.DayNight.NoActionBar 的父主题里默认设置了此属性
  • 全屏 / 沉浸式页面
android:windowActionBar
xml 复制代码
<item name="android:windowActionBar">true</item>
<!-- 或设为 false 移除 ActionBar -->
<item name="android:windowActionBar">false</item>

requestFeature(FEATURE_ACTION_BAR)

使用场景

  • 设为 false → 使用自定义 Toolbar 代替系统的 ActionBar
  • 设为 true → 使用系统 ActionBar
android:windowActionModeOverlay
xml 复制代码
<item name="android:windowActionModeOverlay">true</item>

→ ActionMode(上下文操作栏,如文字选择时的"复制/粘贴/剪切"条)覆盖在 ActionBar 上方而不是推挤 ActionBar。


5.5 系统状态栏/导航栏类

这是实际开发中最常需要自定义的一类属性。

android:statusBarColor / android:navigationBarColor
xml 复制代码
<item name="android:statusBarColor">@color/primary_dark</item>
<item name="android:navigationBarColor">@color/nav_bar</item>
java 复制代码
mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, 0xFF000000);
mNavigationBarColor = a.getColor(R.styleable.Window_navigationBarColor, 0xFF000000);

→ 颜色值存储到 PhoneWindow 字段,之后通过 WindowInsetsController(API 30+)或 View.setSystemUiVisibility()(API 21-29)发送给 SystemUI。

这两个颜色生效的前提条件(极其重要):

ini 复制代码
statusBarColor 生效需要:
  FLAG_TRANSLUCENT_STATUS = true     (windowTranslucentStatus)
  FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS = true  (windowDrawsSystemBarBackgrounds)

navigationBarColor 生效需要:
  FLAG_TRANSLUCENT_NAVIGATION = true  (windowTranslucentNavigation)
  FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS = true  (windowDrawsSystemBarBackgrounds)

使用场景

  • 品牌色状态栏:状态栏颜色与 Toolbar/ActionBar 颜色一致
  • 深色导航栏:配合深色主题,导航栏设为深色
  • 沉浸式页面:状态栏颜色与页面顶部内容融合
android:windowTranslucentStatus
xml 复制代码
<item name="android:windowTranslucentStatus">true</item>
java 复制代码
if (a.getBoolean(R.styleable.Window_windowTranslucentStatus, false)) {
    setFlags(FLAG_TRANSLUCENT_STATUS, FLAG_TRANSLUCENT_STATUS);
}

LP.flags |= FLAG_TRANSLUCENT_STATUS

使用场景

  • 图片/视频延伸到状态栏:如个人主页头部大图
  • 自定义状态栏颜色 :配合 windowDrawsSystemBarBackgrounds + statusBarColor
  • Material Design edge-to-edge:让状态栏成为 UI 的一部分

单独设置此属性时的视觉效果

scss 复制代码
没有 FLAG_TRANSLUCENT_STATUS 时:
┌─ 纯黑状态栏 ──────────────┐  ← 不透明,挡住内容
├──────────────────────────┤
│                          │
│  Window Content          │  ← 内容被限制在状态栏之下
│                          │
└──────────────────────────┘

仅 FLAG_TRANSLUCENT_STATUS(未设 DRAWS_SYSTEM_BAR_BACKGROUNDS):
┌─ 40% 黑色半透明状态栏 ────┐  ← 半透明 scrim
├──────────────────────────┤
│  Window Content          │  ← 内容延伸到状态栏后面
│  (在状态栏后面可见)        │
│                          │
└──────────────────────────┘

FLAG_TRANSLUCENT_STATUS + FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS:
┌─ statusBarColor = blue ──┐  ← 自定义颜色
├──────────────────────────┤
│  Window Content          │  ← 内容延伸到状态栏后面
│  (在蓝色状态栏后面可见)     │
│                          │
└──────────────────────────┘
android:windowTranslucentNavigation
xml 复制代码
<item name="android:windowTranslucentNavigation">true</item>

LP.flags |= FLAG_TRANSLUCENT_NAVIGATION

与上面完全对称,作用于底部导航栏。对于全面屏手势设备(Android 10+),导航栏非常窄(通常只有一个横条),这个属性影响的是导航手势区域的颜色。

android:windowDrawsSystemBarBackgrounds
xml 复制代码
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
java 复制代码
if (a.getBoolean(R.styleable.Window_windowDrawsSystemBarBackgrounds, false)) {
    setFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
             FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
}

LP.flags |= FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS

核心作用 :告诉 WMS「系统栏的背景色由我的 statusBarColor / navigationBarColor 决定,你不要画默认的半透明黑色背景了」。

这三个属性必须成组使用

xml 复制代码
<!-- 设置状态栏颜色的"正确姿势"三部曲 -->
<item name="android:windowTranslucentStatus">true</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:statusBarColor">@color/primary_dark</item>

为什么 Theme.Material3.DayNight.NoActionBar 不需要手动设前两个?

因为 Google 在 Theme.Material3.DayNight 中已经预设了这些值为 true(至少在 API 21+ 的版本中)。如果你的 Theme 没有继承 Material3,或基于旧主题,就需要手动添加。

android:windowLightStatusBar
xml 复制代码
<item name="android:windowLightStatusBar">true</item>

→ 在 Android 11+ (API 30+) 中通过 WindowInsetsController.setSystemBarsAppearance() 设置 APPEARANCE_LIGHT_STATUS_BARS

java 复制代码
// API 30+ 的实现路径
final WindowInsetsController controller = getWindowInsetsController();
if (controller != null) {
    controller.setSystemBarsAppearance(
        APPEARANCE_LIGHT_STATUS_BARS,   // 外观值
        APPEARANCE_LIGHT_STATUS_BARS);  // mask
}

作用:状态栏的背景是浅色(如白色)时,状态栏图标(时间、电池、信号等)变为深色。

使用场景

  • 浅色主题:如果 statusBarColor 是浅色(如白色、浅灰),必须设置此属性使图标可读
  • 白色状态栏:iOS 风格的白色顶部栏
  • 透明背景 + 浅色内容:图片延伸到状态栏后面且图片顶部是浅色

API 差异

API 级别 实现方式
API 23-29 View.setSystemUiVisibility(SYSTEM_UI_FLAG_LIGHT_STATUS_BAR)
API 30+ WindowInsetsController.setSystemBarsAppearance(APPEARANCE_LIGHT_STATUS_BARS)
Theme 属性 上述两个路径的声明式封装
android:windowLightNavigationBar
xml 复制代码
<item name="android:windowLightNavigationBar">true</item>

→ 浅色导航栏背景 + 深色导航栏图标(三条横线或药丸按钮)。

使用场景:浅色底部的 UI(如浅色底部导航栏 + 浅色系统导航栏)。

android:enforceNavigationBarContrast / android:enforceStatusBarContrast
xml 复制代码
<item name="android:enforceNavigationBarContrast">true</item>

→ API 29+,当系统栏内容(图标/文字)与你的窗口内容颜色对比度不足时,强制在系统栏后面添加一层半透明 scrim 确保可读性。

使用场景:当你的状态栏/导航栏颜色与背景图片/内容颜色相近,导致图标不可读时,开启此属性可自动添加保护层。Android 15 的 Edge-to-Edge 模式默认启用此行为。

android:windowFullscreen
xml 复制代码
<item name="android:windowFullscreen">true</item>

→ 传统全屏模式(隐藏所有系统栏)。在现代开发中,推荐使用 FLAG_TRANSLUCENT_STATUS + FLAG_TRANSLUCENT_NAVIGATION + FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS 的方式实现全屏(因为用户可以随时下滑唤出系统栏),或者使用 WindowInsetsController.hide() / WindowInsetsController.show()


5.6 软键盘(SoftInput)类

android:windowSoftInputMode
xml 复制代码
<item name="android:windowSoftInputMode">stateVisible|adjustResize</item>

这是最复杂但也最常用的属性之一。整个值是一个位掩码,分为状态 (state) 和调整 (adjust) 两部分,用 | 组合。

完整取值表

XML 值 常量 位值 含义
状态部分(低 4 bits)
stateUnspecified STATE_UNSPECIFIED 0 系统决定(通常是 hide)
stateUnchanged STATE_UNCHANGED 1 保持上一 Activity 的键盘状态
stateHidden STATE_HIDDEN 2 进入时键盘隐藏
stateAlwaysHidden STATE_ALWAYS_HIDDEN 3 窗口获得焦点时隐藏键盘
stateVisible STATE_VISIBLE 4 进入时键盘显示
stateAlwaysVisible STATE_ALWAYS_VISIBLE 5 窗口获得焦点时显示键盘
调整部分(高 4 bits)
adjustUnspecified ADJUST_UNSPECIFIED 0x00 系统决定(通常 = resize)
adjustResize ADJUST_RESIZE 0x10 键盘弹出时压缩窗口
adjustPan ADJUST_PAN 0x20 键盘弹出时平移窗口
adjustNothing ADJUST_NOTHING 0x30 键盘弹出时不做任何调整

PhoneWindow 源码消费

java 复制代码
// generateLayout() → 实际在 Window.java 基类中
final int softInputMode = a.getInt(
    R.styleable.Window_windowSoftInputMode, 0);
if (softInputMode != 0) {
    getAttributes().softInputMode = softInputMode;  // 直接赋值
    mHasSoftInputMode = true;  // 标记为"已设置",防止回退
}

LayoutParams 影响LP.softInputMode 直接赋值

adjustResize vs adjustPan 的深入对比

复制代码
adjustResize:
┌──────────────────┐
│  Toolbar         │  ← 保持可见
│  ┌────────────┐  │
│  │ ScrollView │  │  ← 窗口被压缩,可以滚动
│  │ EditText   │  │  ← 焦点 EditText 自动可见(在可见区域内)
│  └────────────┘  │
├──────────────────┤
│    键盘区域       │
└──────────────────┘
适用:列表/表单(内容可滚动)


adjustPan:
┌──────────────────┐
│                  │
│                  │  ← 整个窗口向上平移
│  EditText        │  ← 焦点 EditText 保持可见
├──────────────────┤
│    键盘区域       │
├──────────────────┤
│  Toolbar         │  ← Toolbar 可能被顶出屏幕!
└──────────────────┘
适用:简单表单(少量输入框在页面底部时)

使用场景

场景 推荐模式 原因
聊天页面(底部输入框 + 消息列表) adjustResize 列表内容随键盘弹出自动上移
登录页面(顶部 Logo + 底部输入框) adjustResize 整体缩放,Logo 可见但变小
简单表单(少量底部输入框) adjustPan 平移即可露出输入框
全屏视频(键盘用于弹幕) adjustNothing 键盘弹出不应移动视频画面
搜索页面(搜索框在顶部的列表) adjustResize 列表需要重新布局
沉浸式游戏(虚拟键盘) adjustNothing 游戏画面不应被键盘影响

stateAlwaysHidden 的妙用

xml 复制代码
<item name="android:windowSoftInputMode">stateAlwaysHidden</item>

→ 用户点击 EditText 时键盘不会自动弹出,除非用户主动点击才会弹出。最适合搜索页(用户不想进入页面就看到键盘)。

默认值 :如果没有设置 windowSoftInputMode,默认值是 STATE_UNCHANGED | ADJUST_RESIZE


5.7 转场动画类

API 21+ 引入的 Activity Transition 框架,所有属性在 generateLayout() 读取。

转场属性全景图
css 复制代码
Activity A  →  Activity B  的过程涉及的转场:

windowExitTransition       ← Activity A 内容退出(如 Fade)
windowEnterTransition      ← Activity B 内容进入(如 Explode)

用户按返回键 (B → A):

windowReturnTransition     ← Activity B 内容退出
windowReenterTransition    ← Activity A 内容重新进入

Shared Element(如头像从列表页飞入详情页):

windowSharedElementExitTransition   ← A 的共享元素退出
windowSharedElementEnterTransition  ← B 的共享元素进入
windowSharedElementReenterTransition← 返回时重新进入
windowSharedElementReturnTransition ← 返回时退出
android:windowActivityTransitions
xml 复制代码
<item name="android:windowActivityTransitions">true</item>

→ 总开关。必须设为 true,后续的所有 Transition 设置才会生效。

android:windowEnterTransition / android:windowExitTransition
xml 复制代码
<item name="android:windowEnterTransition">@transition/explode</item>
<item name="android:windowExitTransition">@transition/fade</item>

使用场景

  • 页面切换动画 :替代 overridePendingTransition()
  • Material Motion:Material Design 的转场系统
  • Shared Element Transition:必须配合这些属性才能工作
android:windowReturnTransition / android:windowReenterTransition

返回时的转场动画(按返回键时触发)。默认与 exit/enter 相同,但可以设置为不同:

xml 复制代码
<item name="android:windowEnterTransition">@transition/slide_right</item>  <!-- 从右侧滑入 -->
<item name="android:windowReturnTransition">@transition/slide_left</item>  <!-- 从左侧滑出 -->
android:windowSharedElementEnterTransition / android:windowSharedElementExitTransition

Shared Element 的内容转场。默认为 @transition/move(即系统内置的 ChangeBounds + ChangeTransform + ChangeClipBounds 组合)。

xml 复制代码
<item name="android:windowSharedElementEnterTransition">@transition/change_image_transform</item>
android:windowAllowEnterTransitionOverlap / android:windowAllowReturnTransitionOverlap
xml 复制代码
<item name="android:windowAllowEnterTransitionOverlap">true</item>

→ 设为 true 时,调用 startActivity() 后,当前 Activity 的退出动画和下一个 Activity 的进入动画并行执行 ;设为 false 时,串行执行(先退出完再进入)。

使用场景

  • 流畅体验 :通常建议 allowEnterTransitionOverlap=true(两个动画叠加更流畅)
  • 避免闪烁:如果两个动画有重叠冲突,设为 false
android:windowTransitionBackgroundFadeDuration
xml 复制代码
<item name="android:windowTransitionBackgroundFadeDuration">300</item>

→ 转场过程中,windowBackground 的淡出/淡入动画时长(毫秒)。这对于避免 Shared Element Transition 期间出现"白色闪烁"非常有用------设置一个合理的 fade duration 让背景渐变过渡。

代码中的等价实现
java 复制代码
// 在 Activity 中代码设置 Transition(与 Theme 属性等价)
getWindow().requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS);
getWindow().setEnterTransition(new Explode());
getWindow().setExitTransition(new Fade());
getWindow().setAllowEnterTransitionOverlap(true);

5.8 像素格式与绘制类

android:windowIsTranslucent
xml 复制代码
<item name="android:windowIsTranslucent">true</item>

这是影响最深远的属性之一。它直接改变了 Window 所在 Surface 的像素格式。

PhoneWindow 源码全链路

java 复制代码
// 第 1 步:构造函数中标记
if (a.getBoolean(R.styleable.Window_windowIsTranslucent, false)) {
    // 先清除一些 flags(这些 flags 与 translucent 不兼容)
    setFlags(0,
        FLAG_NOT_TOUCHABLE   // translucent 意味着需要触摸
        | FLAG_NOT_FOCUSABLE // translucent 意味着可以获得焦点
    );
    // 设置输入特性
    params.inputFeatures |= INPUT_FEATURE_NO_INPUT_CHANNEL;
}

// 第 2 步:generateLayout() 中应用
if (mTranslucent) {
    lp.format = PixelFormat.TRANSLUCENT;  // ← 核心!
}

// 第 3 步:后续初始化
if (mTranslucent) {
    // translucent 窗口不设置 windowBackground 的前景 scrim
    // 因为窗口本身就是半透明的
}

LayoutParams 影响

字段 变化
LP.format OPAQUETRANSLUCENT

PixelFormat.TRANSLUCENT vs OPAQUE 对 SurfaceFlinger 的影响

makefile 复制代码
OPAQUE (默认):
  SurfaceFlinger 将该 Layer 标记为 "不透明"
  合成时不需要读取下层 Layer 的像素
  性能:快(跳过混合运算)

TRANSLUCENT:
  SurfaceFlinger 将该 Layer 标记为 "带 alpha 通道"
  合成时读取每个像素的 alpha 值,与下层做逐像素混合
  性能:慢(增加 GPU 负载和带宽消耗)

完整的半透明 Activity Theme

xml 复制代码
<style name="Theme.Transparent" parent="Theme.Material3.DayNight.NoActionBar">
    <!-- 这 3 个缺一不可 -->
    <item name="android:windowIsTranslucent">true</item>
    <item name="android:windowBackground">@android:color/transparent</item>
    <item name="android:windowNoTitle">true</item>

    <!-- 可选优化 -->
    <item name="android:windowDisablePreview">true</item>  <!-- 避免启动闪烁 -->
    <item name="android:windowAnimationStyle">@null</item> <!-- 移除默认动画 -->
</style>

常见问题诊断

症状 原因
背景是黑色不是透明的 只设了 windowIsTranslucent=true,没设 windowBackground=transparent
状态栏区域是黑色的 状态栏没设 translucent,半透明只对 content 生效
触摸穿透到了下层 App windowIsTranslucent 清理了 FLAG_NOT_TOUCHABLE,确认是否在其他地方又被设置了
启动时短暂白屏 设置了 windowBackground=transparent 但没设置 windowDisablePreview=true
android:windowSwipeToDismiss
xml 复制代码
<item name="android:windowSwipeToDismiss">true</item>

→ 启用滑动关闭手势(iOS 风格的侧滑返回)。

使用场景

  • 图片预览 / 大图浏览:下滑关闭
  • 视频播放:下滑缩小/关闭
  • 模态页面:iOS 风格的下滑 dismiss

5.9 FitSystemWindows 与 Edge-to-Edge 类

android:fitsSystemWindows

这是 View 属性而非 Window 属性,但在 Theme 中设置时会作用于 DecorView:

xml 复制代码
<item name="android:fitsSystemWindows">true</item>

→ 设置 DecorView 的 fitsSystemWindows = true。DecorView 在 dispatchApplyWindowInsets() 时会先消费掉状态栏/导航栏的 insets(为其 padding),然后将剩余的 insets 分发给子 View。

使用场景

  • 需要内容自动避开状态栏:如标准的列表页面
  • 与 CoordinatorLayout 配合:CoordinatorLayout 自身会处理 insets

fitsSystemWindows = true 的效果

ini 复制代码
状态栏 24dp ─┐
            │ ← DecorView 消费了此 inset,给自身加 paddingTop=24dp
            │   子 View 收到调整后的 insets(状态栏部分已消耗)
内容区域    │
            │  效果:内容自动在状态栏下方开始,不会被遮挡
导航栏 48dp ─┘
android:windowOptOutEdgeToEdgeEnforcement
xml 复制代码
<item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>

Android 15+ (API 35) 新增。从 API 35 开始,targetSdk=35 的应用默认 强制启用 Edge-to-Edge 模式------即系统自动设置 FLAG_LAYOUT_IN_SCREENFLAG_TRANSLUCENT_STATUSFLAG_TRANSLUCENT_NAVIGATION 等标志,使窗口内容延伸到系统栏后面。

如果你需要退出这个行为(例如你的旧 UI 大量依赖 fitsSystemWindows 而没有使用 WindowInsets),设置此属性为 true:

xml 复制代码
<item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>

建议:尽量适配 Edge-to-Edge 而非 opt-out。Google 的长期方向是全面 Edge-to-Edge。

android:windowLayoutInDisplayCutoutMode
xml 复制代码
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>

可选值:

XML 值 常量 行为
default (0) LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT 竖屏:不允许伸入刘海;横屏:允许
shortEdges (1) LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES 允许内容延伸到短边的刘海区域
never (2) LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER 永远不允许延伸到刘海
always (3) LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS 系统栏透明时允许延伸到任何方向的刘海
java 复制代码
int cutoutMode = a.getInt(
    R.styleable.Window_windowLayoutInDisplayCutoutMode, 0);
if (cutoutMode != 0) {
    lp.layoutInDisplayCutoutMode = cutoutMode;
}

LayoutParams 影响LP.layoutInDisplayCutoutMode

使用场景

场景 推荐模式 原因
普通列表页面 default 竖屏不伸入刘海,安全稳妥
全屏视频/游戏 shortEdges 利用顶部刘海两侧区域显示内容
横屏全屏 always 让内容充满整个屏幕(包括刘海后面)
刘海区域有重要 UI 元素 never 确保 UI 不被刘海遮挡

5.10 其他行为属性

android:windowCloseOnTouchOutside
xml 复制代码
<item name="android:windowCloseOnTouchOutside">true</item>
java 复制代码
if (a.getBoolean(R.styleable.Window_windowCloseOnTouchOutside, false)) {
    setCloseOnTouchOutsideIfNotSet(true);
}

LayoutParams 影响LP.flags |= FLAG_NOT_TOUCH_MODAL | FLAG_WATCH_OUTSIDE_TOUCH

完整实现链路

scss 复制代码
用户触摸 Dialog 外部区域
  → WMS 发送 ACTION_OUTSIDE 到该 Dialog
    → PhoneWindow 收到 ACTION_OUTSIDE
      → 回调 to Window.onTouchEvent()
        → 调用 dispatchTouchEvent()
          → 因为 closeOnTouchOutside=true
            → 判断触摸坐标在 DecorView 外部
              → 调用 Activity.finish() (如果 attached 到 Activity)

使用场景

  • Dialog 样式 ActivityTheme.AppCompat.Dialog 默认开启
  • 底部菜单 / ActionSheet
  • Dropdown 菜单
  • PopupWindow
android:windowAnimationStyle
xml 复制代码
<item name="android:windowAnimationStyle">@style/MyWindowAnimation</item>
java 复制代码
mAnimationStyle = a.getResourceId(
    R.styleable.Window_windowAnimationStyle, 0);

LayoutParams 影响LP.windowAnimations = mAnimationStyle

这个资源 ID 传给 WMS,WMS 在窗口出现/消失时查找对应的 WindowAnimation 资源并播放。

自定义窗口动画

xml 复制代码
<!-- res/values/styles.xml -->
<style name="MyWindowAnimation">
    <!-- 进入动画 -->
    <item name="android:activityOpenEnterAnimation">@anim/slide_in_right</item>
    <!-- 退出动画 -->
    <item name="android:activityOpenExitAnimation">@anim/slide_out_left</item>
    <!-- 返回时的进入动画 -->
    <item name="android:activityCloseEnterAnimation">@anim/slide_in_left</item>
    <!-- 返回时的退出动画 -->
    <item name="android:activityCloseExitAnimation">@anim/slide_out_right</item>
</style>

使用场景:任何需要自定义窗口转场动画的场景------做预设动画、淡入淡出、从底部弹出等。

android:windowBackgroundBlurRadius
xml 复制代码
<item name="android:windowBackgroundBlurRadius">25dp</item>

→ API 31+,配合 windowBlurBehind 使用,设置背景模糊半径。值越大越模糊,但 GPU 开销越大。

android:backgroundDimAmount
xml 复制代码
<item name="android:backgroundDimAmount">0.6</item>

→ 配合 FLAG_DIM_BEHIND 使用,设置下层窗口变暗的程度(0.0=不变暗,1.0=全黑)。Dialog 的默认值大约是 0.5。

android:backgroundDimEnabled
xml 复制代码
<item name="android:backgroundDimEnabled">true</item>

→ 是否启用背景变暗(通常与 FLAG_DIM_BEHIND 联动)。

android:windowSplashScreenBackground (API 31+)
xml 复制代码
<item name="android:windowSplashScreenBackground">@color/splash_bg</item>

→ Android 12+ SplashScreen API 使用的启动窗口背景色。等价于在代码中:

java 复制代码
SplashScreen splashScreen = SplashScreen.installSplashScreen(this);
// 背景色通过 Theme 属性设置,代码中不需要额外调用
android:windowSplashScreenAnimationDuration (API 31+)
xml 复制代码
<item name="android:windowSplashScreenAnimationDuration">300</item>

→ SplashScreen 动画持续时间(毫秒)。注意:系统会根据应用冷启动耗时决定实际显示时长,此值只是一个参考上限。

android:windowSplashScreenIcon (API 31+)

→ SplashScreen 中心图标(通常不需要手动设置,系统用应用图标)。

android:windowContentTransitions

→ 允许窗口内容在布局变化时启用 Transition(如 TransitionManager.beginDelayedTransition)。

android:windowHideAnimation / android:windowShowAnimation

→ 已废弃,被 windowAnimationStyle 和 Transition 框架取代。


6. 完整映射表速查

Theme XML 属性 PhoneWindow 读取入口 目标字段 设置的 Flag/值
Flags 映射
windowNoDisplay 构造函数 LP.flags `FLAG_NOT_TOUCHABLE
windowDisablePreview 构造函数 LP.flags FLAG_DISABLE_PREVIEW
windowEnableSplitTouch 构造函数 LP.flags FLAG_SPLIT_TOUCH
windowShowWallpaper 构造函数 LP.flags FLAG_SHOW_WALLPAPER
windowBlurBehind 构造函数 LP.flags FLAG_BLUR_BEHIND (API 31+)
windowSecure 构造函数 LP.flags + InputChannel FLAG_SECURE
windowNotTouchable generateLayout LP.flags FLAG_NOT_TOUCHABLE
windowFullscreen generateLayout LP.flags FLAG_FULLSCREEN
windowTranslucentStatus generateLayout LP.flags FLAG_TRANSLUCENT_STATUS (+ 隐式 LAYOUT_IN_SCREEN)
windowTranslucentNavigation generateLayout LP.flags FLAG_TRANSLUCENT_NAVIGATION
windowDrawsSystemBarBackgrounds generateLayout LP.flags FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
windowDimBehind generateLayout LP.flags FLAG_DIM_BEHIND
windowCloseOnTouchOutside generateLayout LP.flags `FLAG_NOT_TOUCH_MODAL
尺寸映射
windowFixedWidth generateLayout LP.width 固定像素值
windowFixedHeight generateLayout LP.height 固定像素值
windowFixedWidthMajor generateLayout LP.width 条件覆写(长边)
windowFixedWidthMinor generateLayout LP.width 条件覆写(短边)
windowMinWidthMajor generateLayout LP.minWidth 最小宽度(长边)
windowMinWidthMinor generateLayout LP.minWidth 最小宽度(短边)
windowElevation generateLayout LP.elevation float (px)
windowIsFloating 构造函数 mIsFloatingLP.width/height/gravity WRAP_CONTENT + CENTER
系统栏映射
statusBarColor generateLayout mStatusBarColor → InsetsController Color int
navigationBarColor generateLayout mNavigationBarColor → InsetsController Color int
windowLightStatusBar generateLayout InsetsController APPEARANCE_LIGHT_STATUS_BARS
windowLightNavigationBar generateLayout InsetsController APPEARANCE_LIGHT_NAVIGATION_BARS
enforceNavigationBarContrast generateLayout InsetsController Boolean
enforceStatusBarContrast generateLayout InsetsController Boolean
软键盘映射
windowSoftInputMode generateLayout LP.softInputMode 位掩码组合
像素与绘制映射
windowIsTranslucent 构造函数 + generateLayout LP.format PixelFormat.TRANSLUCENT
windowClipToOutline generateLayout LP.flags FLAG_LAYOUT_NO_LIMITS
转场动画映射
windowActivityTransitions generateLayout mActivityTransitionsAllowed boolean
windowEnterTransition generateLayout mEnterTransition Transition ref
windowExitTransition generateLayout mExitTransition Transition ref
windowReenterTransition generateLayout mReenterTransition Transition ref
windowReturnTransition generateLayout mReturnTransition Transition ref
windowSharedElementEnterTransition generateLayout mSharedElementEnterTransition Transition ref
windowSharedElementExitTransition generateLayout mSharedElementExitTransition Transition ref
windowAllowEnterTransitionOverlap generateLayout mAllowEnterTransitionOverlap boolean
windowAllowReturnTransitionOverlap generateLayout mAllowReturnTransitionOverlap boolean
windowTransitionBackgroundFadeDuration generateLayout mBackgroundFadeDurationMillis long (ms)
背景映射
windowBackground generateLayout mBackgroundDrawable → DecorView Drawable/Color
windowBackgroundFallback generateLayout mBackgroundFallback Drawable/Color
windowBackgroundBlurBehind generateLayout mBackgroundBlurBehind boolean
windowBackgroundBlurRadius generateLayout mBackgroundBlurRadius int
backgroundDimAmount generateLayout LP.dimAmount float
Feature 映射
windowNoTitle generateLayout requestFeature(FEATURE_NO_TITLE) ---
windowActionBar generateLayout requestFeature(FEATURE_ACTION_BAR) ---
windowActionModeOverlay generateLayout requestFeature(FEATURE_ACTION_MODE_OVERLAY) ---
动画映射
windowAnimationStyle 构造函数 LP.windowAnimations style resource id
Cutout 映射
windowLayoutInDisplayCutoutMode generateLayout LP.layoutInDisplayCutoutMode int enum
Edge-to-Edge 映射
windowOptOutEdgeToEdgeEnforcement 构造函数 (API 35+) 退出强制 Edge-to-Edge boolean
其他
windowContentOverlay generateLayout mContentOverlay → DecorView Drawable
windowTitleSize generateLayout TitleView textSize px
windowSwipeToDismiss generateLayout 注册滑动监听器 boolean
windowSplashScreenBackground --- (SplashScreen API) 启动画面背景 Color
windowSplashScreenAnimationDuration --- (SplashScreen API) 启动画面动画时长 int (ms)
windowContentTransitions generateLayout mContentTransitionsAllowed boolean

7. 实战场景

以下每个场景都给出了完整的 Theme XML 配置等价的 Java 代码,以及关键属性的解释。

7.1 全屏 Activity

效果:隐藏状态栏和导航栏,Window 占据全部屏幕。

xml 复制代码
<!-- res/values/themes.xml -->
<style name="Theme.Fullscreen" parent="Theme.Material3.DayNight.NoActionBar">
    <item name="android:windowFullscreen">true</item>
    <item name="android:windowNoTitle">true</item>
</style>

等价 Java 代码

java 复制代码
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
    setContentView(R.layout.activity_fullscreen);
}

现代推荐方式(Android 11+,允许用户下滑唤出系统栏):

java 复制代码
// 使用 WindowInsetsController 而非 FLAG_FULLSCREEN
getWindow().getInsetsController().hide(WindowInsets.Type.systemBars());
// 或让系统栏以半透明方式浮在内容上方
getWindow().getInsetsController().setSystemBarsBehavior(
    WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);

7.2 透明背景 Activity / Dialog 样式 Activity

效果:Activity 的背景完全透明,可以看到下层窗口内容。

xml 复制代码
<style name="Theme.Transparent" parent="Theme.Material3.DayNight.NoActionBar">
    <item name="android:windowIsTranslucent">true</item>
    <item name="android:windowBackground">@android:color/transparent</item>
    <item name="android:windowNoTitle">true</item>
    <item name="android:windowDisablePreview">true</item>
    <item name="android:windowAnimationStyle">@null</item>
</style>

在 AndroidManifest 中使用

xml 复制代码
<activity
    android:name=".TransparentActivity"
    android:theme="@style/Theme.Transparent" />

使用范例

java 复制代码
public class TransparentActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_transparent);
        // 整个 Window 背景透明,只有 R.layout.activity_transparent
        // 中的非透明 View 可见
    }
}

Dialog 样式 Activity(与上面类似,但加上了浮动窗口行为):

xml 复制代码
<style name="Theme.DialogStyle" parent="Theme.Material3.DayNight.NoActionBar">
    <item name="android:windowIsTranslucent">true</item>
    <item name="android:windowBackground">@android:color/transparent</item>
    <item name="android:windowIsFloating">true</item>
    <item name="android:windowCloseOnTouchOutside">true</item>
    <item name="android:windowNoTitle">true</item>
    <item name="android:backgroundDimEnabled">true</item>
    <item name="android:backgroundDimAmount">0.5</item>
</style>

7.3 自定义状态栏颜色(Material 风格)

效果:状态栏颜色与 ActionBar/Toolbar 颜色统一。

xml 复制代码
<style name="Theme.CustomStatusBar" parent="Theme.Material3.DayNight.NoActionBar">
    <!-- 三部曲:缺一不可 -->
    <item name="android:windowTranslucentStatus">true</item>
    <item name="android:windowDrawsSystemBarBackgrounds">true</item>
    <item name="android:statusBarColor">@color/colorPrimaryDark</item>

    <!-- 如果是浅色状态栏,加上这个 -->
    <item name="android:windowLightStatusBar">true</item>

    <!-- 同样处理导航栏 -->
    <item name="android:windowTranslucentNavigation">true</item>
    <item name="android:navigationBarColor">@color/colorPrimaryDark</item>
</style>

Java 等价代码

java 复制代码
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
getWindow().addFlags(
    WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
getWindow().setStatusBarColor(ContextCompat.getColor(this, R.color.colorPrimaryDark));

// Android 11+
getWindow().getInsetsController().setSystemBarsAppearance(
    WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS,
    WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS);

7.4 图片查看器(沉浸式)

效果:图片充满屏幕,状态栏和导航栏以半透明浮层方式覆盖,点击可切换。

xml 复制代码
<style name="Theme.ImageViewer" parent="Theme.Material3.DayNight.NoActionBar">
    <!-- 系统栏透明,内容延伸到后面 -->
    <item name="android:windowTranslucentStatus">true</item>
    <item name="android:windowTranslucentNavigation">true</item>
    <item name="android:windowDrawsSystemBarBackgrounds">true</item>

    <!-- 状态栏和导航栏:半透明深色 -->
    <item name="android:statusBarColor">#40000000</item>
    <item name="android:navigationBarColor">#40000000</item>

    <!-- 深色系统栏图标(因为半透明深色背景上暗色图标看不清) -->
    <item name="android:windowLightStatusBar">false</item>
    <item name="android:windowLightNavigationBar">false</item>

    <!-- 背景黑色,适合图片查看 -->
    <item name="android:windowBackground">@android:color/black</item>
</style>

点击切换显示/隐藏系统栏的代码

java 复制代码
private boolean isSystemBarsVisible = true;

private void toggleSystemBars() {
    if (isSystemBarsVisible) {
        // 隐藏系统栏
        getWindow().getInsetsController().hide(
            WindowInsets.Type.systemBars());
    } else {
        // 显示系统栏
        getWindow().getInsetsController().show(
            WindowInsets.Type.systemBars());
    }
    isSystemBarsVisible = !isSystemBarsVisible;
}

7.5 锁屏上显示窗口

效果:在锁屏上方显示一个窗口(如来电界面、闹钟)。

xml 复制代码
<style name="Theme.LockScreenOverlay" parent="Theme.Material3.DayNight.NoActionBar">
    <item name="android:windowShowWhenLocked">true</item>
    <item name="android:windowTurnScreenOn">true</item>
    <item name="android:windowDismissKeyguard">true</item>
    <item name="android:windowKeepScreenOn">true</item>
</style>

⚠️ 注意windowShowWhenLockedwindowTurnScreenOnwindowDismissKeyguard 等属性不能直接在 Theme XML 中设置 ,因为它们需要特殊权限或必须在代码中使用 setFlags() 动态设置。上述 XML 仅是示意对应关系。实际实现为:

java 复制代码
// 在 Activity.onCreate() 中(必须在 setContentView 之前)
getWindow().addFlags(
    WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
    | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
    | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
    | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

7.6 禁止截屏的敏感页面

效果:阻止系统截屏和录屏。

xml 复制代码
<style name="Theme.Secure" parent="Theme.Material3.DayNight.NoActionBar">
    <item name="android:windowSecure">true</item>
</style>

Java 等价代码

java 复制代码
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
    setContentView(R.layout.activity_secure);
}

7.7 软键盘适配表单页面

场景一:聊天页面(底部输入框 + 消息列表)

xml 复制代码
<style name="Theme.Chat" parent="Theme.Material3.DayNight.NoActionBar">
    <item name="android:windowSoftInputMode">stateHidden|adjustResize</item>
</style>
  • stateHidden:进入页面时不弹出键盘
  • adjustResize:键盘弹出时压缩消息列表,使最新消息可见

场景二:登录页面(顶部 Logo + 底部输入框)

xml 复制代码
<style name="Theme.Login" parent="Theme.Material3.DayNight.NoActionBar">
    <item name="android:windowSoftInputMode">stateHidden|adjustResize</item>
</style>
  • 同样使用 adjustResize,但需要对布局做处理------如使用 ConstraintLayout 让 Logo 随窗口压缩而缩放或上移

场景三:搜索页面

xml 复制代码
<style name="Theme.Search" parent="Theme.Material3.DayNight.NoActionBar">
    <item name="android:windowSoftInputMode">stateAlwaysHidden|adjustResize</item>
</style>
  • stateAlwaysHidden:即使用户上次在别的页面打开了键盘,进入搜索页也不自动弹出

8. 最佳实践与常见陷阱

8.1 statusBarColor 不生效的 4 种可能

xml 复制代码
<!-- 正确配置(缺一不可) -->
<item name="android:windowTranslucentStatus">true</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:statusBarColor">@color/custom</item>

检查清单

  1. windowTranslucentStatus 是否为 true
  2. windowDrawsSystemBarBackgrounds 是否为 true
  3. statusBarColor 是否被父 Theme 覆盖?(使用 styles.xmlparent 链追查)
  4. ☐ 代码中是否调用了 getWindow().setStatusBarColor(Color.TRANSPARENT) 或其他 setter 覆盖了 Theme 值?

8.2 透明 Activity 背景显示为黑色

xml 复制代码
<!-- 错误 -->
<item name="android:windowIsTranslucent">true</item>

<!-- 正确:同时设置透明背景 -->
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@android:color/transparent</item>

原因windowIsTranslucent 只改变了 Surface 的像素格式(TRANSLUCENT),但 DecorView 的背景仍然从 Theme 继承为默认的白色(或主题默认颜色)。如果不覆写 windowBackground 为透明,DecorView 会用不透明的默认背景填满整个窗口。

8.3 windowSoftInputMode Theme vs 代码的优先级

Theme 默认值 → 代码覆写 :代码中的 getWindow().setSoftInputMode() 会完全覆盖 Theme 中的 windowSoftInputMode

java 复制代码
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // ★ 必须在 super.onCreate() 之后,
    //    在 setContentView() 之前调用
    getWindow().setSoftInputMode(
        WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
    setContentView(R.layout.my_layout);
}

为什么必须在 setContentView 之前? 因为 setContentView 触发了 generateLayout(),而 generateLayout() 中会检查 mHasSoftInputMode。如果代码中已经调用过 setSoftInputMode(),则 mHasSoftInputMode = true,Theme 中的值被跳过。

8.4 Flags 设置的正确姿势

java 复制代码
// addFlags:添加 flags(OR 操作,不影响其他 bit)
getWindow().addFlags(FLAG_SECURE);
// 等价于:flags |= FLAG_SECURE

// clearFlags:清除 flags(AND NOT 操作)
getWindow().clearFlags(FLAG_SECURE);
// 等价于:flags &= ~FLAG_SECURE

// setFlags(mask, value):
//   mask 中为 1 的 bit → 设为 value 中对应 bit 的值
//   mask 中为 0 的 bit → 保持不变
getWindow().setFlags(FLAG_SECURE, FLAG_SECURE);  // 打开
getWindow().setFlags(0, FLAG_SECURE);             // 关闭
// 等价于:flags = (flags & ~FLAG_SECURE) | (FLAG_SECURE & FLAG_SECURE)

// getAttributes() 后直接修改(需要额外调用 setAttributes)
WindowManager.LayoutParams lp = getWindow().getAttributes();
lp.flags |= FLAG_SECURE;
getWindow().setAttributes(lp);  // 触发 relayout

8.5 Android 15 Edge-to-Edge 迁移

targetSdk = 35 时必须面对的迁移:

老代码 (依赖 fitsSystemWindows):

xml 复制代码
<item name="android:fitsSystemWindows">true</item>

新代码(适配 Edge-to-Edge):

kotlin 复制代码
// 方案 1:在 root View 中处理 WindowInsets
ViewCompat.setOnApplyWindowInsetsListener(rootView) { view, insets ->
    val statusBars = insets.getInsets(WindowInsetsCompat.Type.statusBars())
    val navBars = insets.getInsets(WindowInsetsCompat.Type.navigationBars())
    view.setPadding(
        view.paddingLeft,
        statusBars.top,
        view.paddingRight,
        navBars.bottom
    )
    WindowInsetsCompat.CONSUMED
}

// 方案 2(不想适配):退出强制模式
// 在 Theme 中:
<item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>

8.6 windowClipToOutline 不生效?

windowClipToOutline 需要同时满足以下条件:

  1. windowClipToOutline = true
  2. windowBackground 设置了带有圆角/自定义形状的 Drawable
  3. windowElevation 已设置(需要 Z 轴高度来触发阴影/裁剪)

8.7 Shared Element Transition 闪烁

如果 Shared Element Transition 期间出现白色闪烁:

xml 复制代码
<!-- 增加背景淡出时长,平滑过渡 -->
<item name="android:windowTransitionBackgroundFadeDuration">300</item>
<!-- 确保背景不是纯白 -->
<item name="android:windowBackground">@color/window_background</item>

9. 总结

Android Theme 属性到 PhoneWindow 的映射链路核心归纳为四层:

scss 复制代码
第 1 层:定义层
  R.styleable.Window(~120 个属性),在 attrs.xml 中声明
  ↓
第 2 层:读取层
  PhoneWindow 构造函数(~15 个属性)+ generateLayout()(~80 个属性)
  通过 TypedArray 读取
  ↓
第 3 层:分发层
  根据属性类型分发到不同目标:
  - requestFeature()    → Feature flags(影响 DecorView 结构)
  - addFlags()/setFlags() → LayoutParams.flags
  - getAttributes()     → LayoutParams 各字段
  - setStatusBarColor() → InsetsController → SystemUI
  - requestFitSystemWindows() → View insets 分发
  ↓
第 4 层:消费层
  WindowManager.LayoutParams → IPC 传递 → WMS → SurfaceFlinger
  最终决定了窗口的 Z-order、尺寸、像素格式、输入事件策略、系统栏行为

核心要点

  • flags 是 Bitmask,正确理解每个 flag 的语义才能正确配置 Theme
  • 系统栏三件套windowTranslucentStatus + windowDrawsSystemBarBackgrounds + statusBarColor)是最常见的组合,缺一不可
  • windowIsTranslucent 改变了 Surface 像素格式,不仅是"背景透明",更是向下层窗口承诺了 alpha 通道
  • windowSoftInputMode 要在 setContentView 之前设置,否则 Theme 覆盖无效
  • Android 15 Edge-to-Edge 是未来趋势,尽早适配

理解这条链路,可以帮助你:

  • 精准定位 "为什么我的 window 样式不生效"
  • 在代码中精确覆写 Theme 默认值
  • 设计自定义 Dialog / 半透明 / 全屏 / 沉浸式等特殊窗口效果
  • 调试 WMS 相关的窗口行为异常

参考资料

  • AOSP frameworks/base/core/res/res/values/attrs.xml --- Window styleable 定义(120+ 属性)
  • AOSP frameworks/base/core/java/android/view/Window.java --- Window 抽象基类,flags/type 定义
  • AOSP frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java --- 唯一实现,所有消费逻辑
  • AOSP frameworks/base/core/java/android/view/WindowManager.java --- LayoutParams 类(flags 常量、字段定义)
  • AOSP frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java --- WMS,flags 的真正消费者
  • AOSP frameworks/native/services/surfaceflinger/ --- SurfaceFlinger,Surface 合成
  • Android Developer --- Window
  • Android Developer --- 系统栏
  • Android Developer --- Edge-to-Edge

本文基于 Android 14 (API 34) 源码分析,涵盖 Android 15 (API 35) 新增属性。

本文约 15000 字,阅读时间约 40 分钟。建议收藏,作为随手查阅的速查手册。

相关推荐
心中有国也有家1 小时前
AtomGit Flutter 鸿蒙客户端:错误处理与优雅降级策略
android·javascript·flutter·华为·harmonyos
-SOLO-12 小时前
停止gradle命令
android
_祝你今天愉快12 小时前
Android Binder 驱动 - 启动 ServiceManager 进程
android
农村小镇哥12 小时前
ThinkPHP-MVC什么是框架
android
换元不配限13 小时前
Jetpack Compose 状态管理指南
android·状态管理·compose·状态·单向数据流·jetpack compose·状态容器
爱笑鱼16 小时前
JNI(二):Java 里的一个 long,为什么能找到 C++ 对象?
android
爱笑鱼16 小时前
JNI(一):nativePollOnce() 到底去了哪里?
android
hqk18 小时前
玩Android×AI Coding:从产品到代码,完整跑通一套 AI 软件研发工作流
android·ai编程·vibecoding
安卓修改大师19 小时前
安卓修改大师实战:深入解析Android主题Theme修改与沉浸式界面实现
android