Browse Source
- 添加Android系统悬浮窗服务实现翻译悬浮窗功能 - 实现录音悬浮条服务支持显示/更新/隐藏操作 - 新增iOS画中画(PiP)内容视图支持动态更新翻译文本 - 完善权限检查和事件回传机制 - 更新README详细说明插件功能和使用方式newdev_shunjiawei
28 changed files with 122 additions and 17 deletions
@ -0,0 +1,120 @@ |
|||||
|
# floating_ui_plugin |
||||
|
|
||||
|
该目录是一个本地 Flutter 插件包,当前在项目中主要承担「悬浮 UI」相关的原生能力: |
||||
|
|
||||
|
- Android:翻译悬浮窗(系统悬浮窗 / overlay window)+ 录音悬浮条(计时/暂停/关闭)+ 悬浮窗权限检查/跳转。 |
||||
|
- iOS:为系统画中画(PiP)提供可渲染的原生内容 View,并支持动态更新翻译文本。 |
||||
|
|
||||
|
> 说明:Flutter 包名为 floating_ui_plugin(见本插件 pubspec.yaml),主工程通过 path 依赖引用本目录。 |
||||
|
|
||||
|
## 主要能力 |
||||
|
|
||||
|
### 1) Android 翻译悬浮窗(Translation Floating Window) |
||||
|
|
||||
|
- 通道:`MethodChannel('floating_window')` |
||||
|
- 原理:Flutter 调用通道方法 → Android 启动/更新 `FloatingWindowService` → service 通过系统 WindowManager 显示 overlay view。 |
||||
|
- 交互回传:service 会反向调用同一通道,把状态/点击/关闭事件回传给 Flutter。 |
||||
|
|
||||
|
支持的方法(Flutter → Android): |
||||
|
- `enableFloatingWindow`:启动悬浮窗服务(如果已在运行,相当于确保存在)。 |
||||
|
- `disableFloatingWindow`:停止悬浮窗服务。 |
||||
|
- `showFloatingWindow`:显示悬浮窗(当前实现等价于启动服务)。 |
||||
|
- `hideFloatingWindow`:隐藏悬浮窗(stopSelf)。 |
||||
|
- `updateContent(Map)`:更新展示内容。 |
||||
|
- `setPosition({x,y})`:设置悬浮窗位置。 |
||||
|
|
||||
|
回传的方法(Android → Flutter): |
||||
|
- `onFloatingWindowStateChanged({state})`:`hidden|minimized|expanded` |
||||
|
- `onFloatingWindowClosed()`:用户关闭。 |
||||
|
- `onFloatingWindowClicked()`:用户点击语言区域(service 内部会 bringAppToFront)。 |
||||
|
|
||||
|
数据协议(updateContent): |
||||
|
- `sourceText` / `translatedText` |
||||
|
- `sourceLanguage` / `targetLanguage` |
||||
|
- `timestamp`(毫秒时间戳) |
||||
|
- `isIntermediate`(是否中间态) |
||||
|
|
||||
|
对应实现: |
||||
|
- Android 插件入口:[NativePlugin.kt](file:///Users/liuxiamin/work/flutter_projects/deep_voice/local_plugins/floating_ui_plugin/android/src/main/kotlin/com/example/native_plugin/NativePlugin.kt) |
||||
|
- 悬浮窗服务:[FloatingWindowService.kt](file:///Users/liuxiamin/work/flutter_projects/deep_voice/local_plugins/floating_ui_plugin/android/src/main/kotlin/com/example/native_plugin/FloatingWindowService.kt) |
||||
|
|
||||
|
### 2) Android 悬浮窗权限(Overlay Permission) |
||||
|
|
||||
|
- 通道:`MethodChannel('overlay_permission')` |
||||
|
- 方法: |
||||
|
- `hasOverlayPermission`:是否具备 SYSTEM_ALERT_WINDOW |
||||
|
- `requestOverlayPermission`:跳转系统页面申请 |
||||
|
|
||||
|
对应实现:[NativePlugin.kt](file:///Users/liuxiamin/work/flutter_projects/deep_voice/local_plugins/floating_ui_plugin/android/src/main/kotlin/com/example/native_plugin/NativePlugin.kt) |
||||
|
|
||||
|
### 3) Android 录音悬浮条(Recording Floating Bar) |
||||
|
|
||||
|
- 通道:`MethodChannel('native_plugin')` |
||||
|
- 原理:Flutter 下发 show/update/hide → Android 启动/更新 `RecordingFloatingService` → service 以 overlay 方式显示一个小型悬浮条。 |
||||
|
|
||||
|
支持的方法(Flutter → Android): |
||||
|
- `showRecordingFloatingBar({duration, recordingType, isPaused})` |
||||
|
- `updateRecordingFloatingBar({duration, recordingType, isPaused})` |
||||
|
- `hideRecordingFloatingBar()` |
||||
|
|
||||
|
用户操作回传(Android → Flutter): |
||||
|
- 事件通道:`EventChannel('native_plugin/recording_floating_actions')` |
||||
|
- action 字符串: |
||||
|
- `togglePause`:点击暂停/继续 |
||||
|
- `openMeetingRecord`:点击中间区域,拉起 App 并打开录音页 |
||||
|
- `closeFloatingBar`:关闭悬浮条 |
||||
|
|
||||
|
对应实现: |
||||
|
- 服务:[RecordingFloatingService.kt](file:///Users/liuxiamin/work/flutter_projects/deep_voice/local_plugins/floating_ui_plugin/android/src/main/kotlin/com/example/native_plugin/RecordingFloatingService.kt) |
||||
|
- 事件桥接:[NativePlugin.emitRecordingFloatingAction](file:///Users/liuxiamin/work/flutter_projects/deep_voice/local_plugins/floating_ui_plugin/android/src/main/kotlin/com/example/native_plugin/NativePlugin.kt#L26-L39) |
||||
|
|
||||
|
## iOS:PiP 内容 View(Picture-in-Picture Content View) |
||||
|
|
||||
|
- 通道:`MethodChannel('native_plugin')` |
||||
|
- 目标:创建一个用于 PiP 渲染的原生 `UIView`(`PlayerView`),并支持后续更新显示内容。 |
||||
|
|
||||
|
支持的方法: |
||||
|
- `createPipContentView()`:返回一个 `viewId`(本质是 PlayerView 的指针值)。 |
||||
|
- `updatePipContentView({viewId?, lang, cn, en})`:更新内容。 |
||||
|
- `disposePipContentView(viewId)`:释放。 |
||||
|
|
||||
|
对应实现: |
||||
|
- 插件入口:[NativePlugin.swift](file:///Users/liuxiamin/work/flutter_projects/deep_voice/local_plugins/floating_ui_plugin/ios/Classes/NativePlugin.swift) |
||||
|
- 具体 UI:[PlayerView.swift](file:///Users/liuxiamin/work/flutter_projects/deep_voice/local_plugins/floating_ui_plugin/ios/Classes/PlayerView.swift) |
||||
|
|
||||
|
## 在主工程中的调用链路(现状) |
||||
|
|
||||
|
### Android:翻译悬浮窗 |
||||
|
|
||||
|
- 入口:翻译页控制器 [TranslationController](file:///Users/liuxiamin/work/flutter_projects/deep_voice/lib/modules/translation/controllers/translation_controller.dart) |
||||
|
- `toggleFloatingWindow()` → `_enableFloatingWindow()` |
||||
|
- 管理器:[FloatingWindowManager](file:///Users/liuxiamin/work/flutter_projects/deep_voice/lib/core/services/floating_window_manager.dart) |
||||
|
- `enableFloatingWindow()`:先通过 [OverlayPermissionUtil](file:///Users/liuxiamin/work/flutter_projects/deep_voice/lib/core/utils/overlay_permission_util.dart) 请求权限,再调用 `TranslationFloatingWindowApi.enable()` |
||||
|
- `_setupMethodCallHandler()`:接收 `onFloatingWindowStateChanged/onFloatingWindowClicked/onFloatingWindowClosed` 并更新状态/路由跳转 |
||||
|
- 内容更新:`TranslationController._updateFloatingWindowWithTranslation()` → `FloatingWindowManager.updateFloatingWindowContent()` → `updateContent(Map)` |
||||
|
|
||||
|
### Android:录音悬浮条 |
||||
|
|
||||
|
- 入口:会议录音控制器 [MeetingRecordController](file:///Users/liuxiamin/work/flutter_projects/deep_voice/lib/modules/meeting/controllers/meeting_record_controller.dart) |
||||
|
- `showBackgroundRecordingIndicator()` → `_startAndroidRecordingFloatingBar()` → `NativePlugin.showRecordingFloatingBar()` |
||||
|
- 定时器每秒 `updateRecordingFloatingBar()` 更新 duration/isPaused |
||||
|
- `_setupRecordingFloatingBarActionListener()` 监听 `recordingFloatingActionStream` 并处理 `togglePause/openMeetingRecord/closeFloatingBar` |
||||
|
|
||||
|
### iOS:翻译悬浮显示(PiP) |
||||
|
|
||||
|
- 入口同样在 [TranslationController](file:///Users/liuxiamin/work/flutter_projects/deep_voice/lib/modules/translation/controllers/translation_controller.dart) |
||||
|
- `_startPip()`:`NativePlugin.createPipContentView()` → 将 viewId 传给第三方 PiP 组件的 `PipOptions.contentView` |
||||
|
- `_updateFloatingWindowWithTranslation()`:`NativePlugin.updatePipContentView({viewId, cn, en, lang})` |
||||
|
|
||||
|
## 权限与 Manifest |
||||
|
|
||||
|
- Android 需要:`android.permission.SYSTEM_ALERT_WINDOW` |
||||
|
- 本插件自带 AndroidManifest 声明(包含两项 service): |
||||
|
- [android/src/main/AndroidManifest.xml](file:///Users/liuxiamin/work/flutter_projects/deep_voice/local_plugins/floating_ui_plugin/android/src/main/AndroidManifest.xml) |
||||
|
|
||||
|
## 平台差异与注意事项 |
||||
|
|
||||
|
- Android 与 iOS 的“悬浮显示”实现不同:Android 用系统悬浮窗;iOS 走系统 PiP。 |
||||
|
- `createPipContentView/updatePipContentView/disposePipContentView` 仅 iOS 实现;Android 侧没有实现这些 method,请务必用 `Platform.isIOS` 保护调用。 |
||||
|
- `floating_window` / `overlay_permission` / `native_plugin/recording_floating_actions` 都是固定通道名,避免在其他地方重复注册同名通道。 |
||||
|
- 工程 `android/app/src/main/kotlin/com/saitong/voitrans/` 下存在同名通道的旧实现文件(未在 MainActivity 注册),当前实际走的是本插件实现;如后续要启用 app 侧实现,需要先解决通道名冲突与 service 声明问题。 |
||||
@ -0,0 +1 @@ |
|||||
|
rootProject.name = 'floating_ui_plugin' |
||||
@ -1,15 +0,0 @@ |
|||||
# native_plugin |
|
||||
|
|
||||
A new Flutter plugin project. |
|
||||
|
|
||||
## Getting Started |
|
||||
|
|
||||
This project is a starting point for a Flutter |
|
||||
[plug-in package](https://flutter.dev/to/develop-plugins), |
|
||||
a specialized package that includes platform-specific implementation code for |
|
||||
Android and/or iOS. |
|
||||
|
|
||||
For help getting started with Flutter development, view the |
|
||||
[online documentation](https://docs.flutter.dev), which offers tutorials, |
|
||||
samples, guidance on mobile development, and a full API reference. |
|
||||
|
|
||||
@ -1 +0,0 @@ |
|||||
rootProject.name = 'native_plugin' |
|
||||
Loading…
Reference in new issue