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
相关产品推荐
相关产品推荐

