-
Notifications
You must be signed in to change notification settings - Fork 188
架构设计
FloatingX 3.0 把「浮窗」拆成三个正交的角色:Host、Engine、Feature。 了解它们的分工,能帮你判断某个行为该配在哪、为什么换页时状态不会丢、以及怎么扩展。
| 角色 | 回答的问题 | 你会直接接触到的 |
|---|---|---|
| Host | 浮窗挂在哪 |
appHost {} / systemHost {} / viewGroupHost() / fragmentHost() / fxScope {}
|
| Engine | 浮窗现在是什么状态、命令怎么执行 |
FxState、control.show() / hide() / moveTo()
|
| Feature | 容器有什么行为 |
anchor / gesture / animation / modal 等配置,以及 addFeature(...)
|
内容 view 归 engine 所有,不归 host。所以换页、换 host 都不会重建内容—— 这是 3.0 里「浮窗跨页存活」的基础。
一个 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 级浮窗时,配置、监听器和当前位置都还在。
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、被黑名单卸下再回来,浮窗都不会丢。
容器本身只负责「装内容」,所有行为都是 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-system 的 SystemWindowFeature(同步
WindowManager.LayoutParams)与 KeyboardFeature(keyboard(...) 登记的 EditText),
floatingx-compose 的 ComposeOwnerFeature(把 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.WindowManager、androidx.fragment、
androidx.compose、androidx.lifecycle、androidx.appcompat。
所以按需依赖是真的按需——不用系统浮窗就不会带进 WindowManager 相关代码,
不用 Compose 就不会带进 Compose 依赖。