Android 的 JavaFX 端口中的事件处理

Event handlling in JavaFX Port for Android

我在 java fx 纸牌应用程序上使用鼠标事件处理程序。它专为桌面设计。然后我发现了 JavaFx 端口。所以我尝试测试我的应用程序是否可以在移动设备上运行。显示效果不错,如下图。

我的问题是当我尝试拖放卡片时。它使应用程序崩溃。可能是因为我的事件处理?

我在真实设备(Kindle Fire)和虚拟机(Blue Stacks)上试过了 以下是 Blue Stacks VM 的错误日志。

07-24 13:59:51.313  6441  6474 I System.out: don't add points, primary = -1
07-24 13:59:51.313  6441  6474 I System.out: Top Card: 9H
07-24 13:59:51.313  6441  6474 I System.out: Source Card: 8S
07-24 13:59:51.313  6441  6474 I System.out: createNewCard(): Creating card...
07-24 13:59:51.313  6441  6474 I System.out: createNewCard(): Setting card images...
07-24 13:59:51.333  6441  6474 I System.out: createNewCard(): Setting card name...
07-24 13:59:51.333  6441  6474 I System.out: createNewCard(): Setting card Event Filter...
07-24 13:59:51.333  6441  6474 I System.out: createNewCard(): Setting card location..
07-24 13:59:51.333  6441  6474 I System.out: createNewCard(): Setting card color...
07-24 13:59:51.333  6441  6474 I System.out: createNewCard(): Returning card...
07-24 13:59:51.333  1881  1963 D BstCommandProcessor-Application: Application crash has been observed.
07-24 13:59:51.333  6441  6474 I Process : Sending signal. PID: 6441 SIG: 9
07-24 13:59:51.333  6441  6474 D AndroidRuntime: procName from cmdline: com.gluonapplication2
07-24 13:59:51.333  6441  6474 E AndroidRuntime: in writeCrashedAppName, pkgName :com.gluonapplication2
07-24 13:59:51.333  6441  6474 D AndroidRuntime: file written successfully with content: com.gluonapplication2 StringBuffer : ;com.gluonapplication2
07-24 13:59:51.333  6441  6474 E AndroidRuntime: FATAL EXCEPTION: JavaFX Application Thread
07-24 13:59:51.333  6441  6474 E AndroidRuntime: Process: com.gluonapplication2, PID: 6441
07-24 13:59:51.333  6441  6474 E AndroidRuntime: java.lang.NoSuchMethodError: java.util.ArrayList.stream
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.gluonapplication2.views.SolitaireEvent.lambda$dragDropped(SolitaireEvent.java:142)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.gluonapplication2.views.SolitaireEvent.access$lambda(SolitaireEvent.java)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.gluonapplication2.views.SolitaireEvent$$Lambda.handle(Unknown Source)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at javafx.event.Event.fireEvent(Event.java:198)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at javafx.scene.Scene$DnDGesture.fireEvent(Scene.java:2937)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at javafx.scene.Scene$DnDGesture.processTargetDrop(Scene.java:3163)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at javafx.scene.Scene$DnDGesture.access00(Scene.java:2913)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at javafx.scene.Scene$DropTargetListener.drop(Scene.java:2877)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.sun.javafx.tk.quantum.GlassSceneDnDEventHandler.lambda$handleDragDrop1(GlassSceneDnDEventHandler.java:95)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.sun.javafx.tk.quantum.GlassSceneDnDEventHandler.access$lambda(GlassSceneDnDEventHandler.java)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.sun.javafx.tk.quantum.GlassSceneDnDEventHandler$$Lambda.run(Unknown Source)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at java.security.AccessController.doPrivileged(AccessController.java:52)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.sun.javafx.tk.quantum.GlassSceneDnDEventHandler.handleDragDrop(GlassSceneDnDEventHandler.java:92)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleDragDrop5(GlassViewEventHandler.java:672)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.sun.javafx.tk.quantum.GlassViewEventHandler.access$lambda(GlassViewEventHandler.java)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.sun.javafx.tk.quantum.GlassViewEventHandler$$Lambda.get(Unknown Source)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:391)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleDragDrop(GlassViewEventHandler.java:671)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.sun.glass.ui.View.handleDragDrop(View.java:712)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.sun.glass.ui.View.notifyDragDrop(View.java:1037)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.sun.glass.ui.monocle.MonocleView.notifyDragDrop(MonocleView.java:163)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.sun.glass.ui.monocle.MouseInput.notifyMouse(MouseInput.java:248)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.sun.glass.ui.monocle.MouseInput.lambda$postMouseEvent0(MouseInput.java:227)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.sun.glass.ui.monocle.MouseInput.access$lambda(MouseInput.java)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.sun.glass.ui.monocle.MouseInput$$Lambda.run(Unknown Source)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.sun.glass.ui.monocle.RunnableProcessor.runLoop(RunnableProcessor.java:92)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.sun.glass.ui.monocle.RunnableProcessor.enterNestedEventLoop(RunnableProcessor.java:107)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.sun.glass.ui.monocle.MonocleApplication._enterNestedEventLoop(MonocleApplication.java:144)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.sun.glass.ui.monocle.MonocleApplication.enterDnDEventLoop(MonocleApplication.java:371)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.sun.glass.ui.monocle.MonocleDnDClipboard.pushToSystem(MonocleDnDClipboard.java:54)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.sun.glass.ui.SystemClipboard.flush(SystemClipboard.java:51)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.sun.glass.ui.ClipboardAssistance.flush(ClipboardAssistance.java:59)
07-24 13:59:51.333  6441  6474 E AndroidRuntime:        at com.sun.java
07-24 13:59:51.343  1881  1963 W BstCommandProcessor-Application: in sendHttpRequest, requestType is of CRASH_APP type but one of the requiredInfo is NULL, crashedApp = com.bluestacks.BstCommandProcessor.BstCrashedAppInfo@4e79b1e8
07-24 13:59:51.343  1881  1963 D BstCommandProcessor-Application: in sendHttpRequest, request to send to (fqdn): http://10.0.2.2:2861/AppCrashedInfo
07-24 13:59:51.343  1881  1963 D BstCommandProcessor-Application: data: {"packageName":"com.gluonapplication2","shortPackageName":"com.gluonapplication2","versionCode":1,"versionName":"1.0"}
07-24 13:59:51.363  1677  1924 I ActivityManager: Process com.gluonapplication2 (pid 6441) has died.
07-24 13:59:51.363  1677  1880 I WindowState: WIN DEATH: Window{4e8b30d8 u0 com.gluonapplication2/javafxports.android.FXActivity}
07-24 13:59:51.363  1677  1924 W ActivityManager: Force removing ActivityRecord{4eaf8ee8 u0 com.gluonapplication2/javafxports.android.FXActivity t9}: app died, no saved state

