【问题标题】:JavaFx Slider thumb listenerJavaFx Slider 拇指监听器
【发布时间】:2021-11-08 04:46:11
【问题描述】:

我正在尝试使用 java fx 滑块创建媒体播放器。我想将播放按钮与滑块拇指结合起来。因此,当用户单击滑块的拇指时,它将激活媒体播放器进行播放,并且在播放时滑块将相应移动。从文档中,侦听器用于滑块而不是滑块的拇指。有什么建议或想法如何实现监听器?

【问题讨论】:

  • 不知道为什么要重新打开它,它仍然太宽泛了 ;) 您想要究竟实现什么?您指的是哪些听众,确切地?做一些研究(研究样式以及如何从其样式类中查找节点),应用您学到的知识,然后返回 minimal reproducible example 以证明没有按预期工作。

标签: java javafx slider


【解决方案1】:

我不认为 Slider 控件是为此目的而实现的,这可能是可能的,但需要大量的黑客攻击,为什么你必须这样限制自己?

我建议实现一个简单的自定义控件并将其样式设置为看起来像滑块,或者更好的是您可以在拇指内添加一个图标,以指示状态(播放/暂停),您基本上可以用它做任何您想做的事情.

我编写了以下示例,它使用 StackPane 作为父级,然后将 StackPanes 用于子级(全宽栏和进度条,也是拇指):

我将类命名为 MediaSlider,它扩展了 StackPane

import java.util.function.Consumer;

import javafx.beans.binding.DoubleBinding;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.geometry.Pos;
import javafx.scene.Cursor;
import javafx.scene.effect.DropShadow;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;

public class MediaSlider extends StackPane {
    private String main = "#6200ee";
    private String sec = "#c6aee6";
    
    private DoubleProperty value;

    private StackPane thumb;

    private ImageView icon;
    private Image playImage, pauseImage;
    private Runnable onThumbClick;

    private boolean dragged = false;
    
    private Consumer<Double> onSlide;
    private double seek;
    
    public MediaSlider() {
        setAlignment(Pos.CENTER_LEFT);
        setMaxHeight(50);

        value = new SimpleDoubleProperty(0);

        StackPane bar = createRect(sec);
        bar.maxWidthProperty().bind(widthProperty());

        StackPane fill = createRect(main);
        fill.maxWidthProperty()
                .bind(widthProperty().subtract(heightProperty()).multiply(value).add(heightProperty().divide(2)));

        thumb = new StackPane();
        thumb.setMaxWidth(Region.USE_PREF_SIZE);
        thumb.setMinWidth(Region.USE_PREF_SIZE);
        thumb.prefWidthProperty().bind(heightProperty());
        thumb.setCursor(Cursor.HAND);

        Circle circle = new Circle();
        circle.radiusProperty().bind(heightProperty().divide(2));
        circle.setFill(Color.web(main));

        playImage = new Image(getClass().getResourceAsStream("/play.png"));
        pauseImage = new Image(getClass().getResourceAsStream("/pause.png"));

        icon = new ImageView(playImage);
        icon.fitWidthProperty().bind(thumb.widthProperty().divide(2));
        icon.fitHeightProperty().bind(thumb.heightProperty().divide(2));

        thumb.getChildren().addAll(circle, icon);
        thumb.setEffect(new DropShadow(10, Color.gray(.0, .4)));
        
        DoubleBinding thumbPos = widthProperty().subtract(thumb.widthProperty()).multiply(value);
        
        thumb.translateXProperty().bind(thumbPos);

        thumb.setOnMousePressed(e -> {
            dragged = false;
            thumb.translateXProperty().unbind();
        });
        
        thumb.setOnMouseDragged(e -> {
            double preX = thumb.getTranslateX();
            double x = e.getX() - getHeight() / 2 + preX;
            seek = x / (getWidth() - getHeight());
            seek = seek > 1 ? 1 : seek < 0 ? 0 : seek;
            thumb.setTranslateX(Math.max(0, Math.min(getWidth() - getHeight(), x)));
            dragged = true;
        });
        
        thumb.setOnMouseReleased(e -> {
            if(dragged) {
                if(onSlide != null) {
                    onSlide.accept(seek);
                }
            }else if(onThumbClick != null) {
                onThumbClick.run();
            }
            thumb.translateXProperty().bind(thumbPos);
        });

        getChildren().addAll(bar, fill, thumb);

    }

