You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JavaFX中同时处理键盘事件与Button点击的问题求助

JavaFX游戏中Button与键盘事件冲突的解决方法

我用JavaFX写了个简易游戏,通过Button实现暂停功能:

@FXML private Button btnPause;

暂停状态由私有字段private boolean isPause控制,点击方法切换状态:

@FXML private void pauseClick(ActionEvent event){ isPause = !isPause; }

我在HelloApplication类里处理键盘事件:

scene.setOnKeyPressed(e -> {
            if (e.getCode() == KeyCode.ENTER)
                HelloController.jump = true;
            if (e.getCode() == KeyCode.RIGHT || e.getCode() == KeyCode.D)
                HelloController.right = true;
            if (e.getCode() == KeyCode.LEFT || e.getCode() == KeyCode.A)
                HelloController.left = true;
        });

scene.setOnKeyReleased(e -> {
            if (e.getCode() == KeyCode.RIGHT || e.getCode() == KeyCode.D)
                HelloController.right = false;
            if (e.getCode() == KeyCode.LEFT || e.getCode() == KeyCode.A)
                HelloController.left = false;
        });

但只要添加了这个Button,所有键盘按键都被识别为按钮点击,无法正常触发键盘事件。请问怎么同时处理Button点击和键盘事件?


相关代码

HelloApplication代码

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.stage.Stage;
import javafx.stage.StageStyle;

import java.io.IOException;

public class HelloApplication extends Application {
    @Override
    public void start(Stage stage) throws IOException {
        FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml"));
        Scene scene = new Scene(fxmlLoader.load(), 711, 400);

        stage.initStyle(StageStyle.UNDECORATED);
        stage.setScene(scene);

        scene.setOnKeyPressed(e -> {
            if (e.getCode() == KeyCode.ENTER)
                HelloController.jump = true;
            if (e.getCode() == KeyCode.RIGHT || e.getCode() == KeyCode.D)
                HelloController.right = true;
            if (e.getCode() == KeyCode.LEFT || e.getCode() == KeyCode.A)
                HelloController.left = true;
        });

        scene.setOnKeyReleased(e -> {
            if (e.getCode() == KeyCode.RIGHT || e.getCode() == KeyCode.D)
                HelloController.right = false;
            if (e.getCode() == KeyCode.LEFT || e.getCode() == KeyCode.A)
                HelloController.left = false;
        });

        stage.show();
    }

    public static void main(String[] args) {
        launch();
    }
}

Controller代码

import java.net.URL;
import java.util.ResourceBundle;

import javafx.animation.*;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.input.ActionEvent;
import javafx.scene.shape.Rectangle;
import javafx.util.Duration;

public class HelloController {

    @FXML
    private ResourceBundle resources;

    @FXML
    private URL location;

    @FXML
    private Rectangle bg1, bg2, player, enemy;

    @FXML
    private Button btnPause;

    @FXML
    private Label labelPause;

    private final int BG_WEIGHT = 711;

    private ParallelTransition parallelTransition;
    private TranslateTransition enemyTrans;

    public static boolean right = false, left = false, jump = false;

    private boolean isPause = false;

    private int playerSpeed = 3, jumpSpeed = 4;

    AnimationTimer timer = new AnimationTimer() {
        @Override
        public void handle(long l) {
            if (jump && player.getLayoutY() > 60f)
                player.setLayoutY(player.getLayoutY() - jumpSpeed);
            else if (player.getLayoutY() <= 185f){
                jump = false;
                player.setLayoutY(player.getLayoutY() + jumpSpeed);
            }

            if (right && player.getLayoutX() < 100)
                player.setLayoutX(player.getLayoutX() + playerSpeed);
            if (left && player.getLayoutX() > 0)
                player.setLayoutX(player.getLayoutX() - playerSpeed);

            if (isPause && !labelPause.isVisible()) {
                labelPause.setVisible(true);
                playerSpeed = 0;
                jumpSpeed = 0;
                parallelTransition.pause();
                enemyTrans.pause();
            }
            else if (!isPause && labelPause.isVisible()) {
                labelPause.setVisible(false);
                playerSpeed = 3;
                jumpSpeed = 4;
                parallelTransition.play();
                enemyTrans.play();
            }
        }
    };

    @FXML
    private void pauseClick(ActionEvent event){
        isPause = !isPause;
    }