这是我的 SolitaireEvent.Java 代码。我评论了错误日志告诉的第 142 行

public class SolitaireEvent {

    Pane tempPane;
    double locationY;
    EventHandler mouseDrag;
    DataFormat cardDataFormat;
    AlertDialog alert;
    static ArrayList<Card> cardList;
    ArrayList<Pane> topPanes;

    public SolitaireEvent(Pane tempPane, double locationY, EventHandler mouseDrag, DataFormat cardDataFormat) {
        this.tempPane = tempPane;
        this.locationY = locationY;
        this.mouseDrag = mouseDrag;
        this.cardDataFormat = cardDataFormat;
    }

    public SolitaireEvent(ArrayList<Pane> topPanes, Pane tempPane, double locationY, EventHandler mouseDrag, DataFormat cardDataFormat) {
        this.topPanes = topPanes;
        this.tempPane = tempPane;
        this.locationY = locationY;
        this.mouseDrag = mouseDrag;
        this.cardDataFormat = cardDataFormat;
    }

    public SolitaireEvent(DataFormat cardDataFormat) {
        this.cardDataFormat = cardDataFormat;
    }

    public void dragDetected(Object object) {
        final Card card = (Card) object;

        card.setOnDragDetected((MouseEvent event) -> {

            // drag was detected, start drag-and-drop gesture
            System.out.println("onDragDetected");
            cardList = new ArrayList<>();

            Dragboard db = card.startDragAndDrop(TransferMode.ANY);
            for (int i = tempPane.getChildren().indexOf(card); i < tempPane.getChildren().size(); i++) {

                Card cardMove = (Card) tempPane.getChildren().get(i);
                System.out.println("cardList Source Pane: " + tempPane + " Source Card: " + cardMove.getName());
                cardList.add(cardMove);
            }
            Image [] image = new Image[cardList.size()];


                //db.setDragView(image);
            // put a string on dragboard
            ClipboardContent content = new ClipboardContent();
            content.put(cardDataFormat, cardList);

            db.setContent(content);

            event.consume();
        });
    }

