Repository Wiki
isHarryh/Ark-Pets

主界面架构与 FXML 页面路由

Ark-Pets 桌面启动器(Launcher)的图形界面由一组位于 assets/UI/ 目录下的 JavaFX FXML 文件构成,其中 RootModule.fxml 作为主窗口骨架,通过侧边栏菜单按钮在「模型 / 行为 / 选项」三个功能模块页之间切换,形成一套基于容器可见性的轻量级页面路由机制。

目的与范围(Purpose and Scope)

本页覆盖以下内容:

  • 主窗口 RootModule.fxml 的完整场景图(Scene Graph)结构与布局参数;
  • 侧边栏(Sidebar)导航控件的构成:公告入口、三个菜单按钮与「启动」按钮;
  • FXML 页面路由机制:模块容器(wrapper)如何承载 ModelsModule / BehaviorModule / SettingsModule 三个模块页;
  • assets/UI/ 下全部 7 个 FXML 文件与 Main.css 样式表的职责划分;
  • FXML 与控制器 cn.harryh.arkpets.controllers.RootModule 的声明式绑定契约(fx:controller / fx:id)。

以下内容有意留给兄弟页面,本页不展开:

  • 各功能模块页内部的控件与业务逻辑(模型页、行为页、选项页各有独立页面);
  • 对话框(公告、下载、日志)的内部实现;
  • 控制器 Java 类的具体事件处理代码与桌面宠物启动流程(本页源码读取范围限于 FXML 声明,控制器实现细节请参阅对应源码)。

概述(Overview)

Ark-Pets 的启动器界面采用 JavaFX + JFoenix 技术栈(FXML 中通过 <?import com.jfoenix.controls.*?> 引入 JFXButton 等控件,命名空间为 JavaFX 17)。整个界面被拆分为:

类别文件职责
主窗口骨架RootModule.fxml承载侧边栏导航与模块容器,绑定根控制器
功能模块页ModelsModule.fxml / BehaviorModule.fxml / SettingsModule.fxml模型、行为、选项三个可切换的内容页
对话框AnnounceDialog.fxml / DownloadDialog.fxml / LogDialog.fxml公告、模型下载、日志查看弹窗
样式表Main.css全部 styleClass 的视觉定义

这种「一个根窗口 + 多个模块页」的设计意图是:主窗口只负责导航与容器编排,功能内容各自独立成 FXML 文件,从而让每个功能模块可以单独开发、单独维护,界面的静态结构(布局、锚点、样式类)全部声明在 FXML 中,而运行时行为(页面切换、事件响应)由控制器 Java 代码接管。

架构(Architecture)

整体 FXML 组成与路由关系

Loading diagram...

上图的关键结论(均可从 FXML 源码验证):

  1. RootModule.fxml 是唯一的"壳":它声明了控制器 cn.harryh.arkpets.controllers.RootModule,并把界面划分为左侧 140px 侧边栏与右侧模块容器区。
  2. 三个模块页是被"承载"的从属页面:FXML 中以注释 <!-- ***** 1.2. Wrappers for modules ***** --> 标记的容器区(wrapper1 等)默认 visible="false",由侧边栏菜单按钮触发切换。
  3. 对话框是独立弹窗:公告、下载、日志不在主窗口的模块切换体系内,而是由独立 FXML 定义的 Stage/Dialog。

主窗口场景图层级

Loading diagram...

层级解读:

  • rootContainer(620×410)比 root(600×400)四周各多出 10px,这 10px 差值配合 root-container / shadowed 等 styleClass 用于渲染窗口阴影等装饰效果——这是把"视觉留白"与"逻辑窗口"分离的典型做法。
  • body(600×376)比 root 少 24px 高度,即内容主体贴底对齐(BOTTOM_CENTER),顶部 24px 属于 root 层的装饰/标题区域。
  • 内容区 AnchorPane 通过锚点(Anchor)绝对定位两块区域:sidebar 锚定 leftAnchor=0,模块容器锚定 leftAnchor=140,即内容区从侧边栏右边缘开始,二者互不重叠。

主窗口节点树解析(RootModule.fxml)

顶层容器与控制器绑定

