JComponentに垂直配置した数字のカウントアップアニメーションを実行する
Total: 222, Today: 1, Yesterday: 1
Posted by aterai at
Last-modified:
Summary
JComponentで数字が垂直回転アニメーションするホイールコンポーネントを作成し、これを各桁に組み合わせてカウンターコンポーネントを作成します。
Screenshot

Advertisement
Source Code Examples
class DigitWheel extends JComponent {
private static final int DIGIT_HEIGHT = 80;
private static final int WHEEL_HEIGHT = DIGIT_HEIGHT * 10;
private final Timer animationTimer = new Timer(16, e -> animateScroll());
private double currentY;
private double targetY;
@Override public Dimension getPreferredSize() {
return new Dimension(55, DIGIT_HEIGHT);
}
private void animateScroll() {
double delta = targetY - currentY;
boolean threshold = Math.abs(delta) < .01;
if (threshold) {
currentY = targetY;
animationTimer.stop();
} else {
// Smoothly decelerate as it approaches the target
double speed = Math.min(.25, .5 + Math.abs(delta) / 2000d);
currentY += delta * speed;
}
repaint();
}
public void setTargetDigit(int digit, boolean isReset) {
if (isReset) {
// For reset, normalize coordinates to return to zero via the shortest path
targetY = digit * DIGIT_HEIGHT;
currentY %= WHEEL_HEIGHT;
if (currentY < 0) {
currentY += WHEEL_HEIGHT;
}
} else {
double nextTargetY = digit * DIGIT_HEIGHT;
double normalizedY = currentY % WHEEL_HEIGHT;
if (normalizedY < 0) {
normalizedY += WHEEL_HEIGHT;
}
double distance = nextTargetY - normalizedY;
if (distance < 0) {
// Ensure the wheel always rotates forward (slot machine style)
distance += WHEEL_HEIGHT;
}
targetY = currentY + distance;
}
if (!animationTimer.isRunning()) {
animationTimer.start();
}
}
@Override protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(
RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setClip(0, 0, getWidth(), getHeight());
// Draw background
g2.setColor(Color.DARK_GRAY);
g2.fillRect(0, 0, getWidth(), getHeight());
g2.setFont(getFont().deriveFont(Font.BOLD, 55f));
FontMetrics fm = g2.getFontMetrics();
for (int i = 0; i < 10; i++) {
double posY = (i * DIGIT_HEIGHT) - (currentY % WHEEL_HEIGHT);
// Coordinate correction for looping display
if (posY < -DIGIT_HEIGHT) {
posY += WHEEL_HEIGHT;
} else if (posY > WHEEL_HEIGHT - DIGIT_HEIGHT) {
posY -= WHEEL_HEIGHT;
}
// Highlight the number when it's near the center
double distFromCenter = Math.abs(posY);
g2.setColor(distFromCenter < 10.0 ? Color.WHITE : Color.GRAY);
int drawX = (getWidth() - fm.stringWidth(String.valueOf(i))) / 2;
int drawY = (int) (posY + (DIGIT_HEIGHT / 2d) + (fm.getAscent() / 2d) - 5d);
g2.drawString(String.valueOf(i), drawX, drawY);
}
g2.dispose();
}
}
View in GitHub: Java, KotlinDescription
DigitWheel0-9の数字を垂直に並べてGraphics2D#drawString(...)で描画し、ホイールコンポーネントを作成- ホイール風に見えるように中央に近い数字を明るく強調
Timerでホイールのy座標を更新して現在の数値が中央に移動するアニメーションをホイールの回転として表現- カウントアップの場合、ホイールはスロットマシンやオドメーター(走行距離計)のように常に前方に回転するアニメーションを実行
- リセットの場合、最短ルートで
0に戻るアニメーションを実行
- リセットの場合、最短ルートで
Graphics2D#setClip(...)で表示窓枠内のみ描画
Odometer- 複数のDigitWheelを保持し、数値を表示するカウンターコンポーネント
- このサンプルでは
5桁分のDigitWheelをFlowLayoutを設定したJPanelに追加してカウンターを作成 - 以下のように現在の数値の右側の桁(一の位)から順に数値を抽出し対応する
DigitWheelに割り当てる
class Odometer extends JPanel {
private final List<DigitWheel> wheels = new ArrayList<>();
protected Odometer(int digitCount) {
super(new FlowLayout(FlowLayout.CENTER, 4, 0));
IntStream.range(0, digitCount).forEach(i -> {
DigitWheel wheel = new DigitWheel();
wheels.add(wheel);
add(wheel);
});
}
public void updateValue(int value) {
boolean isReset = value == 0;
int remainingValue = value;
// Process from right to left (ones place first)
for (int i = wheels.size() - 1; i >= 0; i--) {
int digit = remainingValue % 10;
wheels.get(i).setTargetDigit(digit, isReset);
remainingValue /= 10;
}
}
}