    public void dragOver(Object e) {
        //System.out.println("onDragOver");

        Pane targetPane = (Pane) e;

        targetPane.setOnDragOver((DragEvent event) -> {

            // data is dragged over the target
            // accept it only if it is  not dragged from the same node
            // and if it has a string data
            if (event.getGestureSource() != targetPane
                    && event.getDragboard().hasContent(cardDataFormat)) {
                // allow for both copying and moving, whatever user chooses
                event.acceptTransferModes(TransferMode.ANY);

            }
            event.consume();
        });
    }

    public void dragEntered(Object e) {
        Pane targetPane = (Pane) e;
        targetPane.setOnDragEntered((DragEvent event) -> {
            // the drag-and-drop gesture entered the target
            System.out.println("onDragEntered");
            // show to the user that it is an actual gesture target
            if (event.getGestureSource() != targetPane && event.getDragboard().hasContent(cardDataFormat)) {
                //targetPane.setBackground(new Background(new BackgroundFill(Color.GREEN, CornerRadii.EMPTY, Insets.EMPTY)));
            }
            event.consume();
        });
    }

    public void dragExited(Object e) {
        //System.out.println("onDragExited");
        Pane target = (Pane) e;

        target.setOnDragExited((DragEvent event) -> {
            // mouse moved away, remove the graphical cues
            // target.setBackground(new Background(new BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY)));
            event.consume();
        });
    }

    public void dragDropped(Object e) {
        Pane targetPane = (Pane) e;

        targetPane.setOnDragDropped((DragEvent event) -> {
            // Get the Dragboard data
            Dragboard db = event.getDragboard();
            boolean success = false;
            // if there is an image data on dragboard, read it and use it
            if (db.hasContent(cardDataFormat)) {
                Card targetTopCard = getTargetTopCard(targetPane);
                ArrayList<Card> cardSourceList = (ArrayList<Card>) db.getContent(cardDataFormat);

                if (checkCards(cardSourceList, targetPane, targetTopCard)) {
                    // Line 142 is the next line
                    cardList.stream().forEach((sourceCard) -> {
                        dragDone(sourceCard);
                    });

                    targetTopCard = null;
                    cardList = null;
                }

            }

            // transferred and used
            event.setDropCompleted(success);
            event.consume();
        });
    }

    private boolean checkCards(ArrayList<Card> cardSourceList, Pane targetPane, Card targetTopCard) {
        boolean success = false;
        SolitaireRule solRule = new SolitaireRule();

        for (Card sourceCard : cardSourceList) {
            if (!checkTopPanes(targetPane)) {
                if (!targetPane.getChildren().isEmpty()) {
                    if (!solRule.sameColor(sourceCard.getName(), targetTopCard.getName())) {
                        if (solRule.compareRank(sourceCard.getName(), targetTopCard.getName())) {
                            locationY = getLocationY(targetPane);
                            Card card = createNewCard(sourceCard.getName());
                            targetPane.getChildren().add(card);
                            success = true;
                        } else {
                            //alert = new AlertDialog(Alert.AlertType.WARNING, "Warning", "Same Color", "The cards are the same colors.");
                            success = false;
                            break;
                        }
                    } else {
                        //alert = new AlertDialog(Alert.AlertType.WARNING, "Warning", "Same Color", "The cards are the same colors.");
                        success = false;
                        break;
                    }
                } else {
                    success =  acceptKing(targetTopCard, sourceCard, targetPane);
                }
                targetTopCard = sourceCard;
            } else {
                success = acceptAce(targetTopCard, sourceCard, targetPane);
                break;
            }
        }
        return success;
    }

    private boolean checkTopPanes(Pane targetPane) {

        boolean found = false;

        for (Pane pane : topPanes) {

            if (pane == targetPane) {
                found = true;
                System.out.println("Found Pane: " + targetPane);
                break;
            }
        }
        return found;
    }