xml
1<StackPane fx:id="rootContainer" prefHeight="410.0" prefWidth="620.0" styleClass="root-container" 2 stylesheets="@Main.css" xmlns="http://javafx.com/javafx/17.0.12" xmlns:fx="http://javafx.com/fxml/1" 3 fx:controller="cn.harryh.arkpets.controllers.RootModule"> 4 <!-- *************** Root Node *************** --> 5 <StackPane fx:id="root" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" prefHeight="400.0" 6 prefWidth="600.0" styleClass="root" StackPane.alignment="TOP_CENTER"> 7 8 <!-- ********** 1. Main Content ********** --> 9 <StackPane fx:id="body" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" prefHeight="376.0" 10 prefWidth="600.0" StackPane.alignment="BOTTOM_CENTER">

Source: RootModule.fxml

这段声明包含四个关键契约:

  • fx:controller="cn.harryh.arkpets.controllers.RootModule":FXML 加载时由 FXMLLoader 实例化该控制器,FXML 中所有 fx:id 会注入到控制器对应的 @FXML 字段(字段名与 fx:id 一致)。
  • stylesheets="@Main.css":使用 FXML 相对路径 引用样式表,要求 Main.css 与 FXML 在打包后位于同一目录。
  • xmlns="http://javafx.com/javafx/17.0.12":界面按 JavaFX 17 的 FXML 方言编写。
  • maxWidth/maxHeight/minWidth/minHeight="-Infinity":解除尺寸约束,允许窗口缩放时按 pref 值布局。

侧边栏:导航与启动控件

xml
1<!-- ***** 1.1. Sidebar ***** --> 2<Pane id="Sidebar" fx:id="sidebar" cache="true" cacheHint="SPEED" 3 maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" 4 minWidth="-Infinity" prefHeight="376.0" prefWidth="140.0" styleClass="shadowed" 5 AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0" StackPane.alignment="BOTTOM_LEFT"> 6 <Text id="Title" layoutX="12.0" layoutY="48.0" text="ArkPets" textAlignment="CENTER" 7 wrappingWidth="117.0"> 8 </Text> 9 <Line endX="128.0" layoutY="60.0" startX="12.0" stroke="#0000009e"/>

Source: RootModule.fxml

侧边栏的三个设计要点:

  1. 位图缓存:cache="true" cacheHint="SPEED" 将侧边栏渲染结果缓存为位图。侧边栏内容(标题、按钮图标)基本静态,缓存可以显著降低每帧重绘开销;代价是若运行时修改侧边栏内容需注意缓存失效。
  2. 绝对定位:Pane 是无布局管理器的裸容器,内部控件用 layoutX/layoutY 手工定位——适合这种固定尺寸、永不换行的窄栏。
  3. 阴影样式:styleClass="shadowed" 的视觉效果定义在 Main.css 中,与结构解耦。

菜单按钮:三页路由的触发点

xml
1<GridPane alignment="CENTER" layoutY="95.0" prefHeight="180.0" prefWidth="140.0" styleClass="menu"> 2 <!-- 列/行约束省略 --> 3 <JFXButton fx:id="menuBtn1" mnemonicParsing="false" prefHeight="40.0" prefWidth="140.0" 4 styleClass="menu-btn" text="模型" textAlignment="CENTER"> 5 <!-- graphic 内为 SVGPath 图标,省略 --> 6 </JFXButton> 7 <JFXButton fx:id="menuBtn2" mnemonicParsing="false" prefHeight="40.0" prefWidth="140.0" 8 styleClass="menu-btn" text="行为" textAlignment="CENTER" GridPane.rowIndex="1"> 9 <!-- graphic 省略 --> 10 <JFXButton fx:id="menuBtn3" mnemonicParsing="false" prefHeight="40.0" prefWidth="140.0" 11 styleClass="menu-btn" text="选项" textAlignment="CENTER" GridPane.rowIndex="2"> 12 <!-- graphic 省略 -->

Source: RootModule.fxml

菜单区使用 GridPane 单列三行(每行 RowConstraints),三个 JFXButton 分别绑定 fx:id="menuBtn1/2/3",显示文本为「模型 / 行为 / 选项」,每个按钮的 graphic 中内嵌一个 SVGPath(矢量图标,直接以路径数据写死在 FXML 里,无需图片资源)。styleClass="menu-btn" 统一了三者的外观,使"当前选中页"的高亮状态可以仅靠 CSS 类切换实现。

