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

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();
    }
}

额外优化说明

  1. 替换Label为TextFlow后,每个字母都是独立的Text节点,可以单独设置颜色。
  2. 监听inputField的textProperty,比监听键盘事件更可靠,能处理粘贴、删除等操作。
  3. 修正了原代码中calculateWordsPerMinute的整除精度问题,以及calculateAverageWPM的逻辑错误。
  4. 增加文件存在判断,避免读取不存在的词典文件时的异常。

内容的提问来源于stack exchange,提问作者Fraction

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 21:14:54