JavaFX 圈選 仿百度地圖路線
import javafx.application.Application;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Label;
import javafx.scene.effect.DropShadow;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Polygon;
import javafx.scene.shape.Polyline;
import javafx.scene.shape.Shape;
import javafx.stage.Stage;
import java.util.ArrayList;
import java.util.List;
public class PolygonTest2 extends Application {
// 最近一次儲存的箭尾
Point2D arrowTailPoint;
// 最近的箭是否已繪製
boolean isHasLastArrow;
// 上一個居中折線的點
Point2D lastPolylinePoint2D;
// 居中折線的起點
Point2D startPolylinePoint2D;
public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { Polygon polygon = new Polygon(); polygon.setStroke(Color.GREEN); polygon.setStrokeWidth(8); polygon.setFill(null); Polyline polyline = new Polyline(); polyline.setStrokeWidth(2); polyline.setStroke(Color.YELLOW); DropShadow polylineDropShadow = new DropShadow(); polylineDropShadow.setRadius(5); polyline.setEffect(polylineDropShadow); Pane pane = new Pane(); pane.setPrefWidth(1000); pane.setMaxHeight(500); // 箭尾間隔 double arrowInternal = 100; // 箭身長度 double arrowLength = 10; // 界線和居中折線間的距離 double polylineDistance = polygon.getStrokeWidth() / 2; // 箭身中點垂足到箭頭一側終點的距離,是箭頭斜度的關鍵引數 double arrowHeadAngleKeyValue = polylineDistance - 2; // 一側折線(在順時針時表現為外側,在逆時針時表現為內側) Polyline firstSidePolyline = new Polyline(); firstSidePolyline.setStroke(Color.BLACK); firstSidePolyline.setStrokeWidth(1); // 另一側折線(在順時針時表現為內側,在逆時針時表現為外側) Polyline secondSidePolyline = new Polyline(); secondSidePolyline.setStroke(Color.BLACK); secondSidePolyline.setStrokeWidth(1); List<Polyline> arrowHeadPolylineList = new ArrayList<>(); List<Polyline> arrowBodyPolylineList = new ArrayList<>(); Label label1 = new Label("測試標籤1"); label1.setTranslateX(50); label1.setTranslateY(50); Label label2 = new Label("測試標籤2"); label2.setTranslateX(100); label2.setTranslateY(100); Label label3 = new Label("測試標籤3"); label3.setTranslateX(150); label3.setTranslateY(150); // 加入測試標籤 pane.getChildren().addAll(label1,label2,label3); pane.addEventHandler(MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() { @Override public void handle(MouseEvent event) { if (event.getButton().equals(MouseButton.PRIMARY)) { // 清空 pane.getChildren().clear(); arrowHeadPolylineList.clear(); arrowBodyPolylineList.clear(); polygon.getPoints().clear(); firstSidePolyline.getPoints().clear(); secondSidePolyline.getPoints().clear(); polyline.getPoints().clear(); // 加入測試標籤 pane.getChildren().addAll(label1,label2,label3); // 加入折線 pane.getChildren().add(polyline); polyline.getPoints().add(event.getX()); polyline.getPoints().add(event.getY()); polygon.getPoints().add(event.getX()); polygon.getPoints().add(event.getY()); arrowTailPoint = new Point2D(event.getX(), event.getY()); lastPolylinePoint2D = new Point2D(event.getX(), event.getY()); startPolylinePoint2D = new Point2D(event.getX(), event.getY()); isHasLastArrow = false; } } }); pane.addEventHandler(MouseEvent.MOUSE_DRAGGED, new EventHandler<MouseEvent>() { @Override public void handle(MouseEvent event) { if (event.getButton().equals(MouseButton.PRIMARY)) { polygon.getPoints().add(event.getX()); polygon.getPoints().add(event.getY()); polyline.getPoints().add(event.getX()); polyline.getPoints().add(event.getY()); // 繪製兩側界線(平移居中線) // 上一個點 double lastPolylinePointX = lastPolylinePoint2D.getX(); double lastPolylinePointY = lastPolylinePoint2D.getY(); // 當前點 double currentPlylinePointX = event.getX(); double currentPlylinePointY = event.getY(); // 當前點和上一個點的橫縱座標差 double polylinePointDeleteX = currentPlylinePointX - lastPolylinePointX; double polylinePointDeleteY = currentPlylinePointY - lastPolylinePointY; // 折線垂直線斜率 double polylineVerticalArc = Math.PI / 2 - (Math.atan(Math.abs(polylinePointDeleteY / polylinePointDeleteX))); // 內外折線和居中折線的橫向和縱向距離 double polylineInternalX = polylineDistance * Math.cos(polylineVerticalArc); double polylineInternalY = polylineDistance * Math.sin(polylineVerticalArc); // 折線加點 firstSidePolyline.getPoints().addAll(new Double[]{currentPlylinePointX + ((currentPlylinePointY >= lastPolylinePointY) ? -1 : 1) * polylineInternalX, currentPlylinePointY + ((currentPlylinePointX <= lastPolylinePointX) ? -1 : 1) * polylineInternalY}); secondSidePolyline.getPoints().addAll(new Double[]{currentPlylinePointX + ((currentPlylinePointY >= lastPolylinePointY) ? 1 : -1) * polylineInternalX, currentPlylinePointY + ((currentPlylinePointX <= lastPolylinePointX) ? 1 : -1) * polylineInternalY}); // 儲存當前點 lastPolylinePoint2D = new Point2D(currentPlylinePointX, currentPlylinePointY); // 繪製箭(根據箭尾和箭頭垂直線得到箭頭兩側終點) if (!isHasLastArrow && arrowTailPoint.distance(new Point2D(event.getX(), event.getY())) >= arrowLength) { // 箭尾座標 double arrowTailX = arrowTailPoint.getX(); double arrowTailY = arrowTailPoint.getY(); // 初始箭頭座標 double arrowHeadX = event.getX(); double arrowHeadY = event.getY(); // 實際箭身長 double arrowRealLength = new Point2D(arrowHeadX, arrowHeadY).distance(new Point2D(arrowTailX, arrowTailY)); // 最終箭頭座標 arrowHeadX = arrowTailX + (arrowLength / arrowRealLength) * (arrowHeadX - arrowTailX); arrowHeadY = arrowTailY + (arrowLength / arrowRealLength) * (arrowHeadY - arrowTailY); // 箭身中點座標 double arrowMiddleX = (arrowTailX + arrowHeadX) / 2; double arrowMiddleY = (arrowTailY + arrowHeadY) / 2; // 箭頭和尾的橫縱座標差 double arrowHTDeleteX = arrowHeadX - arrowTailX; double arrowHTDeleteY = arrowHeadY - arrowTailY; // 箭身垂直線斜率對應的弧度 double arrowVerticalArc = Math.PI / 2 - (Math.atan(Math.abs(arrowHTDeleteY / arrowHTDeleteX))); // 箭頭兩側終點與箭身中點的橫向和縱向距離 double arrowHSMInternalX = arrowHeadAngleKeyValue * Math.cos(arrowVerticalArc); double arrowHSMInternalY = arrowHeadAngleKeyValue * Math.sin(arrowVerticalArc); //System.out.println(arrowHSMInternalX + "--箭頭兩側終點與箭身中點的橫向和縱向距離--" + arrowHSMInternalY); // 箭頭兩側終點 double arrowHeadSideX1; double arrowHeadSideX2; double arrowHeadSideY1; double arrowHeadSideY2; if (arrowHTDeleteX * arrowHTDeleteY <= 0) { // 箭身呈矩陣副對角線方向 arrowHeadSideX1 = arrowMiddleX + arrowHSMInternalX; arrowHeadSideY1 = arrowMiddleY + arrowHSMInternalY; arrowHeadSideX2 = arrowMiddleX - arrowHSMInternalX; arrowHeadSideY2 = arrowMiddleY - arrowHSMInternalY; } else { // 箭身呈矩陣主對角線方向 arrowHeadSideX1 = arrowMiddleX + arrowHSMInternalX; arrowHeadSideY1 = arrowMiddleY - arrowHSMInternalY; arrowHeadSideX2 = arrowMiddleX - arrowHSMInternalX; arrowHeadSideY2 = arrowMiddleY + arrowHSMInternalY; } //System.out.println(arrowHeadSideX1 + "_" + arrowHeadSideY1 + "--箭頭兩側終點--" + arrowHeadSideX2 + "_" + arrowHeadSideY2); // 繪製箭頭 Polyline arrowHeadPolyline = new Polyline(); arrowHeadPolyline.setStroke(Color.WHITE); arrowHeadPolyline.setStrokeWidth(2); arrowHeadPolyline.getPoints().add(arrowHeadSideX1); arrowHeadPolyline.getPoints().add(arrowHeadSideY1); arrowHeadPolyline.getPoints().add(arrowHeadX); arrowHeadPolyline.getPoints().add(arrowHeadY); arrowHeadPolyline.getPoints().add(arrowHeadSideX2); arrowHeadPolyline.getPoints().add(arrowHeadSideY2); // 加入箭頭 arrowHeadPolylineList.add(arrowHeadPolyline); // 繪製箭身 /*Polyline arrowBodyPolyline = new Polyline(); arrowBodyPolyline.setStroke(Color.WHITE); arrowBodyPolyline.setStrokeWidth(2); arrowBodyPolyline.getPoints().add(arrowTailX); arrowBodyPolyline.getPoints().add(arrowTailY); arrowBodyPolyline.getPoints().add(arrowHeadX); arrowBodyPolyline.getPoints().add(arrowHeadY); // 加入箭身 arrowBodyPolylineList.add(arrowBodyPolyline);*/ isHasLastArrow = true; } // 儲存下一個箭尾 if (arrowTailPoint.distance(new Point2D(event.getX(), event.getY())) >= arrowInternal) { arrowTailPoint = new Point2D(event.getX(), event.getY()); isHasLastArrow = false; } } } }); pane.addEventHandler(MouseEvent.MOUSE_RELEASED, new EventHandler<MouseEvent>() { @Override public void handle(MouseEvent event) { // 清空 pane.getChildren().clear(); // 加入測試標籤 pane.getChildren().addAll(label1,label2,label3); // 加入居中折線對應的多邊形、兩側界線 pane.getChildren().addAll(polygon, firstSidePolyline, secondSidePolyline); // 加入箭頭 for(Polyline polyline : arrowHeadPolylineList){ pane.getChildren().add(polyline); } // 加入箭身 for(Polyline polyline : arrowBodyPolylineList){ pane.getChildren().add(polyline); } // 繪製兩側界線 // 當前點 double currentPlylinePointX = event.getX(); double currentPlylinePointY = event.getY(); // 終點 double endPolylinePointX = startPolylinePoint2D.getX(); double endPolylinePointY = startPolylinePoint2D.getY(); // 當前點和上一個點的橫縱座標差 double polylinePointDeleteX = currentPlylinePointX - endPolylinePointX; double polylinePointDeleteY = currentPlylinePointY - endPolylinePointY; // 折線垂直線斜率 double polylineVerticalArc = Math.PI / 2 - (Math.atan(Math.abs(polylinePointDeleteY / polylinePointDeleteX))); // 內外折線和居中折線的橫向和縱向距離 double polylineInternalX = polylineDistance * Math.cos(polylineVerticalArc); double polylineInternalY = polylineDistance * Math.sin(polylineVerticalArc); // 折線加點 firstSidePolyline.getPoints().addAll(new Double[]{endPolylinePointX + ((endPolylinePointY >= currentPlylinePointY) ? -1 : 1) * polylineInternalX, endPolylinePointY + ((endPolylinePointX <= currentPlylinePointX) ? -1 : 1) * polylineInternalY}); secondSidePolyline.getPoints().addAll(new Double[]{endPolylinePointX + ((endPolylinePointY >= currentPlylinePointY) ? 1 : -1) * polylineInternalX, endPolylinePointY + ((endPolylinePointX <= currentPlylinePointX) ? 1 : -1) * polylineInternalY}); Polygon polygonTemp = new Polygon(); polygonTemp.getPoints().addAll(polygon.getPoints()); List<Label> labelList = new ArrayList<>(); labelList.add(label1); labelList.add(label2); labelList.add(label3); // 被選中的圖形物件 List<Label> selectedElmentList = new ArrayList<>(); for(Label label : labelList){ if (isIntersectWithPolygon(polygonTemp, label)) { selectedElmentList.add(label); } } String alertInfo; if(selectedElmentList.size() == 0){ alertInfo = "你未圈選任何測試標籤!"; }else{ alertInfo = "你圈選了"; } for(int i = 0;i < selectedElmentList.size();i++){ Label label = selectedElmentList.get(i); alertInfo += label.getText() + (i < selectedElmentList.size() - 1 ? "、" : "。"); } Alert alert = new Alert(Alert.AlertType.INFORMATION, alertInfo, ButtonType.FINISH); alert.show(); } }); primaryStage.setScene(new Scene(pane, 1000, 500)); primaryStage.show(); } private boolean isIntersectWithPolygon(Polygon polygon, Label label) { try { if (polygon != null && label != null) { Node displayNode = label; List<Shape> shapeList = getShapeListFromNode(displayNode); for (Shape shape : shapeList) { // 圈選的圖形特別複雜時,會產生效率問題 Shape intersectShape = Shape.intersect(polygon, shape); if (intersectShape.getBoundsInLocal().getWidth() != -1) { return true; } } } } catch (Exception e) { e.printStackTrace(); } return false; } private static List<Shape> getShapeListFromNode(Node node) { List<Shape> shapeList = new ArrayList<Shape>(); if (node instanceof Parent) { Parent parent = (Parent) node; ObservableList<Node> subNodeList = parent.getChildrenUnmodifiable(); for (Node subNode : subNodeList) { List<Shape> subNodeShapeList = getShapeListFromNode(subNode); shapeList.addAll(subNodeShapeList); } } else if (node instanceof Shape) { shapeList.add((Shape) node); } return shapeList; }
}