Java JSlider精度问题

我有一个N JSliders的列表(N不会在程序上改变,只是在我添加更多function时。目前N等于4)。 所有滑块值的总和必须等于100.当一个滑块移动时,其余的滑块应进行调整。 每个滑块的值范围为0到100。

目前我在更改滑块时使用此逻辑(伪代码):

newValue = currentSlider.getValue otherSliders = allSliders sans currentSlider othersValue = summation of otherSliders values properOthersValue = 100 - newValue ratio = properOthersValue / othersValue for slider in otherSlider slider.value = slider.getValue * ratio 

此设置的问题是滑块的值存储为整数。 因此,当我调整滑块时,我会遇到精度问题:根据比率值,滑块会根据或不移动。 此外,总价值并不总是达到100。

有没有人有一个解决这个问题的方法而不创建一个支持浮点数或双打的全新JSlider类?

如果您想要我想要的行为示例,请访问: Humble Indie Bundle并滚动到页面底部。

谢谢

ps将值乘以比率允许用户将值“锁定”为0.但是,当4个滑块中的3个为0且第4个滑块为100且我移动第4个滑块时,我不确定该怎么办下。 使用上面的逻辑,3个滑块以0为值保持不变,第4个滑块移动到用户放置的位置,这使得总数小于100,这是不正确的行为。

编辑

这是SSCCE:

 import javax.swing.*; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import java.awt.*; import java.util.LinkedList; public class SliderDemo { static LinkedList sliders = new LinkedList(); static class SliderListener implements ChangeListener { boolean updating = false; public void stateChanged(ChangeEvent e) { if (updating) return; updating = true; JSlider source = (JSlider)e.getSource(); int newValue = source.getValue(); LinkedList otherSliders = new LinkedList(sliders); otherSliders.remove(source); int otherValue = 0; for (JSlider slider : otherSliders) { otherValue += slider.getValue(); } int properValue = 100 - newValue; double ratio = properValue / (double)otherValue; for (JSlider slider : otherSliders) { int currentValue = slider.getValue(); int updatedValue = (int) (currentValue * ratio); slider.setValue(updatedValue); } int total = 0; for (JSlider slider : sliders) { total += slider.getValue(); } System.out.println("Total = " + total); updating = false; } } public static void main(String[] args) { JFrame frame = new JFrame("SliderDemo"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); Container container = frame.getContentPane(); JPanel sliderPanel = new JPanel(new GridBagLayout()); container.add(sliderPanel); SliderListener listener = new SliderListener(); GridBagConstraints gbc = new GridBagConstraints(); gbc.gridx = 0; int sliderCount = 4; int initial = 100 / sliderCount; for (int i = 0; i < sliderCount; i++) { gbc.gridy = i; JSlider slider = new JSlider(0, 100, initial); slider.addChangeListener(listener); slider.setMajorTickSpacing(50); slider.setPaintTicks(true); sliders.add(slider); sliderPanel.add(slider, gbc); } frame.pack(); frame.setVisible(true); } } 

为什么不让JSlider模型的粒度从0到1000000变得更精细,并且总和为1000000? 使用LabelTable的正确Dictionary ,用户可能不会知道它不会从0到100。

