• 追加された行はこの色です。
  • 削除された行はこの色です。
TITLE:Timerでアニメーションするアイコンを作成
#navi(../)
*Timerでアニメーションするアイコンを作成 [#k26d808e]
>編集者:[[Terai Atsuhiro>terai]]~
作成日:2006-03-13~
更新日:&lastmod;
---
category: swing
folder: AnimeIcon
title: Timerでアニメーションするアイコンを作成
tags: [Timer, Animation, Icon]
author: aterai
pubdate: 2006-03-13T00:29:10+09:00
description: javax.swing.Timerを使って、アニメーションするアイコンを作成します。
image: https://lh4.googleusercontent.com/_9Z4BYR88imo/TQTHuI0XeDI/AAAAAAAAARo/CVs615Dtkqs/s800/AnimeIcon.png
---
* 概要 [#summary]
`javax.swing.Timer`を使って、アニメーションするアイコンを作成します。

#contents
#download(https://lh4.googleusercontent.com/_9Z4BYR88imo/TQTHuI0XeDI/AAAAAAAAARo/CVs615Dtkqs/s800/AnimeIcon.png)

**概要 [#k5877eb2]
javax.swing.Timerを使って、アニメーションするアイコンを作成します。
* サンプルコード [#sourcecode]
#code(link){{
class AnimatedLabel extends JLabel implements ActionListener {
  private final Timer animator;
  private final AnimeIcon icon = new AnimeIcon();
  public AnimatedLabel() {
    super();
    animator = new Timer(100, this);
    setIcon(icon);
    addHierarchyListener(new HierarchyListener() {
      @Override public void hierarchyChanged(HierarchyEvent e) {
        if ((e.getChangeFlags() & HierarchyEvent.DISPLAYABILITY_CHANGED) != 0
            && !e.getComponent().isDisplayable()) {
          stopAnimation();
        }
      }
    });
  }

#screenshot
  @Override public void actionPerformed(ActionEvent e) {
    icon.next();
    repaint();
  }

**サンプルコード [#z490c564]
 class AnimeIcon extends JComponent implements ActionListener {
   private boolean flag = false;
   private final javax.swing.Timer animator;
   private final Vector list = new Vector();
   public void animationStart() {
     flag = true;
     animator.start();
   }
   public void animationStop() {
     flag = false;
     animator.stop();
   }
   public AnimeIcon() {
     super();
     animator = new javax.swing.Timer(100, this);
     //setBackground(Color.white);
     double r  = 2.0d;
     double sx = 1.0d;
     double sy = 1.0d;
     list.addElement(new Ellipse2D.Double(sx+3*r, sy+0*r, 2*r, 2*r));
     list.addElement(new Ellipse2D.Double(sx+5*r, sy+1*r, 2*r, 2*r));
     list.addElement(new Ellipse2D.Double(sx+6*r, sy+3*r, 2*r, 2*r));
     list.addElement(new Ellipse2D.Double(sx+5*r, sy+5*r, 2*r, 2*r));
     list.addElement(new Ellipse2D.Double(sx+3*r, sy+6*r, 2*r, 2*r));
     list.addElement(new Ellipse2D.Double(sx+1*r, sy+5*r, 2*r, 2*r));
     list.addElement(new Ellipse2D.Double(sx+0*r, sy+3*r, 2*r, 2*r));
     list.addElement(new Ellipse2D.Double(sx+1*r, sy+1*r, 2*r, 2*r));
     int iw = (int)(r*8)+(int)(sx*2);
     int ih = (int)(r*8)+(int)(sy*2);
     setPreferredSize(new Dimension(iw, ih));
   }
   public void paintComponent(Graphics g) {
     Graphics2D g2d = (Graphics2D) g;
     g2d.setPaint(getBackground());
     g2d.fillRect(0, 0, getWidth(), getHeight());
     g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                          RenderingHints.VALUE_ANTIALIAS_ON);
     Iterator it = list.iterator();
     if(flag) {
       float alpha = 0.1f;
       while(it.hasNext()) {
         g2d.setPaint(new Color(0.5f,0.5f,0.5f,alpha));
         g2d.fill((Shape)it.next());
         alpha = alpha + 0.1f;
       }
     }else{
       while(it.hasNext()) {
         g2d.setPaint(new Color(0.6f,0.6f,0.6f));
         g2d.fill((Shape)it.next());
       }
     }
   }
   public void actionPerformed(ActionEvent e) {
     list.addElement(list.remove(0));
     repaint();
   }
 }
  public void startAnimation() {
    icon.setRunning(true);
    animator.start();
  }

-&jnlp;
-&jar;
-&zip;
  public void stopAnimation() {
    icon.setRunning(false);
    animator.stop();
  }
}

**解説 [#md0e801a]
上記のサンプルでは、円のアルファ値が変化するFireFox風のアニメーションアイコンを作成しています。
class AnimeIcon implements Icon, Serializable {
  private static final long serialVersionUID = 1L;
  private static final Color ELLIPSE_COLOR = new Color(.5f, .5f, .5f);
  private static final double R  = 2d;
  private static final double SX = 1d;
  private static final double SY = 1d;
  private static final int WIDTH  = (int) (R * 8 + SX * 2);
  private static final int HEIGHT = (int) (R * 8 + SY * 2);
  private final List<Shape> list = new ArrayList<Shape>(Arrays.asList(
        new Ellipse2D.Double(SX + 3 * R, SY + 0 * R, 2 * R, 2 * R),
        new Ellipse2D.Double(SX + 5 * R, SY + 1 * R, 2 * R, 2 * R),
        new Ellipse2D.Double(SX + 6 * R, SY + 3 * R, 2 * R, 2 * R),
        new Ellipse2D.Double(SX + 5 * R, SY + 5 * R, 2 * R, 2 * R),
        new Ellipse2D.Double(SX + 3 * R, SY + 6 * R, 2 * R, 2 * R),
        new Ellipse2D.Double(SX + 1 * R, SY + 5 * R, 2 * R, 2 * R),
        new Ellipse2D.Double(SX + 0 * R, SY + 3 * R, 2 * R, 2 * R),
        new Ellipse2D.Double(SX + 1 * R, SY + 1 * R, 2 * R, 2 * R)));

JTextAreaに表示している作業状況はダミーで、実際はThread.sleep()で時間を稼いでいるだけです。
  private boolean isRunning;
  public void next() {
    if (isRunning) {
      list.add(list.remove(0));
    }
  }

円がいびつだったので、アンチエイリアスをかけています。Java SE 6 では、「小さな円(曲線)が円に見えなかった問題」が解消されているようです([[参考>http://www.02.246.ne.jp/~torutk/jvm/mustang.html#SEC26]])。
  public void setRunning(boolean isRunning) {
    this.isRunning = isRunning;
  }

//**参考リンク
**コメント [#o8846e0b]
- 色の濃い円が時計回りに回転するように変更しました。 -- [[terai]] &new{2006-03-15 (水) 11:12:08};
  @Override public void paintIcon(Component c, Graphics g, int x, int y) {
    Graphics2D g2 = (Graphics2D) g.create();
    g2.setPaint(c == null ? Color.WHITE : c.getBackground());
    g2.fillRect(x, y, getIconWidth(), getIconHeight());
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                        RenderingHints.VALUE_ANTIALIAS_ON);
    g2.setColor(ELLIPSE_COLOR);
    g2.translate(x, y);
    int size = list.size();
    for (int i = 0; i < size; i++) {
      float alpha = isRunning ? (i + 1) / (float) size : .5f;
      g2.setComposite(AlphaComposite.getInstance(
          AlphaComposite.SRC_OVER, alpha));
      g2.fill(list.get(i));
    }
    // g2.translate(-x, -y);
    g2.dispose();
  }

  @Override public int getIconWidth() {
    return WIDTH;
  }

  @Override public int getIconHeight() {
    return HEIGHT;
  }
}
}}

* 解説 [#explanation]
上記のサンプルでは、スタートボタンを押すと(`JTextArea`に表示している作業状況はランダムに`Thread.sleep(...)`を実行して時間稼ぎしている)アイコンが`FireFox`風にアニメーションします。

アニメーションは、`8`個の小さな円からアイコンを生成して、それぞれのインデックスを順に変更することで行っています。

- アイコンの生成
-- リスト(`ArrayList`)に、座標の異なる円(`Ellipse2D.Double`)を`8`個生成して追加
- インデックスの変更
-- `Timer`を使って、リストの先頭にある円を最後に移動
- 異なるアルファ値で円を描画
-- インデックスに応じたアルファ値でそれぞれの円を描画
-- `Java 1.6.0`では「小さな円(曲線)が円に見えなかった問題」が解消されている([http://www.02.246.ne.jp/~torutk/jvm/mustang.html#SEC26 参考])

----
一々自分で座標を計算して`new Ellipse2D`を`8`個も並べるのを避けたい、またはもうすこし正確に円を配置したい場合は、`AffineTransform`などを使ってアイコンを生成する方法もあります。

#code{{
class AnimeIcon2 implements Icon, Serializable {
  private static final long serialVersionUID = 1L;
  private static final Color ELLIPSE_COLOR = new Color(.5f, .8f, .5f);
  private final List<Shape> list = new ArrayList<>();
  private final Dimension dim;
  private boolean isRunning;
  public AnimeIcon2() {
    super();
    int r = 4;
    Shape s = new Ellipse2D.Float(0, 0, 2 * r, 2 * r);
    for (int i = 0; i < 8; i++) {
      AffineTransform at = AffineTransform.getRotateInstance(
          i * 2 * Math.PI / 8);
      at.concatenate(AffineTransform.getTranslateInstance(r, r));
      list.add(at.createTransformedShape(s));
    }
    // int d = (int) (r * 2*(1 + 2 * Math.sqrt(2)));
    int d = (int) r * 2 * (1 + 3); // 2 * Math.sqrt(2) is nearly equal to 3.
    dim = new Dimension(d, d);
  }

  @Override public int getIconWidth() {
    return dim.width;
  }

  @Override public int getIconHeight() {
    return dim.height;
  }

  @Override public void paintIcon(Component c, Graphics g, int x, int y) {
    Graphics2D g2 = (Graphics2D) g.create();
    g2.setPaint(c == null ? Color.WHITE : c.getBackground());
    g2.fillRect(x, y, getIconWidth(), getIconHeight());
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                        RenderingHints.VALUE_ANTIALIAS_ON);
    g2.setColor(ELLIPSE_COLOR);
    int xx = x + dim.width / 2;
    int yy = y + dim.height / 2;
    g2.translate(xx, yy);
    int size = list.size();
    for (int i = 0; i < size; i++) {
      float alpha = isRunning ? (i + 1) / (float) size : .5f;
      g2.setComposite(AlphaComposite.getInstance(
          AlphaComposite.SRC_OVER, alpha));
      g2.fill(list.get(i));
    }
    // g2.translate(-xx, -yy);
    g2.dispose();
  }

  public void next() {
    if (isRunning) {
      list.add(list.remove(0));
    }
  }

  public void setRunning(boolean isRunning) {
    this.isRunning = isRunning;
  }
}
}}

* 参考リンク [#reference]
- [https://docs.oracle.com/javase/jp/8/docs/api/javax/swing/Icon.html Icon (Java Platform SE 8)]

* コメント [#comment]
#comment
- 色の濃い円が時計回りに回転するように変更しました。 -- &user(aterai); &new{2006-03-15 (水) 11:12:08};
- ロードインジケータと呼ぶらしい。 -- &user(aterai); &new{2007-07-11 (水) 23:49:44};
- %%このサンプルでは、`Swing Tutorial`に存在した古い`SwingWorker`を使用しているが、%% [http://java.net/projects/swingworker Swingworker — Java.net] にある`JDK 1.6`からバックポートされた`org.jdesktop.swingworker.SwingWorker`を使用 %%したほうが良さそう。そのうち修正する予定。%% 変更修正済。 -- &user(aterai); &new{2009-12-17 (木) 01:44:29};

#comment