Skip to content

架构设计

petterp edited this page Aug 30, 2026 · 1 revision

架构

FloatingX 3.0 把「浮窗」拆成三个正交的角色:Host、Engine、Feature。 了解它们的分工,能帮你判断某个行为该配在哪、为什么换页时状态不会丢、以及怎么扩展。

三个角色

角色 回答的问题 你会直接接触到的
Host 浮窗挂在哪 appHost {} / systemHost {} / viewGroupHost() / fragmentHost() / fxScope {}
Engine 浮窗现在是什么状态、命令怎么执行 FxStatecontrol.show() / hide() / moveTo()
Feature 容器有什么行为 anchor / gesture / animation / modal 等配置,以及 addFeature(...)

内容 view 归 engine 所有,不归 host。所以换页、换 host 都不会重建内容—— 这是 3.0 里「浮窗跨页存活」的基础。

Host:浮窗挂在哪

一个 host 负责创建容器、把它挂到某个地方、报告可用区,并在宿主消失时通知 engine。

Host 挂在哪 特点
AppHost 当前前台 Activity 的 DecorView(默认,可切 CONTENT 换页时把同一个容器静默 reparent,engine 状态、feature、动画都不重来;被黑白名单 / filter 拒绝的页面上整体卸下。见 App 级全局浮窗
SystemHost WindowManager 窗口 悬浮窗权限三策略;权限被拒时 requestSwap(fallback) 降级到 AppHost(相当于 2.x 的 SYSTEM_AUTO)。见 系统级浮窗
ViewGroupHost 任意 ViewGroup 浮窗只在这个容器内活动,不进注册表,生命周期归调用方。见 局部浮窗
FragmentHost Fragment 的根 view onCreate 里就能写,view 就绪后自动挂上,fragment destroy 时自动 cancel;同样不进注册表。见 局部浮窗

host 之间可以互换:swapHost 会保留 anchor、listener 与 feature, 所以系统浮窗降级成 App 级浮窗时,配置、监听器和当前位置都还在。

Engine:状态机 + 命令队列

INSTALLED ──attach──> ATTACHED ──show──> SHOWN
    │                     │                │
    └────────── cancel ───┴────────────────┘
                          ↓
                     CANCELLED(终态)
  • INSTALLED:已创建,容器还没挂上。
  • ATTACHED:容器已挂载,但还不可见。
  • SHOWN:可见。
  • CANCELLED:终态;cancel() 幂等,之后再调 show/hide/moveTo/moveBy 会抛 IllegalStateException

命令队列:host 还没就绪时调用 show() / hide() / moveTo() 不会丢, 命令先入队,host 就绪后按序回放。所以 install 写在 Application.onCreate、 Activity 的 onCreate、甚至 Service 里都成立。

宿主丢失时onHostLost 会保留 desiredVisible——你希望它显示,它就在下一个宿主上继续显示。 换页、旋转、Activity recreate、被黑名单卸下再回来,浮窗都不会丢。

Feature:容器行为插件

容器本身只负责「装内容」,所有行为都是 FxFeature

public interface FxFeature {
    public fun onAttach(scope: FxFeatureScope)
    public fun onDetach()
    public fun onCancel() {}
    public fun onRemove() {}
    public fun onConfigChanged(old: FxConfig, new: FxConfig) {}
    public fun onContentSizeChanged(size: FxSize) {}
    public fun onBoundsChanged() {}
    public fun onShow() {}
    public fun onHide() {}
}

内置 feature(由配置项自动装配,你平时只写配置):

Feature 负责 对应配置
LocationFeature 锚点、margin、overflow、safeArea、吸附、位置持久化 anchor / margin / overflow / safeArea / adsorb / persist
GestureFeature 拖动、点击、长按、起拖区域、子 view 冲突、触摸透传 gesture {}
AnimationFeature show / hide 动画 animation(...)
ModalScrimFeature 拦截内容之外的触摸,可选点外部自动 hide modal(...)

host 与 compose 模块还会自行追加:floatingx-systemSystemWindowFeature(同步 WindowManager.LayoutParams)与 KeyboardFeaturekeyboard(...) 登记的 EditText), floatingx-composeComposeOwnerFeature(把 FxComposeOwner 的 Lifecycle 跟着容器状态推进)。

feature 之间不互相引用,共享数据一律走 FxFeatureScope(能拿到 control / config / container / host / logger,并可 commitAnchor(...) / dispatch { … } / requestRelayout())。

扩展方式

需要一个内置配置覆盖不到的行为时,写一个 FxFeature,而不是改容器或包一层 ViewGroup

class LogFeature : FxFeature {
    override fun onAttach(scope: FxFeatureScope) { /* 拿 scope.control / scope.container */ }
    override fun onDetach() {}
    override fun onBoundsChanged() { /* 可用区变了 */ }
}

// 安装时装配
FloatingX.install("tag") {
    layout(R.layout.fx_card)
    addFeature(LogFeature())
    appHost(app)
}
// 或运行时增删
control.addFeature(LogFeature())
control.removeFeature(feature)
// 配置里按条件摘掉某类 feature
control.update { removeFeatures { it is LogFeature } }

定位为什么这么稳

3.0 存的是 FxAnchor(gravity, dx, dy)——「贴哪条边 + 从那条边向内的偏移」, 而不是左上角的绝对坐标。内容尺寸变化、屏幕旋转、可用区变化时,位置按锚点重算, 所以贴着的那条边不动,也不需要 2.x 那种「强行修复」开关。

几何类型(FxGeometry)是纯 Kotlin 的,不依赖 android.graphics.*FxLayoutResolver / FxAdsorbResolver 因此是可以脱离设备验证的纯函数。 这一条是 3.0 修掉一大票尺寸 / 旋转类 issue 的根因,对照见 Issue 覆盖矩阵

模块依赖边界

floatingx-core 是纯 View 实现:不依赖 android.view.WindowManagerandroidx.fragmentandroidx.composeandroidx.lifecycleandroidx.appcompat。 所以按需依赖是真的按需——不用系统浮窗就不会带进 WindowManager 相关代码, 不用 Compose 就不会带进 Compose 依赖。


上手请看 快速开始,配置项清单见 配置项,返回 Home

Clone this wiki locally