值得注意:这三个按钮在 FXML 中没有声明 onAction 属性,说明点击事件的绑定发生在控制器 Java 代码中(例如 setOnAction),而不是 FXML 声明式绑定。这也是下文路由机制需要在控制器侧闭环的原因。

启动按钮与模块容器

xml
1<JFXButton id="Launch-btn" fx:id="launchBtn" layoutX="25.0" layoutY="310.0" mnemonicParsing="false" 2 prefHeight="36.0" prefWidth="90.0" text="启动" textAlignment="CENTER"> 3 <!-- graphic 内为启动图标 SVGPath,省略 --> 4</JFXButton> 5... 6<!-- ***** 1.2. Wrappers for modules ***** --> 7<AnchorPane fx:id="wrapper1" visible="false" AnchorPane.leftAnchor="140.0" AnchorPane.topAnchor="0.0"/>

Source: RootModule.fxml

  • launchBtn(「启动」)固定在侧边栏底部(layoutY="310"),是主窗口的核心动作按钮,负责启动桌面宠物。
  • FXML 中以注释明确划分出的 Wrappers for modules 区域是页面路由的物理基础:wrapper1 是一个空 AnchorPane,默认 visible="false",锚定在侧边栏右侧(leftAnchor=140)。模块页内容挂载到这些 wrapper 中,通过切换 visible 实现"页面切换"。

页面路由机制(FXML 页面路由)

路由模型:容器可见性切换

Ark-Pets 没有引入独立的路由框架,而是使用了 JavaFX 下最轻量的多页方案——单窗口 + 容器可见性切换:

Loading diagram...

该模型的取舍:

  • 优点:无需销毁/重建节点,页面切换只是可见性标志位翻转,开销极低;所有模块页可常驻内存,切换无加载延迟;FXML 结构简单直观。
  • 代价:所有页面同时存在于场景图中,需要控制器维护"当前页"状态并保证互斥显示(同一时刻只有一个 wrapper 可见)。

路由的声明式证据

从 FXML 可验证的路由要素:

  1. 目标页存在性:assets/UI/ 目录下存在 ModelsModule.fxml、BehaviorModule.fxml、SettingsModule.fxml 三个独立模块页文件,与 menuBtn1/2/3 的文本「模型 / 行为 / 选项」一一对应。
  2. 容器就位且默认隐藏:wrapper1 声明为 visible="false"(RootModule.fxml L120),说明初始状态下内容区不显示任何模块页。
  3. 菜单按钮具备稳定句柄:fx:id="menuBtn1/2/3" 使控制器能以字段方式持有按钮引用,用于绑定事件与设置选中态样式。

说明:本页源码读取范围限于 FXML 声明;wrapper 与模块 FXML 的具体装载方式(fx:include 静态包含或控制器 FXMLLoader 动态加载)、事件绑定的具体实现位于 cn.harryh.arkpets.controllers.RootModule 控制器 Java 源码中,请结合控制器源码阅读。

FXML 文件职责总表

文件角色关键 fx:id(已验证部分)
RootModule.fxml主窗口骨架 + 路由容器rootContainer、root、body、sidebar、annoEntrance、menuBtn1/2/3、launchBtn、wrapper1
ModelsModule.fxml模型页(模块页之一)未在本页读取范围内
BehaviorModule.fxml行为页(模块页之一)未在本页读取范围内
SettingsModule.fxml选项页(模块页之一)未在本页读取范围内
AnnounceDialog.fxml公告对话框未在本页读取范围内
DownloadDialog.fxml模型下载对话框未在本页读取范围内
LogDialog.fxml日志对话框未在本页读取范围内
Main.css全局样式表,定义 root-container、root、shadowed、menu、menu-btn、btn-plain-dark、btn-with-icon、btn-icon 等 styleClass—

FXML 与控制器的绑定契约

FXML 通过两类标识与控制器建立联系,本主窗口的完整契约如下:

fx:idJavaFX/JFoenix 类型职责
rootContainerStackPane顶层容器,挂载 Main.css 与控制器
rootStackPane600×400 窗口主体(含顶部装饰区)
bodyStackPane600×376 内容主体
sidebarPane140×376 左侧导航栏(位图缓存)
annoEntranceJFXButton「公告」入口,打开 AnnounceDialog
menuBtn1JFXButton「模型」菜单按钮 → 模型页
menuBtn2JFXButton「行为」菜单按钮 → 行为页
menuBtn3JFXButton「选项」菜单按钮 → 选项页
launchBtnJFXButton「启动」按钮 → 启动桌面宠物
wrapper1AnchorPane模块页容器,默认 visible=false

