JavaFx: 滑动条 - 双向带颜色移动

5

背景:

我正在尝试创建一个实时股票走势的可视化表示,就像这两个附加的图像一样(绿色表示正值,琥珀色表示负值,左下角显示当天最低价值,右下角显示当天最高价值),并将其放置在JavaFx的TableView中。该运动将受到实时数据流的控制。因此,如果滑动标记越过了当天的开盘价值,它需要能够双向滑动并更改颜色。

研究:

我总是参考这个非常好的链接-> 链接,但在这种情况下,我不确定是否可以使用ProgressBar或Slider进行操作。

如果有任何关于如何实现这一点的指针,将不胜感激。

正值

负值


不确定是否可以通过“ProgressBar”实现此功能,但我确定您可以使用“Line”形状自己制作一个。 - Muzib
我认为你可以只用一个滑块来完成这个任务。使用CSS和线性渐变(类似于transparent 0%,transparent x%,green x%,green y%,transparent y%,transparent 100%)。使用内联样式的CSS,并在值更改时更新它。 - James_D
2个回答

3
一个可能的解决方案是将两个ProgressBar打包到一个HBox中。
下面的示例不应被视为完整的实现,而应视为这个想法的演示:

import javafx.application.Application;
import javafx.beans.binding.DoubleBinding;
import javafx.beans.binding.When;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import src.tests.BiDiProgressBar.Indicator;

public class BiDiProgressBar extends Application {

    @Override public void start(Stage stage) {

        final HBox indicator = new Indicator(60);
        stage.setScene(new Scene(indicator));
        stage.show();

        //run test
        Thread th = new Thread(new TestTask((Indicator)indicator));
        th.setDaemon(true);
        th.start();
    }


    class Indicator extends HBox{

        //properties representing indicator min max, mid and current values
        DoubleProperty minValue = new SimpleDoubleProperty(0);
        DoubleProperty maxValue = new SimpleDoubleProperty(100);
        DoubleProperty nominalValue = new SimpleDoubleProperty(50);
        DoubleProperty currentlValue;

        //properties representing full, neg and pos ranges
        DoubleProperty rangeValue = new SimpleDoubleProperty();
        DoubleProperty posRangeValue = new SimpleDoubleProperty();
        DoubleProperty negRangeValue = new SimpleDoubleProperty();

        private ProgressBar negProgBar, posProgBar;
        private static final double indicatorWidth = 200; //in pixles

        Indicator(double nomlValue){

            super(0);
            setNominalValue(nomlValue);
            rangeValue.bind(maxValue.subtract(minValue));
            negRangeValue.bind(nominalValue.subtract(minValue));
            posRangeValue.bind(rangeValue.subtract(negRangeValue));
            currentlValue = new SimpleDoubleProperty(nominalValue.get());

            negProgBar = new ProgressBar();
            negProgBar.rotateProperty().set(180); //rotate to progress from left to right

            posProgBar = new ProgressBar();
            setProgBarWidth();
            setProgBarValue();
            getChildren().addAll(negProgBar, posProgBar);

            getStylesheets().add(getClass().getResource("css/indicator.css").toExternalForm());
            negProgBar.getStyleClass().add("neg-bar");
            posProgBar.getStyleClass().add("pos-bar");
        }

        private void setNominalValue(double nominalValue) {
            nominalValue = withInRange(nominalValue);
            this.nominalValue.set(nominalValue);
        }

        private void setProgBarWidth() {
            double width = (indicatorWidth * nominalValue.get())/rangeValue.get();
            negProgBar.setPrefWidth(width);
            posProgBar.setPrefWidth(indicatorWidth-width);
        }

        private void setProgBarValue() {

            final DoubleBinding posBinding = new When(currentlValue.greaterThan(nominalValue))
                        .then((currentlValue.subtract(nominalValue)).divide(posRangeValue))
                        .otherwise(0.);
            final DoubleBinding negBinding = new When(currentlValue.lessThan(nominalValue))
                    .then((nominalValue.subtract(currentlValue)).divide(negRangeValue))
                    .otherwise(0.);
            posProgBar.progressProperty().bind(posBinding);
            negProgBar.progressProperty().bind(negBinding);
        }

        private double withInRange(double value) {
            value = (value < minValue.get()) ? minValue.get() : value;
            value = (value > maxValue.get()) ? maxValue.get() : value;
            return value ;
        }

        public double getMin() {return minValue.get();}

        public double getMax() {return maxValue.get();}

        public double getNominal() {return nominalValue.get();}

        public void setProgress(double value) {
            currentlValue.set(withInRange(value));
        }
    }

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

class TestTask extends Task<Void>{

    private Indicator indicator;
    public TestTask(Indicator indicator) {
        this.indicator = indicator;
    }

