主界面架构与 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 组成与路由关系
上图的关键结论(均可从 FXML 源码验证):
RootModule.fxml是唯一的"壳":它声明了控制器cn.harryh.arkpets.controllers.RootModule,并把界面划分为左侧 140px 侧边栏与右侧模块容器区。- 三个模块页是被"承载"的从属页面:FXML 中以注释
<!-- ***** 1.2. Wrappers for modules ***** -->标记的容器区(wrapper1等)默认visible="false",由侧边栏菜单按钮触发切换。 - 对话框是独立弹窗:公告、下载、日志不在主窗口的模块切换体系内,而是由独立 FXML 定义的 Stage/Dialog。
主窗口场景图层级
层级解读:
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)
顶层容器与控制器绑定
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 值布局。
侧边栏:导航与启动控件
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
侧边栏的三个设计要点:
- 位图缓存:
cache="true" cacheHint="SPEED"将侧边栏渲染结果缓存为位图。侧边栏内容(标题、按钮图标)基本静态,缓存可以显著降低每帧重绘开销;代价是若运行时修改侧边栏内容需注意缓存失效。 - 绝对定位:
Pane是无布局管理器的裸容器,内部控件用layoutX/layoutY手工定位——适合这种固定尺寸、永不换行的窄栏。 - 阴影样式:
styleClass="shadowed"的视觉效果定义在Main.css中,与结构解耦。
菜单按钮:三页路由的触发点
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 声明式绑定。这也是下文路由机制需要在控制器侧闭环的原因。
启动按钮与模块容器
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 下最轻量的多页方案——单窗口 + 容器可见性切换:
该模型的取舍:
- 优点:无需销毁/重建节点,页面切换只是可见性标志位翻转,开销极低;所有模块页可常驻内存,切换无加载延迟;FXML 结构简单直观。
- 代价:所有页面同时存在于场景图中,需要控制器维护"当前页"状态并保证互斥显示(同一时刻只有一个 wrapper 可见)。
路由的声明式证据
从 FXML 可验证的路由要素:
- 目标页存在性:
assets/UI/目录下存在ModelsModule.fxml、BehaviorModule.fxml、SettingsModule.fxml三个独立模块页文件,与menuBtn1/2/3的文本「模型 / 行为 / 选项」一一对应。 - 容器就位且默认隐藏:
wrapper1声明为visible="false"(RootModule.fxml L120),说明初始状态下内容区不显示任何模块页。 - 菜单按钮具备稳定句柄:
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:id | JavaFX/JFoenix 类型 | 职责 |
|---|---|---|
rootContainer | StackPane | 顶层容器,挂载 Main.css 与控制器 |
root | StackPane | 600×400 窗口主体(含顶部装饰区) |
body | StackPane | 600×376 内容主体 |
sidebar | Pane | 140×376 左侧导航栏(位图缓存) |
annoEntrance | JFXButton | 「公告」入口,打开 AnnounceDialog |
menuBtn1 | JFXButton | 「模型」菜单按钮 → 模型页 |
menuBtn2 | JFXButton | 「行为」菜单按钮 → 行为页 |
menuBtn3 | JFXButton | 「选项」菜单按钮 → 选项页 |
launchBtn | JFXButton | 「启动」按钮 → 启动桌面宠物 |
wrapper1 | AnchorPane | 模块页容器,默认 visible=false |
控制器类名由根节点 fx:controller 属性唯一确定:
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.css | FXML 相对路径,须同目录打包 |
| 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 声明可推导的边界与风险点:
- 初始空白页风险:
wrapper1默认visible="false",若控制器初始化时未主动选中默认模块页,主窗口右侧内容区将保持空白。这是路由初始化必须处理的边界条件。 - 事件绑定不可静态发现:菜单按钮未在 FXML 中声明
onAction,导航逻辑完全依赖控制器代码。遗漏绑定不会在 FXML 加载阶段报错,只会在运行时表现为"点击无响应",排查时需同时检查 FXML 的fx:id与控制器字段名是否一致。 - 样式表相对路径脆弱:
stylesheets="@Main.css"要求 FXML 与 CSS 打包后同目录。若资源打包路径变化,FXMLLoader不会因样式缺失而失败,但root-container、shadowed等样式将全部失效,界面"裸奔"。 - JFoenix 依赖:
<?import com.jfoenix.controls.*?>要求 classpath 中包含 JFoenix 库;缺失时 FXML 加载直接抛出LoadException。 - 缓存与动态内容冲突:
sidebar的cacheHint="SPEED"位图缓存在侧边栏内容静态时是优化,但若未来在侧边栏加入动态元素(如计数徽标),需要手动失效缓存,否则显示不更新。 - 互斥显示约束:可见性路由要求控制器保证同一时刻仅一个模块容器可见;若状态管理出错,可能出现两页叠加(均渲染于同一
AnchorPane区域内)。
性能与扩展点
- 性能:主窗口的渲染热区是右侧模块内容区;侧边栏通过位图缓存(
cache/cacheHint)消除重复绘制。页面切换本身只是visible翻转,无节点重建成本。 - 新增模块页的扩展路径(依据现有结构推导的标准步骤):
- 在
assets/UI/新建模块 FXML(可参考ModelsModule.fxml等既有结构); - 在
RootModule.fxml侧边栏GridPane中追加一行RowConstraints与JFXButton(styleClass="menu-btn",分配新fx:id); - 在 Wrappers 区域追加对应
AnchorPane容器(leftAnchor=140,visible="false"); - 在
RootModule控制器中新增对应@FXML字段、装载新模块 FXML 并纳入可见性互斥切换逻辑。
- 在
- 样式扩展:所有视觉定制集中在
Main.css;新增按钮复用menu-btn即可获得与现有菜单一致的交互外观。
测试覆盖
本页源码读取范围内未发现针对 FXML 布局或路由的自动化测试文件;FXML 结构的正确性主要依赖运行时人工验证与 FXMLLoader 加载阶段的异常暴露。
相关链接(Related Links)
- 主窗口 FXML:RootModule.fxml
- 模块页 FXML:ModelsModule.fxml · BehaviorModule.fxml · SettingsModule.fxml
- 对话框 FXML:AnnounceDialog.fxml · DownloadDialog.fxml · LogDialog.fxml
- 全局样式表:Main.css
各模块页与对话框的内部实现详情,请参阅本目录下的兄弟页面(模型页、行为页、选项页、对话框相关页面)。