HarmonyOS APP《画伴梦工厂》开发第65篇-闪控球FloatingBall交互设计
第7.11篇:闪控球(Floating Ball)交互设计
难度:⭐⭐⭐ 高级
前置知识:第 7.10 篇 标准悬浮窗开发
涉及源文件:参考 Window Kit 开发文档

概述
从"画伴梦工厂"的需求出发——一个儿童绘画创作工具,用户需要在涂鸦、调色、选择笔刷、浏览画册等操作之间高频切换。传统的顶部导航栏和侧边抽屉菜单在手机屏幕上占据了宝贵的显示空间,而频繁的页面跳转又打断了创作流。理想情况下,用户应当能在任何界面、任何时刻以最少的操作步骤访问最核心的工具——这就引出了本篇文章的核心主题:闪控球(Floating Ball)。
HarmonyOS 7 在 HDC 2026 上推出的 Floating Ball(闪控球)是一种全新的交互范式。它区别于第 7.10 篇介绍的标准悬浮窗——悬浮窗是一个"悬浮的窗口",承载着完整的页面内容;而闪控球是一个"悬浮的快捷入口",以一个可拖拽、可贴边隐藏的圆形图标浮在所有应用界面之上,轻轻一点即可唤出工具菜单或快捷操作。
如果说标准悬浮窗是鸿蒙多窗交互体系的"大屏模式",那闪控球就是它的"微交互模式"——极简、全能、无处不在。本文将深入解析闪控球的 API 设计、交互形态、视觉规范、权限模型,以及如何在"画伴梦工厂"中利用闪控球构建丝滑的快捷工具入口。
一、闪控球——创新交互范式的设计哲学
1.1 从物理世界到数字界面
闪控球的设计灵感来源于一个朴素的需求:在物理世界中,工具总是触手可及的。画家的工作台上,画笔和调色板就在手边;厨房里,刀具和调料架就在视线范围内。但在数字空间中,工具被埋藏在层层菜单和页面跳转之后。
闪控球试图还原"工具在侧"的物理体验——一个始终悬浮在屏幕边缘的圆形小球,如同工作台上的一个磁吸工具架。它不主动打扰用户(贴边隐藏),但在需要时又能够即刻响应(点击唤起)。
1.2 闪控球 vs 闪控窗 vs 标准悬浮窗
为了清晰地理解闪控球的定位,我们需要先区分鸿蒙 7 多窗交互体系中的三个核心概念:
| 对比维度 | 闪控球(Floating Ball) | 闪控窗(Flash Window) | 标准悬浮窗(Standard Window) |
|---|---|---|---|
| 交互形态 | 圆形小球,贴边隐藏 | 卡片/面板,瞬态弹出 | 矩形窗口,常驻显示 |
| 触发方式 | 点击贴边小球唤出 | 从特定区域滑动/点击 | 由应用创建和控制 |
| 显示时长 | 常驻贴边,瞬态唤出 | 操作完成后自动收起 | 由应用生命周期管理 |
| 内容承载 | 图标 + 快捷菜单/弹窗 | 轻量信息卡片 | 完整 ArkUI 页面 |
| 用户控制 | 可拖拽、可贴边、可删除 | 被动响应 | 可拖拽、可关闭 |
| 核心场景 | 工具快捷入口 | 信息快速预览 | 多功能悬浮窗口 |
简单来说:闪控球解决的是"入口"问题,闪控窗解决的是"预览"问题,标准悬浮窗解决的是"窗口"问题。三者构成了从入口到内容再到窗口的完整交互链路。
1.3 核心交互模型
闪控球的交互模型可以用四个字概括:看不见,但触得到。
屏幕初始状态
┌──────────────────────────────────────┐
│ │
│ │
│ ╭───╮ │
│ │ ⚪ │ ← 闪控球 │
│ ╰───╯ │
│ │
│ │
│ │
└──────────────────────────────────────┘
用户点击闪控球
┌──────────────────────────────────────┐
│ │
│ ┌──── 快捷菜单 ────┐ │
│ │ 🖌 画笔 │ │
│ │ 🎨 调色板 │ │
│ │ 📂 作品集 │ │
│ │ ⚙️ 设置 │ │
│ └────────────────┘ │
│ ╭───╮ │
│ │ ⚪ │ ← 保留在原位 │
│ ╰───╯ │
│ │
└──────────────────────────────────────┘
拖拽到屏幕边缘
┌──────────────────────────────────────┐
│ ╭───╮ │
│ │ ⚪ │ ← 贴边半隐藏,仅露出边缘 │
│ ╰───╯ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────┘
- 贴边隐藏:拖拽到屏幕边缘时自动吸附,仅露出约 1/3 宽度,减少遮挡
- 点击唤起:点击露出的边缘部分,小球弹出展示完整形态,同时弹出操作菜单
- 拖拽重定位:长按拖拽可自由移动位置,松手自动贴边
- 长按删除:长按进入编辑模式,可选择删除/关闭闪控球功能
二、API 全景——从创建到销毁
2.1 能力检测:isFloatingBallEnabled
在创建闪控球之前,必须先检查当前设备是否支持闪控球能力。这是因为闪控球依赖系统级的 Window Manager 服务,并非所有 HarmonyOS 设备都具备此能力:
import { floatingBall } from '@kit.WindowKit';
function checkFloatingBallSupport(): boolean {
const isEnabled = floatingBall.isFloatingBallEnabled();
if (!isEnabled) {
console.warn('当前设备不支持闪控球功能');
// 可降级使用标准悬浮窗
}
return isEnabled;
}
isFloatingBallEnabled 返回 boolean 值,底层检测逻辑包括:
- 系统版本是否 ≥ API 26
- 设备类型是否支持(手机、平板、折叠屏通常支持;部分轻量设备可能不支持)
- 用户是否在星盾超级隐私管控中开启了闪控球权限
如果设备不支持,建议降级使用第 7.10 篇介绍的标准悬浮窗,或使用普通入口按钮替代。
2.2 创建闪控球:createFloatingBall
能力检测通过后,通过 createFloatingBall 创建闪控球实例:
import { floatingBall } from '@kit.WindowKit';
async function initFloatingBall(context: Context) {
try {
const ball = await floatingBall.createFloatingBall(context, {
icon: $r('app.media.floating_ball_icon'), // 自定义图标资源
iconSize: 56, // 图标尺寸(vp)
edgeMargin: 8, // 贴边后距边缘距离(vp)
edgeHideRatio: 0.7, // 贴边后隐藏比例 0~1
enableClick: true, // 启用点击响应
enableDrag: true, // 允许拖拽
enableLongPressDelete: true // 允许长按删除
});
console.info('闪控球创建成功');
return ball;
} catch (err) {
console.error('闪控球创建失败:' + JSON.stringify(err));
return null;
}
}
核心参数详解:
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
icon |
ResourceStr | 是 | — | 闪控球显示的图标 |
iconSize |
number | 否 | 48 | 图标尺寸(vp),范围 32~80 |
edgeMargin |
number | 否 | 4 | 贴边后距离屏幕边缘的距离(vp) |
edgeHideRatio |
number | 否 | 0.6 | 贴边后隐藏比例(0=完全可见,1=完全隐藏) |
enableClick |
boolean | 否 | true | 是否响应点击 |
enableDrag |
boolean | 否 | true | 是否允许用户拖拽 |
enableLongPressDelete |
boolean | 否 | true | 是否允许长按删除 |
2.3 启动闪控球:startFloatingBall
创建后的闪控球实例并不会自动显示,需要显式调用 start 方法启动:
private ball?: floatingBall.FloatingBall;
async function startBall() {
if (!this.ball) {
this.ball = await floatingBall.createFloatingBall(this.context, {
icon: $r('app.media.tools_icon'),
iconSize: 56
});
}
try {
await this.ball.start();
console.info('闪控球已启动');
} catch (err) {
console.error('闪控球启动失败:' + JSON.stringify(err));
}
}
// 在 UIAbility 的 onWindowStageCreate 之后调用
// 或者用户在设置页面中手动开启
onWindowStageCreate(windowStage: window.WindowStage) {
// 页面初始化逻辑...
this.startBall();
}
启动时机:建议在
UIAbility.onWindowStageCreate之后或应用主页首次渲染完成后启动闪控球。过早启动可能导致冲突,过晚启动则影响用户体验。
2.4 更新配置:update
在运行时动态更新闪控球的配置,例如切换图标以适应不同的工具状态:
// 进入涂鸦模式——更新图标为画笔
async function switchToDrawingMode() {
await this.ball?.update({
icon: $r('app.media.brush_icon'),
iconSize: 56,
enableClick: true
});
}
// 进入调色板模式——更新图标为色盘
async function switchToPaletteMode() {
await this.ball?.update({
icon: $r('app.media.palette_icon'),
iconSize: 56
});
}
// 禁用点击(例如在编辑锁定状态下)
async function lockBallInteraction() {
await this.ball?.update({
enableClick: false,
enableDrag: false
});
}
2.5 停止和销毁:stop
不再需要闪控球时,应当及时停止并销毁以释放系统资源:
// 停止闪控球(隐藏并从屏幕移除)
async function stopBall() {
try {
await this.ball?.stop();
console.info('闪控球已停止');
} catch (err) {
console.error('停止闪控球失败:' + JSON.stringify(err));
}
}
// 彻底销毁实例
async function destroyBall() {
try {
await this.ball?.destroy();
this.ball = undefined;
console.info('闪控球已销毁');
} catch (err) {
console.error('销毁闪控球失败:' + JSON.stringify(err));
}
}
// 在 Ability 的生命周期中清理
onWindowStageDestroy() {
this.destroyBall();
}
2.6 事件监听
闪控球提供了一组完善的事件,用于监听用户交互和状态变化:
// 注册事件监听
private registerEventListeners() {
if (!this.ball) return;
// 状态变更事件——贴边/拖拽/隐藏状态变化
this.ball.on('stateChange', (state: floatingBall.FloatingBallState) => {
switch (state) {
case floatingBall.FloatingBallState.DOCKED:
console.info('闪控球已贴边隐藏');
this.updateUIForDockedState();
break;
case floatingBall.FloatingBallState.ACTIVE:
console.info('闪控球已激活弹出');
this.updateUIForActiveState();
break;
case floatingBall.FloatingBallState.DRAGGING:
console.info('闪控球正在被拖拽');
break;
case floatingBall.FloatingBallState.DELETED:
console.info('闪控球已被用户删除');
this.ball = undefined;
break;
}
});
// 点击事件——用户点击闪控球时的回调
this.ball.on('click', () => {
console.info('用户点击了闪控球');
// 弹出工具菜单
this.showToolMenu();
});
// 长按删除事件——用户在编辑模式下确认删除
this.ball.on('longPressDelete', () => {
console.info('用户长按删除了闪控球');
// 执行清理逻辑
this.cleanupBallResources();
});
// 拖拽位置变更事件
this.ball.on('positionChange', (position: { x: number; y: number }) => {
console.info(`闪控球位置变更:(${position.x}, ${position.y})`);
// 可用于记录用户偏好的位置
});
}
// 取消事件监听
private unregisterEventListeners() {
this.ball?.off('stateChange');
this.ball?.off('click');
this.ball?.off('longPressDelete');
this.ball?.off('positionChange');
}
支持的事件类型一览:
| 事件名 | 回调参数 | 触发时机 |
|---|---|---|
stateChange |
FloatingBallState |
贴边/激活/拖拽/删除等状态转换 |
click |
void |
用户点击闪控球时 |
longPressDelete |
void |
用户长按并确认删除时 |
positionChange |
{ x: number, y: number } |
闪控球位置发生变化时 |
dragStart |
void |
开始拖拽时 |
dragEnd |
{ x: number, y: number } |
拖拽结束时(携带最终位置) |
三、视觉规范——4 种文本布局模板
3.1 模板分类
闪控球弹出的快捷菜单支持 4 种预定义的文本布局模板,覆盖了从纯图标到文字说明的不同场景:
模板 A:纯图标网格(3×3)
适用于功能入口较多(6~9 个)的场景,仅显示图标,简洁高效:
┌─────────────────────────┐
│ ┌──┐ ┌──┐ ┌──┐ │
│ │🖌│ │🎨│ │📂│ │
│ └──┘ └──┘ └──┘ │
│ ┌──┐ ┌──┐ ┌──┐ │
│ │📷│ │🎬│ │⚙️│ │
│ └──┘ └──┘ └──┘ │
└─────────────────────────┘
模板 B:图标 + 单行文字(列表式)
适用于功能入口较少(3~5 个)但需要文字说明的场景,是"画伴梦工厂"中最常用的模板:
┌─────────────────────────┐
│ 🖌 画笔选择 │
│ 🎨 调色板 │
│ 📂 我的作品 │
│ ⚙️ 设置 │
└─────────────────────────┘
模板 C:图标 + 双行文字(卡片式)
每个功能项包含图标、标题和副标题,适用于需要额外说明的场景:
┌─────────────────────────┐
│ ┌──────┐ │
│ │ 🖌 │ 画笔选择 │
│ │ │ 切换笔刷样式 │
│ └──────┘ │
│ ┌──────┐ │
│ │ 🎨 │ 调色板 │
│ │ │ 调整颜色参数 │
│ └──────┘ │
└─────────────────────────┘
模板 D:自定义布局
完全由开发者自定义 ArkUI 组件作为弹窗内容,适合高度定制化的场景:
// 自定义布局模板配置
const customTemplate: floatingBall.MenuTemplate = {
type: floatingBall.TemplateType.CUSTOM,
component: () => {
// 返回一个自定义的 @Component 构建函数
return MyCustomToolPanel();
}
};
3.2 设备适配尺寸
闪控球的尺寸会随设备类型自动调整,开发者可以根据设备断点给出推荐值:
| 设备类型 | 图标尺寸(vp) | 弹窗最大宽度 | 贴边后可见宽度 |
|---|---|---|---|
| 手机(< 600vp) | 44~48 | 280vp | 12~16vp |
| 大屏手机(600~840vp) | 48~56 | 320vp | 14~18vp |
| 平板(840~1320vp) | 56~64 | 400vp | 16~20vp |
| 折叠屏展开/2in1(≥ 1320vp) | 64~80 | 480vp | 18~24vp |
闪控球的贴边隐藏机制是其视觉设计的关键——确保在隐藏状态下仍然保留足够的视觉提示,让用户知道"这里有个东西可以点":
贴边隐藏状态(右边缘)
┌──────────────────────────────────┐
│ │
│ │
│ │
│ ┌──╮ │
│ │◉ │ ← 露出约 1/3 圆形的弧形边缘
│ └──╯ │
│ │
│ │
└──────────────────────────────────┘
弧形边缘的视觉设计建议:
- 露出宽度不少于 12vp,确保用户能够目视发现
- 弧形边缘使用系统磨砂材质,与背景融合
- 可叠加微弱呼吸动画(可选),增强视觉提示
四、配置详解——FloatingBallConfiguration 与 FloatingBallParams
4.1 FloatingBallConfiguration
FloatingBallConfiguration 是创建闪控球时的完整配置对象,定义了闪控球的外观、行为和交互参数:
interface FloatingBallConfiguration {
// === 必填参数 ===
icon: ResourceStr; // 闪控球图标资源
// === 外观参数 ===
iconSize?: number; // 图标尺寸(32~80vp,默认 48)
backgroundColor?: ResourceColor; // 背景颜色(默认半透明磨砂)
backgroundBlur?: boolean; // 是否启用磨砂模糊背景(默认 true)
shadowElevation?: number; // 阴影高度(0~24,默认 8)
// === 贴边行为 ===
edgeMargin?: number; // 贴边后距边缘距离(0~20vp,默认 4)
edgeHideRatio?: number; // 贴边后隐藏比例(0~1,默认 0.6)
edgeSnapThreshold?: number; // 贴边吸附阈值(vp,默认 40)
// === 交互控制 ===
enableClick?: boolean; // 启用点击(默认 true)
enableDrag?: boolean; // 启用拖拽(默认 true)
enableLongPressDelete?: boolean; // 启用长按删除(默认 true)
enableAutoHide?: boolean; // 空闲时自动贴边隐藏(默认 true)
// === 弹窗配置 ===
menuTemplate?: MenuTemplate; // 点击后弹出的菜单模板
menuWidth?: number; // 菜单宽度(vp)
menuHeight?: number; // 菜单高度(vp)
// === 安全与合规 ===
maxOverlapRatio?: number; // 与其他悬浮元素的最大重叠比例(0~1,默认 0.3)
}
4.2 FloatingBallParams
FloatingBallParams 是启动和更新闪控球时的可配置参数,与 FloatingBallConfiguration 基本一致,但不包含创建时不可修改的字段:
// 启动时传递额外参数
const startParams: floatingBall.FloatingBallParams = {
initialPosition: {
x: 'right', // 初始靠右贴边
y: 'center' // 垂直居中
},
showOnStart: true // 启动后立即显示
};
await this.ball?.start(startParams);
// 或者指定具体坐标
const customPosition: floatingBall.FloatingBallParams = {
initialPosition: {
x: 350, // 距离屏幕左边缘 350vp
y: 600 // 距离屏幕顶部 600vp
},
showOnStart: true
};
初始位置配置说明:
| 配置方式 | 示例 | 适用场景 |
|---|---|---|
| 字符串对齐 | { x: 'left', y: 'top' } |
快速定位到屏幕角落 |
| 字符串对齐 | { x: 'right', y: 'center' } |
靠右居中,大多数场景的默认位置 |
| 具体坐标 | { x: 350, y: 600 } |
恢复用户上次保存的位置 |
| 混合模式 | { x: 'right', y: 300 } |
水平靠右,垂直指定偏移 |
4.3 完整接入示例
综合以上 API,一个完整的闪控球接入流程如下:
import { floatingBall } from '@kit.WindowKit';
import { UIAbility } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
export default class EntryAbility extends UIAbility {
private floatingBall?: floatingBall.FloatingBall;
// 初始化并启动闪控球
async initFloatingBall(): Promise<void> {
// 1. 能力检测
if (!floatingBall.isFloatingBallEnabled()) {
console.warn('当前设备不支持闪控球');
return;
}
try {
// 2. 创建配置
const config: floatingBall.FloatingBallConfiguration = {
icon: $r('app.media.main_tools'),
iconSize: 56,
backgroundColor: '#E8FFFFFF', // 半透明白色
backgroundBlur: true,
shadowElevation: 12,
edgeMargin: 8,
edgeHideRatio: 0.65,
enableClick: true,
enableDrag: true,
enableLongPressDelete: true,
menuTemplate: {
type: floatingBall.TemplateType.LIST_WITH_LABEL,
items: [
{ icon: '✏️', label: '新建涂鸦' },
{ icon: '🎨', label: '调色板' },
{ icon: '📂', label: '作品集' },
{ icon: '⚙️', label: '设置' }
]
},
menuWidth: 240,
menuHeight: 320
};
// 3. 创建实例
this.floatingBall = await floatingBall.createFloatingBall(
this.context,
config
);
// 4. 注册事件
this.registerBallEvents();
// 5. 启动显示
await this.floatingBall.start({
initialPosition: { x: 'right', y: 'center' },
showOnStart: true
});
console.info('闪控球启动成功');
} catch (err) {
const bizErr = err as BusinessError;
console.error(`闪控球操作失败 [${bizErr.code}]: ${bizErr.message}`);
}
}
private registerBallEvents(): void {
this.floatingBall?.on('stateChange', (state) => {
// 状态变更处理
});
this.floatingBall?.on('click', () => {
// 点击处理——已在 menuTemplate 中定义了菜单,此事件可作为额外自定义逻辑
console.info('闪控球被点击');
});
}
// 更新闪控球图标——例如在不同工具模式间切换
async updateBallIcon(icon: Resource): Promise<void> {
try {
await this.floatingBall?.update({ icon });
} catch (err) {
console.error('更新闪控球图标失败:' + JSON.stringify(err));
}
}
// 生命周期清理
onWindowStageDestroy(): void {
this.floatingBall?.destroy();
this.floatingBall = undefined;
}
}
五、权限模型——系统级管控防滥用
5.1 ohos.permission.USE_FLOAT_BALL
与标准悬浮窗的 ohos.permission.SYSTEM_FLOAT_WINDOW 不同,闪控球使用独立的权限声明:
// module.json5 中声明权限
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.USE_FLOAT_BALL",
"reason": "$string:float_ball_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "always"
}
}
]
}
}
权限对比:
| 权限项 | ohos.permission.USE_FLOAT_BALL | ohos.permission.SYSTEM_FLOAT_WINDOW |
|---|---|---|
| 归属能力 | 闪控球(Floating Ball) | 标准悬浮窗(Standard Window) |
| 敏感级别 | 系统级敏感权限 | 系统级敏感权限 |
| 授予方式 | 安装时授予(install-time) | 安装时授予(install-time) |
| 用户控制 | 星盾超级隐私管控可关闭 | 星盾超级隐私管控可关闭 |
| 应用市场审核 | 需要提交功能说明 | 需要提交功能说明 |
5.2 运行时权限检查
闪控球权限属于 install-time 权限,但建议在调用 API 前进行运行时检查,避免 API 调用失败:
import { abilityAccessCtrl } from '@kit.AbilityKit';
import { floatingBall } from '@kit.WindowKit';
async function checkAndInitFloatingBall(context: Context): Promise<boolean> {
// 第一步:检查系统能力
if (!floatingBall.isFloatingBallEnabled()) {
console.warn('系统不支持闪控球能力');
return false;
}
// 第二步:检查权限状态
const atManager = abilityAccessCtrl.createAtManager();
const grantStatus = await atManager.checkAccessToken(
context.abilityInfo.applicationInfo.accessTokenId,
'ohos.permission.USE_FLOAT_BALL'
);
if (grantStatus !== abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
console.warn('闪控球权限未授予');
// 引导用户前往星盾隐私管控开启
showPermissionGuide();
return false;
}
return true;
}
5.3 防滥用安全机制
闪控球的系统级权限管控设计充分考虑了防止滥用的需求——一个悬浮在所有界面之上的交互元素如果不加约束,很容易被恶意应用利用:
| 安全维度 | 管控机制 | 保障目标 |
|---|---|---|
| 可见性 | 闪控球始终可见,无法创建透明的"隐形"闪控球 | 防止恶意隐藏覆盖 |
| 可控性 | 用户可通过长按删除、拖拽移动,可在星盾设置中关闭 | 用户完全掌控 |
| 触控穿透 | 闪控球的触控区域严格限定在图标范围内,不覆盖周围区域 | 不干扰其他应用点击 |
| 数量限制 | 同一时间同一用户仅可运行一个闪控球实例 | 防止多个闪控球互相干扰 |
| 生命周期 | 应用退到后台时闪控球自动停用 | 防止后台恶意使用 |
| 重叠限制 | 闪控球与系统关键 UI 的重叠比例受 maxOverlapRatio 限制 |
不遮挡系统状态栏/通知 |
安全设计原则:闪控球的系统级管控遵循"最小必要"原则——它只为用户提供"一个"快捷入口,而不是"一群"悬浮元素。这与其他移动平台上泛滥的"悬浮球"形成了鲜明对比。
六、场景设计——购物/学习/社交/创作
6.1 购物场景:全局比价助手
用户在电商应用中浏览商品时,闪控球提供一键比价入口:
// 电商场景下的闪控球配置
const shoppingBallConfig: floatingBall.FloatingBallConfiguration = {
icon: $r('app.media.price_tag_icon'),
iconSize: 48,
menuTemplate: {
type: floatingBall.TemplateType.LIST_WITH_LABEL,
items: [
{ icon: '🔍', label: '扫描比价' },
{ icon: '📊', label: '价格走势' },
{ icon: '💬', label: '历史最低价提醒' },
{ icon: '🛒', label: '加入购物车' }
]
}
};
用户在浏览商品详情时点击闪控球 → 弹出比价菜单 → 选择"扫描比价" → 系统自动识别当前商品并展示多平台价格 → 用户点击跳转到对应平台。
6.2 学习场景:悬浮词典/翻译
在学习外语或阅读外文资料时,闪控球提供随叫随到的翻译工具:
// 学习场景下的闪控球配置
const learningBallConfig: floatingBall.FloatingBallConfiguration = {
icon: $r('app.media.translate_icon'),
iconSize: 52,
menuTemplate: {
type: floatingBall.TemplateType.CARD_WITH_SUBTITLE,
items: [
{ icon: '🌐', label: '划词翻译', subtitle: '选中文本后自动翻译' },
{ icon: '📖', label: '词典查询', subtitle: '输入或选中单词查询释义' },
{ icon: '🎧', label: '语音跟读', subtitle: 'AI 发音示范 + 跟读评分' },
{ icon: '📝', label: '生词本', subtitle: '添加到个人生词收藏' }
]
}
};
用户在学习应用中阅读英文文章时,遇到生词 → 点击闪控球 → 选择"划词翻译" → 选中文字后悬浮显示翻译结果 → 关闭后继续阅读。整个过程不离开阅读页面,学习体验连续流畅。
6.3 社交场景:快捷消息入口
在游戏或沉浸式应用中,闪控球提供轻量的消息通知和快捷回复入口:
// 社交场景下的闪控球配置
const socialBallConfig: floatingBall.FloatingBallConfiguration = {
icon: $r('app.media.message_icon'),
iconSize: 52,
enableLongPressDelete: false, // 社交场景中不建议用户删除
menuTemplate: {
type: floatingBall.TemplateType.CUSTOM,
component: () => MessagePreviewPanel() // 自定义消息预览面板
}
};
// 更新消息未读状态
async function updateUnreadBadge(count: number) {
await this.floatingBall?.update({
icon: $r('app.media.message_icon'),
badge: {
count: count,
maxCount: 99,
color: '#FF3B30' // 红色未读标记
}
});
}
用户在全屏游戏或视频观看中收到消息 → 闪控球显示红点未读标记 → 点击查看消息预览 → 选择"快捷回复" → 弹出迷你输入框 → 回复后继续游戏/观影。
6.4 创作场景:"画伴梦工厂"的闪控球设计
回到"画伴梦工厂"项目本身——闪控球最核心的价值在于为儿童绘画创作提供一个随时可用的快捷工具箱:
// 画伴梦工厂闪控球完整配置
const drawingBallConfig: floatingBall.FloatingBallConfiguration = {
icon: $r('app.media.magic_wand'),
iconSize: 56,
backgroundColor: '#F0FFFFFF',
backgroundBlur: true,
shadowElevation: 12,
edgeMargin: 8,
edgeHideRatio: 0.65,
enableClick: true,
enableDrag: true,
enableLongPressDelete: true,
menuTemplate: {
type: floatingBall.TemplateType.LIST_WITH_LABEL,
items: [
{ icon: '✏️', label: '画笔选择' },
{ icon: '🎨', label: '调色板' },
{ icon: '🪣', label: '油漆桶填充' },
{ icon: '👁️', label: '预览全图' },
{ icon: '📸', label: '拍照识别' },
{ icon: '📂', label: '作品管理' }
]
},
menuWidth: 260,
menuHeight: 380
};
创作场景下的交互流程:
用户正在涂鸦
│
▼
需要切换画笔 → 点击屏幕边缘闪控球
│
▼
闪控球弹出 → 显示 6 个快捷工具选项
│
▼
选择"画笔选择" → 弹窗转换为笔刷选择面板
│
▼
选择目标笔刷 → 面板收起 → 闪控球贴边隐藏
│
▼
用户继续涂鸦,体验未被打断
分场景动态更新图标:
| 当前模式 | 闪控球图标 | 快捷菜单内容 |
|---|---|---|
| 涂鸦模式 | 🪄 魔法棒 | 画笔/调色板/油漆桶/预览/拍照识别/作品管理 |
| 调色模式 | 🎨 调色盘 | 预设色板/自定义颜色/吸管取色/最近颜色 |
| 作品浏览 | 📂 文件夹 | 全部作品/收藏夹/最近删除/分享/导出 |
| AI 生成 | 🤖 机器人 | 生成进度/取消任务/历史生成/风格参考 |
这种动态切换让闪控球不再只是一个"固定的入口",而是随着用户当前所处的上下文自动调整功能集——这种上下文感知能力正是闪控球区别于传统悬浮球的核心竞争力。
七、与标准悬浮窗的协作——闪控球 + 闪控窗组合
7.1 分层协作模式
闪控球和标准悬浮窗并非互斥的两种能力,而是可以配合使用组成多层交互体系:
屏幕交互层次(从底到顶)
┌──────────────────────────────────────┐
│ 主应用内容层 │ ← 用户的涂鸦/浏览主界面
├──────────────────────────────────────┤
│ 标准悬浮窗(闪控窗) │ ← 承载内容的悬浮窗口
│ ┌──────────────────────┐ │
│ │ 调色板悬浮窗 │ │
│ │ 颜色/笔刷/参数调整 │ │
│ └──────────────────────┘ │
├──────────────────────────────────────┤
│ 闪控球 │ ← 快捷入口(始终在最顶层)
│ ╭───╮ │
│ │ ⚪ │ │
│ ╰───╯ │
└──────────────────────────────────────┘
7.2 协作交互流程
一个典型的分层协作场景——用户在涂鸦中需要精细调色:
1. 用户点击闪控球 → 弹出快捷菜单
2. 选择"调色板" → 闪控球创建标准悬浮窗
3. 调色板悬浮窗打开,展示完整调色界面
4. 用户在悬浮窗中调整颜色参数
5. 颜色实时应用到主画布上
6. 调整完成 → 关闭悬浮窗
7. 闪控球恢复贴边隐藏状态
// 闪控球点击事件中打开标准悬浮窗
// 实现闪控球 → 闪控窗的联动
import { window, floatingBall } from '@kit.WindowKit';
class FloatingBallManager {
private ball?: floatingBall.FloatingBall;
private paletteWindow?: window.StandardWindow;
// 初始化闪控球,点击时打开调色板悬浮窗
async init(context: Context) {
this.ball = await floatingBall.createFloatingBall(context, {
icon: $r('app.media.magic_wand'),
iconSize: 56,
menuTemplate: {
type: floatingBall.TemplateType.LIST_WITH_LABEL,
items: [
{ icon: '🎨', label: '打开调色板' },
{ icon: '✏️', label: '画笔设置' },
{ icon: '📂', label: '作品管理' }
]
}
});
// 拦截点击事件,不展示默认菜单而是直接创建悬浮窗
this.ball.on('click', () => {
this.openPaletteFloatingWindow(context);
});
await this.ball.start();
}
// 创建标准悬浮窗作为调色板
private async openPaletteFloatingWindow(context: Context) {
if (this.paletteWindow) {
await this.paletteWindow.show();
return;
}
this.paletteWindow = await window.createStandardWindow(context, {
title: '调色板',
width: 300,
height: 400,
content: 'pages/PalettePanel',
windowBackgroundBlur: window.WindowBackgroundBlurType.BLUR_EFFECT_BEHIND_LIGHT,
draggable: true,
params: {
source: 'floatingBall'
}
});
this.paletteWindow.on('windowEvent', (event) => {
if (event.eventType === window.StandardWindowEventType.WINDOW_DESTROYED) {
this.paletteWindow = undefined;
}
});
await this.paletteWindow.show();
}
}
7.3 三种交互形态的选择策略
面对闪控球、闪控窗和标准悬浮窗三种能力,开发者在实际项目中应该如何选择?
| 场景 | 推荐形态 | 理由 |
|---|---|---|
| 需要随时唤出的工具入口 | 闪控球 | 贴边隐藏不占空间,一键唤出 |
| 轻量信息预览(< 5 秒) | 闪控窗 | 瞬态弹出,用完即走 |
| 需要长期操作的浮动面板 | 标准悬浮窗 | 承载完整 UI,可常驻使用 |
| 工具入口 + 内容预览组合 | 闪控球 → 闪控窗 | 入口在最顶层,内容在下一层 |
| 工具入口 + 完整面板组合 | 闪控球 → 标准悬浮窗 | 入口和面板分层协作 |
八、安全与最佳实践
8.1 权限合规检查清单
在应用中集成闪控球前,请确保满足以下合规要求:
- 在
module.json5中声明ohos.permission.USE_FLOAT_BALL权限 - 在
string.json中提供权限使用原因的本地化描述 - 在应用市场提交上架时附上闪控球使用场景说明
- 应用功能说明中明确告知用户闪控球的存在和用途
- 提供用户关闭闪控球的设置开关
- 在
privacy.json中说明悬浮内容的行为
8.2 稳健性设计
// 闪控球的稳健性封装
class SafeFloatingBallManager {
private ball?: floatingBall.FloatingBall;
private context: Context;
private isDestroyed: boolean = false;
constructor(context: Context) {
this.context = context;
}
async safeInit(): Promise<boolean> {
try {
// 1. 前置检测
if (!floatingBall.isFloatingBallEnabled()) {
console.warn('设备不支持闪控球');
return false;
}
// 2. 创建
this.ball = await floatingBall.createFloatingBall(
this.context,
this.getDefaultConfig()
);
// 3. 注册异常处理
this.ball.on('stateChange', (state) => {
if (state === floatingBall.FloatingBallState.DELETED) {
// 用户手动删除,清理引用
this.ball = undefined;
}
});
// 4. 启动
await this.ball.start({
initialPosition: { x: 'right', y: 'center' },
showOnStart: true
});
return true;
} catch (err) {
console.error('闪控球初始化失败:' + JSON.stringify(err));
// 不抛异常,静默降级
return false;
}
}
async safeUpdate(config: Partial<floatingBall.FloatingBallConfiguration>) {
if (!this.ball || this.isDestroyed) return;
try {
await this.ball.update(config);
} catch (err) {
console.error('闪控球更新失败:' + JSON.stringify(err));
}
}
async safeDestroy() {
if (!this.ball || this.isDestroyed) return;
this.isDestroyed = true;
try {
await this.ball.destroy();
this.ball = undefined;
} catch (err) {
console.error('闪控球销毁失败:' + JSON.stringify(err));
}
}
private getDefaultConfig(): floatingBall.FloatingBallConfiguration {
return {
icon: $r('app.media.default_tools'),
iconSize: 52,
backgroundBlur: true,
edgeHideRatio: 0.6,
enableLongPressDelete: true,
menuTemplate: {
type: floatingBall.TemplateType.LIST_WITH_LABEL,
items: [
{ icon: '🛠️', label: '工具' },
{ icon: '⚙️', label: '设置' }
]
}
};
}
}
8.3 性能考量
闪控球对性能的影响主要体现在以下方面:
- 渲染开销:闪控球使用系统级渲染通道,与主应用 UI 线程解耦,性能开销极低
- 内存占用:单个闪控球实例约占用 5~10MB 内存,在合理范围内
- 事件传递:点击事件的事件传递链路经过 Window Manager 调度,时延在 16ms 以内
- 贴边动画:贴边隐藏/弹出的动画由系统渲染引擎以 120fps 运行,无额外性能损耗
最佳实践建议:
- 不要在闪控球的
click事件中执行耗时操作(如网络请求) - 点击事件中如果需要加载数据,使用异步操作并提供加载状态反馈
- 切换图标时避免频繁更新(建议频率不低于 300ms 间隔)
- 应用进入后台时及时停止闪控球,回到前台时重新启动
总结
闪控球(Floating Ball)是 HarmonyOS 7 多窗交互体系中极具创新性的交互范式。它以简洁的圆形悬浮小球形态,解决了移动端应用中"工具入口与内容空间"之间的核心矛盾——用户既需要随时访问工具,又不希望工具占用宝贵的显示区域。
| 维度 | 核心内容 | 关键要点 |
|---|---|---|
| 交互范式 | 贴边隐藏 / 点击唤起 / 拖拽重定位 / 长按删除 | "看不见,但触得到"的极简交互 |
| API 设计 | isFloatingBallEnabled → createFloatingBall → start → update → stop/destroy |
全生命周期管理 |
| 视觉规范 | 4 种文本布局模板 + 设备自适应尺寸 | 图标网格 / 列表式 / 卡片式 / 自定义 |
| 权限安全 | ohos.permission.USE_FLOAT_BALL + 星盾管控 |
安装时授权,用户完全可控 |
| 事件体系 | stateChange / click / longPressDelete / positionChange | 全面覆盖用户交互场景 |
| 场景设计 | 购物比价 / 学习翻译 / 社交消息 / 创作工具 | 以"入口"为核心的场景适配 |
| 分层协作 | 闪控球 + 闪控窗 + 标准悬浮窗 | 从入口到窗口的完整交互链路 |
对于"画伴梦工厂"项目,闪控球提供了一个优雅的方案来解决儿童绘画创作中的工具切换痛点——一个随叫随到、用完即走、从来不打扰的魔法小圆球。它将 6 个核心创作工具(画笔、调色板、油漆桶、预览、拍照识别、作品管理)压缩在一个贴边小球中,让儿童创作者能够专注于画布本身,而不是在菜单和页面之间反复穿越。
闪控球的设计理念——以用户为中心,以内容为优先,以最少干扰为准则——正是鸿蒙交互设计哲学的微观体现。在下一篇文章中,我们将进一步探索闪控窗(Flash Window)的瞬态信息预览设计,完成从"入口"到"内容"到"窗口"的完整多窗交互拼图。
思考题:在"画伴梦工厂"中,闪控球提供了 6 个工具入口。但不同的用户群体(儿童 vs 家长 vs 教师)需要的工具集是不同的。如何设计一个"上下文感知 + 用户画像感知"的闪控球——让它在儿童涂鸦模式下显示画笔和调色板,在家长管理模式下显示作品统计和导出,在教师模式下显示课堂任务分配?结合本文的
updateAPI 和应用路由状态,给出你的多模式设计方案。
更多推荐


所有评论(0)