使用WritableImage的JavaFX透明光标

时间:2016-09-25 23:01:21

标签: java javafx cursor

  

修改应答

您可以查看Fabian's answer以及此库(https://github.com/goxr3plus/JFXCustomCursor

  

实际问题

我想在JavaFX中创建一个淡出的光标,因此我正在使用WritableImage并且我不断读取原始{{1}的像素并将它们写入新的Image。然后我使用WritableImage将自定义光标设置为Scene,下面是完整代码( 尝试一下 )。

问题是需要透明像素的黑色像素。

  

请注意,以下所有类都必须包含示例

     

码(主要):

ImageCursor(writableImage)
  

代码(的 FadingCursor )(编):

package sample;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;

public class Main extends Application {

    FadingCursor fade = new FadingCursor();

    @Override
    public void start(Stage primaryStage) throws Exception {

        primaryStage.setWidth(300);
        primaryStage.setHeight(300);

        Scene scene = new Scene(new FlowPane());

        primaryStage.setScene(scene);

        fade.startFade(scene,100);

        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }

}
  

图像(需要下载)(右键单击 - >将图像另存为...):

enter image description here

2 个答案:

答案 0 :(得分:1)

您可以将每个像素的不透明度设置为一个值,具体取决于此处的循环变量:

color = new Color(color.getRed(), color.getGreen(), color.getBlue(), counter / 10.00);

对于透明像素(不透明度= 0),您实际上增加不透明度,使得存储在其他通道中的值(在这种情况下为0 /黑色)可见。您需要确保透明像素保持透明,通常这样做:

color = new Color(color.getRed(), color.getGreen(), color.getBlue(), (counter / 10.00) * color.getOpacity());

或者您可以使用deriveColor

color = color.deriveColor(0, 1, 1, counter / 10d);

修改

出于某种原因,ImageCursor似乎不喜欢完全透明的图像。如果通过添加

至少有一个像素不是完全透明,则可以检查这是否有效
pixelWriter.setColor(0, 0, new Color(0, 0, 0, 0.01));

在写入图像的for循环之后。

要解决此问题,您只需使用Cursor.NONE代替ImageCursor并使用完全透明的图片:

for (; counter >= 1; counter--) {
    ...
}
Platform.runLater(() -> scene.setCursor(Cursor.NONE));

替代方案,无需重新创建图像/光标

您可以通过在Scene的根部移动图像来自己模拟光标。这不会使图像显示超出Scene的范围,但您可以将动画应用于ImageView以进行淡化,而不是手动修改每个像素的不透明度...

public class CursorSimulator {

    private final FadeTransition fade;

    public CursorSimulator(Image image, Scene scene, ObservableList<Node> rootChildrenWriteable, double hotspotX, double hotspotY) {
        ImageView imageView = new ImageView(image);
        imageView.setManaged(false);
        imageView.setMouseTransparent(true);

        fade = new FadeTransition(Duration.seconds(2), imageView);
        fade.setFromValue(0);
        fade.setToValue(1);

        // keep image on top
        rootChildrenWriteable.addListener((Observable o) -> {
            if (imageView.getParent() != null
                    && rootChildrenWriteable.get(rootChildrenWriteable.size() - 1) != imageView) {
                // move image to top, after changes are done...
                Platform.runLater(() -> imageView.toFront());
            }
        });
        scene.addEventFilter(MouseEvent.MOUSE_ENTERED, evt -> {
            rootChildrenWriteable.add(imageView);
        });
        scene.addEventFilter(MouseEvent.MOUSE_EXITED, evt -> {
            rootChildrenWriteable.remove(imageView);
        });
        scene.addEventFilter(MouseEvent.MOUSE_MOVED, evt -> {
            imageView.setLayoutX(evt.getX() - hotspotX);
            imageView.setLayoutY(evt.getY() - hotspotY);
        });
        scene.setCursor(Cursor.NONE);
    }

    public void fadeOut() {
        fade.setRate(-1);
        if (fade.getStatus() != Animation.Status.RUNNING) {
            fade.playFrom(fade.getTotalDuration());
        }
    }

    public void fadeIn() {
        fade.setRate(1);
        if (fade.getStatus() != Animation.Status.RUNNING) {
            fade.playFromStart();
        }
    }

}
@Override
public void start(Stage primaryStage) {
    Button btn = new Button("Say 'Hello World'");
    btn.setOnAction((ActionEvent event) -> {
        System.out.println("Hello World!");
    });

    StackPane root = new StackPane();
    root.getChildren().add(btn);

    Scene scene = new Scene(root, 500, 500);

    Image image = new Image("http://i.stack.imgur.com/OHj1R.png");
    CursorSimulator simulator = new CursorSimulator(image, scene, root.getChildren(), 32, 50);
    scene.setOnMouseClicked(new EventHandler<MouseEvent>() {

        private boolean fadeOut = true;

        @Override
        public void handle(MouseEvent event) {
            if (fadeOut) {
                simulator.fadeOut();
            } else {
                simulator.fadeIn();
            }
            fadeOut = !fadeOut;
        }
    });

    primaryStage.setScene(scene);
    primaryStage.show();
}

答案 1 :(得分:0)

  

这个问题的原因是创建了一种可以的游标   例如,我想让它有一个淡入淡出效果   希望创建自定义游标的未来用户我创建了一个   github上的库,我将在这里显示一些代码:   https://github.com/goxr3plus/JFXCustomCursor

     

代码:

import javafx.application.Platform;
import javafx.beans.Observable;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;

/**
 * This class allows you to set as a Cursor in a JavaFX Scene,whatever you want
 * ,even a video!. <br>
 * <br>
 * <b>What you have to do is create a basic layout,for example:</b><br>
 * #-->A BorderPane which contains a MediaView,<br>
 * #-->A StackPane which contains an animated ImageView,<br>
 * #-->A Pane which contains an animated Rectangle or something more complex
 * etc..)<br>
 * 
 * <br>
 * <br>
 * The options are unlimited!
 * 
 * @author GOXR3PLUS
 * @param <T>
 * @Version 1.0
 */
public class JFXCustomCursor {

    private SimpleIntegerProperty hotSpotX = new SimpleIntegerProperty();
    private SimpleIntegerProperty hotSpotY = new SimpleIntegerProperty();

    private Scene scene;
    private Pane sceneRoot;
    private Pane content;
    private EventHandler<MouseEvent> eventHandler1;
    private EventHandler<MouseEvent> eventHandler2;
    private EventHandler<MouseEvent> eventHandler3;

    /**
     * Constructor
     * 
     * @param scene
     *            The Scene of your Stage
     * @param sceneRoot
     *            The Root of your Stage Scene
     * @param content
     *            The content of the JFXCustomCursor class
     * @param hotspotX
     *            Represents the location of the cursor inside the content on X
     *            axis
     * @param hotspotY
     *            Represents the location of the cursor inside the content on Y
     *            axis
     */
    public JFXCustomCursor(Scene scene, Pane sceneRoot, Pane content, int hotspotX, int hotspotY) {

        // Go
        setRoot(scene, sceneRoot, content, hotspotX, hotspotY);

    }

    /**
     * This method changes the content of the JFXCustomCursor
     * 
     * @param scene
     *            The Scene of your Stage
     * @param sceneRoot
     *            The Root of your Stage Scene
     * @param content
     *            The content of the JFXCustomCursor class
     * @param hotspotX
     *            Represents the location of the cursor inside the content on X
     *            axis
     * @param hotspotY
     *            Represents the location of the cursor inside the content on Y
     *            axis
     */
    public void setRoot(Scene scene, Pane sceneRoot, Pane content, int hotSpotX, int hotSpotY) {

        // Keep them in case of unRegister-reRegister
        unRegister(); // has to be called before the below happens
        this.scene = scene;
        this.sceneRoot = sceneRoot;
        this.content = content;

        // hot spots
        this.hotSpotX.set(hotSpotX);
        this.hotSpotX.set(hotSpotY);

        // cursor container
        content.setManaged(false);
        content.setMouseTransparent(true);

        // Keep the Content on the top of Scene
        ObservableList<Node> observable = sceneRoot.getChildren();
        observable.addListener((Observable osb) -> {
            if (content.getParent() != null && observable.get(observable.size() - 1) != content) {
                // move the cursor on the top
                Platform.runLater(content::toFront);
            }
        });

        if (!observable.contains(content))
            observable.add(content);

        // Add the event handlers
        eventHandler1 = evt -> {
            if (!sceneRoot.getChildren().contains(content))
                observable.add(content);
        };
        eventHandler2 = evt -> observable.remove(content);

        eventHandler3 = evt -> {
            content.setLayoutX(evt.getX() - hotSpotX);
            content.setLayoutY(evt.getY() - hotSpotY);
        };

        scene.addEventFilter(MouseEvent.MOUSE_ENTERED, eventHandler1);
        scene.addEventFilter(MouseEvent.MOUSE_EXITED, eventHandler2);
        scene.addEventFilter(MouseEvent.MOUSE_MOVED, eventHandler3);

    }

    /**
     * Unregisters the CustomCursor from the Scene completely
     */
    public void unRegister() {
        if (scene != null) {
            sceneRoot.getChildren().remove(content);
            scene.removeEventFilter(MouseEvent.MOUSE_ENTERED, eventHandler1);
            scene.removeEventFilter(MouseEvent.MOUSE_EXITED, eventHandler2);
            scene.removeEventFilter(MouseEvent.MOUSE_MOVED, eventHandler3);
        }
    }

    /**
     * Re register the CustomCursor to the Scene,<b>this method is
     * experimental(use with caution!)</b>
     */
    public void reRegister() {
        if (scene != null)
            setRoot(scene, sceneRoot, content, hotSpotX.get(), hotSpotY.get());
    }

    public SimpleIntegerProperty hotSpotXProperty() {
        return hotSpotX;
    }

    public SimpleIntegerProperty hotSpotYProperty() {
        return hotSpotY;
    }

}