    @Override
    protected Void call() throws Exception {

        for(double value = indicator.getNominal();
                value < (indicator.getMax() +5) ; //exceed max
                value++)    {
            indicator.setProgress(value);
            delay(100);
        }

        for(double value = indicator.getMax();
                value > (indicator.getMin() -5 ) ;  value--)    {
            indicator.setProgress(value);
            delay(100);
        }

        return null;
    }

    private void delay(long millis) {

        try {
            Thread.sleep(millis);
        } catch (InterruptedException ex) { ex.printStackTrace();   }
    }
}

结果如下:

结果如下:

enter image description here

两个 ProgressBar 相遇的点表示某天的起始值。 在最终实现中,任何时候只应该有一个进度条处于活动状态。

indicator.css

.root { -fx-background-color: black; -fx-padding: 15; }
.progress-bar > .track {
  -fx-text-box-border: white;
  -fx-control-inner-background: lightgrey;
  -fx-border-radius: 0px ;
  -fx-background-radius: 0px ;
}

.pos-bar  { -fx-accent: green; }
.neg-bar  { -fx-accent: red; }

2

您可以使用滑块或进度条来实现这一点,使用CSS更改轨道的背景颜色。对于进度条,只需使其透明即可。

基本思路是使用线性“渐变”。如果起始值为x%,当前值为y%,其中y>x,则需要在以下位置设置颜色停止:

(default 0%, default x%, green x%, green y%, default y%, default 100%)

其中default是默认的背景颜色。(同样地,如果y < x,则用红色替换绿色,xy交换。)

下面是SSCCE示例:

import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.Slider;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class BidirectionalSlider extends Application {

    @Override
    public void start(Stage primaryStage) {
        double startingValue = 0.5 ;
        Slider slider = new Slider(0, 1, startingValue);

        slider.styleProperty().bind(Bindings.createStringBinding(() -> {

            double min = slider.getMin();
            double max = slider.getMax();
            double value = slider.getValue() ;

            return createSliderStyle(startingValue, min, max, value);

        }, slider.valueProperty()));

        ProgressBar progressBar1 = new ProgressBar(0.1);
        ProgressBar progressBar2 = new ProgressBar(0.9);

        progressBar1.styleProperty().bind(Bindings.createStringBinding(() -> 
            createSliderStyle(startingValue, 0.0, 1.0, progressBar1.getProgress()), 
            progressBar1.progressProperty()));

        progressBar2.styleProperty().bind(Bindings.createStringBinding(() -> 
            createSliderStyle(startingValue, 0.0, 1.0, progressBar2.getProgress()), 
            progressBar2.progressProperty()));

        VBox root = new VBox(5, slider, progressBar1, progressBar2);
        root.setAlignment(Pos.CENTER);

        Scene scene = new Scene(root, 400, 400);
        scene.getStylesheets().add("style.css");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private String createSliderStyle(double startingValue, double min, double max, double value) {
        StringBuilder gradient = new StringBuilder("-slider-track-color: ");
        String defaultBG = "derive(-fx-control-inner-background, -5%) " ;
        gradient.append("linear-gradient(to right, ").append(defaultBG).append("0%, ") ;

        double valuePercent = 100.0 * (value - min) / (max - min);

        double startingValuePercent = startingValue * 100.0;


        if (valuePercent > startingValuePercent) {
            gradient.append(defaultBG).append(startingValuePercent).append("%, ");
            gradient.append("green ").append(startingValuePercent).append("%, ");
            gradient.append("green ").append(valuePercent).append("%, ");
            gradient.append(defaultBG).append(valuePercent).append("%, ");
            gradient.append(defaultBG).append("100%); ");
        } else {
            gradient.append(defaultBG).append(valuePercent).append("%, ");
            gradient.append("red ").append(valuePercent).append("%, ");
            gradient.append("red ").append(startingValuePercent).append("%, ");
            gradient.append(defaultBG).append(startingValuePercent).append("%, ");
            gradient.append(defaultBG).append("100%); ");               
        }
        return gradient.toString() ;
    }

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

还有基础的 CSS 文件 (style.css):

.slider, .progress-bar {
    -slider-track-color: derive(-fx-control-inner-background, -5%) ;
}

.slider .track, .progress-bar .track {
    -fx-background-color: 
        -fx-shadow-highlight-color,
        linear-gradient(to bottom, derive(-fx-text-box-border, -10%), -fx-text-box-border),
        -slider-track-color ;
}

.progress-bar .bar {
    -fx-background-color: transparent ;
}

enter image description here enter image description here


如果你真的喜欢进度条,你可以使用相同的CSS技巧来处理进度条的轨迹(将min设置为0.0,将max设置为1.0,将进度设置为value),然后只需使进度条本身透明即可。 - James_D
先生,谢谢。 偏向滑块的方法。 请澄清一下,是否可以将滑块中的圆圈更改为我最初发布的图片中所见的泪滴形状? - iCoder
找到了这个链接,它展示了如何更改图标图片 - https://dev59.com/LmvXa4cB1Zd3GeqPFQsb - iCoder

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接