更新记录
1.0.0(2026-03-06)
- 新增 Android UTS 悬浮窗插件
dh-float-window - 支持
Float/FloatBridge双向 JSON 协议通信 - 支持生命周期、窗口控制、内容控制、事件广播、结构化函数调用
- 支持 WebView -> Uni 请求响应模型(request/response)
平台兼容性
uni-app(4.0)
| Vue2 | Vue3 | Chrome | Safari | app-vue | app-nvue | Android | Android插件版本 | iOS | 鸿蒙 |
|---|---|---|---|---|---|---|---|---|---|
| - | - | - | - | - | - | 5.0 | 1.0.0 | × | - |
| 微信小程序 | 支付宝小程序 | 抖音小程序 | 百度小程序 | 快手小程序 | 京东小程序 | 鸿蒙元服务 | QQ小程序 | 飞书小程序 | 小红书小程序 | 快应用-华为 | 快应用-联盟 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| - | - | - | - | - | - | - | - | - | - | - | - |
其他
| 多语言 | 暗黑模式 | 宽屏模式 |
|---|---|---|
| √ | √ | √ |
UniApp Android 悬浮窗插件(应用级浮窗 / 悬浮球 / WebView通信)
Android UTS 悬浮窗插件,提供 WebView 悬浮窗创建、窗口控制、WebView与UniApp双向通信、函数调用、多拖拽模式,适用于工具类、助手类、悬浮控制面板等场景。
- 平台:
Android - 语言:
UTS + JavaScript - 推荐入口:
core/float.api.js - 底层入口:
getFloatManager()
目录
- 插件定位
- 能力特性
- 坐标与尺寸统一规范
- 安装与目录结构
- Quick Start
- 选择哪套 API
- UniApp API(高层 Float)
- UniApp API(底层 getFloatManager)
- HTML API(FloatBridge)
- DragMode 说明
- Safeguard 说明
- 完整示例
- 常见问题
- 类型定义
插件定位
dh-float-window 面向 UniApp Android 场景,核心目标是把悬浮窗业务从“事件拼接 + 回调地狱”升级为“结构化协议 + Promise 风格 + 自动保障”。
插件包含两层能力:
| 层级 | 入口 | 适用场景 |
|---|---|---|
| 高层封装 | @/uni_modules/dh-float-window/core/float.api.js |
业务开发(推荐) |
| 底层原生 | getFloatManager() |
插件扩展、性能优化、协议定制 |
能力特性
| 特性 | 说明 |
|---|---|
| 多窗口 | 同时创建多个窗口,按 id 管理 |
| 双向通信 | UniApp 与 HTML 双向 emit/request |
| 函数调用 | UniApp 直接 call HTML 全局方法 |
| 请求响应 | HTML request 到 UniApp,支持 Promise |
| 窗口控制 | show/hide/destroy/setRect/setDragMode |
| 触摸策略 | focusable/touchThrough 细粒度控制 |
| 失联保障 | 内置心跳检测、自动恢复、失败兜底关闭 |
| 透明背景 | WebView 支持透明背景与圆角小组件样式 |
坐标与尺寸统一规范
插件第一原则:
x / y / width / height输入单位统一为 CSS 逻辑像素(css px)- 插件内部负责换算到 Android 原生窗口需要的物理像素
- 插件不内置业务设计稿规则(如
750/rpx/rem)
这条规则的直接含义:
- 你在 UniApp 里传
width: 360,语义是 360 css px - HTML 在标准 viewport(
initial-scale=1)下写width: 360px时,尺寸可对齐 - 如果你的项目使用 750 设计稿,请在业务调用侧自行换算后再传给插件
业务侧换算示例(仅项目侧,不属于插件规则):
function fromDesign750(value) {
const info = uni.getWindowInfo();
return Math.round((info.windowWidth / 750) * value);
}
await Float.create({
id: 'demo',
x: fromDesign750(50),
y: fromDesign750(400),
width: fromDesign750(630),
height: fromDesign750(460),
url: htmlUrl('float-demo.html'),
});
建议:同一页面避免多套缩放体系叠加(如窗口尺寸用 750 缩放,同时 HTML 内再混用 rem 根缩放与固定 px),否则会出现视觉尺寸偏差。
安装与目录结构
插件位于:src/uni_modules/dh-float-window
| 路径 | 说明 |
|---|---|
utssdk/app-android/index.uts |
Android 原生实现 |
utssdk/interface.uts |
UTS 类型接口 |
utssdk/index.uts |
UTS 导出入口 |
index.d.ts |
底层 TS 类型 |
core/float.api.js |
高层 Promise API |
core/float.api.d.ts |
高层 TS 类型 |
Quick Start
1. UniApp 侧创建悬浮窗
import Float from '@/uni_modules/dh-float-window/core/float.api.js';
function htmlUrl(file) {
// #ifdef APP-PLUS
return 'file://' + plus.io.convertLocalFileSystemURL(`_www/static/html/example/${file}`);
// #endif
return '';
}
async function openFloat() {
const granted = await Float.ensurePermission();
if (!granted) {
return;
}
await Float.create({
id: 'demo_main',
url: htmlUrl('float-demo.html'),
x: 24,
y: 180,
width: 760,
height: 560,
dragMode: 2,
focusable: true,
touchThrough: false,
backgroundColor: '#00000000',
dataJson: JSON.stringify({ from: 'quick-start' }),
});
}
2. HTML 侧读取初始化数据并发消息
<script>
function onReady() {
const data = window.FloatBridge.getInitData();
console.log('initData', data);
window.FloatBridge.emit('ping', { ts: Date.now(), from: 'html' });
}
if (window.FloatBridge) {
onReady();
} else {
window.addEventListener('float-bridge-ready', onReady, { once: true });
}
</script>
3. UniApp 侧监听并响应 HTML 请求
Float.on('demo_main', 'ping', (payload) => {
console.log('ping from html', payload);
});
Float.onRequest('demo_main', 'getTime', () => {
return { now: Date.now() };
});
选择哪套 API
推荐结论
生产业务优先使用 core/float.api.js,仅在需要更底层控制时使用 getFloatManager()。
评估对比
| 维度 | core/float.api.js |
getFloatManager() |
|---|---|---|
| 使用难度 | 低 | 高 |
| 返回值风格 | Promise | boolean / callback |
| 消息协议 | 已封装 | 需手工 JSON |
| 请求响应 | 内置 onRequest |
需手工 respondRequest |
| 失联保障 | 内置 Safeguard | 需自行实现 |
| 适用场景 | 业务开发 | 框架扩展、调优 |
UniApp API(高层 Float)
入口:@/uni_modules/dh-float-window/core/float.api.js
import Float from '@/uni_modules/dh-float-window/core/float.api.js';
生命周期与窗口控制
| 方法 | 参数 | 返回 | 说明 |
|---|---|---|---|
canDrawOverlays() |
- | boolean |
检查悬浮窗权限 |
openPermissionSettings() |
- | boolean |
拉起系统设置 |
ensurePermission() |
- | Promise<boolean> |
检查悬浮窗权限,不足时拉起系统设置 |
create(options) |
FloatCreateOptions |
Promise<void> |
创建窗口并自动启动 Safeguard |
show(id) |
string |
Promise<void> |
显示窗口 |
hide(id) |
string |
Promise<void> |
隐藏窗口并暂停保活计时 |
destroy(id) |
string |
Promise<void> |
销毁窗口并清理状态 |
setRect(id, rect, animate?) |
id, {x,y,width,height}, boolean |
Promise<void> |
修改位置和尺寸 |
setDragMode(id, mode) |
id, number |
Promise<void> |
设置拖拽模式 |
setZIndex(id, zIndex) |
id, number |
Promise<void> |
设置层级 |
setFocusable(id, enabled) |
id, boolean |
Promise<void> |
设置可聚焦 |
setTouchThrough(id, enabled) |
id, boolean |
Promise<void> |
设置触摸穿透 |
setAppScope(id, scope) |
id, string |
Promise<void> |
业务作用域标记 |
loadUrl(id, url) |
id, string |
Promise<void> |
加载 URL |
loadHtml(id, html) |
id, string |
Promise<void> |
加载 HTML 字符串 |
reload(id) |
id |
Promise<void> |
刷新页面 |
消息与调用
| 方法 | 参数 | 返回 | 说明 |
|---|---|---|---|
emit(id, type, payload?) |
id, string, object |
Promise<void> |
向指定窗口发送事件 |
broadcast(type, payload?) |
string, object |
Promise<void> |
广播到所有窗口 |
call(id, options) |
{ method, args?, needResult? } |
Promise<any> |
调用 HTML 全局方法 |
on(id, type, cb) |
id, type, callback |
void |
监听窗口消息 |
off(id, type, cb?) |
id, type, callback? |
void |
取消监听 |
onRequest(id, type, handler) |
id, type, handler |
void |
注册 HTML request 处理器 |
offRequest(id, type) |
id, type |
void |
取消 request 处理 |
Safeguard
| 方法 | 参数 | 返回 | 说明 |
|---|---|---|---|
enableSafeguard(id, options?) |
id, FloatGuardOptions |
void |
开启/更新保障策略 |
disableSafeguard(id) |
id |
void |
关闭保障 |
runSafeguardCheck(id) |
id |
Promise<void> |
手动执行一次心跳检查 |
高频示例
const id = 'demo_main';
await Float.create({
id,
url: htmlUrl('float-demo.html'),
x: 30,
y: 180,
width: 760,
height: 560,
dragMode: 2,
focusable: true,
touchThrough: false,
dataJson: JSON.stringify({ userId: 'u1001' }),
});
Float.on(id, 'search', (payload) => {
console.log('search keyword =>', payload?.keyword);
});
Float.onRequest(id, 'sum', (payload) => {
return { sum: Number(payload?.a || 0) + Number(payload?.b || 0) };
});
const ret = await Float.call(id, {
method: 'demoAdd',
args: [7, 8],
needResult: true,
});
console.log('demoAdd result =>', ret);
UniApp API(底层 getFloatManager)
入口:@/uni_modules/dh-float-window
import { getFloatManager } from '@/uni_modules/dh-float-window';
const manager = getFloatManager();
API 表
| 方法 | 返回 | 说明 |
|---|---|---|
canDrawOverlays() |
boolean |
是否有悬浮窗权限 |
openOverlayPermissionSettings() |
void |
打开系统悬浮窗授权页 |
create(options) |
boolean |
创建窗口 |
show(id) |
boolean |
显示窗口 |
hide(id) |
boolean |
隐藏窗口 |
destroy(id) |
boolean |
销毁窗口 |
setRect(id, rect, animate) |
boolean |
位置尺寸 |
setDragMode(id, mode) |
boolean |
拖拽模式 |
setZIndex(id, zIndex) |
boolean |
层级 |
setFocusable(id, enabled) |
boolean |
焦点 |
setTouchThrough(id, enabled) |
boolean |
穿透 |
setAppScope(id, scope) |
boolean |
作用域 |
loadUrl(id, url) |
boolean |
加载 URL |
loadHtml(id, html) |
boolean |
加载 HTML |
reload(id) |
boolean |
刷新 |
emit(id, type, payloadJson) |
boolean |
发送消息 |
broadcast(type, payloadJson) |
boolean |
广播消息 |
call(id, options) |
boolean |
调用 HTML 方法 |
respondRequest(id, requestId, ok, payloadJson) |
boolean |
响应 request |
onMessage(callback) |
void |
全量消息监听 |
offMessage() |
void |
清除消息监听 |
底层示例
const id = 'native_demo';
if (!manager.canDrawOverlays()) {
manager.openOverlayPermissionSettings();
}
const ok = manager.create({
id,
url: htmlUrl('float-demo.html'),
x: 40,
y: 220,
width: 680,
height: 420,
dragMode: 2,
focusable: true,
touchThrough: false,
dataJson: JSON.stringify({ from: 'manager-demo' }),
});
if (!ok) {
throw new Error('create failed');
}
manager.onMessage((messageJson) => {
const message = JSON.parse(messageJson);
console.log('[onMessage]', message.type, message.source, message.payload);
if (message.type === 'request:getTime') {
const requestId = message?.payload?.requestId;
manager.respondRequest(message.source, requestId, true, JSON.stringify({ now: Date.now() }));
}
});
HTML API(FloatBridge)
在悬浮窗页面加载完成后,插件会注入 window.FloatBridge。
API 表
| 方法 | 参数 | 返回 | 说明 |
|---|---|---|---|
getWindowId() |
- | string |
当前窗口 ID |
getInitData() |
- | any |
读取 create.dataJson |
emit(type, payload, target?) |
string, any, string? |
void |
发事件到 UniApp |
request(type, payload, target?) |
string, any, string? |
Promise<any> |
请求 UniApp 并等待响应 |
close() |
- | void |
关闭当前窗口 |
hide() |
- | void |
隐藏窗口 |
show() |
- | void |
显示窗口 |
resize(width, height) |
number, number |
void |
调整窗口尺寸 |
move(x, y) |
number, number |
void |
移动窗口 |
setDragMode(mode) |
number |
void |
设置拖拽模式(0不可拖拽,1自由拖拽,2吸边拖拽) |
backToApp(data?) |
object? |
void |
回到主应用 |
HTML 示例
<script>
function ready() {
const id = window.FloatBridge.getWindowId();
const initData = window.FloatBridge.getInitData();
console.log('windowId =>', id);
console.log('initData =>', initData);
window.FloatBridge.emit('ping', { from: 'html', ts: Date.now() });
window.FloatBridge.request('sum', { a: 11, b: 29 })
.then((res) => {
console.log('sum result =>', res);
})
.catch((err) => {
console.error('sum error =>', err);
});
}
if (window.FloatBridge) {
ready();
} else {
window.addEventListener('float-bridge-ready', ready, { once: true });
}
</script>
DragMode 说明
dragMode 决定窗口是否可拖拽以及拖拽行为。
| 值 | 名称 | 行为 |
|---|---|---|
0 |
none |
不可拖拽 |
1 |
free |
自由拖拽 |
2 |
edge |
拖拽后自动吸附左右边缘 |
推荐实践
| 场景 | 建议 dragMode |
|---|---|
| 大型主面板 | 2 |
| 小型工具卡片 | 1 |
| Toast/纯展示层 | 0 |
示例
await Float.setDragMode('demo_main', 2);
<script>
// HTML 内切换拖拽模式
window.FloatBridge.setDragMode(1);
</script>
Safeguard 说明
Safeguard 是悬浮窗失联保护机制,默认在 Float.create() 后自动开启。
机制流程:
- UniApp 定时向窗口发心跳
__float_heartbeat__ - HTML 侧自动回应
__float_heartbeat_ack__ - 超时累计达到阈值后,按恢复链路执行
- 失败时可选择自动关闭窗口,避免假死
恢复链路:
reloadhide + showdestroy + recreate- 关闭窗口(
closeOnFail = true)
默认配置(来自源码):
| 字段 | 默认值 | 说明 |
|---|---|---|
enabled |
true |
是否启用 |
intervalMs |
12000 |
心跳间隔 |
timeoutMs |
8000 |
单次超时 |
maxMissed |
2 |
连续失败阈值 |
closeOnFail |
true |
恢复失败是否关闭 |
配置示例
Float.enableSafeguard('demo_main', {
intervalMs: 10000,
timeoutMs: 6000,
maxMissed: 2,
closeOnFail: true,
});
保障事件示例
Float.on('system', 'guard-warning', (payload) => {
console.warn('guard warning =>', payload);
});
Float.on('system', 'guard-failed', (payload) => {
console.error('guard failed =>', payload);
});
完整示例
UniApp 页面(节选)
源码参考:src/pages/float-demo/index.vue
import Float from '@/uni_modules/dh-float-window/core/float.api.js';
const MAIN_ID = 'float_demo_main';
function htmlUrl(file) {
return 'file://' + plus.io.convertLocalFileSystemURL(`_www/static/html/example/${file}`);
}
async function runDemo() {
const granted = await Float.ensurePermission();
if (!granted) return;
await Float.create({
id: MAIN_ID,
url: htmlUrl('float-demo.html'),
x: 24,
y: 180,
width: 760,
height: 560,
dragMode: 2,
focusable: true,
touchThrough: false,
backgroundColor: '#00000000',
dataJson: JSON.stringify({ from: 'demo-page' }),
});
Float.on(MAIN_ID, 'ping', (payload) => {
console.log('ping =>', payload);
});
Float.onRequest(MAIN_ID, 'sum', (p) => ({
sum: Number(p?.a || 0) + Number(p?.b || 0),
}));
}
HTML 页面(节选)
源码参考:src/static/html/example/float-demo.html
<script>
function demoAdd(a, b) {
return Number(a || 0) + Number(b || 0);
}
async function bootstrap() {
const initData = window.FloatBridge.getInitData();
console.log('initData =>', initData);
window.FloatBridge.emit('ping', { msg: 'hello from web' });
const time = await window.FloatBridge.request('getTime', { from: 'float-demo' });
console.log('getTime =>', time);
}
if (window.FloatBridge) {
bootstrap();
} else {
window.addEventListener('float-bridge-ready', bootstrap, { once: true });
}
</script>
常见问题
1. 设置同样 width/height/x/y,新插件看起来更小
通常是业务侧出现了重复缩放(例如:调用前做过 750 换算,HTML 内又叠加一层 rem 缩放并混用 px)。
插件规则固定为 css px,请统一业务侧坐标系,避免多重缩放叠加。
2. HTML 里 FloatBridge 偶发未定义
建议等待 float-bridge-ready 事件后再调用。
3. 如何避免“悬浮窗卡死但还在屏幕上”
启用 Safeguard 并监听 guard-warning / guard-failed,必要时提示用户重开窗口。
4. 请求超时怎么处理
Float.call(...needResult: true) 与 FloatBridge.request(...) 都应在业务层加重试或降级提示。
类型定义
- 底层类型:
src/uni_modules/dh-float-window/index.d.ts - 高层类型:
src/uni_modules/dh-float-window/core/float.api.d.ts
建议 TS 项目优先使用高层类型与高层 API。

收藏人数:
购买源码授权版(
试用
赞赏(0)
下载 139
赞赏 1
下载 11350185
赞赏 1869
赞赏
京公网安备:11010802035340号