更新记录
1.0.2(2026-10-22)
发版
1.0.1(2026-10-22)
修复:Android 上 openFloatWindow 返回成功但悬浮窗看不见
- z-order:uni-app x 的页面根视图在
onLoad之后才挂到decorView,后加的子 View 会盖住 悬浮窗。现在挂载后立即bringToFront(),并在 0/120/600ms 各补一次,同时监听decorView布局变化持续纠正(close()时移除监听)。 - 面板不再全透明:
WebView背景由TRANSPARENT改为白色(视频内容用黑色)。 之前页面未加载/加载失败时,整个窗口等于隐形。 - 尺寸按密度换算:
sizePreset/size/ballSize/panelSize/edgePadding语义是 dp, 之前当 px 下发,3x 屏上只有 1/3 大小(medium 仅 116×87dp)。现按viewport.density换算。 - 诊断增强:
getFloatWindowDiagnostics新增windowOnTop(是否在 decorView 最上层)、windowFrame(原生 view 真实宽高/可见性)、density,可直接定位「挂上了但看不见」。
平台兼容性
uni-app(5.13)
| Vue2 | Vue3 | Chrome | Safari | app-vue | app-nvue | Android | iOS | 鸿蒙 |
|---|---|---|---|---|---|---|---|---|
| √ | - | - | - | - | - | 4.4 | 12 | √ |
| 微信小程序 | 支付宝小程序 | 抖音小程序 | 百度小程序 | 快手小程序 | 京东小程序 | 鸿蒙元服务 | QQ小程序 | 飞书小程序 | 小红书小程序 | 快应用-华为 | 快应用-联盟 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| - | - | - | - | - | - | - | - | - | - | - | - |
uni-app x(5.13)
| Chrome | Safari | Android | iOS | 鸿蒙 | 微信小程序 |
|---|---|---|---|---|---|
| - | - | 5.0 | 12 | √ | - |
其他
| 多语言 | 暗黑模式 | 宽屏模式 | 蒸汽模式 |
|---|---|---|---|
| × | × | × | √ |
ai-hzh 悬浮窗与画中画
面向 uni-app / uni-app x 的悬浮窗、系统画中画、H5 ↔ App 双向通信 UTS 插件。 一套 API 覆盖 Android / iOS / HarmonyOS,支持应用内悬浮、Android 跨应用悬浮、球-面板切换、 拖拽吸附、位置记忆、远程 H5 通信与视频画中画。
- 插件 ID:
ai-hzh - 版本:
1.0.1 - 要求:HBuilderX 5.13+ / uni-app x 5.13+,必须制作自定义基座(含原生能力)
界面预览
App 内原生播放
| iOS | Android | HarmonyOS |
|---|---|---|
![]() |
![]() |
![]() |
画中画浮在手机桌面
| iOS | Android | HarmonyOS |
|---|---|---|
![]() |
![]() |
![]() |
iOS 截图为真机(模拟器
isPictureInPictureSupported()恒为false,PiP 只能真机验证); Android 为 Android 15 模拟器,HarmonyOS 为 DevEco 模拟器。
1. 平台能力矩阵
| 能力 | Android | iOS | HarmonyOS |
|---|---|---|---|
应用内悬浮(inApp) |
✅ | ✅ | ✅ |
跨应用悬浮(overlay) |
✅ 需系统权限 | ❌ 自动降级为 inApp |
❌ 自动降级为 inApp |
| 球 / 面板切换 | ✅ | ✅ | ✅ |
| 拖拽 / 边缘吸附 / 位置记忆 | ✅ | ✅ | ✅ |
| 尺寸预设 / 自定义尺寸 | ✅ | ✅ | ✅ |
网页内容(url) |
✅ | ✅ | ✅ |
本地资源(localAsset) |
✅ | ✅ | ✅ |
本地文件(localFile) |
❌ | ❌ | ✅(仅画中画) |
| H5 ↔ App 双向通信 | ✅ | ✅ | ✅ |
| 网页摄像头 / 麦克风 | ✅ | ✅ | ❌ |
| 系统画中画 | ✅ Android 8.0+ | ✅ | ✅ |
| 画中画播放/暂停/跳转 | ✅ | ✅ | ✅ |
宿主唤回(bringHostAppToFront) |
✅ | ❌ | ❌ |
不支持的能力不会静默失败,会走
fail并返回对应错误码(见第 6 节)。
2. 安装
- 把
uni_modules/ai-hzh整个目录放进项目(不要改目录名,不要只拷 utssdk 里的文件)。 - HBuilderX 中「运行 → 运行到手机或模拟器 → 制作自定义调试基座」,安装后运行。
- 只从插件根目录导入 API:
import { openFloatWindow, closeFloatWindow } from '@/uni_modules/ai-hzh'
3. 快速开始
import {
AIHZHError,
FloatWindowCapabilities,
FloatWindowState,
H5MessagePayload,
getCapabilities,
openFloatWindow,
closeFloatWindow,
onH5Message
} from '@/uni_modules/ai-hzh'
getCapabilities({
success: (capabilities: FloatWindowCapabilities): void => {
if (!capabilities.inApp) {
console.log('当前平台不支持悬浮窗', capabilities.restrictedReason)
return
}
openFloatWindow({
mode: 'inApp',
content: { type: 'url', value: 'https://example.com/panel' },
sizePreset: 'medium',
dragEnabled: true,
edgeSnap: true,
rememberPosition: true,
success: (state: FloatWindowState): void => {
console.log('已打开', state.position, state.size)
},
fail: (err: AIHZHError): void => {
console.log('打开失败', err.errCode, err.errMsg)
}
})
}
})
// 事件 API 收的是 { listener } 对象,不是裸回调
onH5Message({
listener: (message: H5MessagePayload): void => {
console.log('H5 发来消息', message.event, message.data)
}
})
.uvue 里必须给回调参数写显式类型。UTS 不会从
options对象反推 lambda 参数类型, 省略注解会报Cannot infer type for this parameter。
Android 跨应用悬浮
import { checkOverlayPermission, openOverlayPermissionSettings, openFloatWindow } from '@/uni_modules/ai-hzh'
checkOverlayPermission({
success(permission) {
if (!permission.granted) {
if (permission.canOpenSettings) openOverlayPermissionSettings()
return
}
openFloatWindow({
mode: 'overlay',
focusMode: 'passthrough',
content: { type: 'url', value: 'https://example.com/tool' },
sizePreset: 'small',
edgeSnap: true,
rememberPosition: true
})
}
})
授权后必须回到 App 重新
checkOverlayPermission确认granted === true,再打开overlay。
画中画
import { enterPictureInPicture, pausePictureInPicture, seekPictureInPicture } from '@/uni_modules/ai-hzh'
enterPictureInPicture({
content: {
type: 'url',
// 测试视频:42.7MB,支持 Range,可直接用于进度拖动
value: 'https://mp-e8fb124d-4949-40ed-9db8-fb30109550f4.cdn.bspapp.com/1.mp4',
isVideoStream: true
},
startPositionMs: 2000,
loop: false,
success(state) {
console.log('已进入画中画', state.playing, state.durationMs)
}
})
H5 ↔ App
H5 侧(页面由本插件悬浮窗承载时):
<script>
// bridgeName 在 content 里声明,例如 'AiHzhBridge'
window.AiHzhBridge.postMessage(JSON.stringify({
event: 'submitOrder',
data: { orderNo: 'DEMO-001' },
requestId: 'req-h5-001'
}))
window.addEventListener('AiHzhBridgeMessage', function (event) {
console.log('H5 收到 App 消息', event.detail)
})
</script>
App 侧:
import { sendMessageToH5 } from '@/uni_modules/ai-hzh'
sendMessageToH5({
event: 'appGreeting',
data: { text: '你好,H5' },
requestId: 'req-app-001',
success(res) {
console.log('已投递', res.bytes, 'bytes →', res.targetOrigin)
}
})
安全约束(三端一致,UTS 侧统一校验)
- 远程桥 / 媒体采集必须
https,且content.allowedOrigins必须声明精确 Origin(如https://example.com,不带路径/通配符)。 - 消息必须含非空
event,序列化后 ≤ 256KB。 - 当前页面 Origin 不在白名单内时:
sendMessageToH5返回9015012,H5 发来的消息被丢弃。 bridgeName必须是合法 JS 标识符,长度 ≤ 64。
测试资源
| 用途 | 地址 |
|---|---|
| 画中画 / 悬浮窗内视频 | https://mp-e8fb124d-4949-40ed-9db8-fb30109550f4.cdn.bspapp.com/1.mp4 |
| H5 桥示例页 | uni_modules/ai-hzh/static/bridge-demo.html |
该视频为 MP4(42.7MB,Accept-Ranges: bytes),适合验证起播位置、进度拖动与循环播放。
示例工程 pages/index/index.uvue 里的 videoUrl 用的就是它,直接改这一处即可换片源。
4. API
所有异步 API 都支持 success / fail / complete。
能力与权限
| API | 说明 |
|---|---|
getCapabilities(options) |
读取能力矩阵 |
getCapabilitiesSync() |
同步读取能力矩阵 |
checkOverlayPermission(options) |
查询 Android 悬浮权限 |
openOverlayPermissionSettings(options?) |
跳转系统授权页 |
bringHostAppToFront(options?) |
Android overlay 唤回宿主 |
生命周期
| API | 说明 |
|---|---|
openFloatWindow(options) |
打开,成功返回 FloatWindowState |
updateFloatWindow(options) |
动态更新(只传要改的字段) |
closeFloatWindow(options?) |
关闭并释放 |
showFloatWindow / hideFloatWindow |
显隐 |
bringFloatWindowToFront |
应用内提升层级 |
openFloatWindow 主要参数:
| 参数 | 类型 | 默认 | 说明 |
|---|---|---|---|
mode |
inApp / overlay / pip |
Android overlay,其余 inApp |
悬浮模式 |
focusMode |
passthrough / interactive |
overlay 默认 passthrough |
Android 焦点模式 |
content |
FloatWindowContent |
必填 | 页面或媒体内容 |
position |
{x, y} |
右下角 | 初始坐标 |
size / sizePreset |
{width,height} / small…fullscreen |
medium |
尺寸 |
displayMode |
ball / panel |
无 | 球或面板 |
ballSize / panelSize |
{width,height} |
56×56 / 320×240 | 球/面板尺寸 |
dragEnabled / edgeSnap / rememberPosition |
boolean | true / false / false | 拖拽行为 |
keepInScreen / edgePadding |
boolean / number | true / 12 | 安全区修正 |
单位约定
size/sizePreset/ballSize/panelSize/edgePadding→ dp(iOS pt、HarmonyOS vp 同值), 插件内部按屏幕密度换算成物理像素再下发原生。position与getFloatWindowPosition的返回值 → 物理 px(原样读写,便于位置记忆与回填)。之前版本把预设尺寸当 px 用,在 3x 屏上只有 1/3 大小,看起来像「没显示」;1.0.1 起已修正。
位置 / 尺寸 / 内容
setFloatWindowPosition、getFloatWindowPosition、setFloatWindowSize、
setFloatWindowSizePreset、setFloatWindowDragEnabled、setFloatWindowContent、
setFloatWindowDisplayMode、toggleFloatWindowDisplayMode、
setMediaPlaybackRequiresUserGesture(仅 Android 生效)。
状态与诊断
getFloatWindowState、getFloatWindowDiagnostics(含 Origin、桥状态、权限、监听器数量)。
事件
| 事件 | 载荷 |
|---|---|
onMove / offMove |
{x, y, width, height, dragging} |
onClick / offClick |
{x, y} |
onContentEvent / offContentEvent |
{type, url, code, message, progress},type 含 pageBegin/pageEnd/pageError/progress 与 pip* |
onDisplayModeChange / offDisplayModeChange |
{displayMode, size, position} |
onH5Message / offH5Message |
{event, data, requestId, sourceOrigin, isMainFrame, timestamp} |
offXxx() 一律清空该事件的全部监听(不区分是否传 listener)——iOS 的 Swift 闭包不是
Equatable,无法按引用精确移除单个回调,为保持三端行为一致统一为「清空」语义。
页面卸载时务必调用;同一事件重复 onXxx 会叠加多个监听,需要替换请先 off。
画中画
enterPictureInPicture、exitPictureInPicture、playPictureInPicture、
pausePictureInPicture、seekPictureInPicture、getPictureInPicturePlaybackState、
preparePictureInPicture(打开原生播放器并等待进入画中画)、
cancelPictureInPicturePreparation(放弃已准备的播放器;鸿蒙等价于 exit)。
兼容别名
open / setConfig / close / show / hide / toFront / setPosition / getPosition /
setSize / setDragEnable / setShowPattern / setContent(新项目建议用完整名)。
5. 权限配置
Android
插件 utssdk/app-android/AndroidManifest.xml 已声明:
SYSTEM_ALERT_WINDOW / INTERNET / CAMERA / RECORD_AUDIO / MODIFY_AUDIO_SETTINGS /
FOREGROUND_SERVICE / WAKE_LOCK
SYSTEM_ALERT_WINDOW 属于特殊权限,必须走 openOverlayPermissionSettings 让用户手动授予。
网页采集摄像头/麦克风前,宿主需先拿到系统运行时权限(插件在 onPermissionRequest 时会尝试申请,
但 uni-app 的 Activity 无法回调结果,建议宿主自行先申请一次)。
iOS
最低系统版本 iOS 15.0(utssdk/app-ios/config.json 的 deploymentTarget)。
当前 Xcode 26 只接受 ≥15.0 的部署目标,设成 13.0 会直接编译失败。
插件 Info.plist 已追加 UIBackgroundModes: audio、NSCameraUsageDescription、
NSMicrophoneUsageDescription、NSPhotoLibraryUsageDescription。
HarmonyOS
远程网页需要 ohos.permission.INTERNET(uni-app x 默认已声明)。
6. 错误码
| 错误码 | 含义 |
|---|---|
9015001 |
平台或能力不支持 |
9015002 |
参数或内容不合法 |
9015003 |
Android 跨应用悬浮权限未授予 |
9015004 |
悬浮窗尚未打开 |
9015005 |
操作状态冲突(重复打开) |
9015006 |
平台不允许该模式 |
9015007 |
媒体来源/格式/控制不支持 |
9015008 |
原生系统操作失败 |
9015009 |
操作忙 |
9015010 |
内容容器或消息桥不可用 |
9015011 |
可信 Web 配置不合法 |
9015012 |
页面未获得桥接授权 |
9015013 |
H5 消息桥尚未就绪 |
9015014 |
H5 消息格式无效 |
9015015 |
H5 消息超过 256KB |
9015016 |
App 向 H5 派发失败 |
7. 目录结构
uni_modules/ai-hzh
├── package.json
├── readme.md / changelog.md
├── static/bridge-demo.html # H5 ↔ App 通信示例
└── utssdk
├── interface.uts # 对外契约(类型 + 函数签名)
├── unierror.uts # 错误码表
├── index.uts # Web / 小程序兜底(统一返回 9015001)
├── shared
│ ├── core.uts # 纯逻辑:校验 / 几何 / 桥安全(可单测)
│ └── bridge.uts # 回调投递 + 事件分发
├── app-android
│ ├── index.uts # 桥接层
│ ├── AIHZHNative.kt # 门面 + 状态机 + 权限
│ ├── AIHZHFloatWindow.kt # WindowManager 悬浮窗 + WebView + 拖拽
│ ├── AIHZHPipActivity.kt # 画中画宿主(ExoPlayer,支持 MP4/HLS)
│ ├── config.json / AndroidManifest.xml
├── app-ios
│ ├── index.uts
│ ├── AIHZHNative.swift # 门面 + 状态机
│ ├── AIHZHFloatWindow.swift # 独立 UIWindow + WKWebView + 手势
│ ├── AIHZHPip.swift # AVPlayerLayer + AVPictureInPictureController
│ └── config.json / Info.plist
└── app-harmony
├── index.uts # OverlayManager + 状态机
└── FloatOverlay.ets # ArkUI 浮层声明式 UI
8. 已知限制
- HarmonyOS 画中画依赖系统 PiP 开关(
PiPWindow.isPiPEnabled()):设备未开启时capabilities.pip为false,enterPictureInPicture返回9015007;悬浮窗与 H5 桥不受影响。 - HarmonyOS 网页采集不支持(
webMediaCapture: false)。 - iOS 不支持跨应用悬浮,
mode: 'overlay'自动降级为inApp(checkOverlayPermission返回granted: false)。 - 画中画跳转:Android 在 PiP 窗口内仅提供播放/暂停与关闭按钮,
seekPictureInPicture需 App 在前台;iOS 通过MPRemoteCommandCenter支持进度拖动。 localFile仅鸿蒙画中画使用,且限制在应用files/cache/temp目录内的普通 MP4。localAsset在 Android 上依次探测assets/<path>、assets/www/<path>、assets/apps/<path>, 不同 HBuilderX 版本静态资源落盘位置不同;探测失败会抛pageError。

收藏人数:
购买源码授权版(
试用
使用 HBuilder 导入示例项目
赞赏(0)






下载 15
赞赏 0
下载 12705083
赞赏 1957
赞赏
京公网安备:11010802035340号