更新记录

1.0.0(2026-10-16)

安卓悬浮窗


平台兼容性

uni-app(5.26)

Vue2 Vue2插件版本 Vue3 Vue3插件版本 Chrome Safari app-vue app-vue插件版本 app-nvue app-nvue插件版本 Android Android插件版本 iOS 鸿蒙
√ 1.0.0 √ 1.0.0 × × √ 1.0.0 √ 1.0.0 10.0 1.0.0 × ×
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 小红书小程序 快应用-华为 快应用-联盟
× × × × × × × × × × × ×

uni-app x(5.26)

Chrome Safari Android Android插件版本 iOS 鸿蒙 微信小程序
× × 10.0 1.0.0 × × ×

其他

多语言 暗黑模式 宽屏模式 蒸汽模式
× × × √

基于 UTS 的 Android 系统级悬浮窗插件。

  • 使用 WindowManager + WebView 实现的系统级悬浮窗,支持长按拖拽、吸边、圆角、背景色
  • 支持 html 与 url 二选一:只传 html 直接渲染内容;传 url 时加载页面,html(若传)追加到页面末尾
  • HTML 内可通过 yaoFloatWindow.postMessage(data) 向原生回传消息(可多次触发)
  • 支持 onMessage 独立注册消息回调,跨 show/update 持续生效,无需重复传
  • 支持动态更新悬浮窗尺寸,可传 animated 参数控制是否动画过渡

将 yao-floating-window 目录放入项目的 uni_modules/ 下,HBuilderX 会自动识别。

仅支持 Android 端,不支持 iOS。 原生代码修改后必须重新制作自定义基座才能生效。

引入方式

import * as floatingWindow from '@/uni_modules/yao-floating-window'

API

方法 说明
floatingWindow.hasOverlayPermission() 是否已授予悬浮窗权限(同步,返回 boolean)
floatingWindow.requestOverlayPermission(options) 申请悬浮窗权限(跳转系统设置页,返回时回调)
floatingWindow.showFloatingWindow(options) 显示悬浮窗
floatingWindow.updateFloatingWindow(options) 更新悬浮窗内容/尺寸(悬浮窗显示中才有效)
floatingWindow.hideFloatingWindow() 隐藏(关闭)悬浮窗
floatingWindow.isFloatingWindowShowing() 悬浮窗是否正在显示(同步,返回 boolean)
floatingWindow.onMessage({ callback }) 注册消息回调(只需一次,跨 show/update 持续生效)

调用示例

<template>
    <view class="page">
        <view class="card">
            <text>悬浮窗权限:{{ permission ? '已授予' : '未授予' }}</text>
            <text>悬浮窗显示中:{{ showing ? '是' : '否' }}</text>
            <button size="mini" @click="refreshStatus">刷新状态</button>
            <button v-if="!permission" size="mini" type="warn" @click="requestPermission">申请悬浮窗权限</button>
        </view>

        <view class="card">
            <text>方式一:自定义 HTML 代码(html 参数,吸边拖拽)</text>
            <button type="primary" @click="showByHtml">显示悬浮窗(HTML 代码)</button>
        </view>

        <view class="card">
            <text>方式二:HTML 文件(url 参数,支持 /static/、_www/ 相对路径或绝对路径)</text>
            <button type="primary" @click="showByFile">显示悬浮窗(HTML 文件)</button>
        </view>

        <view class="card">
            <button size="mini" @click="updateContent">更新内容</button>
            <button size="mini" @click="updateSize">修改大小</button>
            <button size="mini" type="warn" @click="hideWindow">隐藏悬浮窗</button>
        </view>

        <view class="card">
            <text>来自悬浮窗的消息:{{ messages || '暂无' }}</text>
        </view>
    </view>
</template>

