Summary

JComponentで数字が垂直回転アニメーションするホイールコンポーネントを作成し、これを各桁に組み合わせてカウンターコンポーネントを作成します。

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, Kotlin

Description

  • DigitWheel
    • 0-9の数字を垂直に並べてGraphics2D#drawString(...)で描画し、ホイールコンポーネントを作成
    • ホイール風に見えるように中央に近い数字を明るく強調
    • Timerでホイールのy座標を更新して現在の数値が中央に移動するアニメーションをホイールの回転として表現
    • カウントアップの場合、ホイールはスロットマシンやオドメーター(走行距離計)のように常に前方に回転するアニメーションを実行
      • リセットの場合、最短ルートで0に戻るアニメーションを実行
    • Graphics2D#setClip(...)で表示窓枠内のみ描画
  • Odometer
    • 複数のDigitWheelを保持し、数値を表示するカウンターコンポーネント
    • このサンプルでは5桁分のDigitWheelFlowLayoutを設定した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;
    }
  }
}

Reference

Comment