    public void setOnSlide(Consumer<Double> onSlide) {
        this.onSlide = onSlide;
    }
    
    public void setOnThumbClick(Runnable onThumbClick) {
        this.onThumbClick = onThumbClick;
    }

    public void pause() {
        icon.setImage(playImage);
    }

    public void play() {
        icon.setImage(pauseImage);
    }

    public void setValue(double value) {
        this.value.set(value);
    }

    public double getValue() {
        return value.get();
    }

    public DoubleProperty valueProperty() {
        return value;
    }

    private StackPane createRect(String color) {
        StackPane res = new StackPane();
        res.maxHeightProperty().bind(heightProperty().divide(5));
        res.setStyle("-fx-background-color: " + color + ";-fx-background-radius: 5;");
        return res;
    }
}

然后在app中使用如下方式

import java.io.IOException;
import java.net.URISyntaxException;

import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.scene.media.MediaPlayer.Status;
import javafx.stage.Stage;
import javafx.util.Duration;

public class App extends Application {

    @Override
    public void start(Stage stage) throws IOException, URISyntaxException {
        StackPane root = new StackPane();
        root.setPadding(new Insets(20));
        root.setStyle("-fx-background-color: #e5e5e5");

        // load media
        MediaPlayer player = new MediaPlayer(new Media(getClass().getResource("/song.mp3").toURI().toString()));

        player.setOnReady(() -> {
            player.play();
            player.pause();
        });
        
        // instantiate your custom slider class
        MediaSlider s = new MediaSlider();

        // bind slider position to media position
        s.valueProperty().bind(Bindings.createDoubleBinding(() -> {
            double val = player.getCurrentTime().toSeconds() / player.getTotalDuration().toSeconds();
            return Double.isNaN(val) ? .0 : val;
        }, player.currentTimeProperty()));

        // handle click events
        s.setOnThumbClick(() -> {
            if (player.getStatus().equals(Status.PLAYING)) {
                s.pause();
                player.pause();
                player.seek(player.getCurrentTime());
            } else {
                s.play();
                player.play();
            }
        });

        //handle mouse release after sliding by setting the current time
        s.setOnSlide(v -> {
            player.seek(Duration.seconds(player.getTotalDuration().toSeconds() * v));
        });

        // replay on end of media
        player.setOnEndOfMedia(() -> player.seek(Duration.ZERO));

        root.getChildren().add(s);

        Scene scene = new Scene(root, 600, 300);
        stage.setScene(scene);
        stage.show();
    }

}

这是它的外观:

【讨论】:

  • 您好!感谢您的建议。我获得了一些见解。使用这个自定义滑块,我将无法实现一些功能,例如沿着轨道拖动滑块?
  • 将需要大量的黑客攻击并不是真的 - 所需要的只是抓住拇指(令人惊讶的是,它的样式类没有在 css 参考中指定,所以它是依赖它是“拇指”有点笨拙)。其他一切都是简单的 fx 基础 a) 使用取决于不/播放的图像的样式 b) 安装事件处理程序以播放/暂停播放器 c) 滑块值与播放器 currentTime 的适当同步
  • @kleopatra 这是一个非常通用的声明,关于击败 javafx 控件来做一些它没有实现的事情,而实现你需要的事情可以更直接
  • @qt.qt 您必须手动添加,我相信您可以处理拇指上的鼠标拖动事件,将尽快将其添加到我的答案中
  • @qt.qt 我已经更新了我的答案以支持滑动滑块
猜你喜欢
  • 1970-01-01
  • 2015-11-01
  • 1970-01-01
  • 2019-08-16
  • 2018-10-28
  • 1970-01-01
  • 2013-11-15
  • 2016-10-12
  • 1970-01-01
相关资源
最近更新 更多