它來了它來了,最後一期終於來了。理論上該講的全都講完了,只剩下那個拖了好幾期的自定義控制項和一個比較沒有存在感的設置功能沒有講。所以這次就重點介紹它們倆吧。 首先我們快速瀏覽下設置的實現,上圖: 然後是控制器代碼: SettingsController.java package controllers ...
它來了它來了,最後一期終於來了。理論上該講的全都講完了,只剩下那個拖了好幾期的自定義控制項和一個比較沒有存在感的設置功能沒有講。所以這次就重點介紹它們倆吧。
首先我們快速瀏覽下設置的實現,上圖:
然後是控制器代碼:
SettingsController.java
package controllers;
import components.GameEnum;
import javafx.animation.FadeTransition;
import javafx.animation.RotateTransition;
import javafx.animation.SequentialTransition;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.RadioButton;
import javafx.scene.control.TextField;
import javafx.scene.control.ToggleGroup;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.util.Duration;
import static components.Constant.*;
/**
* @description: 設置界面控制邏輯
* @author: 郭小柒w
* @time: 2023/6/15
*/
public class SettingsController {
@FXML // 單選按鈕, 難度
private RadioButton easy, medium, hard, custom;
@FXML // 文本框組, 自定義游戲數據
private TextField numWidth, numHeight, numBomb;
@FXML // 保存按鈕
private Button save;
@FXML // 輔助效果圖
private ImageView loading;
// 單選按鈕組
private ToggleGroup degree;
public void initialize() {
// 文本框預設不可編輯
numWidth.setEditable(false);
numHeight.setEditable(false);
numBomb.setEditable(false);
// 首先嘗試使用已保存設置
switch (GAME) {
case MEDIUM:
medium.setSelected(true);
break;
case HARD:
hard.setSelected(true);
break;
case CUSTOM:
custom.setSelected(true);
numWidth.setEditable(true);
numHeight.setEditable(true);
numBomb.setEditable(true);
numWidth.setText(GAME.width + "");
numHeight.setText(GAME.height + "");
numBomb.setText(GAME.bomb + "");
break;
default:
easy.setSelected(true);
break;
}
// 單選按鈕分組
degree = new ToggleGroup();
easy.setToggleGroup(degree);
medium.setToggleGroup(degree);
hard.setToggleGroup(degree);
custom.setToggleGroup(degree);
// 難度按鈕選中事件
degree.selectedToggleProperty().addListener(((observable, oldValue, newValue) -> {
String id = ((RadioButton) newValue).getId();
// 只有選中自定義難度情況下可編輯文本框, 預設不可編輯
if (id.equals("custom")) {
GAME = GameEnum.CUSTOM;
numWidth.setEditable(true);
numHeight.setEditable(true);
numBomb.setEditable(true);
} else {
// 清空文本框並設置為不可編輯
numWidth.setText(null);
numHeight.setText(null);
numBomb.setText(null);
numWidth.setEditable(false);
numHeight.setEditable(false);
numBomb.setEditable(false);
if (id.equals("easy")) {
GAME = GameEnum.EASY;
} else if (id.equals("medium")) {
GAME = GameEnum.MEDIUM;
} else {
GAME = GameEnum.HARD;
}
}
}));
// 保存按鈕點擊事件
save.setOnMouseClicked(event -> {
try {
// 如果是自定義難度, 保存輸入的值
if (GAME == GameEnum.CUSTOM) {
try {
// 保存自定義輸入
GAME.setWidth(Integer.parseInt(numWidth.getText()));
GAME.setHeight(Integer.parseInt(numHeight.getText()));
GAME.setBomb(Integer.parseInt(numBomb.getText()));
} catch (NumberFormatException e) {
// 輸入問題導致的轉換失敗, 按簡單設置處理
GAME.setWidth(9);
GAME.setHeight(9);
GAME.setBomb(10);
}
}
// 設置用於動畫效果的圖片
loading.setImage(new Image(LOAD_IMG));
loading.setVisible(true);
// 點擊保存時的動畫效果,分兩步, 1:旋轉緩衝 2:圖片淡出
RotateTransition transition1 = new RotateTransition(Duration.seconds(1), loading);
// 旋轉角度
transition1.setByAngle(360);
transition1.setOnFinished(event1 -> {
loading.setImage(new Image(SAVE_IMG));
});
FadeTransition transition2 = new FadeTransition(Duration.seconds(1), loading);
// 不透明度變化
transition2.setFromValue(1);
transition2.setToValue(0);
SequentialTransition sequence = new SequentialTransition(transition1, transition2);
// 播放動畫
sequence.play();
} catch (Exception e) {
System.out.println("Error on [Class:SettingsController, Method:initialize, Event: save]=>");
e.printStackTrace();
}
});
}
}
和上期排行版難度按鈕類似,都是單選按鈕分組然後設置對應點擊事件。不同的是不像排行版切換那樣直觀,這裡需要一個提示來讓玩家清楚保存是生效了的,所以我設置了保存按鈕和對應的動畫提示。下麵介紹自定義控制項的實現(設置真的沒有存在感,哈哈哈)。
LedNumber,這個東西可是費了我老半天勁。我的思路是既然想在界面上顯示,他要麼是佈局要麼是控制項,經過嘗試後發現還是控制項合理。所以這個自定義類要繼承 Control 類,然後按照要求實現 createDefaultSkin 方法。到這裡我就不會了,它要求的返回值類型為 Skin<?>,這是啥,沒見過啊。求助萬能的GPT後大概明白了它的要求(我理解的不一定准確)——控制項顯示是需要有Skin的,沒有的話就類似無內容的Label,不設置背景色在界面上看起來就跟沒有一樣。所以我按GPT的提示創建了對應的skin類 LedNumberSkin,併在裡面進行外觀設計。
設計思路受這篇文章啟發:https://blog.csdn.net/hx0_0_8/article/details/8012448
其思想就是將數字看作由以下七個線段組合而成,不同數字使用不同的線段:
只是這樣看起來還不夠美觀,所以可以對這些線段的拼接處進行處理,比如下麵這種形式(繪製的有些簡陋,代碼中是可以控制連接處貼合的,適當留白更立體):
那麼代碼中是如何實現的呢?對於每一條邊,可以使用多邊形Polygon類實現,只需要依次寫入它的坐標即可(必須是順時針或者逆時針,起始點位置不做要求),如下:
/**
* 計算自定義多邊形各頂點坐標
*
* @param toward 多邊形朝向 [01234: 右下左上中]
* @param x 起始點橫坐標
* @param y 起始點縱坐標
* @return 坐標數組
*/
public ArrayList<Double> getPoints(int toward, double x, double y) {
ArrayList<Double> points = new ArrayList();
// 添加起始點坐標
points.add(x);
points.add(y);
// 按順時針方向依次添加其餘坐標
switch (toward) {
case 0:
points.add(x + height);
points.add(y + height);
points.add(x + height);
points.add(y + height + lenShort);
points.add(x);
points.add(y + lenLong);
break;
case 1:
points.add(x + lenLong);
points.add(y);
points.add(x + height + lenShort);
points.add(y + height);
points.add(x + height);
points.add(y + height);
break;
case 2:
points.add(x + height);
points.add(y - height);
points.add(x + height);
points.add(y + height + lenShort);
points.add(x);
points.add(y + lenShort);
break;
case 3:
points.add(x + lenShort);
points.add(y);
points.add(x + height + lenShort);
points.add(y + height);
points.add(x - height);
points.add(y + height);
break;
case 4:
points.add(x + height);
points.add(y - height + 2);
points.add(x + height + lenShort);
points.add(y - height + 2);
points.add(x + lenLong);
points.add(y);
points.add(x + height + lenShort);
points.add(y + height - 2);
points.add(x + height);
points.add(y + height - 2);
break;
}
return points;
}
有了繪製方法,接下來就是具體數字需要的初始化方法:
/**
* 構建點陣數字需要的邊
*/
public void init() {
// 初始化
for (int i = 0; i < 7; ++i) {
polygons[i] = new Polygon();
}
// 計算出各多邊形的頂點坐標
polygons[0].getPoints().addAll(getPoints(0, 1, 1));
polygons[1].getPoints().addAll(getPoints(1, 2, 0));
polygons[2].getPoints().addAll(getPoints(2, height + lenShort + 3, height + 1));
polygons[3].getPoints().addAll(getPoints(2, height + lenShort + 3, height + lenLong + 3));
polygons[4].getPoints().addAll(getPoints(3, height + 2, lenLong * 2 - 1));
polygons[5].getPoints().addAll(getPoints(0, 1, lenLong + 3));
polygons[6].getPoints().addAll(getPoints(4, 2, lenLong + 2));
// 根據edges數組判斷每條邊待設置的顏色
for (int i = 0; i < 7; ++i) {
if(edges[index][i]) {
polygons[i].setFill(Color.web("#FF0000"));
} else {
polygons[i].setFill(Color.web("#680404"));
}
pane.getChildren().add(polygons[i]);
}
getChildren().add(pane);
}
註:edges為二維boolean數組,控制每條邊的顏色顯示 [true:亮紅色, false:暗紅色]
這樣就有了每個數字對應的外觀,由於我使用的jdk版本較早,所以不支持 LedNumber 類運行中修改 Skin,所以採用以下方式實現數字顯示切換:
/**
* 切換數字顯示
* @param index 要轉化成的數字
*/
public void switchSkin(int index) {
// 清空當前控制項的子節點, 重新添加
getChildren().clear();
LedNumber newLedNumber = new LedNumber(index);
getChildren().add(newLedNumber);
}
這樣就完成了LED數字的顯示,感覺這個想法還是很不錯的。而且在這個基礎上你甚至還可以自己實現電子萬年曆之類的程式,有興趣的伙伴可以嘗試下。到此本次掃雷項目已經介紹差不多了,如果您還有疑問歡迎在評論區留言,有緣明年再見(開鴿!)
——————————————我———是———分———割———線—————————————
稀里糊塗地講完啦,不知道大家能理解多少