---
category: swing
folder: DotNavigationSlideshow
title: JToggleButtonでドットインジケーターを作成する
title-en: Create a dot indicator with JToggleButton
tags: [JToggleButton, JLayeredPane, Animation]
author: aterai
pubdate: 2026-03-16T00:22:39+09:00
description: JToggleButtonでドットインジケーターを作成してページ下部に配置し、マウスクリックなどでスクロールアニメーション付きの画面遷移を実行します。
summary-jp: JToggleButtonでドットインジケーターを作成してページ下部に配置し、マウスクリックなどでスクロールアニメーション付きの画面遷移を実行します。
summary-en: Create a dot indicator with JToggleButton and place it at the bottom of the page, and perform a screen transition with a scroll animation when the mouse is clicked, etc.
image: https://drive.google.com/uc?id=1_XbkqoUbbWYn3wFnlL2NUGlB8h0-M_eF
---
* Summary [#summary]
JToggleButtonでドットインジケーターを作成してページ下部に配置し、マウスクリックなどでスクロールアニメーション付きの画面遷移を実行します。
`JToggleButton`でドットインジケーターを作成してページ下部に配置し、マウスクリックなどでスクロールアニメーション付きの画面遷移を実行します。
// #en{{Create a dot indicator with `JToggleButton` and place it at the bottom of the page, and perform a screen transition with a scroll animation when the mouse is clicked, etc.}}
#download(https://drive.google.com/uc?id=1_XbkqoUbbWYn3wFnlL2NUGlB8h0-M_eF)
* Source Code Examples [#sourcecode]
#code(link){{
private void setupDots() {
int totalImages = images.size();
ButtonGroup group = new ButtonGroup();
for (int i = 0; i < totalImages; i++) {
JToggleButton dot = makeDotButton(i);
group.add(dot);
dotPanel.add(dot);
}
}
private JToggleButton makeDotButton(int index) {
JToggleButton dot = new JToggleButton(new DotIcon(), index == 0);
dot.setBorderPainted(false);
dot.setContentAreaFilled(false);
dot.setFocusPainted(false);
dot.setBorder(BorderFactory.createEmptyBorder());
dot.setCursor(new Cursor(Cursor.HAND_CURSOR));
dot.addActionListener(e -> {
if (index != currentIndex) {
navigateTo(index, index > currentIndex);
}
});
return dot;
}
private void navigateTo(int index, boolean moveRight) {
Component c = dotPanel.getComponent(index);
if (c instanceof JToggleButton && !animationPanel.isAnimating()) {
((JToggleButton) c).setSelected(true);
ImageIcon nextImage = images.get(index);
currentIndex = index;
animationPanel.startAnimation(nextImage, moveRight);
requestFocusInWindow();
}
}
private class NavigateHandler extends KeyAdapter
implements MouseWheelListener {
@Override public void keyPressed(KeyEvent e) {
int totalImages = images.size();
int keyCode = e.getKeyCode();
if (keyCode == KeyEvent.VK_LEFT) {
navigateTo((currentIndex - 1 + totalImages) % totalImages, false);
} else if (keyCode == KeyEvent.VK_RIGHT) {
navigateTo((currentIndex + 1) % totalImages, true);
}
}
@Override public void mouseWheelMoved(MouseWheelEvent e) {
int totalImages = images.size();
int rotation = e.getWheelRotation();
if (rotation < 0) {
navigateTo((currentIndex - 1 + totalImages) % totalImages, false);
} else {
navigateTo((currentIndex + 1) % totalImages, true);
}
}
}
}}
* Description [#description]
- スライドショーを`JLayeredPane`に`OverlayLayout`を設定して作成:
-- 背面レイヤー(`JLayeredPane.DEFAULT_LAYER`)にアニメーションパネルを追加
-- 前面レイヤー(`JLayeredPane.PALETTE_LAYER`)にドットナビゲーションパネルや前後移動ボタンを配置した透明パネルを追加
- 前面レイヤーを`JPanel`に`BorderLayout`を設定して作成:
-- `JToggleButton`で作成したドットをスライドショーに表示する画像の数だけ作成して透明な`JPanel`に`FlowLayout.CENTER`で配置、さらにこのドットナビゲーションパネルをラップする`JPanel(BorderLayout)`の`BorderLayout.SOUTH`に配置、最後に前面レイヤー`JPanel`の`BorderLayout.CENTER`に配置
--- 通常の`BorderLayout`では`BorderLayout.SOUTH`に配置したコンポーネントの幅が`BorderLayout.WEST`、`BorderLayout.EAST`に配置したコンポーネントの高さより優先されるので、これを逆にするためドットナビゲーションパネルを別`JPanel(BorderLayout)`でラップしている
--- `KeyListener`や`MouseWheelListener`での画面遷移は前後移動のみだが、ドットボタンのクリックは間の画面を飛ばして目的の画面にスライドアニメーションで移動可能
-- `JButton`で作成した前後移動ボタンを`BorderLayout.WEST`、`BorderLayout.EAST`に配置
- 背面レイヤーを`EaseOut`を用いた画像スライドアニメーションを処理する`JPanel`で作成:
#code{{
class AnimationPanel extends JPanel {
private static final int DURATION = 250; // ms
private static final int FRAME_RATE = 60;
private final Timer timer;
private Image currentImage;
private Image nextImage;
private double animationProgress; // 0.0 to 1.0
private boolean moveRight = true;
public AnimationPanel(ImageIcon initialImage) {
super();
this.currentImage = initialImage.getImage();
int delay = 1000 / FRAME_RATE;
timer = new Timer(delay, new ActionListener() {
private long startTime;
@Override public void actionPerformed(ActionEvent e) {
boolean b0 = animationProgress == 0d;
if (b0) {
startTime = System.currentTimeMillis();
animationProgress = .001; // Start
} else {
long elapsed = System.currentTimeMillis() - startTime;
animationProgress = (double) elapsed / DURATION;
boolean b1 = animationProgress >= 1d;
if (b1) {
animationProgress = 1d;
completeAnimation();
}
}
repaint();
}
});
timer.setInitialDelay(0);
}
public void startAnimation(ImageIcon nextImageIcon, boolean isMoveRight) {
this.nextImage = nextImageIcon.getImage();
this.moveRight = isMoveRight;
this.animationProgress = 0d;
timer.start();
}
private void completeAnimation() {
timer.stop();
currentImage = nextImage; // Swap images
// nextImage = null;
animationProgress = 0d;
}
public boolean isAnimating() {
return timer.isRunning();
}
private double easeOut(double t) {
return t * (2d - t);
}
@Override protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (currentImage == null) {
return;
}
Graphics2D g2 = (Graphics2D) g.create();
int width = getWidth();
int height = getHeight();
if (isAnimating() && nextImage != null) {
double easedProgress = easeOut(animationProgress);
int offsetX = (int) (width * easedProgress);
if (moveRight) {
// Slide to left (new image comes from right)
g2.drawImage(currentImage, -offsetX, 0, width, height, this);
g2.drawImage(nextImage, width - offsetX, 0, width, height, this);
} else {
// Slide to right (new image comes from left)
g2.drawImage(currentImage, offsetX, 0, width, height, this);
g2.drawImage(nextImage, -width + offsetX, 0, width, height, this);
}
} else {
// Static state
g2.drawImage(currentImage, 0, 0, width, height, this);
}
g2.dispose();
}
}
}}
* Reference [#reference]
- [[GridLayoutとJScrollPaneを使ったグリッド単位での表示切り替え>Swing/GridScrollAnimation]]
* Comment [#comment]
#comment
#comment