JSliderを上下に配置した範囲選択スライダーを作成する
Total: 12, Today: 12, Yesterday: 0
Posted by aterai at
Last-modified:
Summary
JSliderを調整つまみとして選択範囲と目盛り表示用JLabelの上下に配置し、範囲選択スライダーを作成します。
Screenshot

Advertisement
Source Code Examples
class RangeSliderPanel extends JPanel {
private final JSlider lowerSlider;
private final JSlider upperSlider;
protected RangeSliderPanel(int min, int max, int lowInit, int highInit) {
super(new BorderLayout(0, 0));
upperSlider = createSlider(min, max, highInit, false);
lowerSlider = createSlider(min, max, lowInit, true);
ChangeListener cl = e -> {
if (lowerSlider.getValue() > upperSlider.getValue()) {
if (Objects.equals(e.getSource(), lowerSlider)) {
lowerSlider.setValue(upperSlider.getValue());
} else {
upperSlider.setValue(lowerSlider.getValue());
}
}
repaint();
};
lowerSlider.addChangeListener(cl);
upperSlider.addChangeListener(cl);
add(upperSlider, BorderLayout.NORTH);
add(new RangeBar(lowerSlider, upperSlider));
add(lowerSlider, BorderLayout.SOUTH);
}
@Override public final Component add(Component comp) {
return super.add(comp);
}
@Override public final void add(Component comp, Object constraints) {
super.add(comp, constraints);
}
private static JSlider createSlider(int min, int max, int val, boolean isUp) {
return new JSlider(min, max, val) {
@Override public void updateUI() {
super.updateUI();
setUI(new TriangleUI(this, isUp));
setOpaque(false);
setPaintTicks(false);
setPaintLabels(false);
}
@Override public Dimension getPreferredSize() {
Dimension d = super.getPreferredSize();
d.height = 10;
return d;
}
};
}
}
View in GitHub: Java, KotlinDescription
- 上限、下限を指定するつまみとして
JSliderを作成- 水平
JSliderの矢印つまみのデフォルトの方向は下向きでこれを上向きに変更する方法もあるが、このサンプルではBasicSliderUI#paintThumb(...)をオーバーライドして独自の上向き・下向き三角形を描画する JSlider側の目盛りやラベルは不要なので、JSlider#setPaintTicks(false)、JSlider#setPaintLabels(false)などで非表示化JSliderのトラックも不要なのでBasicSliderUI#paintTrack(...)をオーバーライドして非表示化JSlider側のラベルが非表示なのでJSlider側のトラックの余白は0となるが、このサンプルでは選択範囲と目盛り表示用JLabelでトラック外側にラベルを表示するので、BasicSliderUI#calculateTrackBuffer()をオーバーライドして余白を確保する
- 水平
class TriangleUI extends BasicSliderUI {
private final boolean isUpward;
protected TriangleUI(JSlider b, boolean isUpward) {
super(b);
this.isUpward = isUpward;
}
@Override public void paintTrack(Graphics g) {
// nothing to paint
}
@Override public void paintFocus(Graphics g) {
// nothing to paint
}
@Override protected void calculateTrackBuffer() {
if (slider.getOrientation() == JSlider.HORIZONTAL) {
trackBuffer = RangeBar.PAD;
} else {
super.calculateTrackBuffer();
}
}
@Override public void paintThumb(Graphics g) {
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(
RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(new Color(40, 44, 52));
Rectangle r = SwingUtilities.calculateInnerArea(slider, null);
int h = 8;
// int x = thumbRect.x + (thumbRect.width - w) / 2;
// int y = isUpward ? thumbRect.y : thumbRect.y + thumbRect.height - h;
int x = thumbRect.x; // + (thumbRect.width - w) / 2;
int y = isUpward ? r.y : r.y + r.height - h;
int[] xps = {x, x + thumbRect.width / 2, x + thumbRect.width};
int[] yps = isUpward ? new int[] {y + h, y, y + h} : new int[] {y, y + h, y};
g2.fillPolygon(xps, yps, xps.length);
g2.dispose();
}
}
- 選択範囲と目盛り表示用コンポーネントを
JLabelで作成- 範囲部分をマウスでドラッグすると範囲幅を一定に上限と下限を合わせて変更可能
- 水平
JSliderのみ対応で垂直JSliderには未対応
class RangeBar extends JLabel {
public static final int BAR_HEIGHT = 24;
public static final int PAD = 20;
private static final Color MAJOR_TICK_COLOR = new Color(180, 180, 185);
private static final Color MINOR_TICK_COLOR = new Color(210, 210, 215);
private static final Color TRACK_BGC = new Color(230, 230, 235);
private static final Color RANGE_COLOR = new Color(0, 180, 255, 120);
private final JSlider low;
private final JSlider up;
private final Point dragStartPt = new Point(0, 0);
private int slLow;
private int slUp;
private transient MouseAdapter dragListener;
protected RangeBar(JSlider low, JSlider up) {
super();
this.low = low;
this.up = up;
}
@Override public void updateUI() {
removeMouseListener(dragListener);
removeMouseMotionListener(dragListener);
super.updateUI();
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
dragListener = new MouseAdapter() {
@Override public void mousePressed(MouseEvent e) {
dragStartPt.setLocation(e.getPoint());
slLow = low.getValue();
slUp = up.getValue();
repaint();
}
@Override public void mouseReleased(MouseEvent e) {
dragStartPt.setLocation(-100, -100);
repaint();
}
@Override public void mouseDragged(MouseEvent e) {
if (dragStartPt.x >= 0) {
updateRange(e.getX() - dragStartPt.x);
}
repaint();
}
};
addMouseListener(dragListener);
addMouseMotionListener(dragListener);
}
private void updateRange(int diff) {
double trackW = low.getWidth() - PAD * 2d;
int range = low.getMaximum() - low.getMinimum();
int delta = (int) Math.round(diff * range / trackW);
int ln = slLow + delta;
int un = slUp + delta;
if (ln >= low.getMinimum() && un <= low.getMaximum()) {
low.setValue(ln);
up.setValue(un);
}
}
@Override public Dimension getPreferredSize() {
return new Dimension(300, BAR_HEIGHT);
}
@Override protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(
RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
int w = getWidth() - PAD * 2 - 1;
int cy = getHeight() / 2;
int barH = BAR_HEIGHT - 1;
// 1. paint Track
paintTrack(g2, w, cy, barH);
// 2. paint Ticks
paintTicks(g2, w, cy, barH);
int lx = getPositionX(low);
int ux = getPositionX(up);
// 3. Range bar
paintRangeBar(g2, cy, barH, lx, ux);
// 4. Numeric text
paintNumber(g2, cy, lx, ux);
g2.dispose();
}
private static void paintTrack(Graphics2D g2, int w, int cy, int barH) {
g2.setColor(TRACK_BGC);
Shape track = new RoundRectangle2D.Float(
PAD, cy - barH / 2f, w, barH, 4f, 4f);
g2.fill(track);
g2.setColor(TRACK_BGC.darker());
g2.draw(track);
}
private static void paintTicks(Graphics2D g2, int w, int cy, int barH) {
// g2.setStroke(new BasicStroke(1f));
for (int i = 0; i <= 100; i += 2) {
int tx = PAD + (i * w / 100);
if (i % 10 == 0) {
// MajorTick
g2.setColor(MAJOR_TICK_COLOR);
g2.drawLine(tx, cy - barH / 2, tx, cy + barH / 2);
} else {
// MinorTick
g2.setColor(MINOR_TICK_COLOR);
g2.drawLine(tx, cy - 4, tx, cy + 4);
}
}
}
private static void paintRangeBar(
Graphics2D g2, int cy, int barH, int lx, int ux) {
g2.setPaint(RANGE_COLOR);
Shape bar = new RoundRectangle2D.Float(
lx, cy - barH / 2f, ux - lx, barH, 4f, 4f);
g2.fill(bar);
g2.setColor(RANGE_COLOR.darker());
g2.draw(bar);
}
private void paintNumber(Graphics2D g2, int cy, int lx, int ux) {
g2.setColor(UIManager.getColor("Button.foreground"));
String txtLow = String.valueOf(low.getValue());
String txtUp = String.valueOf(up.getValue());
FontMetrics fm = g2.getFontMetrics();
int gap = 2;
int ty = cy + fm.getAscent() / 2 - 1;
g2.drawString(txtLow, lx - fm.stringWidth(txtLow) - gap, ty);
g2.drawString(txtUp, ux + gap, ty);
}
private static int getPositionX(JSlider slider) {
int iv = slider.getValue() - slider.getMinimum();
int range = slider.getMaximum() - slider.getMinimum();
double v = (double) iv / range;
Rectangle r = SwingUtilities.calculateInnerArea(slider, null);
return PAD + (int) (v * (r.width - PAD * 2d));
}
}
Reference
- ernieyu/Swing-range-slider: Swing range slider demo application
- 上記の
ernieyu/Swing-range-sliderは下限をJSlider#getValue()、上限をJSlider#getValue() + JSlider#getExtent()とし、Extentの幅を範囲に見立ててひとつのJSliderで範囲選択スライダーコンポーネントを作成している
- 上記の
- JSliderのUIや色を変更する
- JSliderの数値テキストラベルを更新する
- JSliderのトラック内部に目盛りを描画する
- JSliderの目盛り表示位置をトラック上部に変更する
- JLabelとIconで作成した検索位置表示バーをマウスで操作する