控制器类名由根节点 fx:controller 属性唯一确定:

xml
fx:controller="cn.harryh.arkpets.controllers.RootModule"

Source: RootModule.fxml

布局与样式配置参考

主窗口的关键布局参数(均来自 FXML 声明,非代码硬编码):

参数值说明
rootContainer pref 尺寸620 × 410最外层容器,含四周 10px 装饰留白
root pref 尺寸600 × 400逻辑窗口主体,TOP_CENTER 对齐
body pref 尺寸600 × 376内容主体,BOTTOM_CENTER 对齐(顶部留 24px)
sidebar pref 尺寸140 × 376侧边栏,leftAnchor=0、topAnchor=0
wrapper 锚点leftAnchor=140内容区起始于侧边栏右缘
sidebar 缓存cache=true、cacheHint=SPEED侧边栏位图缓存,加速渲染
样式表@Main.cssFXML 相对路径,须同目录打包
FXML 命名空间javafx/17.0.12基于 JavaFX 17
UI 控件库JFoenix(com.jfoenix.controls.*)JFXButton 等 Material 风格控件

已验证的 styleClass 清单(定义于 Main.css):root-container、root、shadowed、menu、menu-btn、btn-plain-dark、btn-with-icon、btn-icon。

失败模式、边界情况与注意事项

基于 FXML 声明可推导的边界与风险点:

  1. 初始空白页风险:wrapper1 默认 visible="false",若控制器初始化时未主动选中默认模块页,主窗口右侧内容区将保持空白。这是路由初始化必须处理的边界条件。
  2. 事件绑定不可静态发现:菜单按钮未在 FXML 中声明 onAction,导航逻辑完全依赖控制器代码。遗漏绑定不会在 FXML 加载阶段报错,只会在运行时表现为"点击无响应",排查时需同时检查 FXML 的 fx:id 与控制器字段名是否一致。
  3. 样式表相对路径脆弱:stylesheets="@Main.css" 要求 FXML 与 CSS 打包后同目录。若资源打包路径变化,FXMLLoader 不会因样式缺失而失败,但 root-container、shadowed 等样式将全部失效,界面"裸奔"。
  4. JFoenix 依赖:<?import com.jfoenix.controls.*?> 要求 classpath 中包含 JFoenix 库;缺失时 FXML 加载直接抛出 LoadException。
  5. 缓存与动态内容冲突:sidebar 的 cacheHint="SPEED" 位图缓存在侧边栏内容静态时是优化,但若未来在侧边栏加入动态元素(如计数徽标),需要手动失效缓存,否则显示不更新。
  6. 互斥显示约束:可见性路由要求控制器保证同一时刻仅一个模块容器可见;若状态管理出错,可能出现两页叠加(均渲染于同一 AnchorPane 区域内)。

性能与扩展点

  • 性能:主窗口的渲染热区是右侧模块内容区;侧边栏通过位图缓存(cache/cacheHint)消除重复绘制。页面切换本身只是 visible 翻转,无节点重建成本。
  • 新增模块页的扩展路径(依据现有结构推导的标准步骤):
    1. 在 assets/UI/ 新建模块 FXML(可参考 ModelsModule.fxml 等既有结构);
    2. 在 RootModule.fxml 侧边栏 GridPane 中追加一行 RowConstraints 与 JFXButton(styleClass="menu-btn",分配新 fx:id);
    3. 在 Wrappers 区域追加对应 AnchorPane 容器(leftAnchor=140,visible="false");
    4. 在 RootModule 控制器中新增对应 @FXML 字段、装载新模块 FXML 并纳入可见性互斥切换逻辑。
  • 样式扩展:所有视觉定制集中在 Main.css;新增按钮复用 menu-btn 即可获得与现有菜单一致的交互外观。

测试覆盖

本页源码读取范围内未发现针对 FXML 布局或路由的自动化测试文件;FXML 结构的正确性主要依赖运行时人工验证与 FXMLLoader 加载阶段的异常暴露。

各模块页与对话框的内部实现详情,请参阅本目录下的兄弟页面(模型页、行为页、选项页、对话框相关页面)。

Sources

(1 files)