5. WindowManager.LayoutParams 详解:type、flags、token、gravity、x/y、width/height、format 等核心属性
说实话,WindowManager.LayoutParams 这个类,我每次看源码都觉得它像个「百宝箱」。它定义了窗口的几乎所有行为特征。你想想看,一个窗口在屏幕上怎么显示、能不能被触摸、要不要显示在状态栏下面……这些全由它说了算。
今天我们就把它拆开揉碎了讲。我会结合我这些年踩过的坑,帮你把每个核心属性都吃透。
5.1 type:窗口的「身份牌」
type 决定了窗口的层级和用途。说白了,就是告诉系统:「我是谁,我该待在哪一层」。
Android 把窗口分成了三大类,我习惯用一张表来记:
| 类型 | 取值范围 | 典型用途 | 层级特点 |
|---|---|---|---|
| 应用窗口 | FIRST_APPLICATION_WINDOW ~ LAST_APPLICATION_WINDOW (1~99) | Activity 的主窗口 | 最底层,受 Activity 生命周期约束 |
| 子窗口 | FIRST_SUB_WINDOW ~ LAST_SUB_WINDOW (1000~1999) | PopupWindow、Dialog 等 | 依附于父窗口,随父窗口移动 |
| 系统窗口 | FIRST_SYSTEM_WINDOW ~ LAST_SYSTEM_WINDOW (2000~2999) | Toast、状态栏、输入法 | 最高层,需要系统权限 |
这里有个坑,我必须要说。我曾经在项目里直接用 TYPE_SYSTEM_OVERLAY 做悬浮窗,结果在 Android 10 以上的设备上直接崩溃。为什么?因为系统对 overlay 权限收紧了。现在你必须用 TYPE_APPLICATION_OVERLAY,并且申请 SYSTEM_ALERT_WINDOW 权限。
TYPE_PHONE、TYPE_SYSTEM_ALERT 等旧类型已被废弃。请统一使用 TYPE_APPLICATION_OVERLAY。
5.2 flags:窗口的「行为开关」
flags 是一个 int 型的位掩码。每个 bit 控制一个行为。我刚开始学的时候觉得这玩意儿很绕,后来发现其实就两个思路:要么控制窗口「怎么显示」,要么控制窗口「怎么交互」。
常用的 flags 我列几个:
- FLAG_NOT_FOCUSABLE:窗口不获取焦点。触摸事件会穿透到下层窗口。做悬浮窗必备。
- FLAG_NOT_TOUCHABLE:窗口不响应触摸。相当于一个「透明玻璃」。
- FLAG_KEEP_SCREEN_ON:保持屏幕常亮。做视频播放器时常用。
- FLAG_FULLSCREEN:隐藏状态栏。注意,这个和系统 UI 的隐藏逻辑不同。
- FLAG_LAYOUT_IN_SCREEN:窗口布局时忽略状态栏区域。配合
FLAG_FULLSCREEN使用效果更佳。
嗯,这里有个小技巧。如果你想让一个窗口既不可触摸,又不可聚焦,可以这样写:
LayoutParams params = new LayoutParams();
params.flags = LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_NOT_TOUCHABLE;
用「或」运算组合多个 flag,这是 Android 的经典做法。
5.3 token:窗口的「通行证」
token 是个容易让人困惑的属性。我一开始以为它就是个标识符,后来才发现它其实是窗口和某个「宿主」绑定的凭证。
简单说,token 的作用就是:告诉 WindowManager,这个窗口属于谁。
比如,你通过 Dialog 添加一个窗口,系统会自动把 Dialog 的 token 传进去。如果你自己手动添加窗口,就必须从 Activity 或 View 中获取 token:
// 从 Activity 获取 token
IBinder token = activity.getWindow().getAttributes().token;
// 或者从 View 获取
IBinder token = view.getWindowToken();
5.4 gravity:窗口的「定位锚点」
gravity 决定了窗口在屏幕上的位置。它和 View 的 gravity 类似,但作用范围是整个屏幕。
常用的值:
Gravity.TOP | Gravity.LEFT:左上角Gravity.CENTER:正中间Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL:底部居中
配合 x/y 使用,可以实现精确的偏移定位。比如:
params.gravity = Gravity.TOP | Gravity.LEFT;
params.x = 100; // 距离屏幕左边 100px
params.y = 200; // 距离屏幕顶部 200px
这里有个细节:x 和 y 的偏移方向是相对于 gravity 指定的锚点。如果 gravity 是 Gravity.CENTER,那么 x 和 y 就是从屏幕中心开始偏移。
5.5 x/y 与 width/height:尺寸与位置
这两个属性组决定了窗口的「物理存在」。
x/y:窗口左上角相对于 gravity 锚点的偏移量。单位是像素。
width/height:窗口的宽高。可以指定具体像素值,也可以用 WRAP_CONTENT 或 MATCH_PARENT。
我习惯这样理解:gravity 定「锚点」,x/y 定「偏移」,width/height 定「大小」。三者配合,就能把窗口放在屏幕的任何位置。
LayoutParams params = new LayoutParams();
params.gravity = Gravity.CENTER;
params.x = 0;
params.y = 0;
params.width = 500;
params.height = 300;
// 这样窗口就会出现在屏幕正中央,宽 500px,高 300px
5.6 format:窗口的「像素格式」
format 指定了窗口的像素格式。说白了,就是告诉系统:「我的窗口要用什么颜色深度来渲染」。
常用的值:
- PixelFormat.TRANSPARENT:透明。适合悬浮窗、弹幕等。
- PixelFormat.TRANSLUCENT:半透明。性能比 TRANSPARENT 好一点。
- PixelFormat.OPAQUE:不透明。性能最好,但窗口不能有透明区域。
- PixelFormat.RGBA_8888:32 位色深,支持透明度。最常用。
PixelFormat.OPAQUE。这能让 SurfaceFlinger 跳过混合步骤,提升渲染性能。我在做视频播放器时,把播放窗口从 RGBA_8888 改成 OPAQUE,帧率提升了 5% 左右。
5.7 其他重要属性
除了上面这些,还有几个属性值得提一下:
- softInputMode:控制软键盘弹出时窗口的调整行为。比如
SOFT_INPUT_ADJUST_RESIZE或SOFT_INPUT_ADJUST_PAN。 - screenOrientation:强制窗口的屏幕方向。一般用在 Activity 上,但也可以对系统窗口设置。
- alpha:窗口的整体透明度。0.0 完全透明,1.0 完全不透明。
- dimAmount:窗口后面的变暗程度。Dialog 常用这个效果。
5.8 知识体系总览
下面这张图是我自己整理的 LayoutParams 核心属性关系图。你看一眼就能明白它们各自负责什么:
5.9 实战中的组合用法
光讲理论没意思。我分享一个我实际做过的悬浮球功能,看看这些属性怎么配合:
LayoutParams params = new LayoutParams();
params.type = LayoutParams.TYPE_APPLICATION_OVERLAY; // 系统悬浮窗
params.flags = LayoutParams.FLAG_NOT_FOCUSABLE // 不抢焦点
| LayoutParams.FLAG_NOT_TOUCH_MODAL // 事件不阻塞
| LayoutParams.FLAG_LAYOUT_IN_SCREEN; // 全屏布局
params.gravity = Gravity.TOP | Gravity.LEFT; // 左上角锚点
params.x = 100; // 初始位置
params.y = 200;
params.width = 80; // 悬浮球大小
params.height = 80;
params.format = PixelFormat.TRANSLUCENT; // 半透明
params.token = service.getWindowToken(); // 从 Service 获取 token
你看,每个属性都有明确的职责。type 决定能不能显示,flags 决定交互行为,gravity 和 x/y 决定位置,width/height 决定大小,format 决定渲染效果。缺一个都不行。
TYPE_APPLICATION_OVERLAY 的窗口默认会显示在状态栏下面。如果你想让窗口覆盖状态栏,必须加上 FLAG_LAYOUT_IN_SCREEN 和 FLAG_LAYOUT_NO_LIMITS。这个坑我花了半天才找到原因。
好了,LayoutParams 的核心属性就讲到这里。每个属性都不复杂,但组合起来能玩出很多花样。你只要记住:type 定身份,flags 定行为,gravity 定锚点,x/y 定偏移,width/height 定大小,format 定画质。下次写窗口代码时,按这个思路来,基本不会出错。