例如:

 import java.awt.Dimension; import java.awt.GridLayout; import java.util.ArrayList; import java.util.Dictionary; import java.util.Hashtable; import java.util.List; import javax.swing.*; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; @SuppressWarnings("serial") public class LinkedSliders2 extends JPanel { private static final int SLIDER_COUNT = 5; public static final int SLIDER_MAX_VALUE = 1000; private static final int MAJOR_TICK_DIVISIONS = 5; private static final int MINOR_TICK_DIVISIONS = 20; private static final int LS_WIDTH = 700; private static final int LS_HEIGHT = 500; private JSlider[] sliders = new JSlider[SLIDER_COUNT]; private SliderGroup2 sliderGroup = new SliderGroup2(SLIDER_MAX_VALUE); public LinkedSliders2() { Dictionary myDictionary = new Hashtable(); for (int i = 0; i <= MAJOR_TICK_DIVISIONS; i++) { Integer key = i * SLIDER_MAX_VALUE / MAJOR_TICK_DIVISIONS; JLabel value = new JLabel(String.valueOf(i * 100 / MAJOR_TICK_DIVISIONS)); myDictionary.put(key, value); } setLayout(new GridLayout(0, 1)); for (int i = 0; i < sliders.length; i++) { sliders[i] = new JSlider(0, SLIDER_MAX_VALUE, SLIDER_MAX_VALUE / SLIDER_COUNT); sliders[i].setLabelTable(myDictionary ); sliders[i].setMajorTickSpacing(SLIDER_MAX_VALUE / MAJOR_TICK_DIVISIONS); sliders[i].setMinorTickSpacing(SLIDER_MAX_VALUE / MINOR_TICK_DIVISIONS); sliders[i].setPaintLabels(true); sliders[i].setPaintTicks(true); sliders[i].setPaintTrack(true); sliderGroup.addSlider(sliders[i]); add(sliders[i]); } } @Override public Dimension getPreferredSize() { return new Dimension(LS_WIDTH, LS_HEIGHT); } private static void createAndShowGui() { LinkedSliders2 mainPanel = new LinkedSliders2(); JFrame frame = new JFrame("LinkedSliders"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.getContentPane().add(mainPanel); frame.pack(); frame.setLocationByPlatform(true); frame.setVisible(true); } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { createAndShowGui(); } }); } } class SliderGroup2 { private List sliderModelList = new ArrayList(); private ChangeListener changeListener = new SliderModelListener(); private int maxValueSum; public SliderGroup2(int maxValueSum) { this.maxValueSum = maxValueSum; } public void addSlider(JSlider slider) { BoundedRangeModel model = slider.getModel(); sliderModelList.add(model); model.addChangeListener(changeListener); } private class SliderModelListener implements ChangeListener { private boolean internalChange = false; @Override public void stateChanged(ChangeEvent cEvt) { if (!internalChange) { internalChange = true; BoundedRangeModel sourceModel = (BoundedRangeModel) cEvt.getSource(); int sourceValue = sourceModel.getValue(); int oldSumOfOtherSliders = 0; for (BoundedRangeModel model : sliderModelList) { if (model != sourceModel) { oldSumOfOtherSliders += model.getValue(); } } if (oldSumOfOtherSliders == 0) { for (BoundedRangeModel model : sliderModelList) { if (model != sourceModel) { model.setValue(1); } } internalChange = false; return; } int newSumOfOtherSliders = maxValueSum - sourceValue; for (BoundedRangeModel model : sliderModelList) { if (model != sourceModel) { long newValue = ((long) newSumOfOtherSliders * model .getValue()) / oldSumOfOtherSliders; model.setValue((int) newValue); } } int total = 0; for (BoundedRangeModel model : sliderModelList) { total += model.getValue(); } //!! System.out.printf("Total = %.0f%n", (double)total * 100 / LinkedSliders2.SLIDER_MAX_VALUE); internalChange = false; } } } } 

编辑让SliderGroup2使用BoundedRangeModel而不是JSliders。

滑块将根据比率值抽搐或根本不移动。

HumbleBundle也有同样的问题。 如果您通过键盘移动滑块,则更改仅为1,这意味着它只会转到第一个滑块。 所以你的比率最终会失去同步。

此外,总价值并不总是达到100。

所以你需要进行舍入检查。 如果它没有添加到100,那么您需要确定错误发生的位置。 也许最后一个滑块给出了上述问题?

当4个滑块中的3个处于0且第4个滑块处于100并且我向下移动第4个滑块时,我不确定该怎么做。

HumbleBundle处理它的方式是移动所有切片器。 但是它只允许您以3的增量向下移动滑块,这样您可以将3个滑块中的每一个增加1。

甚至HumbleBundle的实施也并不完美。

借用一些气垫船解决方案,我提出了一种不同的方法。 这种方法的基础是在滑块移动时跟踪“其他滑块”值。 只要您继续滑动同一个滑块,冻结值就会用于计算新值。 然后将任何舍入差异顺序地应用于每个滑块,直到差异用完为止。 使用此方法,您可以将滑块中的增量更改均匀地应用于所有其他滑块。

模型中的值是滑块的实际值,您还可以使用键盘调整滑块:

 import java.awt.*; import java.awt.GridLayout; import java.util.ArrayList; import java.util.List; import javax.swing.*; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; public class SliderGroup implements ChangeListener { private List sliders = new ArrayList(); private int groupSum; private boolean internalChange = false; private JSlider previousSlider; private List otherSliders = new ArrayList(); public SliderGroup(int groupSum) { this.groupSum = groupSum; } public void addSlider(JSlider slider) { sliders.add(slider); slider.addChangeListener(this); } @Override public void stateChanged(ChangeEvent e) { if (internalChange) return; internalChange = true; JSlider sourceSlider = (JSlider)e.getSource(); if (previousSlider != sourceSlider) { setupForSliding(sourceSlider); previousSlider = sourceSlider; } int newSumOfOtherSliders = groupSum - sourceSlider.getValue(); int oldSumOfOtherSliders = 0; for (SliderInfo info : otherSliders) { JSlider slider = info.getSlider(); if (slider != sourceSlider) { oldSumOfOtherSliders += info.getValue(); } } int difference = newSumOfOtherSliders - oldSumOfOtherSliders; if (oldSumOfOtherSliders == 0) { resetOtherSliders( difference / otherSliders.size() ); allocateDifference(difference % otherSliders.size(), true); internalChange = false; return; } double ratio = (double)newSumOfOtherSliders / oldSumOfOtherSliders; for (SliderInfo info : otherSliders) { JSlider slider = info.getSlider(); int oldValue = info.getValue(); int newValue = (int)Math.round(oldValue * ratio); difference += oldValue - newValue; slider.getModel().setValue( newValue ); } if (difference != 0) { allocateDifference(difference, false); } internalChange = false; } private void allocateDifference(int difference, boolean adjustZeroValue) { while (difference != 0) { for (SliderInfo info : otherSliders) { if (info.getValue() != 0 || adjustZeroValue) { JSlider slider = info.getSlider(); if (difference > 0) { slider.getModel().setValue( slider.getValue() + 1 ); difference--; } if (difference < 0) { slider.getModel().setValue( slider.getValue() - 1 ); difference++; } } } } } private void resetOtherSliders(int resetValue) { for (SliderInfo info : otherSliders) { JSlider slider = info.getSlider(); slider.getModel().setValue( resetValue ); } } private void setupForSliding(JSlider sourceSlider) { otherSliders.clear(); for (JSlider slider: sliders) { if (slider != sourceSlider) { otherSliders.add( new SliderInfo(slider, slider.getValue() ) ); } } } class SliderInfo { private JSlider slider; private int value; public SliderInfo(JSlider slider, int value) { this.slider = slider; this.value = value; } public JSlider getSlider() { return slider; } public int getValue() { return value; } } private static JPanel createSliderPanel(int groupSum, int sliderCount) { int sliderValue = groupSum / sliderCount; SliderGroup sg = new SliderGroup(groupSum); JPanel panel = new JPanel( new BorderLayout() ); JPanel sliderPanel = new JPanel( new GridLayout(0, 1) ); panel.add(sliderPanel, BorderLayout.CENTER); JPanel labelPanel = new JPanel( new GridLayout(0, 1) ); panel.add(labelPanel, BorderLayout.EAST); for (int i = 0; i < sliderCount; i++) { JLabel label = new JLabel(); label.setText( Integer.toString(sliderValue) ); labelPanel.add( label ); JSlider slider = new JSlider(0, groupSum, sliderValue); slider.setMajorTickSpacing(25); slider.setMinorTickSpacing(5); slider.setPaintTicks(true); slider.setPaintLabels(true); slider.setPaintTrack(true); slider.addChangeListener( new LabelChangeListener(label) ); sliderPanel.add( slider ); sg.addSlider( slider ); } return panel; } static class LabelChangeListener implements ChangeListener { private JLabel label; public LabelChangeListener(JLabel label) { this.label = label; } @Override public void stateChanged(ChangeEvent e) { JSlider slider = (JSlider)e.getSource(); label.setText( Integer.toString(slider.getValue()) ); } } private static void createAndShowGui() { JPanel panel = createSliderPanel(100, 5); JFrame frame = new JFrame("SliderGroup"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.add(panel); frame.pack(); frame.setLocationByPlatform(true); frame.setVisible(true); } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { createAndShowGui(); } }); } }