    public void dragDone(Card sourceCard) {

        System.out.println("cardList Source Pane: " + tempPane + " cardList Source Card: " + sourceCard);
        tempPane.getChildren().remove(sourceCard);

        if (!tempPane.getChildren().isEmpty()) {
            // Flip the last
            new SolitaireAnimation().flipCard(tempPane, mouseDrag);
        }

    }

    private Card createNewCard(String cardName) {
        System.out.println("createNewCard(): Creating card...");
        Card card = new Card();
        System.out.println("createNewCard(): Setting card images...");
        card.setImage(new Image(SolitaireEvent.class.getResourceAsStream("/" + cardName + card.IMGEXT)));
        System.out.println("createNewCard(): Setting card name...");
        card.setName(cardName);
        System.out.println("createNewCard(): Setting card Event Filter...");
        card.addEventFilter(MouseDragEvent.MOUSE_PRESSED, mouseDrag);
        System.out.println("createNewCard(): Setting card location..");
        card.setLayoutY(locationY);
        System.out.println("createNewCard(): Setting card color...");
        if (cardName.endsWith("H") || cardName.endsWith("D")) {
            card.setIsRed(true);
        } else if (cardName.endsWith("S") || cardName.endsWith("C")) {
            card.setIsBlack(true);
        }
        System.out.println("createNewCard(): Returning card...");
        return card;
    }

    private boolean acceptKing(Card topCard, Card sourceCard, Pane targetPane) {
        boolean success = false;
        Card card = new Card();
        if (sourceCard.getName().substring(0, 1).equals("K")) {
            System.out.println("acceptKing()[Accepted, this is a " + sourceCard.getName().substring(0, 1) + "]");
            card = createNewCard(sourceCard.getName());
            targetPane.getChildren().add(card);
            success = true;
        }

        return success;
    }

    private boolean acceptAce(Card topCard, Card sourceCard, Pane targetPane) {
        boolean success = false;
        Card card = new Card();
        if (targetPane.getChildren().isEmpty()) {
            System.out.println("acceptAce()[Pane is empty]");
            if (sourceCard.getName().substring(0, 1).equals("A")) {
                System.out.println("acceptAce()[This is Ace]");
                card = createNewCard(sourceCard.getName());
                targetPane.getChildren().add(card);
                success = true;
            }
        } else {
            System.out.println("acceptAce()[Not Ace]");
            SolitaireRule solRule = new SolitaireRule();
            if (solRule.foundationRank(sourceCard.getName(), topCard.getName())) {
                card = createNewCard(sourceCard.getName());
                targetPane.getChildren().add(card);
                success = true;
            }
        }

        return success;
    }

    private double getLocationY(Pane targetPane) {

        double returnLocation = 0.0;
        double layoutY = 0.0;

        // Get the y location and the last card of the last index
        if (!tempPane.getChildren().isEmpty()) {
            for (Node children : targetPane.getChildren()) {
                layoutY = children.getLayoutY();
            }
        }

        // Check if the target pane is empty, if true
        // set the location of y to 0 else add 30
        if (targetPane.getChildren().isEmpty()) {
            returnLocation = 0;
        } else if (targetPane == tempPane) {
            returnLocation = layoutY;
        } else {
            returnLocation = layoutY + 30;
        }

        return returnLocation;
    }

    private Card getTargetTopCard(Pane targetPane) {
        Card topCard = null;

        // Get the y location and the last card of the last index
        if (!tempPane.getChildren().isEmpty()) {
            for (Node children : targetPane.getChildren()) {
                topCard = (Card) children;
            }
        }

        return topCard;
    }
}

错误与事件无关。

如果你查看你注释掉的代码:

cardList.stream().forEach((sourceCard) -> {
                    dragDone(sourceCard);
                });

它包含 stream,并且由于 JavaFXPorts 在 Android/iOS 上 运行,Java 7 个版本,Java 8 个流是还不支持。不过,由于 retrolambda project.

支持 Lambda 表达式

改为使用旧的 for 嵌套循环。

如果您确实需要对 Streams 的全面支持,您可以查看 streamsupport project。 Android N 也将支持流和其他 Java 8 项功能,但仍处于预览阶段。