JavaFX中如何修改Label单个字符颜色?打字游戏实现求助
解决打字游戏实时单字母颜色变化问题
你的核心问题在于使用Label.setTextFill()会统一修改所有文本的颜色,无法单独控制单个字母的样式。要实现单字母实时变色,需要使用JavaFX的富文本组件,比如TextFlow,或者给文本添加分段样式属性。下面是具体的修改方案:
关键修改点
- 将原有的
Label wordLabel替换为TextFlow wordTextFlow,TextFlow可以容纳多个带独立样式的Text节点,每个节点对应一个字母。 - 重构输入监听逻辑,根据输入内容逐个判断字母是否正确,给对应位置的
Text节点设置颜色。 - 调整
showNextWord方法,将目标单词拆分为单个Text节点,初始颜色设为黑色。
修改后的完整代码
import javafx.application.Application; import javafx.application.Platform; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.TextField; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.scene.text.Text; import javafx.scene.text.TextFlow; import javafx.stage.Stage; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class TypingGame extends Application { private List<String> words; private int currentIndex; private long startTime; private int correctCount; private TextFlow wordTextFlow; // 替换原Label为TextFlow private TextField inputField; private Label resultLabel; private Button startButton; private ObservableList<Text> letterTexts; // 存储每个字母的Text节点 public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { try { loadWords(); wordTextFlow = new TextFlow(); inputField = new TextField(); resultLabel = new Label(); startButton = new Button("Start Game"); startButton.setOnAction(event -> startGame()); inputField.setOnKeyPressed(event -> { if (event.getCode() == KeyCode.ENTER) { checkWord(); } }); VBox root = new VBox(10); root.setAlignment(javafx.geometry.Pos.CENTER); root.getChildren().addAll(wordTextFlow, inputField, resultLabel, startButton); Scene scene = new Scene(root, 400, 300); primaryStage.setTitle("Typing Game"); primaryStage.setScene(scene); primaryStage.show(); // 输入监听逻辑重构 inputField.textProperty().addListener((obs, oldText, newText) -> { if (currentIndex >= words.size()) return; String targetWord = words.get(currentIndex); String typedText = newText.trim(); // 重置所有字母颜色为黑色,避免残留上一次的颜色 letterTexts.forEach(text -> text.setFill(Color.BLACK)); if (typedText.length() <= targetWord.length()) { for (int i = 0; i < typedText.length(); i++) { if (typedText.charAt(i) == targetWord.charAt(i)) { letterTexts.get(i).setFill(Color.GREEN); } else { letterTexts.get(i).setFill(Color.RED); } } } }); } catch (Exception e) { e.printStackTrace(); } } private void startGame() { Collections.shuffle(words); currentIndex = 0; correctCount = 0; showNextWord(); startTime = System.currentTimeMillis(); inputField.setDisable(false); startButton.setDisable(true); resultLabel.setText(""); } private void showNextWord() { wordTextFlow.getChildren().clear(); if (currentIndex < words.size()) { String targetWord = words.get(currentIndex); letterTexts = FXCollections.observableArrayList(); // 将单词拆分为单个Text节点,添加到TextFlow for (char c : targetWord.toCharArray()) { Text text = new Text(String.valueOf(c)); text.setFill(Color.BLACK); letterTexts.add(text); wordTextFlow.getChildren().add(text); } inputField.clear(); } else { endGame(); } } private void checkWord() { String typedWord = inputField.getText().trim(); String targetWord = words.get(currentIndex); if (typedWord.equals(targetWord)) { // 全对时所有字母设为绿色 letterTexts.forEach(text -> text.setFill(Color.GREEN)); correctCount++; } else { // 错误时重新校验每个字母,错误的标红,正确的保持绿色 for (int i = 0; i < Math.min(typedWord.length(), targetWord.length()); i++) { if (typedWord.charAt(i) != targetWord.charAt(i)) { letterTexts.get(i).setFill(Color.RED); } } } currentIndex++; // 延迟切换下一个单词,让用户看到当前结果 Platform.runLater(() -> { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } showNextWord(); }); } private void endGame() { long endTime = System.currentTimeMillis(); inputField.setDisable(true); startButton.setDisable(false); long elapsedTime = endTime - startTime; int wordsPerMinute = calculateWordsPerMinute(elapsedTime); resultLabel.setText("Game Over!\nWPM: " + wordsPerMinute + "\nAverage WPM: " + calculateAverageWPM()); } private int calculateWordsPerMinute(long elapsedTime) { if (elapsedTime == 0) return 0; double minutes = elapsedTime / 60000.0; // 用浮点计算避免整除丢失精度 double wpm = correctCount / minutes; return (int) Math.round(wpm); } private double calculateAverageWPM() { if (currentIndex == 0) return 0; long totalTime = System.currentTimeMillis() - startTime; double totalMinutes = totalTime / 60000.0; return correctCount / totalMinutes / currentIndex * currentIndex; // 修正原逻辑错误,返回正确整体WPM } private void loadWords() { words = new ArrayList<>(); try { File selectedFile = new File("dictionary/english.txt"); if (selectedFile.exists()) { // 增加文件存在判断 try (BufferedReader reader = new BufferedReader(new FileReader(selectedFile))) { String line; while ((line = reader.readLine()) != null) { words.add(line.trim()); } } catch (IOException e) { e.printStackTrace(); } } else { System.out.println("Dictionary file not found: " + selectedFile.getAbsolutePath()); } } catch (Exception e) { e.printStackTrace(); } } @Override public void stop() { Platform.exit(); } }
额外优化说明
- 替换
Label为TextFlow后,每个字母都是独立的Text节点,可以单独设置颜色。 - 监听
inputField的textProperty,比监听键盘事件更可靠,能处理粘贴、删除等操作。 - 修正了原代码中
calculateWordsPerMinute的整除精度问题,以及calculateAverageWPM的逻辑错误。 - 增加文件存在判断,避免读取不存在的词典文件时的异常。
内容的提问来源于stack exchange,提问作者Fraction
相关产品推荐
相关产品推荐

