6. Window Flag 实战:FLAG_NOT_FOCUSABLE、FLAG_KEEP_SCREEN_ON、FLAG_TRANSLUCENT 等常用 Flag

Window Flag,说白了就是窗口的「行为开关」。你给窗口设置一个 Flag,系统就知道该怎么对待它——要不要抢焦点、要不要保持屏幕常亮、要不要半透明。我刚开始接触这部分时,总觉得 Flag 太多记不住,后来发现其实常用的就那么几个,吃透了它们,大部分窗口定制需求都能搞定。

6.1 FLAG_NOT_FOCUSABLE:不抢焦点的「隐形窗口」

这个 Flag 我用的最多。它的作用很简单:窗口不获取焦点,也不接收按键事件。但注意,它依然能接收触摸事件。

核心行为: 设置了 FLAG_NOT_FOCUSABLE 的窗口,不会从当前焦点窗口抢走焦点。用户按键时,事件会穿透到下面的窗口。

我在项目中遇到过这样一个场景:需要做一个全局悬浮球,点击后弹出菜单。如果悬浮球窗口抢了焦点,用户就无法操作背后的应用了。这时候 FLAG_NOT_FOCUSABLE 就派上了用场。

// 创建一个不抢焦点的悬浮窗口
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.gravity = Gravity.TOP | Gravity.START;

// 注意:不抢焦点的窗口,默认也不会接收触摸事件
// 需要额外设置 FLAG_NOT_TOUCH_MODAL 或 FLAG_WATCH_OUTSIDE_TOUCH
params.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
小技巧: FLAG_NOT_FOCUSABLE 经常和 FLAG_NOT_TOUCH_MODAL 搭配使用。前者让窗口不抢焦点,后者让窗口外的触摸事件能穿透到下层。两者结合,就是典型的「看得见但不干扰用户操作」的窗口。

6.2 FLAG_KEEP_SCREEN_ON:屏幕常亮的「守护者」

这个 Flag 我愿称之为「最省心的 Flag」。你不需要管理 WakeLock,不需要申请电源权限,只需要在 WindowManager.LayoutParams 里加一个标志位,系统就会帮你保持屏幕常亮。

为什么会这样?因为 WindowManager 在底层会帮你管理一个系统级的 WakeLock。窗口显示时自动持有,窗口移除时自动释放。你想想看,这比你自己手动管理 WakeLock 要安全多少倍?

// 视频播放器窗口保持屏幕常亮
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
// 其他参数设置...

// 或者更简单的做法:在 Activity 的 Window 上设置
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
注意: FLAG_KEEP_SCREEN_ON 只在窗口可见时生效。如果窗口被覆盖或隐藏,屏幕常亮效果会自动取消。我曾经踩过这个坑——以为设置了 Flag 就万事大吉,结果窗口被系统弹窗盖住后屏幕就暗了。

6.3 FLAG_TRANSLUCENT:半透明窗口的「开关」

这个 Flag 在 Android 4.4 之前是主流方案,用来实现状态栏和导航栏的透明效果。但现在我建议你优先使用 WindowInsetsControllerSYSTEM_UI_FLAG_LAYOUT_STABLE 等新 API。

不过,FLAG_TRANSLUCENT 在自定义悬浮窗时依然有用。比如做一个半透明的引导层,或者毛玻璃效果的悬浮面板。

// 设置半透明状态栏(旧方案,兼容老设备)
Window window = getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);

// 设置半透明导航栏
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
实际效果: FLAG_TRANSLUCENT_STATUS 会让状态栏变成半透明,并且应用的内容可以绘制到状态栏下方。但注意,这不会自动调整布局,你需要手动给根布局加上 android:fitsSystemWindows="true"

6.4 其他常用 Flag 速查

除了上面三个,还有几个 Flag 我经常用到。这里整理成表格,方便你快速查阅:

Flag 名称 作用 使用场景
FLAG_NOT_TOUCH_MODAL 窗口外的触摸事件可以穿透到下层窗口 悬浮窗、小工具窗口
FLAG_WATCH_OUTSIDE_TOUCH 监听窗口外的触摸事件(不消费事件) 点击窗口外部关闭弹窗
FLAG_LAYOUT_NO_LIMITS 窗口可以绘制到屏幕安全区域之外 全屏沉浸式体验
FLAG_FULLSCREEN 隐藏状态栏(已废弃,建议用新 API) 老项目兼容
FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS 允许窗口绘制系统栏背景 自定义状态栏颜色

6.5 Flag 组合实战:一个悬浮控制面板

光说不练假把式。我们来看一个实际案例:做一个悬浮控制面板,可以拖动,点击外部自动关闭,并且不干扰用户操作。

// 悬浮控制面板的 LayoutParams
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
params.width = 300;
params.height = 400;
params.gravity = Gravity.TOP | Gravity.START;
params.x = 100;
params.y = 200;

// 组合 Flag
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
             | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
             | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;

// 设置半透明背景
params.format = PixelFormat.TRANSLUCENT;

// 添加窗口
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
wm.addView(panelView, params);
避坑指南: 我曾经在组合 FLAG_NOT_FOCUSABLE 和 FLAG_WATCH_OUTSIDE_TOUCH 时,发现点击窗口外部时收不到事件。后来排查发现,FLAG_WATCH_OUTSIDE_TOUCH 需要配合 FLAG_NOT_TOUCH_MODAL 才能正常工作。嗯,这个细节文档里写得很隐晦,我折腾了一下午才找到原因。

6.6 Flag 优先级与冲突处理

有些 Flag 之间会互相影响。比如 FLAG_NOT_FOCUSABLE 和 FLAG_KEEP_SCREEN_ON 一起使用时,屏幕常亮依然有效——因为常亮是系统层面的行为,和焦点无关。

但有些组合就需要小心了:

  • FLAG_NOT_FOCUSABLE + FLAG_NOT_TOUCHABLE:窗口既不抢焦点也不接收触摸,基本就是个「装饰品」
  • FLAG_KEEP_SCREEN_ON + FLAG_DISMISS_KEYGUARD:屏幕常亮且解锁,适合导航类应用
  • FLAG_TRANSLUCENT_STATUS + FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS:两者功能重叠,建议只用一个

6.7 知识体系总览

下面这张图梳理了本章的核心知识点,方便你建立整体认知:

Window Flag 知识体系 Window Flag FLAG_NOT_FOCUSABLE FLAG_KEEP_SCREEN_ON FLAG_TRANSLUCENT 不抢焦点 不接收按键事件 可接收触摸事件 自动管理 WakeLock 窗口可见时生效 无需电源权限 状态栏/导航栏半透明 内容可延伸到系统栏 需配合 fitsSystemWindows 组合使用:FLAG_NOT_FOCUSABLE + FLAG_NOT_TOUCH_MODAL 悬浮窗、引导层、全局覆盖层等场景的标配组合

6.8 总结

Window Flag 是窗口行为的「开关集合」。你不需要记住所有 Flag,但一定要理解每个 Flag 背后的行为逻辑。我个人习惯是:先确定窗口要不要焦点,再决定触摸事件怎么处理,最后考虑屏幕常亮等特殊需求。按这个顺序来,基本不会出错。

嗯,Flag 这块内容其实不难,但细节很多。建议你在真机上多试试不同的组合,看看实际效果。毕竟纸上得来终觉浅,绝知此事要躬行嘛。

公众号:蓝海资料掘金营,微信deep3321