    @FXML
    void initialize() {
        TranslateTransition bgOneTrans = new TranslateTransition(Duration.millis(5000), bg1);
        bgOneTrans.setFromX(0);
        bgOneTrans.setToX(BG_WEIGHT * -1);
        bgOneTrans.setInterpolator(Interpolator.LINEAR);

        TranslateTransition bgTwoTrans = new TranslateTransition(Duration.millis(5000), bg2);
        bgTwoTrans.setFromX(0);
        bgTwoTrans.setToX(BG_WEIGHT * -1);
        bgTwoTrans.setInterpolator(Interpolator.LINEAR);

        enemyTrans = new TranslateTransition(Duration.millis(3500), enemy);
        enemyTrans.setFromX(0);
        enemyTrans.setToX(BG_WEIGHT * -1 - 200);
        enemyTrans.setInterpolator(Interpolator.LINEAR);
        enemyTrans.setCycleCount(Animation.INDEFINITE);
        enemyTrans.play();

        parallelTransition = new ParallelTransition(bgOneTrans, bgTwoTrans);
        parallelTransition.setCycleCount(Animation.INDEFINITE);
        parallelTransition.play();

        timer.start();
    }

}

FXML文件代码

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.shape.Rectangle?>
<?import javafx.scene.text.Font?>

<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="711.0" xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.example.realgame.HelloController">
   <children>
      <Rectangle fx:id="bg1" arcHeight="5.0" arcWidth="5.0" fill="#e2ff1f" height="400.0" stroke="#ffffff00" strokeType="INSIDE" width="711.0" />
      <Rectangle fx:id="bg2" arcHeight="5.0" arcWidth="5.0" fill="#26ff00" height="400.0" layoutX="709.0" stroke="#ffffff00" strokeType="INSIDE" width="711.0" />
      <Rectangle fx:id="player" arcHeight="5.0" arcWidth="5.0" fill="#13b734" height="124.0" layoutX="42.0" layoutY="225.0" stroke="BLACK" strokeType="INSIDE" width="124.0" />
      <Rectangle fx:id="enemy" arcHeight="5.0" arcWidth="5.0" fill="#c51212" height="87.0" layoutX="853.0" layoutY="264.0" stroke="BLACK" strokeType="INSIDE" width="87.0" />
      <Label fx:id="labelPause" focusTraversable="false" layoutX="239.0" layoutY="109.0" text="PAUSE" textFill="#4d0aff" visible="false">
         <font>
            <Font name="Bell MT Bold" size="84.0" />
         </font>
      </Label>
      <Button fx:id="btnPause" layoutX="23.0" layoutY="18.0" mnemonicParsing="false" text="Pause" />
   </children>
</AnchorPane>

解决方案

问题根源是Button默认会自动获取焦点,导致键盘事件优先被Button消费(比如空格、回车会触发按钮点击),无法传递到Scene。以下是两种有效解决方法:

方法1:设置Button的focusTraversable为false

在FXML中给Button添加focusTraversable="false"属性,这样Button不会自动获取焦点,焦点会保留在根容器(AnchorPane)上,键盘事件就能正常被Scene捕获:

<Button fx:id="btnPause" layoutX="23.0" layoutY="18.0" mnemonicParsing="false" text="Pause" focusTraversable="false" />

方法2:让根容器主动获取焦点

  1. 给FXML中的AnchorPane添加fx:id="rootPane",然后在Controller的initialize方法中请求焦点:
@FXML
private AnchorPane rootPane;

@FXML
void initialize() {
    // 其他初始化代码...
    rootPane.setFocusTraversable(true);
    rootPane.requestFocus();
    timer.start();
}

这样即使Button偶尔获取焦点,程序启动时根容器会重新拿到焦点,确保键盘事件正常触发。

额外优化:避免静态变量传递状态

代码中用HelloController.right等静态变量传递键盘状态,这不是JavaFX的最佳实践。建议将键盘事件处理移到Controller中,直接操作实例变量,比如:

// 在Controller中添加键盘事件处理
@FXML
void initialize() {
    // 其他代码...
    rootPane.setOnKeyPressed(e -> {
        if (e.getCode() == KeyCode.ENTER)
            jump = true;
        if (e.getCode() == KeyCode.RIGHT || e.getCode() == KeyCode.D)
            right = true;
        if (e.getCode() == KeyCode.LEFT || e.getCode() == KeyCode.A)
            left = true;
    });

    rootPane.setOnKeyReleased(e -> {
        if (e.getCode() == KeyCode.RIGHT || e.getCode() == KeyCode.D)
            right = false;
        if (e.getCode() == KeyCode.LEFT || e.getCode() == KeyCode.A)
            left = false;
    });

    rootPane.setFocusTraversable(true);
    rootPane.requestFocus();
    timer.start();
}

然后把right、left、jump改成非静态变量:

private boolean right = false, left = false, jump = false;

内容的提问来源于stack exchange,提问作者Антон Ледовский

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 01:10:55