floaty 自定义悬浮窗 — 打造专属悬浮按钮
floaty 模块定位
AutoJs6 自带浮动按钮(内置功能),floaty 模块让你可以创建自己的悬浮窗——完全自定义位置、大小、内容和交互。自定义悬浮窗可以作为脚本的快捷入口、信息显示面板、甚至是一个迷你 UI。
┌────────────┐ ┌───────────────┐ ┌────────┐
│ AutoJs6 │ │ 自定义信息面板 │ │ 迷你UI │
│ 内置浮动按钮│ │ floaty.rawWindow│ │ floaty │
└────────────┘ └───────────────┘ └────────┘
预制功能 自定义布局 迷你完整界面
两种悬浮窗
floaty.rawWindow — 原始悬浮窗
直接用 E4X XML 定义布局,最灵活但需要手动管理:
// 原始悬浮窗 —— 简单圆形按钮
let w = floaty.rawWindow(
<frame gravity="center">
<text text="悬浮" textColor="#FFF" textSize="14sp" bg="#FF5722" w="50" h="50" gravity="center"/>
</frame>
);
// 设置悬浮窗位置
w.setPosition(500, 800);
// 点击事件
w.click(() => {
toast("悬浮窗被点击");
});
floaty.window — 完整窗口
类似迷你应用窗口,有标题栏、调整大小、关闭等:
// 完整悬浮窗 —— 带标题栏
let win = floaty.window(
<vertical bg="#FFF">
<horizontal bg="#FF5722" padding="8">
<text text="我的悬浮窗" textColor="#FFF" w="*"/>
<button id="closeBtn" text="×" textColor="#FFF" bg="#FF5722"/>
</horizontal>
<text id="content" text="这是悬浮窗内容" padding="16" w="*"/>
</vertical>
);
// 设置初始位置和大小
win.setPosition(100, 200);
win.setSize(300, 400);
win.closeBtn.click(() => win.close());
拖动移动
单指拖动
让悬浮窗可以随手指拖动:
let w = floaty.rawWindow(
<frame bg="#FF5722" w="60" h="60" gravity="center">
<text text="拖我" textColor="#FFF" textSize="12sp"/>
</frame>
);
// 设置可拖动(默认全屏范围)
w.setTouchable(true);
let isDragging = false;
let initialX, initialY;
let wX, wY;
w.setOnTouchListener(function(view, event) {
let action = event.getAction();
switch (action) {
case event.ACTION_DOWN:
isDragging = true;
initialX = event.getRawX();
initialY = event.getRawY();
wX = w.getPosition().x;
wY = w.getPosition().y;
return true;
case event.ACTION_MOVE:
if (isDragging) {
let dx = event.getRawX() - initialX;
let dy = event.getRawY() - initialY;
w.setPosition(wX + dx, wY + dy);
}
return true;
case event.ACTION_UP:
case event.ACTION_CANCEL:
isDragging = false;
// 可选:松手后吸附到屏幕边缘
snapToEdge(w);
return true;
}
return false;
});
// 边缘吸附
function snapToEdge(window) {
let screenWidth = device.width;
let half = screenWidth / 2;
let currentX = window.getPosition().x;
if (currentX < half) {
window.setPosition(0, window.getPosition().y);
} else {
window.setPosition(screenWidth - 60, window.getPosition().y);
}
}
autoMove — 简化拖动
// AutoJs6 提供了简化的拖动实现
let w = floaty.rawWindow(...);
// 让整个窗口都能拖动(无需手动处理触摸)
floaty.enableDragging(w);
// 只让某个控件作为拖拽手柄
let w2 = floaty.rawWindow(
<frame>
<text id="handle" text="拖这里" bg="#888" w="*" h="40"/>
<text text="窗口其他内容" textColor="#FFF" marginTop="40"/>
</frame>
);
floaty.enableDragging(w2, w2.handle); // 只 handle 可拖
悬浮窗属性
获取与设置
let w = floaty.rawWindow(...);
// 位置
let pos = w.getPosition();
console.log("位置: (" + pos.x + ", " + pos.y + ")");
w.setPosition(100, 200);
// 大小
w.setSize(300, 400);
let size = w.getSize();
console.log("尺寸: " + size.width + "x" + size.height);
// 可见性
w.show();
w.hide();
w.close(); // 关闭并销毁
// 置顶
w.setTouchable(true); // 能否接收触摸事件
w.setTouchable(false); // 透明穿透(下面的控件仍可操作)
// 悬浮窗类型
// "application" — 默认,需要悬浮窗权限
// "system" — 系统级悬浮(需要 Root 或特殊权限)
控件属性访问
let w = floaty.rawWindow(
<vertical>
<text id="title" text="标题"/>
<button id="action" text="执行"/>
</vertical>
);
// 直接访问子控件
w.title.setText("新标题");
w.action.click(() => {
toast("执行了");
});
实战:实时信息显示面板
/**
* 实时显示脚本运行状态的悬浮面板
*/
function createStatusPanel() {
let panel = floaty.window(
<vertical bg="#CC000000" padding="10">
<horizontal bg="#FF5722" padding="6">
<text text="任务状态" textColor="#FFF" textSize="12sp" w="*"/>
<button id="close" text="×" textColor="#FFF" bg="#FF5722" textSize="12sp"/>
</horizontal>
<vertical padding="8">
<text id="status" text="⏳ 等待启动" textColor="#FFF" textSize="12sp"/>
<text id="count" text="完成: 0" textColor="#8BC34A" textSize="12sp" marginTop="4"/>
<text id="time" text="运行时长: 0s" textColor="#90CAF9" textSize="12sp" marginTop="4"/>
</vertical>
</vertical>
);
panel.setPosition(100, 300);
panel.close.click(() => panel.close());
let startTime = Date.now();
let completedCount = 0;
// 更新函数
function updateStatus(text) {
panel.status.setText(text);
panel.count.setText("完成: " + completedCount);
let elapsed = Math.floor((Date.now() - startTime) / 1000);
panel.time.setText("运行时长: " + elapsed + "s");
}
// 定时刷新运行时间
let timer = setInterval(() => updateStatus(panel.status.text()), 1000);
// 返回控制对象
return {
panel: panel,
update: (text) => {
completedCount++;
updateStatus(text);
},
close: () => {
clearInterval(timer);
panel.close();
}
};
}
// 使用
let status = createStatusPanel();
status.update("✓ 签到完成");
status.update("✓ 清理完成");
注意事项
悬浮窗权限
Android 6.0+ 需要动态请求悬浮窗权限:
if (!floaty.checkPermission()) {
// 跳转到系统设置页授权
floaty.requestPermission();
} else {
// 有权限,正常创建悬浮窗
let w = floaty.rawWindow(...);
}
多个悬浮窗
// 可以同时创建多个悬浮窗
let btn = floaty.rawWindow(<frame bg="#FF0000" w="50" h="50"/>);
let panel = floaty.window(<vertical>...</vertical>);
btn.setPosition(100, 500);
panel.setPosition(200, 300);
// 各自独立管理
btn.close();
panel.close();
清理资源
// 脚本结束时记得关闭所有悬浮窗
events.on("exit", () => {
floaty.closeAll(); // 一键关闭全部
});
与内置浮动按钮的关系
AutoJs6 的浮动按钮本身也是一个 floaty 悬浮窗,只是由 AutoJs6 内置管理。你创建的自定义悬浮窗和内置的可以共存。