我有一个N JSliders的列表(N不会在程序上改变,只是在我添加更多功能时。目前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并滚动到页面底部。
谢谢
P.S。将值乘以比率允许用户将值“锁定”为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<JSlider> sliders = new LinkedList<JSlider>();
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<JSlider> otherSliders = new LinkedList<JSlider>(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);
}
}
答案 0 :(得分:6)
为什么不让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<Integer, JComponent> myDictionary = new Hashtable<Integer, JComponent>();
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<BoundedRangeModel> sliderModelList = new ArrayList<BoundedRangeModel>();
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。
答案 1 :(得分:2)
滑块会根据比率值抽搐或根本不移动。
HumbleBundle有同样的问题。如果您通过键盘移动滑块,则更改仅为1,这意味着它只会转到第一个滑块。所以你的比率最终会失去同步。
此外,总值并不总是加起来为100。
所以你需要进行舍入检查。如果它没有添加到100,那么您需要确定错误发生的位置。也许最后一个滑块给出了上述问题?
我不知道当4个滑块中的3个为0且第4个滑块为100时我该怎么办,我向下移动第4个滑块。
HumbleBundle处理它的方法是移动所有切片器。但是它只允许您以3的增量向下移动滑块,这样您就可以将3个滑块中的每一个增加1个。
即使HumbleBundle的实施也不完美。
答案 2 :(得分:2)
借用一些气垫船解决方案,我提出了一种不同的方法。这种方法的基础是在滑块移动时跟踪“其他滑块”值。只要您继续滑动同一个滑块,冻结值就会用于计算新值。然后将任何舍入差异顺序地应用于每个滑块,直到差异用完为止。使用此方法,您可以将滑块中的增量更改均匀地应用于所有其他滑块。
模型中的值是滑块的实际值,您还可以使用键盘调整滑块:
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<JSlider> sliders = new ArrayList<JSlider>();
private int groupSum;
private boolean internalChange = false;
private JSlider previousSlider;
private List<SliderInfo> otherSliders = new ArrayList<SliderInfo>();
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();
}
});
}
}