<script setup>
    import { ref } from 'vue'
    import { onShow } from '@dcloudio/uni-app'
    import * as floatingWindow from '@/uni_modules/yao-floating-window'

    const permission = ref(false)
    const showing = ref(false)
    const messages = ref('')

    // 注册消息回调(只需一次,跨 show/update 持续生效)
    // callback 必须包在 options 对象里,@UTSJS.keepAlive 才能保活,避免被 GC 回收
    floatingWindow.onMessage({
        callback: (data) => {
            messages.value += data + '\n'
            try {
                const obj = JSON.parse(data)
                if (obj && obj.type === 'close') {
                    hideWindow()
                }
            } catch (e) {
                // 非 JSON 消息,仅展示
            }
        }
    })

    function refreshStatus() {
        permission.value = floatingWindow.hasOverlayPermission()
        showing.value = floatingWindow.isFloatingWindowShowing()
    }

    function requestPermission() {
        floatingWindow.requestOverlayPermission({
            success: (res) => {
                uni.showToast({ title: res.granted ? '授权成功' : '已返回设置页', icon: 'none' })
                refreshStatus()
            },
            fail: (err) => {
                uni.showToast({ title: err.errMsg, icon: 'none' })
                refreshStatus()
            }
        })
    }

    function showByHtml() {
        floatingWindow.showFloatingWindow({
            html: `
                <div style="background:#000000;padding:14px;color:#fff;font-family:sans-serif;">
                    <div style="font-size:15px;font-weight:bold;margin-bottom:8px;">自定义 HTML 悬浮窗</div>
                    <div id="data" style="font-size:12px;margin:8px 0;">等待 getData 数据…</div>
                    <button onclick="yaoFloatWindow.postMessage('hello from html code')">回传消息</button>
                    <script>
                        getData(function (data) {
                            var el = document.getElementById('data');
                            if (el) { el.textContent = 'data: ' + JSON.stringify(data); }
                        });
                    <\/script>
                </div>
            `,
            css: 'html, body { padding: 0; margin: 0; }',
            data: { msg: '来自 uni-app 的数据', time: Date.now() },
            x: 16,
            y: 160,
            width: 240,
            height: 160,
            dragType: 3,        // 1 不可拖拽,2 随意拖拽,3 吸边拖拽
            transparent: true,
            success: () => {
                uni.showToast({ title: '悬浮窗已显示', icon: 'none' })
                refreshStatus()
            },
            fail: (err) => {
                uni.showToast({ title: err.errMsg, icon: 'none' })
                refreshStatus()
            }
        })
    }

    function showByFile() {
        floatingWindow.showFloatingWindow({
            url: '/static/demo-float.html',   // 也可用 _www/xxx、本地绝对路径或 http(s) 链接
            ('悬浮窗页面加载完成')`,
            data: { source: 'file', msg: '页面加载时下发的数据' },
            x: 16,
            y: 160,
            width: 180,
            height: 300,
            dragType: 3,
            transparent: false,
            background: '#E6202020',   // transparent=false 时生效,格式 #RRGGBB / #AARRGGBB
            success: () => {
                uni.showToast({ title: '悬浮窗已显示', icon: 'none' })
                refreshStatus()
            },
            fail: (err) => {
                uni.showToast({ title: err.errMsg, icon: 'none' })
                refreshStatus()
            }
        })
    }

    function updateContent() {
        floatingWindow.updateFloatingWindow({
            data: { message: '新数据', count: 42 },
            success: () => uni.showToast({ title: '已推送新 data', icon: 'none' }),
            fail: (err) => uni.showToast({ title: err.errMsg, icon: 'none' })
        })
    }

    function updateSize() {
        floatingWindow.updateFloatingWindow({
            width: 300,
            height: 200,
            animated: true,    // 平滑过渡(约 250ms 动画)
            success: () => {},
            fail: (err) => {}
        })
    }

    function hideWindow() {
        floatingWindow.hideFloatingWindow()
        refreshStatus()
    }

    onShow(() => {
        refreshStatus()
    })
</script>

demo-float.html 示例

配合 showByFile 使用的 HTML 文件,放在项目的 static/demo-float.html:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        html, body { background: transparent; font-family: sans-serif; user-select: none; -webkit-user-select: none; }
        .card { padding: 14px; color: #fff; }
        .title { font-size: 15px; font-weight: bold; margin-bottom: 8px; }
        .time { font-size: 26px; font-weight: bold; letter-spacing: 1px; margin-bottom: 8px; font-variant-numeric: tabular-nums; }
        .data { font-size: 11px; color: rgba(255,255,255,0.75); margin-bottom: 12px; word-break: break-all; }
        .row { display: flex; gap: 8px; }
        button { flex: 1; border: none; border-radius: 8px; padding: 8px 0; font-size: 13px;
            background: rgba(255,255,255,0.18); color: #fff; }
        button:active { background: rgba(255,255,255,0.32); }
    </style>
</head>
<body>
    <div class="card">
        <div class="title">HTML 文件悬浮窗</div>
        <div class="time" id="time">--:--:--</div>
        <div class="data" id="data">等待 getData 数据…</div>
        <div class="row">
            <button onclick="postHello()">回传消息</button>
            <button onclick="postClose()">关闭悬浮窗</button>
        </div>
    </div>
    <script>
        // 接收 showFloatingWindow / updateFloatingWindow 传入的 data
        function registerDataCallback() {
            if (window.getData) {
                window.getData(function(data) {
                    document.getElementById('data').innerText = JSON.stringify(data);
                });
            } else {
                setTimeout(registerDataCallback, 50);
            }
        }
        registerDataCallback();

        // 实时时钟
        setInterval(function () {
            var el = document.getElementById('time');
            if (el) {
                el.textContent = new Date().toLocaleTimeString('zh-CN', { hour12: false });
            }
        }, 1000);

        // 向原生发送消息(触发 onMessage 回调)
        function postHello() {
            yaoFloatWindow.postMessage(JSON.stringify({ type: 'hello', source: 'file', time: Date.now() }));
        }
        function postClose() {
            yaoFloatWindow.postMessage(JSON.stringify({ type: 'close' }));
        }
    </script>
</body>
</html>

onMessage 注册说明

// 注册消息回调(只需调用一次,跨 show/update 持续生效)
floatingWindow.onMessage({
    callback: (data) => {
        // data 是 HTML 内 yaoFloatWindow.postMessage 传的字符串
        console.log('收到消息:', data)
    }
})

callback 必须包在 { callback: fn } 对象里传入,不能直接传函数。 UTS 的 @UTSJS.keepAlive 只对 options 对象里的回调属性生效,直接参数会被 GC 回收导致回调失效。

ShowOptions 参数

字段 类型 说明
url string 页面地址,支持网络地址、/static/xxx.html、_www/...、本地绝对路径。与 html 二选一
html string HTML 内容字符串;与 url 同时传入时,追加到 url 页面内容之后
javaScript string 页面加载完成后注入执行的 JS 脚本
css string 注入到页面的 CSS 样式
data any 传递给页面的数据(可 JSON 序列化),页面内通过 getData((data) => {...}) 接收
x / y number 初始位置(dp),默认 24 / 300
width / height number 宽高(dp),默认 600 / 300,传 0 表示撑满屏幕
toolbar boolean 是否显示工具条(预留参数,当前版本未实现)
dragType number 拖动类型:1 不可拖拽,2 随意拖拽(默认),3 吸边拖拽
transparent boolean 背景是否透明,默认 true
background string transparent=false 时的背景色,格式 #RRGGBB / #AARRGGBB,默认 #E6202020
focusable boolean 悬浮窗是否可聚焦(true 时 HTML 输入框可弹出键盘),默认 false
success () => void 成功回调
fail (err: FloatWindowFail) => void 失败回调
complete (res: any) => void 完成回调

UpdateOptions 参数

字段 类型 说明
url string 新页面地址(与 html 二选一),含义同 ShowOptions.url
html string 新 HTML 内容;与 url 同时传入时追加到 url 页面之后
javaScript string 重新注入的 JS 脚本
css string 重新注入的 CSS 样式
data any 重新传递给页面的数据(页面不重载时实时推送)
width number 新宽度(dp),传 0 表示撑满屏幕宽;不传则保持当前宽度
height number 新高度(dp),传 0 表示撑满屏幕高;不传则保持当前高度
animated boolean 是否以动画过渡方式更新宽高,默认 false(立即应用)。仅当新旧宽高均为像素值(非撑满)时才走动画,否则退化为立即应用
success () => void 成功回调
fail (err: FloatWindowFail) => void 失败回调
complete (res: any) => void 完成回调

仅作用于当前显示中的悬浮窗,悬浮窗未显示时返回 9040104。

更新大小示例

// 立即更新为 400 x 500
floatingWindow.updateFloatingWindow({
    width: 400,
    height: 500
})

// 平滑过渡到 400 x 500(约 250ms 动画)
floatingWindow.updateFloatingWindow({
    width: 400,
    height: 500,
    animated: true
})

// 只改宽度,高度保持不变
floatingWindow.updateFloatingWindow({
    width: 300
})

// 撑满屏幕宽(撑满因无像素值,会忽略 animated 立即应用)
floatingWindow.updateFloatingWindow({
    width: 0,
    height: 600,
    animated: true
})

页面端(HTML)通信

<script>
    // 接收原生下发的 data
    getData(function (data) {
        console.log('收到原生数据:', data);
    });

    // 向原生发送消息(触发 onMessage)
    yaoFloatWindow.postMessage('hello from html');
    yaoFloatWindow.postMessage(JSON.stringify({ type: 'close' }));
</script>

错误码

errCode 说明
9040101 悬浮窗权限未授予,请先调用 requestOverlayPermission 授权
9040102 参数错误:html 与 file 无效或 HTML 文件不存在
9040103 悬浮窗操作失败
9040104 悬浮窗未显示,请先调用 showFloatingWindow

隐私、权限声明

1. 本插件需要申请的系统权限列表:

需要悬浮窗权限

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集任何数据

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

无

暂无用户评论。