Summary

JComponentの中央で円形スピナーが回転するアニメーションで、不確定モードのローディング画面コンポーネントを作成します。

Source Code Examples

// A value object that only holds the starting angle
// and sweep angle of the arc for one frame.
final class ArcAngles {
  final float startAngle;
  final float sweepAngle;

  ArcAngles(float startAngle, float sweepAngle) {
    this.startAngle = startAngle;
    this.sweepAngle = sweepAngle;
  }
}

// An abstract class that integrates size management
// and timer management common to the four spinners.
abstract class AbstractCircularSpinner extends JComponent {
  protected final float size;
  protected final float stroke;
  protected final Timer timer = new Timer(16, e -> repaint());
  protected final long startTime;

  protected AbstractCircularSpinner(float size, float stroke) {
    super();
    this.size = size;
    this.stroke = stroke;
    this.startTime = System.currentTimeMillis();
    this.timer.start();
  }

  @Override public Dimension getPreferredSize() {
    int totalSize = (int) Math.ceil(size + stroke);
    return new Dimension(totalSize, totalSize);
  }

  @Override public void removeNotify() {
    super.removeNotify();
    timer.stop();
  }

  // Template method body
  // Only two functions, 'finding the angle' and 'drawing based on the angle',
  // are delegated to the subclass.
  @Override protected final void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2 = createValidatedGraphics2D(g);
    float x = (getWidth() - size) / 2f;
    float y = (getHeight() - size) / 2f;
    long elapsed = System.currentTimeMillis() - startTime;
    ArcAngles arc = computeArcAngles(elapsed);
    paintArc(g2, x, y, arc);
    g2.dispose();
  }

  // Template method 1:
  // Find the angle of the arc from the elapsed time
  protected abstract ArcAngles computeArcAngles(long elapsedMillis);

  // Template method 2:
  // Actual drawing after determining the angle
  protected abstract void paintArc(Graphics2D g2, float x, float y, ArcAngles arc);

  // Common Graphics2D initialization process
  protected Graphics2D createValidatedGraphics2D(Graphics g) {
    Graphics2D g2 = (Graphics2D) g.create();
    g2.setRenderingHint(
        RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    g2.setRenderingHint(
        RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
    g2.setRenderingHint(
        RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
    return g2;
  }

  // Common ring generation used in Area drawing system
  protected Area createRing(float x, float y) {
    Area outer = new Area(new Ellipse2D.Float(x, y, size, size));
    float innerSize = size - stroke * 2f;
    float innerOffset = (size - innerSize) / 2f;
    float ax = x + innerOffset;
    float ay = y + innerOffset;
    Area inner = new Area(new Ellipse2D.Float(ax, ay, innerSize, innerSize));
    outer.subtract(inner);
    return outer;
  }

  protected Area createArcArea(Area ring, float startAngle, float sweepAngle) {
    float x = (getWidth() - size) / 2f;
    float y = (getHeight() - size) / 2f;
    float cx = x + size / 2f;
    float cy = y + size / 2f;
    float r = size / 2f + 1f;
    Arc2D arc = new Arc2D.Float(
        cx - r, cy - r, r * 2f, r * 2f, startAngle, sweepAngle, Arc2D.PIE);
    Area arcSector = new Area(arc);
    Area arcArea = new Area(ring);
    arcArea.intersect(arcSector);
    return arcArea;
  }
}
View in GitHub: Java, Kotlin

Description

AbstractCircularSpinnerは、以下の4種類の円形スピナーコンポーネントにおける共通処理(Timerによる定期的な再描画、コンポーネントサイズの管理、描画用Graphics2Dの初期化など)をカプセル化した抽象クラスです。 経過時間に応じた角度の算出(computeArcAnglesメソッド)と、実際の描画処理(paintArcメソッド)をサブクラスに委譲します。

  • 左上: SimpleStrokeSpinner
    • BasicStrokeを使用して円弧(Arc2D)の輪郭線を描画する基本的なスピナー
    • 経過時間に対して一定の速度で開始角度が回転し、円弧の長さは固定値のまま回転する
  • 右上: SimpleAreaSpinner
    • Areaクラスの積集合(intersect)を使用して、ドーナツ型の円(createRing)とパイ型の円弧(Arc2D.PIE)が重なる領域を塗りつぶすスピナー
    • 描画ロジックは異なるが、SimpleStrokeSpinnerと同様に一定の速度と固定の長さで回転する
  • 左下: MaterialStrokeSpinner
    • YouTubeAndroidMaterial Design風に伸縮しながら回転する不確定モードの進行状況インジケータ
    • BasicStrokeを用いた線で描画し、回転速度や円弧の長さが時間経過(イージング)によって滑らかに変化する
  • 右下: MaterialAreaSpinner
    • MaterialStrokeSpinnerと同様の伸縮・回転アニメーションを、Areaを用いた面塗りつぶしで描画するスピナー
    • グラデーションの適用や特殊なクリッピングなど、線描画(Stroke)だけでは表現しづらい複雑な形状のカスタマイズが可能

Reference

Comment