更新记录
1.0.0(2025-12-01)
tsw-floating-uts 悬浮窗插件
插件介绍
这是一个支持系统级和应用内悬浮窗的 uni-app UTS 插件,专为 Android 平台优化设计。该插件提供了灵活的悬浮窗解决方案,让开发者能够轻松实现各种场景下的悬浮窗需求。
核心功能特性
- 系统级悬浮窗:需要悬浮窗权限,可在应用后台和前台显示
- 应用内悬浮窗:不需要特殊权限,仅在应用内显示
- 多内容类型支持:支持文本、图片两种种内容类型
- 智能拖拽功能:支持悬浮窗拖拽,并具备自动吸附到屏幕边缘的特性
- 完善的事件处理:支持点击事件回调和拖动结束回调
- 优雅的错误处理:提供详细的错误日志和状态返回
- 后台交互支持:应用在后台时,通过 Scheme 机制实现点击悬浮窗唤醒应用
技术实现原理
系统级悬浮窗实现
系统级悬浮窗使用 Android 的 WindowManager 服务和 TYPE_APPLICATION_OVERLAY 窗口类型(Android 8.0+)或 TYPE_PHONE 窗口类型(Android 8.0 以下)实现。核心实现包括:
- 使用前台服务(Foreground Service)确保悬浮窗在应用退到后台时仍能正常显示
- 通过 WindowManager.LayoutParams 控制悬浮窗的显示位置、大小和层级
- 使用触摸监听器实现拖拽功能和点击事件处理
- 实现自动吸附到屏幕边缘的功能,提升用户体验
应用内悬浮窗实现
应用内悬浮窗使用 TYPE_APPLICATION_PANEL 窗口类型实现,仅在应用内显示,具有以下特点:
- 不需要特殊权限
- 应用切换到后台时自动隐藏
- 实现方式更轻量,性能开销更小
- 适合仅在应用内需要悬浮提示的场景
安装方式
方法一:通过插件市场安装
在 uni-app 项目中,通过 HBuilderX 插件市场搜索 "tsw-floating-uts" 并安装。
方法二:手动安装
直接复制 uni_modules/tsw-floating-uts 目录到您的 uni-app 项目的 uni_modules 目录下。
权限配置
系统级悬浮窗权限配置
使用系统级悬浮窗需要在 AndroidManifest.xml 中添加以下权限:
<!-- 悬浮窗权限 -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<!-- 如果使用前台服务,还需要添加以下权限 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
权限请求流程
- 调用
checkPermission()方法检查是否已获得悬浮窗权限 - 如果未获得权限,调用
requestPermission()方法请求用户授权 - 授权成功后,即可使用系统级悬浮窗功能
接口说明
1. 权限相关接口
// 检查是否拥有悬浮窗权限
export function checkPermission(): boolean;
// 请求悬浮窗权限
export function requestPermission(): void;
2. 系统级悬浮窗接口
// 显示系统级悬浮窗
export function showFloatingWindow(options: FloatingWindowOptions, callback?: TswFloatingUtsCallback): void;
// 更新系统级悬浮窗内容
export function updateFloatingWindow(options: FloatingWindowOptions, callback?: TswFloatingUtsCallback): void;
// 隐藏系统级悬浮窗
export function hideFloatingWindow(callback?: TswFloatingUtsCallback): void;
// 设置系统级悬浮窗点击监听器
// 注意:应用在前台时直接触发回调,在后台时通过Scheme打开应用
export function setFloatingWindowClickListener(callback?: TswFloatingUtsCallback): void;
3. 应用内悬浮窗接口
// 显示应用内悬浮窗(不需要系统权限)
export function showInAppFloatingWindow(options: FloatingWindowOptions, callback?: TswFloatingUtsCallback): void;
// 隐藏应用内悬浮窗
export function hideInAppFloatingWindow(callback?: TswFloatingUtsCallback): void;
// 设置应用内悬浮窗点击监听器
export function setInAppFloatingWindowClickListener(callback?: TswFloatingUtsCallback): void;
// 设置应用内悬浮窗拖动结束监听器
export function setInAppFloatingWindowDragEndListener(callback?: TswFloatingUtsCallback): void;
// 模拟应用内悬浮窗拖拽 更新应用内悬浮窗位置
export function simulateInAppFloatingWindowDrag(x: number, y: number, callback?: TswFloatingUtsCallback): void;
4. 数据类型定义
// 接口返回结果类型
export type TswFloatingUtsResult = {
code: number; // 0表示成功,-1表示失败
data: any; // 结果数据
};
// 回调函数类型
export type TswFloatingUtsCallback = (result: TswFloatingUtsResult) => void;
// 内容数据类型
export type ContentData = {
// 文本相关属性
text?: string; // 文本内容
textSize?: number; // 文本大小
textColor?: string; // 文本颜色
// 样式相关属性
backgroundColor?: string; // 背景颜色
cornerRadius?: number; // 圆角大小
// 资源相关属性
resourceId?: number; // 图片资源ID(用于本地资源)
imageUrl?: string; // 图片URL
videoUrl?: string; // 视频URL
};
// 悬浮窗配置选项
export type FloatingWindowOptions = {
contentType: string; // 内容类型:"text", "image", "video"
contentData: ContentData; // 内容数据
width?: number; // 宽度,默认200
height?: number; // 高度,默认100
x?: number; // X坐标,默认100
y?: number; // Y坐标,默认200
draggable?: boolean; // 是否可拖拽,默认true
};
// 数据对象
export type UTSJSONObject = Record<string, any>;
使用示例
1. 系统级悬浮窗完整使用流程
import { checkPermission, requestPermission, showFloatingWindow, setFloatingWindowClickListener } from '@/uni_modules/tsw-floating-uts';
// 检查并请求权限
function setupSystemFloatingWindow() {
const hasPermission = checkPermission();
if (hasPermission) {
showSystemFloatingWindow();
} else {
// 请求权限
requestPermission();
// 可以在一定时间后再次检查权限
setTimeout(() => {
const newPermissionStatus = checkPermission();
if (newPermissionStatus) {
showSystemFloatingWindow();
} else {
uni.showToast({
title: '请在设置中开启悬浮窗权限',
icon: 'none'
});
}
}, 1000);
}
}
// 显示系统级悬浮窗
function showSystemFloatingWindow() {
showFloatingWindow({
width: 250,
height: 100,
x: 50,
y: 150,
draggable: true,
contentType: 'text',
contentData: {
text: '系统级悬浮窗示例',
textSize: 16,
textColor: '#FFFFFF',
backgroundColor: '#007AFF',
cornerRadius: 20
}
}, (res) => {
if (res.code === 0) {
console.log('系统级悬浮窗显示成功');
setClickListener();
} else {
console.error('系统级悬浮窗显示失败:', res.data.message);
}
});
}
// 设置点击监听器
function setClickListener() {
setFloatingWindowClickListener((res) => {
console.log('系统悬浮窗被点击,来源:', res.data.from);
// 应用在前台时,这里的代码会直接执行
uni.showToast({
title: '悬浮窗被点击',
icon: 'none'
});
});
}
// 在页面显示时调用
onShow() {
setupSystemFloatingWindow();
}
// 在页面卸载时隐藏悬浮窗
onUnload() {
hideFloatingWindow();
}
2. 应用内悬浮窗使用示例
import { showInAppFloatingWindow, setInAppFloatingWindowClickListener, setInAppFloatingWindowDragEndListener } from '@/uni_modules/tsw-floating-uts';
// 显示应用内悬浮窗(图片类型)
function showImageFloatingWindow() {
showInAppFloatingWindow({
width: 120,
height: 120,
x: 200,
y: 300,
draggable: true,
contentType: 'image',
contentData: {
imageUrl: '/static/logo.png',
cornerRadius: 20
}
}, (res) => {
console.log(res.data.message);
});
}
// 配置事件监听器
function setupListeners() {
// 设置点击监听器
setInAppFloatingWindowClickListener((res) => {
console.log('应用内悬浮窗被点击');
uni.showToast({
title: '点击了图片悬浮窗',
icon: 'none'
});
});
// 设置拖动结束监听器
setInAppFloatingWindowDragEndListener((res) => {
console.log(`悬浮窗拖动到位置: (${res.data.x}, ${res.data.y})`);
// 可以在这里保存悬浮窗位置,下次打开时恢复
saveFloatingWindowPosition(res.data.x, res.data.y);
});
}
// 保存悬浮窗位置
function saveFloatingWindowPosition(x, y) {
uni.setStorageSync('floatingWindowPosition', {
x: x,
y: y
});
}
// 在页面显示时初始化
onShow() {
showImageFloatingWindow();
setupListeners();
}
// 在页面卸载时清理资源
onUnload() {
hideInAppFloatingWindow();
}
3. 视频悬浮窗使用示例
import { showInAppFloatingWindow } from '@/uni_modules/tsw-floating-uts';
// 显示视频悬浮窗
function showVideoFloatingWindow() {
showInAppFloatingWindow({
width: 300,
height: 200,
x: 100,
y: 200,
draggable: true,
contentType: 'video',
contentData: {
videoUrl: 'https://example.com/sample-video.mp4',
cornerRadius: 15
}
}, (res) => {
if (res.code === 0) {
console.log('视频悬浮窗显示成功');
} else {
console.error('视频悬浮窗显示失败:', res.data.message);
}
});
}
4. 高级使用场景 - 复杂布局悬浮窗
以下示例展示如何实现一个带计数和操作按钮的复杂布局悬浮窗:
import { showInAppFloatingWindow, setInAppFloatingWindowClickListener } from '@/uni_modules/tsw-floating-uts';
// 模拟消息计数
let messageCount = 5;
// 显示带计数的通知悬浮窗
function showNotificationFloatingWindow() {
updateNotificationWindow();
}
// 更新通知悬浮窗内容
function updateNotificationWindow() {
// 使用HTML模板生成悬浮窗内容
const notificationTemplate = `
<div style="display:flex;align-items:center;justify-content:space-between;padding:10px;background-color:#007AFF;border-radius:20px;">
<div style="display:flex;align-items:center;">
<div style="width:20px;height:20px;background-color:red;border-radius:50%;display:flex;align-items:center;justify-content:center;margin-right:8px;">
<span style="color:white;font-size:12px;">${messageCount}</span>
</div>
<span style="color:white;">新消息</span>
</div>
<div style="background-color:white;color:#007AFF;padding:4px 8px;border-radius:12px;">查看</div>
</div>
`;
// 注意:当前插件版本不直接支持HTML内容,这里演示的是一个概念,实际实现需要分步骤
showInAppFloatingWindow({
width: 180,
height: 60,
x: 100,
y: 100,
draggable: true,
contentType: 'image',
// 实际项目中,可以将HTML模板转换为图片URL
contentData: {
imageUrl: '/static/notification-icon.png',
cornerRadius: 20
}
}, (res) => {
if (res.code === 0) {
console.log('通知悬浮窗显示成功');
setupNotificationClickListener();
}
});
}
// 设置通知点击监听器
function setupNotificationClickListener() {
setInAppFloatingWindowClickListener((res) => {
console.log('通知悬浮窗被点击');
// 处理通知点击事件
handleNotificationClick();
});
}
// 处理通知点击
function handleNotificationClick() {
// 跳转到消息页面
uni.navigateTo({
url: '/pages/messages/messages'
});
// 重置消息计数
messageCount = 0;
}
5. 自适应内容尺寸悬浮窗
以下示例展示如何创建根据内容自适应尺寸的悬浮窗:
import { showInAppFloatingWindow } from '@/uni_modules/tsw-floating-uts';
// 显示自适应内容尺寸的悬浮窗
function showAdaptiveFloatingWindow(content) {
// 计算内容所需的最小宽度
const minWidth = Math.max(content.length * 12, 150); // 估算文本宽度
showInAppFloatingWindow({
width: minWidth,
height: 'auto', // 自适应高度(如果插件支持)
x: 100,
y: 100,
draggable: true,
contentType: 'text',
contentData: {
text: content,
textSize: 14,
textColor: '#333333',
backgroundColor: '#F0F0F0',
cornerRadius: 15
}
}, (res) => {
if (res.code === 0) {
console.log('自适应悬浮窗显示成功,宽度:', minWidth);
}
});
}
// 使用示例
function showTips() {
showAdaptiveFloatingWindow('这是一条提示信息,会根据内容长度自动调整宽度');
}
6. 多悬浮窗管理
在复杂应用中,可能需要同时管理多个悬浮窗:
import { showInAppFloatingWindow, hideInAppFloatingWindow } from '@/uni_modules/tsw-floating-uts';
// 悬浮窗管理器
class FloatingWindowManager {
constructor() {
this.floatingWindows = new Map(); // 存储不同的悬浮窗
}
// 显示消息通知悬浮窗
showNotificationWindow() {
showInAppFloatingWindow({
width: 160,
height: 60,
x: 50,
y: 100,
draggable: true,
contentType: 'text',
contentData: {
text: '新消息通知',
backgroundColor: '#FF3B30'
}
}, (res) => {
if (res.code === 0) {
this.floatingWindows.set('notification', 'active');
}
});
}
// 显示操作快捷悬浮窗
showQuickActionWindow() {
showInAppFloatingWindow({
width: 80,
height: 80,
x: 250,
y: 200,
draggable: true,
contentType: 'image',
contentData: {
imageUrl: '/static/quick-action.png',
cornerRadius: 40
}
}, (res) => {
if (res.code === 0) {
this.floatingWindows.set('quickAction', 'active');
}
});
}
// 隐藏所有悬浮窗
hideAllWindows() {
this.floatingWindows.forEach((value, key) => {
hideInAppFloatingWindow(); // 注意:当前插件API可能需要调整以支持多窗口
});
this.floatingWindows.clear();
}
// 隐藏特定悬浮窗
hideWindow(windowId) {
if (this.floatingWindows.has(windowId)) {
hideInAppFloatingWindow(); // 注意:当前插件API可能需要调整以支持多窗口
this.floatingWindows.delete(windowId);
}
}
}
// 创建全局悬浮窗管理器实例
const windowManager = new FloatingWindowManager();
// 使用示例
function initFloatingWindows() {
windowManager.showNotificationWindow();
windowManager.showQuickActionWindow();
}
// 在页面卸载时隐藏所有悬浮窗
onUnload() {
windowManager.hideAllWindows();
}
应用在后台时的处理机制
当应用处于后台时,点击系统悬浮窗会通过以下机制唤醒应用:
- 首先尝试使用
launchAppToForeground方法直接将应用带到前台 - 如果上述方法失败,则使用 Scheme URL 方式打开应用
- 应用被唤醒后,可以通过以下方式处理启动参数:
// App.vue
onLaunch() {
// 获取启动参数
const args = plus.runtime.arguments;
if (args && args.includes('from=floating_window')) {
console.log('应用通过悬浮窗点击打开');
// 这里可以执行特定操作,如显示特定页面
this.handleFloatingWindowLaunch();
}
}
methods: {
handleFloatingWindowLaunch() {
// 处理从悬浮窗打开的逻辑
uni.showToast({
title: '从悬浮窗打开',
icon: 'none'
});
// 可以跳转到特定页面
uni.navigateTo({
url: '/pages/floating-callback/floating-callback'
});
}
}
权限和配置说明
系统级悬浮窗权限配置
在使用系统级悬浮窗前,必须完成以下配置:
- AndroidManifest.xml 配置:
<!-- 添加悬浮窗权限 -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<!-- 添加前台服务权限 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- 在 application 标签内添加服务声明 -->
<service
android:name="uts.sdk.modules.tswFloatingUts.FloatingWindowService"
android:exported="false"
android:foregroundServiceType="mediaProjection" />
- 运行时权限请求:
在使用系统级悬浮窗前,必须检查并请求悬浮窗权限。
应用内悬浮窗配置
应用内悬浮窗不需要特殊权限配置,可直接使用。
技术细节与实现说明
窗口类型与层级
- 系统级悬浮窗:使用
TYPE_APPLICATION_OVERLAY(Android 8.0+)或TYPE_PHONE(Android 8.0 以下) - 应用内悬浮窗:使用
TYPE_APPLICATION_PANEL
拖动实现机制
拖动功能通过 View.OnTouchListener 实现,主要逻辑包括:
- ACTION_DOWN:记录初始触摸位置和悬浮窗位置
- ACTION_MOVE:计算偏移量,更新悬浮窗位置
- ACTION_UP:判断是点击还是拖动,执行相应操作
自动吸附功能
当拖动结束时,悬浮窗会根据其中心点距离屏幕左右边缘的距离,自动吸附到最近的边缘,提升用户体验。
圆角实现
对于 Android 5.0(API 21)及以上版本,使用 ViewOutlineProvider 和 clipToOutline 属性实现平滑的圆角效果。
图片加载优化
插件实现了异步图片加载机制,包括:
- 网络图片异步加载,避免阻塞主线程
- 图片加载失败的错误处理和默认占位符
- 圆角裁剪应用
常见问题与解决方案
1. 系统级悬浮窗不显示
- 权限问题:确保已在 AndroidManifest.xml 中添加权限并在运行时请求
- 系统设置:某些手机厂商(如小米、华为等)需要在系统设置中手动开启悬浮窗权限
- Android 版本:Android 8.0+ 需要使用 TYPE_APPLICATION_OVERLAY 窗口类型
- 前台服务:确保前台服务正常运行并显示通知
2. 应用内悬浮窗不显示
- 生命周期:确保在页面 onShow 生命周期中调用 showInAppFloatingWindow
- 坐标位置:检查 x 和 y 坐标是否在屏幕范围内
- 内容设置:确保 contentType 和 contentData 设置正确
3. 点击事件不响应
- 应用状态:应用在后台时,点击事件通过 Scheme 打开应用,请检查 App.vue 中的处理逻辑
- 监听器设置:确保正确设置了点击监听器,且在悬浮窗显示后设置
- Scheme 配置:检查应用的 Scheme 配置是否正确
4. 图片加载失败
- URL 验证:确保图片 URL 格式正确且可访问
- 网络状态:检查设备网络连接状态
- 权限检查:对于网络图片,确保应用有网络访问权限
5. 视频播放问题
- 视频格式:确保视频格式受 Android VideoView 支持
- URL 有效性:检查视频 URL 是否可访问
- 网络状况:视频播放需要良好的网络连接
6. updateFloatingWindow 不生效
- 悬浮窗存在性:确保悬浮窗已经通过 showFloatingWindow 显示
- 参数完整性:确保传递了正确的 contentType 和 contentData
- 错误处理:检查回调函数中的错误信息
性能优化建议
- 合理设置悬浮窗大小:避免创建过大的悬浮窗,影响系统性能
- 及时隐藏悬浮窗:在不需要时及时隐藏悬浮窗,释放资源
- 优化图片加载:对于网络图片,考虑使用图片缓存机制
- 减少不必要的更新:避免频繁更新悬浮窗内容
- 合理处理回调:在回调函数中避免执行耗时操作
更新日志
v1.1.0
- 新增应用内悬浮窗功能(参考 FloatingX 实现)
- 优化系统级悬浮窗的稳定性
- 增加自动吸附到屏幕边缘功能
- 添加拖动结束回调
v1.0.0
- 初始版本,支持系统级悬浮窗功能
- 支持文本、图片和视频三种内容类型
- 实现基础的拖拽和点击功能
开发者信息
- 作者:tsw
- 邮箱:[2418091167@qq.com]
平台兼容性
uni-app(4.85)
| Vue2 | Vue3 | Chrome | Safari | app-vue | app-nvue | Android | iOS | 鸿蒙 |
|---|---|---|---|---|---|---|---|---|
| √ | √ | - | - | - | - | √ | - | - |
| 微信小程序 | 支付宝小程序 | 抖音小程序 | 百度小程序 | 快手小程序 | 京东小程序 | 鸿蒙元服务 | QQ小程序 | 飞书小程序 | 快应用-华为 | 快应用-联盟 |
|---|---|---|---|---|---|---|---|---|---|---|
| - | - | - | - | - | - | - | - | - | - | - |
其他
| 多语言 | 暗黑模式 | 宽屏模式 |
|---|---|---|
| × | × | √ |
1.0.0(2025-12-01)
tsw-floating-uts 悬浮窗插件
插件介绍
这是一个支持系统级和应用内悬浮窗的 uni-app UTS 插件,专为 Android 平台优化设计。该插件提供了灵活的悬浮窗解决方案,让开发者能够轻松实现各种场景下的悬浮窗需求。
核心功能特性
- 系统级悬浮窗:需要悬浮窗权限,可在应用后台和前台显示
- 应用内悬浮窗:不需要特殊权限,仅在应用内显示
- 多内容类型支持:支持文本、图片两种种内容类型
- 智能拖拽功能:支持悬浮窗拖拽,并具备自动吸附到屏幕边缘的特性
- 完善的事件处理:支持点击事件回调和拖动结束回调
- 优雅的错误处理:提供详细的错误日志和状态返回
- 后台交互支持:应用在后台时,通过 Scheme 机制实现点击悬浮窗唤醒应用
技术实现原理
系统级悬浮窗实现
系统级悬浮窗使用 Android 的 WindowManager 服务和 TYPE_APPLICATION_OVERLAY 窗口类型(Android 8.0+)或 TYPE_PHONE 窗口类型(Android 8.0 以下)实现。核心实现包括:
- 使用前台服务(Foreground Service)确保悬浮窗在应用退到后台时仍能正常显示
- 通过 WindowManager.LayoutParams 控制悬浮窗的显示位置、大小和层级
- 使用触摸监听器实现拖拽功能和点击事件处理
- 实现自动吸附到屏幕边缘的功能,提升用户体验
应用内悬浮窗实现
应用内悬浮窗使用 TYPE_APPLICATION_PANEL 窗口类型实现,仅在应用内显示,具有以下特点:
- 不需要特殊权限
- 应用切换到后台时自动隐藏
- 实现方式更轻量,性能开销更小
- 适合仅在应用内需要悬浮提示的场景
安装方式
方法一:通过插件市场安装
在 uni-app 项目中,通过 HBuilderX 插件市场搜索 "tsw-floating-uts" 并安装。
方法二:手动安装
直接复制 uni_modules/tsw-floating-uts 目录到您的 uni-app 项目的 uni_modules 目录下。
权限配置
系统级悬浮窗权限配置
使用系统级悬浮窗需要在 AndroidManifest.xml 中添加以下权限:
<!-- 悬浮窗权限 -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<!-- 如果使用前台服务,还需要添加以下权限 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
权限请求流程
- 调用
checkPermission()方法检查是否已获得悬浮窗权限 - 如果未获得权限,调用
requestPermission()方法请求用户授权 - 授权成功后,即可使用系统级悬浮窗功能
接口说明
1. 权限相关接口
// 检查是否拥有悬浮窗权限
export function checkPermission(): boolean;
// 请求悬浮窗权限
export function requestPermission(): void;
2. 系统级悬浮窗接口
// 显示系统级悬浮窗
export function showFloatingWindow(options: FloatingWindowOptions, callback?: TswFloatingUtsCallback): void;
// 更新系统级悬浮窗内容
export function updateFloatingWindow(options: FloatingWindowOptions, callback?: TswFloatingUtsCallback): void;
// 隐藏系统级悬浮窗
export function hideFloatingWindow(callback?: TswFloatingUtsCallback): void;
// 设置系统级悬浮窗点击监听器
// 注意:应用在前台时直接触发回调,在后台时通过Scheme打开应用
export function setFloatingWindowClickListener(callback?: TswFloatingUtsCallback): void;
3. 应用内悬浮窗接口
// 显示应用内悬浮窗(不需要系统权限)
export function showInAppFloatingWindow(options: FloatingWindowOptions, callback?: TswFloatingUtsCallback): void;
// 隐藏应用内悬浮窗
export function hideInAppFloatingWindow(callback?: TswFloatingUtsCallback): void;
// 设置应用内悬浮窗点击监听器
export function setInAppFloatingWindowClickListener(callback?: TswFloatingUtsCallback): void;
// 设置应用内悬浮窗拖动结束监听器
export function setInAppFloatingWindowDragEndListener(callback?: TswFloatingUtsCallback): void;
// 模拟应用内悬浮窗拖拽 更新应用内悬浮窗位置
export function simulateInAppFloatingWindowDrag(x: number, y: number, callback?: TswFloatingUtsCallback): void;
4. 数据类型定义
// 接口返回结果类型
export type TswFloatingUtsResult = {
code: number; // 0表示成功,-1表示失败
data: any; // 结果数据
};
// 回调函数类型
export type TswFloatingUtsCallback = (result: TswFloatingUtsResult) => void;
// 内容数据类型
export type ContentData = {
// 文本相关属性
text?: string; // 文本内容
textSize?: number; // 文本大小
textColor?: string; // 文本颜色
// 样式相关属性
backgroundColor?: string; // 背景颜色
cornerRadius?: number; // 圆角大小
// 资源相关属性
resourceId?: number; // 图片资源ID(用于本地资源)
imageUrl?: string; // 图片URL
videoUrl?: string; // 视频URL
};
// 悬浮窗配置选项
export type FloatingWindowOptions = {
contentType: string; // 内容类型:"text", "image", "video"
contentData: ContentData; // 内容数据
width?: number; // 宽度,默认200
height?: number; // 高度,默认100
x?: number; // X坐标,默认100
y?: number; // Y坐标,默认200
draggable?: boolean; // 是否可拖拽,默认true
};
// 数据对象
export type UTSJSONObject = Record<string, any>;
使用示例
1. 系统级悬浮窗完整使用流程
import { checkPermission, requestPermission, showFloatingWindow, setFloatingWindowClickListener } from '@/uni_modules/tsw-floating-uts';
// 检查并请求权限
function setupSystemFloatingWindow() {
const hasPermission = checkPermission();
if (hasPermission) {
showSystemFloatingWindow();
} else {
// 请求权限
requestPermission();
// 可以在一定时间后再次检查权限
setTimeout(() => {
const newPermissionStatus = checkPermission();
if (newPermissionStatus) {
showSystemFloatingWindow();
} else {
uni.showToast({
title: '请在设置中开启悬浮窗权限',
icon: 'none'
});
}
}, 1000);
}
}
// 显示系统级悬浮窗
function showSystemFloatingWindow() {
showFloatingWindow({
width: 250,
height: 100,
x: 50,
y: 150,
draggable: true,
contentType: 'text',
contentData: {
text: '系统级悬浮窗示例',
textSize: 16,
textColor: '#FFFFFF',
backgroundColor: '#007AFF',
cornerRadius: 20
}
}, (res) => {
if (res.code === 0) {
console.log('系统级悬浮窗显示成功');
setClickListener();
} else {
console.error('系统级悬浮窗显示失败:', res.data.message);
}
});
}
// 设置点击监听器
function setClickListener() {
setFloatingWindowClickListener((res) => {
console.log('系统悬浮窗被点击,来源:', res.data.from);
// 应用在前台时,这里的代码会直接执行
uni.showToast({
title: '悬浮窗被点击',
icon: 'none'
});
});
}
// 在页面显示时调用
onShow() {
setupSystemFloatingWindow();
}
// 在页面卸载时隐藏悬浮窗
onUnload() {
hideFloatingWindow();
}
2. 应用内悬浮窗使用示例
import { showInAppFloatingWindow, setInAppFloatingWindowClickListener, setInAppFloatingWindowDragEndListener } from '@/uni_modules/tsw-floating-uts';
// 显示应用内悬浮窗(图片类型)
function showImageFloatingWindow() {
showInAppFloatingWindow({
width: 120,
height: 120,
x: 200,
y: 300,
draggable: true,
contentType: 'image',
contentData: {
imageUrl: '/static/logo.png',
cornerRadius: 20
}
}, (res) => {
console.log(res.data.message);
});
}
// 配置事件监听器
function setupListeners() {
// 设置点击监听器
setInAppFloatingWindowClickListener((res) => {
console.log('应用内悬浮窗被点击');
uni.showToast({
title: '点击了图片悬浮窗',
icon: 'none'
});
});
// 设置拖动结束监听器
setInAppFloatingWindowDragEndListener((res) => {
console.log(`悬浮窗拖动到位置: (${res.data.x}, ${res.data.y})`);
// 可以在这里保存悬浮窗位置,下次打开时恢复
saveFloatingWindowPosition(res.data.x, res.data.y);
});
}
// 保存悬浮窗位置
function saveFloatingWindowPosition(x, y) {
uni.setStorageSync('floatingWindowPosition', {
x: x,
y: y
});
}
// 在页面显示时初始化
onShow() {
showImageFloatingWindow();
setupListeners();
}
// 在页面卸载时清理资源
onUnload() {
hideInAppFloatingWindow();
}
权限和配置说明
系统级悬浮窗权限配置
在使用系统级悬浮窗前,必须完成以下配置:
- AndroidManifest.xml 配置:
<!-- 添加悬浮窗权限 -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<!-- 添加前台服务权限 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- 在 application 标签内添加服务声明 -->
<service
android:name="uts.sdk.modules.tswFloatingUts.FloatingWindowService"
android:exported="false"
android:foregroundServiceType="mediaProjection" />
- 运行时权限请求:
在使用系统级悬浮窗前,必须检查并请求悬浮窗权限。
应用内悬浮窗配置
应用内悬浮窗不需要特殊权限配置,可直接使用。
技术细节与实现说明
窗口类型与层级
- 系统级悬浮窗:使用
TYPE_APPLICATION_OVERLAY(Android 8.0+)或TYPE_PHONE(Android 8.0 以下) - 应用内悬浮窗:使用
TYPE_APPLICATION_PANEL
拖动实现机制
拖动功能通过 View.OnTouchListener 实现,主要逻辑包括:
- ACTION_DOWN:记录初始触摸位置和悬浮窗位置
- ACTION_MOVE:计算偏移量,更新悬浮窗位置
- ACTION_UP:判断是点击还是拖动,执行相应操作
自动吸附功能
当拖动结束时,悬浮窗会根据其中心点距离屏幕左右边缘的距离,自动吸附到最近的边缘,提升用户体验。
圆角实现
对于 Android 5.0(API 21)及以上版本,使用 ViewOutlineProvider 和 clipToOutline 属性实现平滑的圆角效果。
图片加载优化
插件实现了异步图片加载机制,包括:
- 网络图片异步加载,避免阻塞主线程
- 图片加载失败的错误处理和默认占位符
- 圆角裁剪应用
常见问题与解决方案
1. 系统级悬浮窗不显示
- 权限问题:确保已在 AndroidManifest.xml 中添加权限并在运行时请求
- 系统设置:某些手机厂商(如小米、华为等)需要在系统设置中手动开启悬浮窗权限
- Android 版本:Android 8.0+ 需要使用 TYPE_APPLICATION_OVERLAY 窗口类型
- 前台服务:确保前台服务正常运行并显示通知
2. 应用内悬浮窗不显示
- 生命周期:确保在页面 onShow 生命周期中调用 showInAppFloatingWindow
- 坐标位置:检查 x 和 y 坐标是否在屏幕范围内
- 内容设置:确保 contentType 和 contentData 设置正确
3. 点击事件不响应
- 应用状态:应用在后台时,点击事件通过 Scheme 打开应用,请检查 App.vue 中的处理逻辑
- 监听器设置:确保正确设置了点击监听器,且在悬浮窗显示后设置
- Scheme 配置:检查应用的 Scheme 配置是否正确
4. 图片加载失败
- URL 验证:确保图片 URL 格式正确且可访问
- 网络状态:检查设备网络连接状态
- 权限检查:对于网络图片,确保应用有网络访问权限
5. updateFloatingWindow 不生效
- 悬浮窗存在性:确保悬浮窗已经通过 showFloatingWindow 显示
- 参数完整性:确保传递了正确的 contentType 和 contentData
- 错误处理:检查回调函数中的错误信息
性能优化建议
- 合理设置悬浮窗大小:避免创建过大的悬浮窗,影响系统性能
- 及时隐藏悬浮窗:在不需要时及时隐藏悬浮窗,释放资源
- 优化图片加载:对于网络图片,考虑使用图片缓存机制
- 减少不必要的更新:避免频繁更新悬浮窗内容
- 合理处理回调:在回调函数中避免执行耗时操作
更新日志
v1.1.0
- 新增应用内悬浮窗功能(参考 FloatingX 实现)
- 优化系统级悬浮窗的稳定性
- 增加自动吸附到屏幕边缘功能
- 添加拖动结束回调
v1.0.0
- 初始版本,支持系统级悬浮窗功能
- 支持文本、图片和视频三种内容类型
- 实现基础的拖拽和点击功能
开发者信息
- 作者:tsw
- 邮箱:[2418091167@qq.com]

收藏人数:
购买源码授权版(
试用
使用 HBuilderX 导入示例项目
赞赏(0)
下载 53
赞赏 0
下载 11600392
赞赏 1815
赞赏
京公网安备:11010802035340号