为了账号安全,请及时绑定邮箱和手机立即绑定

通过 FXML 管理 JavaFX 中的场景切换(性能问题)

通过 FXML 管理 JavaFX 中的场景切换(性能问题)

慕标琳琳 2021-12-01 16:53:37
我正在玩 JavaFX 和 FXML 加载的各种场景。所以我有了写一个处理场景切换的管理器的想法。到目前为止一切正常,但我不确定这是否是一个好的实现。import java.io.IOException;import javafx.fxml.FXMLLoader;import javafx.scene.Scene;import javafx.scene.layout.Pane;import javafx.stage.Stage;public class SceneManager {    private static final String[] fxmlFiles = {"../gui/MainWindow.fxml", "../gui/NewGameWindow.fxml"};    private static SceneManager instance = null;    private static Stage rootStage = null;    private FXMLLoader[] loadedFxml;    private Pane[] loadedPanes;    private Scene[] scenes;    public enum States {        MAIN_MENU, NEW_GAME;    }    private SceneManager() {        try {            this.loadedFxml = new FXMLLoader[States.values().length];            this.loadedPanes = new Pane[States.values().length];            this.scenes = new Scene[States.values().length];            for(int i = 0; i < fxmlFiles.length; i++) {                loadedFxml[i] = new FXMLLoader(getClass().getResource(fxmlFiles[i]));                loadedPanes[i] = loadedFxml[i].load();                scenes[i] = new Scene(loadedPanes[i]);            }            rootStage.setScene(scenes[0]);            rootStage.setResizable(false);            rootStage.show();        } catch(IOException e) {            e.printStackTrace();        }    }    public static SceneManager getInstance() {        if(instance == null) {            instance = new SceneManager();        }        return instance;    }    public static void setUp(Stage stage) {        SceneManager.rootStage = stage;    }    public void switchScene(States state) {        rootStage.setScene(scenes[state.ordinal()]);    }}所以我打算做的是,通过加载器加载 FXML,将其分配给一个窗格,创建所有场景。然后我将一个场景设置为它的起始场景,并通过控制器中的 getInstance().switchScene() 方法完成剩下的工作。它运作良好,但我不确定这是否是一个好方法。
查看完整描述

1 回答

?
撒科打诨

TA贡献1934条经验 获得超2个赞

恕我直言,由于以下几个原因,实现非常糟糕:


单例模式没有正确实现

单例模式用于通过static方法访问包含相关数据/功能的实例,但此实例应包含所有相关数据作为实例字段。


rootStage和setUp是static虽然。


您将数据存储在不再需要的字段中

您永远不会从循环中loadedFxml或loadedPanes在循环外读取。您可以改为在循环体中创建局部变量。


一切都立即加载

对于几个小场景,这可能没有太大区别,但是随着您添加越来越多的场景,这将增加启动时间。考虑延迟加载场景。


场景数据保存在不同的数据结构中

不是什么大问题,但它使类更难维护。该enum数据的一部分用于创建存储/接入场景fxmlFiles中包含的另一半。每次添加/删除场景时,您都需要更新代码的两部分。在这种情况下,最好将 url 数据存储在枚举本身中:


public enum States {

    MAIN_MENU("../gui/MainWindow.fxml"), NEW_GAME("../gui/NewGameWindow.fxml");

    private final url;


    States(String url) {

        this.url = url;

    }

}

for(States state : States.values()) {

    FXMLLoader loader = new FXMLLoader(getClass().getResource(state.url));

    ...

}

请注意..,如果您将程序打包为 jar ,您在此处使用的 url 将停止工作。


但是首先使用 aenum是一个有问题的决定。这样,您将无法在不重新编译的情况下添加/删除场景。


使用static似乎根本没有必要

static如果可能,最好完全避免使用。(请参阅为什么静态变量被认为是邪恶的?)。


如果我假设您只使用SceneManager它加载的场景中的类(并用于显示初始场景)是正确的,那么将SceneManager实例传递给场景的控制器以避免SceneManager.getInstance在这些类中使用并不难(请参阅传递参数 JavaFX FXML ):


控制器的超类


public class BaseController {

    protected SceneManager sceneManager;

    void setSceneManager(SceneManager sceneManager) { // if SceneManager and BaseController are in different packages, change visibility

        this.sceneManager = sceneManager;

    }

}

FXMLLoader loader = ...

Pane pane = loader.load();

BaseController controller = loader.getController();

controller.setSceneManager(this);

为简单起见,使用 url 作为场景的标识符,您可以改进实现:


public class SceneManager {

    

    private final Stage rootStage;


    public SceneManager(Stage rootStage) {

        if (rootStage == null) {

            throw new IllegalArgumentException();

        }

        this.rootStage = rootStage;

    }


    private final Map<String, Scene> scenes = new HashMap<>();


    public void switchScene(String url) {

        Scene scene = scenes.computeIfAbsent(url, u -> {

            FXMLLoader loader = new FXMLLoader(getClass().getResource(u));

            try {

                Pane p = loader.load();

                BaseController controller = loader.getController();

                controller.setSceneManager(this);

                return new Scene(p);

            } catch (IOException ex) {

                throw new RuntimeException(ex);

            }

        });

        rootStage.setScene(scene);

    }

}

这使您可以

  • 为不同阶段创建不同的经理

  • 首先在需要时加载场景

  • 动态添加更多场景

  • 防止switchScene被调用但阶段是的状态null

  • 简化对SceneManagerin 控制器类的访问sceneManager.switchScene

  • SceneManager 在程序完成之前可能可用于垃圾收集,因为没有对它的静态引用。


查看完整回答
反对 回复 2021-12-01
  • 1 回答
  • 0 关注
  • 254 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信