You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JavaFX实现带正负掩码、步长0.25的Spinner及输入限制

JavaFX Spinner 实现指定格式与范围控制

需求回顾

需要实现一个Spinner组件,满足以下要求:

  • 仅接受0.25的倍数数值
  • 显示带正负掩码的格式(如 -1,50、+1,50)
  • 保留两位小数,使用逗号作为小数分隔符
  • 取值范围限制在 -20.00 到 20.00 之间
  • Spinner的编辑TextField需遵循相同规则

解决方案实现

要适配Spinner,需要结合自定义SpinnerValueFactory、格式化转换器和TextFormatter过滤逻辑三部分,以下是完整实现:

1. 自定义BigDecimal转换器

重写BigDecimalStringConverter,实现正数自动添加+号,同时统一使用逗号作为小数分隔符:

public static class FormattedBigDecimalConverter extends BigDecimalStringConverter {
    @Override
    public String toString(BigDecimal value) {
        if (value == null) return "+0,00";
        // 格式化时保留两位小数,正数添加+号,用逗号分隔小数
        DecimalFormat df = new DecimalFormat("+#0.00;-#0.00");
        df.setDecimalFormatSymbols(new DecimalFormatSymbols(Locale.GERMAN)); // 使用逗号作为小数分隔符
        return df.format(value);
    }

    @Override
    public BigDecimal fromString(String value) {
        if (value == null || value.isEmpty()) return BigDecimal.ZERO;
        // 替换逗号为点,兼容解析
        return super.fromString(value.replace(",", "."));
    }
}

2. 自定义SpinnerValueFactory

控制步进为0.25,限制取值范围,同时确保每次调整后的值是0.25的倍数:

public static class StepBigDecimalSpinnerValueFactory extends SpinnerValueFactory<BigDecimal> {
    private final BigDecimal min;
    private final BigDecimal max;
    private final BigDecimal step;

    public StepBigDecimalSpinnerValueFactory(BigDecimal min, BigDecimal max, BigDecimal initialValue, BigDecimal step) {
        this.min = min;
        this.max = max;
        this.step = step;
        setValue(initialValue);

        // 处理递增逻辑
        setIncrement(() -> {
            BigDecimal newValue = getValue().add(step);
            setValue(newValue.compareTo(max) > 0 ? max : newValue);
        });

        // 处理递减逻辑
        setDecrement(() -> {
            BigDecimal newValue = getValue().subtract(step);
            setValue(newValue.compareTo(min) < 0 ? min : newValue);
        });
    }

    // 确保值始终是0.25的倍数
    @Override
    public void setValue(BigDecimal value) {
        if (value != null) {
            // 四舍五入到最近的0.25倍数
            BigDecimal rounded = value.divide(new BigDecimal("0.25"), 0, RoundingMode.HALF_UP)
                                      .multiply(new BigDecimal("0.25"));
            // 限制在范围内
            BigDecimal clamped = rounded.max(min).min(max);
            super.setValue(clamped);
        }
    }
}

3. 配置Spinner并绑定TextFormatter

将转换器、值工厂和TextFormatter整合到Spinner中,复用输入过滤逻辑:

public class TestPane extends BorderPane {
    private final ObjectProperty<BigDecimal> valueProperty = new SimpleObjectProperty<>(BigDecimal.ZERO);

    public TestPane() {
        // 配置Spinner
        Spinner<BigDecimal> spinner = new Spinner<>();
        StepBigDecimalSpinnerValueFactory valueFactory = new StepBigDecimalSpinnerValueFactory(
                new BigDecimal("-20.00"),
                new BigDecimal("20.00"),
                BigDecimal.ZERO,
                new BigDecimal("0.25")
        );
        spinner.setValueFactory(valueFactory);

        // 绑定值到外部属性
        valueProperty.bindBidirectional(valueFactory.valueProperty());

        // 配置编辑器的TextFormatter
        FormattedBigDecimalConverter converter = new FormattedBigDecimalConverter();
        TextFormatter<BigDecimal> textFormatter = new TextFormatter<>(converter, BigDecimal.ZERO, c -> {
            if (!c.getControl().isFocused()) return null;

            String newText = c.getControlNewText();
            // 允许空值、单独的+/-号
            if (newText.isEmpty() || newText.equals("+") || newText.equals("-")) {
                return c;
            }
            // 允许光标在符号位时的编辑
            if ((newText.startsWith("-") && c.getControlCaretPosition() == 0) ||
                (newText.startsWith("+") && c.getControlCaretPosition() == 0)) {
                return c;
            }

            // 尝试解析输入,验证有效性
            try {
                BigDecimal parsed = converter.fromString(newText);
                // 提前验证范围(失去焦点后会再次修正)
                if (parsed.compareTo(new BigDecimal("-20.00")) < 0 || parsed.compareTo(new BigDecimal("20.00")) > 0) {
                    return null;
                }
                return c;
            } catch (NumberFormatException e) {
                return null;
            }
        });

        // 编辑器失去焦点时自动修正格式
        spinner.getEditor().focusedProperty().addListener((obs, oldVal, newVal) -> {
            if (!newVal) {
                textFormatter.setValue(textFormatter.getValue()); // 触发转换器重新格式化
            }
        });

        spinner.getEditor().setTextFormatter(textFormatter);
        // 绑定值工厂与TextFormatter
        valueFactory.valueProperty().bindBidirectional(textFormatter.valueProperty());

        // 添加到界面
        setCenter(new VBox(10,
                new HBox(6, new Text("自定义Spinner"), spinner),
                new HBox(6, new Text("当前值"), new Label(valueProperty.asString()))
        ));
    }
}

关键说明

  • 转换器负责数值与字符串的双向转换,确保显示格式符合要求
  • SpinnerValueFactory控制步进、范围和值的合法性(0.25倍数)
  • TextFormatter过滤非法输入,同时在失去焦点时自动修正显示格式
  • 所有数值计算使用BigDecimal避免浮点数精度问题

内容的提问来源于stack exchange,提问作者kvothe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 16:27:36