Java JSlider精度问题

发布于 2024-12-07 15:34:49 字数 3349 浏览 2 评论 0原文

我有一个 N JSlider 列表(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 并滚动到页面底部。

谢谢 将值乘以比率允许用户将值“锁定”为 0。但是,我不确定当 4 个滑块中的 3 个为 0 并且第 4 个滑块为 100 并且我将第 4 个滑块向下移动时该怎么办。使用上面的逻辑,值 0 的 3 个滑块保持原样,而第 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);
    }
}

I have a list of N JSliders (N does not change procedurally, only as I add more features. Currently N equals 4). The sum of all the sliders values must equal to 100. As one slider moves the rest of the sliders shall adjust. Each slider has values that range from 0 to 100.

Currently I am using this logic when a slider is changed (pseudo-code):

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

The problem with this setup is slider's values are stored as ints. So as I adjust the sliders I get precision problems: sliders will twitch or not move at all depending on the ratio value. Also the total value does not always add up to 100.

Does anyone have a solution to this problem without creating an entirely new JSlider class that supports floats or doubles?

If you want an example of the behavior I want, visit: Humble Indie Bundle and scroll to the bottom of the page.

thank you

p.s. Multiplying the values by the ratio allows for the user to 'lock' values at 0. However, I am not sure what to do when 3 of the 4 sliders are at 0 and the 4th slider is at 100 and I move the 4th slider down. Using the logic above, the 3 sliders with 0 as their value stay put and the 4th slider moves to where the user puts it, which makes the total less than 100, which is improper behavior.

EDIT

Here is the 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);
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

感悟人生的甜 2024-12-14 15:34:49

为什么不让 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 使用 BoundedRangeModels 列表而不是 JSliders。

Why not making the granularity of the JSlider models finer by say having them go from 0 to 1000000, and having the sum be 1000000? With the proper Dictionary for the LabelTable, the user will probably not know that it doesn't go from 0 to 100.

For example:

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;
         }
      }

   }

}

Edited to have SliderGroup2 use a List of BoundedRangeModels rather than JSliders.

粉红×色少女 2024-12-14 15:34:49

滑块会抽动或根本不移动,具体取决于比率值。

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

此外,总价值并不总是等于 100。

因此您需要进行四舍五入检查。如果加起来没有达到 100,那么您需要确定错误出在哪里。也许最后一个滑块考虑到了上述问题?

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

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

即使 HumbleBundle 的实现也并不完美。

sliders will twitch or not move at all depending on the ratio value.

HumbleBundle has the same problem. If you move the slider by the keyboard then the change is only 1, which means it will only ever go to the first slider. So you ratios will eventually get out of sync.

Also the total value does not always add up to 100.

So you need to do a rounding check. If it doesn't add to 100, then you need to decide where the error goes. Maybe the last slider given the above problem?

I am not sure what to do when 3 of the 4 sliders are at 0 and the 4th slider is at 100 and I move the 4th slider down.

The way HumbleBundle handles it is to move all the slicers. However it only allows you to move the slider down increments of 3, so that you can increase each of the 3 sliders by 1.

Even the implementation at HumbleBundle isn't perfect.

寂寞美少年 2024-12-14 15:34:49

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

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

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();
            }
        });
    }
}

Borrowing from some of Hovercrafts solution I came up with a different approach. The basis of this approach is that the "other sliders" values are tracked at the time a slider is moved. As long as you continue to slide the same slider the frozen values are used to calculate the new values. Any rounding differences are then applied sequentially to each slider until the difference is used up. Using this approach you can have incremental changes in the slider applied evenly to all of the other sliders.

The values in the model are the actual values of the slider and you can also use the keyboard to adjust the sliders:

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();
            }
        });
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文