版权声明:就是码字也不容易啊 https://blog.csdn.net/qq_40946921/article/details/85608117
运行效果:
布局有些繁琐,用到多线程(计时)。
菜单窗口(选择文章)以及成绩单窗口不写,留给别人了
不得不说,系统字体有些难看,但是如果用一些个性化字体,别人的电脑如果没有安装这个字体,就会有些问题
代码如下,素材在网盘(自取)
网盘:https://pan.baidu.com/s/1tNp1k0sohDZpqJhOja19Rg
素材需放在与.class同一目录下
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextField;
import javafx.scene.effect.BlendMode;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.*;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;
class MyBox extends HBox{
public Label key,value;
public MyBox(ImageView img,String key,String value,String unit){
this.key=new Label(key);this.value=new Label(value);
img.setFitHeight(30);img.setFitWidth(30);
setAlignment(Pos.CENTER_LEFT);
setSpacing(15);
getChildren().addAll(img,this.key,new Label(":"),this.value,new Label(unit));
}
}
public class Typist extends Application {
Vector<TextFlow> textFlows; //存储文本流 //这些Vector是为了方便创建以后再找到那些组件
Vector<TextField> textFields; //存储文本域
Vector<Vector<Text>> vectors; //二维文本
Vector<Rectangle> rectangles; //矩形背景
GridPane pane;
ScrollPane inputPane;
VBox menu;
MyBox rate, right, error, back, count;
Scene scene;
int second; //计时器
Label label,time,textName;
ImageView btPause,btStop;
Thread thread; //这个进程用来跑时间
int now , size , keyCount , backCount , errorCount , Count; //now用来确定当前所在TextFiled
ImageView clock=new ImageView("clock.png");
private boolean pause = false;
public void init(){
thread=new Thread(()-> {
try {
while (true){
synchronized ((String)" ") {
if (!pause) {
Platform.runLater(() -> {
rate.value.setText(String.valueOf((int)(((double)keyCount/second)*60)));
time.setText(String.format("%02d:%02d", second % 3600 / 60, second % 60));
});
Thread.sleep(1000);
second++;
}
}
}
} catch (Exception ex) { }
});
pane=new GridPane();
scene=new Scene(pane);
menu=new VBox();
label=new Label("时间:");
time=new Label("00:00");
textName=new Label();
btPause=new ImageView("pause.png");
btStop=new ImageView("stop.png");
menu.setPrefSize(200,400);
menu.setAlignment(Pos.TOP_CENTER);
btPause.setOnMouseClicked(e->changePause());
btPause.setFitWidth(75);btPause.setFitHeight(35);
btStop.setFitWidth(75);btStop.setFitHeight(35);
menu.setSpacing(5);
menu.setPadding(new Insets(15,10,10,10));
time.setFont(Font.font("System",FontWeight.BOLD,30));
time.setTextFill(Color.rgb(239,169,37));
clock.setFitWidth(120);clock.setFitHeight(120);
Label label=new Label("开始输入时计时!");
label.setTextFill(Color.rgb(192,36,33));
HBox hbox=new HBox(label,new Label("文章名:"),textName);
hbox.setPrefSize(550,20);
hbox.setSpacing(60);
hbox.setPadding(new Insets(10,10,0,10));
pane.add(hbox,0,0);
Rectangle rectangle=new Rectangle(200,420);
rectangle.setArcWidth(20);rectangle.setArcHeight(20);
rectangle.setFill(Color.rgb(203,230,222));
pane.add(rectangle,1,1);
pane.setBackground(new Background(new BackgroundFill(Color.rgb(217,228,242),null,null)));
}
private void loadText(File file)throws FileNotFoundException ,IOException{ //从file中读取文本并更新组件的数据。
textFlows=new Vector<>();
textFields=new Vector<>();
vectors=new Vector<>();
rectangles=new Vector<>();
inputPane=new ScrollPane();
now=0;size=0;errorCount=0;keyCount=0;backCount=0;
rate=new MyBox(new ImageView("rate.png"),"速 度","-","KPM");
right=new MyBox(new ImageView("right.png"),"正确率","100","%");
error=new MyBox(new ImageView("error.png"),"错 误","0","字");
back= new MyBox(new ImageView("back.png"),"退 格","0","次");
count=new MyBox(new ImageView("count.png"),"总字数","0","字");
HBox bt=new HBox(btPause,btStop);
bt.setPrefWidth(200);
bt.setAlignment(Pos.CENTER);
bt.setSpacing(10);
bt.setPadding(new Insets(5,5,5,5));
pane.add(menu,1,1);
menu.getChildren().addAll(clock,new Label("时间"),time, rate, right, error,back,count, bt);
int count=1;
second=0;
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), "gbk"));
textName.setText(file.getName().substring(0,file.getName().indexOf('.')));
String str;
GridPane grid=new GridPane();
while ((str=reader.readLine())!=null) { //读取文本
TextFlow textFlow = new TextFlow();
TextField textField = new TextField();
Vector<Text> texts=new Vector<>();
textFlow.getChildren().add(new Text(" "));
for (int i = 0; i < str.length(); i++) { //将每行的每个文字设为一个组件
Text t = new Text(str.substring(i, i + 1));
size++;
t.setFont(Font.font("System", FontWeight.BOLD,20));
t.setTextAlignment(TextAlignment.CENTER);
textFlow.getChildren().add(t);
texts.add(t);
}
textField.setFont(Font.font("System",20));
textFlow.setMinHeight(30);
textField.setEditable(false);
pane.setVgap(10);pane.setHgap(5);
pane.setAlignment(Pos.TOP_CENTER);
Rectangle rectangle=new Rectangle(550,80);
rectangle.setArcHeight(20);
rectangle.setArcWidth(10);
rectangle.setFill(Color.rgb(212,233,254));
grid.add(rectangle,0,count);
VBox box=new VBox(textFlow,textField);
box.setPadding(new Insets(10,1,10,1));
textField.textProperty().addListener(ov->{ //监听文本域变动
if(!thread.isAlive())
thread.start(); //开始计时
String s=textField.getText();
for(int i=0;i<texts.size();i++){
if(i<s.length()){ //修改文字高亮
if(s.charAt(i)==texts.get(i).getText().charAt(0)) {
texts.get(i).setFill(Color.rgb(78,203,107));
texts.get(i).setUnderline(false);
}
else {
texts.get(i).setFill(Color.rgb(219,88,96));
texts.get(i).setUnderline(true);
}
}
else {
texts.get(i).setFill(Color.BLACK);
texts.get(i).setUnderline(false);
}
}
update();
});
textField.setOnKeyPressed(e->{ //绑定面板的回车事件,当回车时切换焦点
if(e.getCode()== KeyCode.BACK_SPACE){
backCount++;
back.value.setText(String.valueOf(backCount));
}
else if(e.getCode()== KeyCode.ENTER &&textField.getText().length() >= texts.size()){
textFields.get(now).setEditable(false);
rectangles.get(now).setFill(Color.rgb(212,233,254));
textFields.get(++now).setEditable(true);
textFields.get(now).requestFocus();
rectangles.get(now).setFill(Color.rgb(240,209,210));
}
else{
keyCount++;
}
});
vectors.add(texts);
textFlows.add(textFlow);
textFields.add(textField);
rectangles.add(rectangle);
grid.add(box,0,count++);
}
textFields.lastElement().setOnKeyPressed(e->{
if(e.getCode()== KeyCode.ENTER &&textFields.lastElement().getText().length() >= vectors.lastElement().size()){
textFields.get(now).setEditable(false);
rectangles.get(now).setFill(Color.rgb(212,233,254));
changePause();
}
});
inputPane.setContent(grid);
inputPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS);
inputPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
inputPane.setMinSize(580,420);
inputPane.setPadding(new Insets(5,5,5,5));
pane.add(inputPane,0,1);
reader.close();
textFields.get(now).setEditable(true); //初始化第一个文本框
rectangles.get(now).setFill(Color.rgb(240,209,210));
textFields.get(now).requestFocus();
}
private boolean changePause(){ //改变暂停状态
if(thread.isAlive()){
if(pause){
pause=false;
btPause.setImage(new Image("pause.png"));
textFields.get(now).setEditable(true);
}
else {
pause = true;
btPause.setImage(new Image("run.png"));
textFields.get(now).setEditable(false);
}
}
return pause;
}
private void update(){ //更新数据
errorCount=0;Count=0;
for(int i=0;i<=now;i++){
for(int j=0;j<textFields.get(i).getText().length()&&j<vectors.get(i).size();j++){
if(textFields.get(i).getText().charAt(j)!=vectors.get(i).get(j).getText().charAt(0)) {
errorCount++;
}
Count++;
}
}
error.value.setText(String.valueOf(errorCount));
right.value.setText(String.valueOf((int)((1-(double)errorCount/Count)*100)));
count.value.setText(String.valueOf(Count));
}
private void mainMenu(){ //显示选择(文章)菜单
}
private void over(){ //显示成绩单
}
public void start(Stage stage)throws FileNotFoundException ,IOException{
stage.setScene(scene);
stage.setWidth(800);
stage.setHeight(480);
stage.setResizable(false); //尺寸不可变
stage.show();
loadText(new File("test.txt"));
}
}