Unity 安卓项目适配 Freeform 小窗

一、确定你要的是哪种"小窗"

Android 上实现小窗有三种机制,容易混淆:

机制 触发者 关键配置 API 场景
Freeform 多窗口 系统手势 resizeableActivity="true" 24+ 微信飞书小窗、多任务
PiP(画中画) ❌ App 代码 supportsPictureInPicture="true" 26+ 视频播放、地图导航
Overlay 悬浮窗 ❌ App 代码 SYSTEM_ALERT_WINDOW 权限 23+ 悬浮球、工具栏

我们要的是 Freeform —— 用户手势触发,系统自动缩放窗口,应用不需要写一行代码


二、配置(只需两步)

2.1 AndroidManifest.xml

Assets/Plugins/Android/AndroidManifest.xml

<activity android:name="com.unity3d.player.UnityPlayerActivity"
    android:resizeableActivity="true"
    android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale|layoutDirection|density" />

属性说明:

  • resizeableActivity="true" — 允许系统把应用缩放为 Freeform 小窗
  • configChanges必须包含 screenLayout — 阻止切换小窗时 Activity 重建,避免 Unity 引擎重启
  • 禁止添加 supportsPictureInPicture="true" — 会与 Freeform 手势冲突,导致系统优先走 PiP

2.2 Unity ProjectSettings

在 Unity Editor 中:Edit → Project Settings → Player → Android → Resolution and Presentation → Resizable Window 勾选。

或直接编辑 ProjectSettings/ProjectSettings.asset

androidResizableWindow: 1

这是 Unity 引擎层的独立开关。默认值为 0(关闭),即使 manifest 写对了,Unity 渲染层也会拒绝窗口缩放。这是此前手势不生效的根因


三、Unity 渲染行为

3.1 3D 场景

Unity 使用 OpenGL ES / Vulkan 渲染到 Native SurfaceView。窗口缩放时:

  • Screen.width / Screen.height 自动更新
  • Camera 按新的宽高比重新计算视口
  • 3D 场景不会拉伸变形,只会改变可视范围

3.2 Canvas UI

UI Canvas 的适配取决于 Scaler 模式:

Scaler 模式 小窗下的表现
Scale With Screen Size 自适应,推荐使用
Constant Pixel Size 小窗下 UI 可能过大溢出
Constant Physical Size 小窗下 UI 可能过大溢出

建议在 Canvas Scaler 中设置 Reference Resolution(如 1920×1080),Match 设为 0.5。

3.3 画面拉伸的常见原因

以下操作会导致小窗时画面变形或出黑边:

  • ❌ 代码中调用 Screen.SetResolution() 锁死分辨率
  • ❌ 代码中硬编码 Camera.aspect 为固定值
  • ❌ Canvas Scaler 使用固定像素模式

正确做法: 不锁分辨率,不锁宽高比,Unity 会自动跟随窗口大小。


四、状态检测(可选)

4.1 为什么可选

Freeform 小窗由系统手势触发,不需要应用主动操作。状态检测的作用是锦上添花

  • 在小窗时暂停某些全屏特效
  • 在小窗时调整 UI 字体大小
  • 记录小窗状态用于数据分析

没有检测代码,手势照常生效。

4.2 Unity 中如何检测

#if UNITY_ANDROID && !UNITY_EDITOR
using (var player = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
using (var activity = player.GetStatic<AndroidJavaObject>("currentActivity"))
{
    bool isMultiWindow = activity.Call<bool>("isInMultiWindowMode");
}
#endif

也可监控 Screen.width / Screen.height 变化作为兜底方案(所有品牌通用)。

4.3 isInMultiWindowMode() 各品牌差异

品牌 返回值 生命周期回调
华为 ✅ true onResume(不走 onConfigurationChanged
小米 ✅ true onConfigurationChanged
OPPO ❌ 小窗返回 false onConfigurationChanged
vivo ✅ true onConfigurationChanged
三星 ✅ true onConfigurationChanged

OPPO 的 bug 只能用窗口尺寸 vs 屏幕尺寸对比来判断。Unity 中直接监听 Screen 分辨率变化即可覆盖。


五、按品牌适配总结

适配点 需按品牌处理? 说明
触发手势 系统 ROM 负责,应用不参与
manifest 配置 resizeableActivity="true" 全厂商通用
Unity ProjectSettings androidResizableWindow: 1 一次配置
3D 渲染 Unity 自动处理
Canvas UI 选对 Scaler 模式即可
多窗口状态检测 ⚠️ OPPO 的 isInMultiWindowMode() 小窗下返回 false,用分辨率兜底
Android 原生 UI Unity 用 SurfaceView 渲染,不受 Dialog/PopupWindow/StatusBar 等 OEM 魔改影响

简单说:完全不需要按品牌分别写代码或配置。


六、避坑清单

后果 正确做法
加了 supportsPictureInPicture="true" 系统手势走 PiP 而非 Freeform 不加此属性
androidResizableWindow 未开启 即使 manifest 正确,手势也不生效 设为 1
configChanges 缺少 screenLayout 切小窗时 Activity 重建,Unity 引擎重启 补上此属性
代码里调用 Screen.SetResolution() 小窗时画面变形或黑边 不锁定分辨率
Canvas Scaler 用固定像素模式 小窗下 UI 溢出或太小 改用 Scale With Screen Size

七、参考

posted @ 2026-05-07 16:51  月陌  阅读(65)  评论(0)    收藏  举报