WebToApp 悬浮窗(Floating Window)模式完全指南:配置项详解、系统权限与源码实现原理
【免费下载链接】web-to-appThe most full featured web-to-app toolkit on Android, a complete APK workshop that runs entirely on your phone项目地址: https://gitcode.com/GitHub_Trending/web/web-to-app
本篇指南围绕 WebToApp 项目中 floating-window.md 所定义的「悬浮窗模式」展开,讲解如何在Edit Common Config编辑器中通过Floating window卡片,把一个 Web 应用以可自由缩放、可最小化、可吸附屏幕边缘的悬浮窗形态运行在其他应用之上。读完本文,你将掌握floatingWindowConfig的全部配置字段、各枚举取值(宽高比与边框样式)、系统悬浮窗权限的授予流程,以及前端配置卡片、运行时服务(FloatingWindowService)与导出链路(ApkConfigJsonFactory)之间的完整调用关系。
功能概览与适用场景
悬浮窗模式的核心能力是:不退出当前 App,也能同时使用另一个 Web 应用。典型场景包括:
- 看视频 / 直播的同时,用悬浮小窗继续浏览网页内容;
- 聊天类、文档类 Web 应用以画中画(Picture-in-Picture)形态常驻屏幕角落;
- 临时把一个网页最小化为可拖动的圆形按钮,需要时一键还原。
从 FloatingWindowService.kt 的实现看,悬浮窗本质是一个**前台服务(foreground service)**叠加的TYPE_APPLICATION_OVERLAY窗口,窗口内部承载一个完整配置过的 WebView(含下载桥、翻译桥、原生桥等能力)。因此它不仅适合纯网页,也能承载 PHP、Python、Go、Node.js、WordPress 等需要服务端运行时的应用类型。
配置入口:Edit Common Config 中的 Floating Window 卡片
在应用编辑器的Edit Common Config页中,找到Floating window卡片,打开顶部的启用开关即可进入悬浮窗模式配置。该卡片的 UI 由 FloatingWindowConfigCard.kt 实现,卡片内按Size(尺寸)→ Appearance(外观)→ Behavior(行为)→ Advanced(高级)四个区块组织所有可调项。
配置数据在内存中统一建模为FloatingWindowConfig(定义于 WebApp.kt),所有字段都有默认值,这意味着你可以只改其中一两个字段,其余行为保持默认。
配置项详解
Enable —— 总开关
对应floatingWindowConfig.enabled(默认false)。只有该字段为true时,导出/运行时才会以悬浮窗形态启动应用;关闭则走常规全屏 WebView 模式。
Size —— 窗口尺寸与宽高比
尺寸相关字段及其默认值如下:
| 字段 | 默认值 | 说明 |
|---|---|---|
windowSizePercent | 80 | 统一尺寸百分比(宽高同步缩放) |
widthPercent/heightPercent | 80/80 | 独立宽 / 高百分比 |
lockAspectRatio | false | 是否锁定宽高比 |
aspectRatioMode | FREE | 宽高比模式(见下) |
customAspectRatioWidth/customAspectRatioHeight | 16/9 | 自定义宽高比(仅CUSTOM模式生效) |
在 UI 卡片中,宽度滑条范围为30%~100%;仅当宽高比模式为FREE时才会显示独立的高度滑条。选择任意非FREE的宽高比模式时,卡片会自动把lockAspectRatio置为true。
FloatingWindowAspectRatioMode枚举(WebApp.kt)支持以下取值:
| 枚举值 | 含义 |
|---|---|
SCREEN | 跟随屏幕宽高比 |
FREE | 自由缩放(默认) |
RATIO_16_9 | 16:9 |
RATIO_9_16 | 9:16(竖屏视频友好) |
RATIO_4_3 | 4:3 |
SQUARE | 1:1 |
CUSTOM | 自定义宽:高(滑条范围 1~32) |
文档强调:默认是自由缩放(lockAspectRatio = false、FREE),拖拽窗口四角可以任意改变形状;一旦锁定宽高比,缩放时会被约束在该比例内。从源码看,这个逻辑由 FloatingWindowManager.kt 中的resolveAspectRatio()与calculateBoundedAspectSize()实现:FREE返回null(不约束),其余模式返回对应比例值并在宽/高方向做有界换算。值得注意的一个细节是:SCREEN模式只有在lockAspectRatio = true时才按屏幕比例生效,否则会被降级为FREE处理。
窗口的实际最小尺寸在运行时也有硬约束:拖动缩放手柄时最小宽度为屏幕宽度的 25%、最小高度为屏幕高度的 20%,且不会超过屏幕边界(见createResizeHandle()与clampWindowParamsToScreen())。
Appearance —— 外观
| 字段 | 默认值 | 取值范围 | 说明 |
|---|---|---|---|
opacity | 100 | 30~100(%) | 悬浮窗整体不透明度 |
cornerRadius | 16 | 0~32(dp) | 窗口圆角半径 |
borderStyle | SUBTLE | 见下 | 边框样式 |
FloatingBorderStyle枚举(WebApp.kt)共 4 档,其视觉差异在 FloatingWindowManager.kt 的createStyledBackground()中体现:
| 枚举值 | 视觉效果 |
|---|---|
NONE | 无边框 |
SUBTLE | 2px 深色细边框(#333355),默认档 |
GLOW | 3px 亮蓝边框(#6666FF),视觉上带发光感 |
ACCENT | 3px 紫色强调边框(#8B5CF6) |
不透明度opacity会直接映射到WindowManager.LayoutParams.alpha(opacity / 100f),属于窗口级透明,整窗(含 WebView 内容)都会随之变淡。
Title bar —— 标题栏
| 字段 | 默认值 | 说明 |
|---|---|---|
showTitleBar | true | 是否显示标题栏 |
autoHideTitleBar | false | 是否自动隐藏标题栏(仅显示时生效) |
标题栏高度固定为 40dp,布局模仿 macOS 风格:左侧是三个「红黄绿」圆形按钮(关闭 / 最小化 / 全屏切换),中间显示应用名(空则显示 "WebToApp"),右侧是返回‹与前进›两个导航按钮。返回/前进按钮的可用态由updateNavigationButtons()根据webView.canGoBack()/canGoForward()实时刷新(不可用时置灰、透明度降到 0.35)。
开启autoHideTitleBar后,标题栏会在 3 秒无操作后上滑淡出(AUTO_HIDE_TITLE_DELAY_MS = 3000L),用户一旦触摸窗口任意位置即恢复显示。若关闭标题栏且未锁定位置,窗口顶部会出现一条 22dp 高的拖拽条(带⠿指示符),保证依然可以拖动窗口。
Minimized —— 最小化行为
| 字段 | 默认值 | 说明 |
|---|---|---|
startMinimized | false | 启动时直接进入最小化状态 |
minimizedIconPath | null | 自定义最小化图标路径 |
minimizedIconSizePercent | 100 | 最小化按钮尺寸百分比(50~100) |
minimizedIconEdgeDocking | false | 最小化图标是否吸附屏幕边缘 |
最小化按钮的基础尺寸为 56dp,乘以minimizedIconSizePercent / 100得到最终大小。图标支持三种来源(见loadMinimizedIconBitmap()):content://URI、绝对路径、assets/相对路径;未配置时使用默认的 🌐 图形。开启minimizedIconEdgeDocking后,按钮会以 180ms 的带过冲(Overshoot)动画滑向最近屏幕边缘,并只露出 50% 的宽度/高度(MINI_BUTTON_EDGE_VISIBLE_RATIO = 0.5f),形成「贴边收窄」的常驻效果。点击按钮即可还原窗口,还原位置由最小化按钮位置反推居中计算。
Behavior —— 行为与高级选项
| 字段 | 默认值 | 说明 |
|---|---|---|
rememberPosition | true | 记住窗口位置,下次启动恢复 |
edgeSnapping | true | 拖动松手时吸附屏幕边缘 |
showResizeHandle | true | 右下角显示缩放手柄 |
lockPosition | false | 锁定位置,禁止拖动 |
- 记住位置:窗口坐标写入
SharedPreferences(文件floating_window_prefs,键position_x/position_y),下次启动时优先恢复上次坐标,并做屏幕边界钳制;首次启动默认居中。 - 边缘吸附:拖动松手后,若窗口任一边距屏幕边缘小于 60px(
EDGE_SNAP_THRESHOLD_PX),会以 200ms 动画平滑吸附到对应边缘(performEdgeSnap())。 - 缩放手柄:28dp 大小的
◢手柄固定在窗口右下角,拖动时按当前宽高比模式实时更新LayoutParams。 - 锁定位置:开启后标题栏/拖拽条不再响应拖动手势(
setupDragHandler只在!config.lockPosition时挂载)。
这些开关都集中在卡片底部「Advanced」折叠区,属于进阶调优项。
运行前提:系统悬浮窗权限
文档明确提示:悬浮窗运行需要系统悬浮窗(overlay)权限。这是 Android 的安全限制,不是应用内开关能绕过的。
权限的检查与请求封装在 FloatingWindowService.kt 的伴生对象中:
canDrawOverlays(context):API 23(Android 6.0)及以上通过Settings.canDrawOverlays()检查,以下直接放行;requestOverlayPermission(context):跳转Settings.ACTION_MANAGE_OVERLAY_PERMISSION(带package:参数)引导用户手动授权。
在实际启动链中,ShellActivity.kt 会先检查权限:已授权则直接launchFloatingWindowAndFinish();未授权则发起请求并把启动动作挂起到pendingFloatingWindowLaunch,待用户授权返回后(onResume中再次检查)再补启动。窗口本身使用TYPE_APPLICATION_OVERLAY(Android 8.0+)或旧版TYPE_PHONE类型创建,并常驻一条低优先级前台通知(channelfloating_window_channel,可一键关闭悬浮窗)。
源码级实现:从配置到窗口的完整链路
1. 配置建模
所有配置字段收敛在 FloatingWindowConfig 这一个 data class 中,直接以 Gson 序列化传递。它既用于宿主 App 的编辑器,也用于导出 APK 后的运行时解析(JSON 键名与字段名一一对应)。
2. 运行时启动链路
悬浮窗由前台服务 FloatingWindowService.kt 驱动,通过 Intent Extra 传递配置:
ShellActivity.launchFloatingWindowAndFinish() → startForegroundService(FloatingWindowService, ACTION_SHOW) → 解析 EXTRA_CONFIG(JSON) → FloatingWindowConfig → 组装 WebView 配置器(翻译桥/下载桥/原生桥/扩展模块/伪装配置) → FloatingWindowManager.show(config, appName, url)服务支持 4 种动作:ACTION_SHOW(显示)、ACTION_DISMISS(关闭并停服)、ACTION_MINIMIZE、ACTION_RESTORE,后两个供外部(如通知、辅助入口)控制窗口状态。服务以START_NOT_STICKY返回,销毁时会依次停止服务端运行时、释放 WebView、移除全部悬浮视图。
3. 服务端运行时应用类型
若目标应用属于PHP_APP/PYTHON_APP/GO_APP/NODEJS_APP/WORDPRESS,服务会先以about:blank显示空窗口,再通过ShellServerLauncher.resolveServerBackedTargetUrl()异步拉起对应服务端运行时,拿到真实 URL 后注入 WebView(needsServerStartup()+ 协程分支)。这意味着悬浮窗模式不是网页专属,而是完整复用了 WebToApp 的本地运行时体系。
4. 导出与持久化
导出 APK 时,ApkConfigJsonFactory.kt 会把FloatingWindowConfig全量字段写入floatingWindowConfigJSON 块;对应测试 ApkConfigJsonFactoryTest.kt 验证了aspectRatioMode = "RATIO_16_9"、customAspectRatioWidth = 21、customAspectRatioHeight = 9、minimizedIconSizePercent = 70、minimizedIconEdgeDocking = true等取值能无损往返。而 ShellWebViewConfig.kt 则展示了从 ShellConfig 反解出运行时FloatingWindowConfig的兼容性处理:枚举解析失败时回退到「锁比例则 SCREEN、否则 FREE」的兜底策略,边框解析失败回退SUBTLE。
限制与注意事项
- 权限是硬前提:未授予系统悬浮窗权限时窗口无法显示,授权入口在系统设置页,代码无法静默获取;
- 前台服务限制:在 targetSdk 34+ 上,后台直接启动前台服务可能被系统拒绝(
ShellActivity已捕获该异常并记录日志),实际使用建议从应用前台触发; - 焦点与输入法:窗口默认
FLAG_NOT_FOCUSABLE(不抢焦点、不弹输入法),当页面内输入框获得焦点时,通过注入的IME_FOCUS_TRACKER_JS桥接动态切换焦点状态以唤起输入法,失焦后自动释放——这是悬浮窗场景下保证「悬浮但不打扰」的关键设计; - 部分 Web 能力受限:从
FloatingWindowService的WebViewCallbacks回调可看到,文件选择、地理定位、权限请求在悬浮窗中默认被拒绝/关闭,属于有意的精简策略; - 尺寸与比例钳制:窗口百分比在 30%~100% 之间,比例锁定后按宽/高方向有界换算,不会出现窗口越界或无限缩小。
小结
悬浮窗模式是 WebToApp 面向「多任务并行」场景的关键能力。通过 Edit Common Config 中的 Floating window 卡片,你可以像配置一个桌面级画中画窗口一样,精确控制尺寸、宽高比、外观、标题栏、最小化行为与吸附逻辑;底层则由FloatingWindowConfig → FloatingWindowService → FloatingWindowManager → WindowManager的四层链路承载,从编辑器配置到导出 APK 后的运行时表现保持完全一致。掌握本文的字段语义与权限流程,即可在自己的 Web 应用中一键启用可缩放、可最小化、可贴边的悬浮窗体验。
【免费下载链接】web-to-appThe most full featured web-to-app toolkit on Android, a complete APK workshop that runs entirely on your phone项目地址: https://gitcode.com/GitHub_Trending/web/web-to-app
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考