• 追加された行はこの色です。
  • 削除された行はこの色です。
TITLE:ImageIconの形でJButtonを作成
#navi(../)
*ImageIconの形でJButtonを作成 [#lb2a57b5]
Posted by [[terai]] at 2008-07-21

#contents

**概要 [#h91eef1a]
任意のShapeとその形に透過色を設定した画像を使ってJButtonを作成します。

-&jnlp;
-&jar;
-&zip;

#screenshot

**サンプルコード [#ka72aa46]
#code{{
class RoundButton extends JButton {
  public RoundButton() {
    this(null, null);
  }
  public RoundButton(Icon icon) {
    this(null, icon);
  }
  public RoundButton(String text) {
    this(text, null);
  }
  public RoundButton(Action a) {
    this();
    setAction(a);
  }
  public RoundButton(String text, Icon icon) {
    setModel(new DefaultButtonModel());
    init(text, icon);
    if(icon==null) {
      return;
    }
    int iw = Math.max(icon.getIconWidth(), icon.getIconHeight());
    int sw = 1;
    setBorder(BorderFactory.createEmptyBorder(sw,sw,sw,sw));
    Dimension dim = new Dimension(iw+sw+sw, iw+sw+sw);
    setPreferredSize(dim);
    setMaximumSize(dim);
    setMinimumSize(dim);
    setBackground(Color.BLACK);
    setContentAreaFilled(false);
    setFocusPainted(false);
    //setVerticalAlignment(SwingConstants.TOP);
    setAlignmentY(Component.TOP_ALIGNMENT);
    initShape();
  }
  protected Shape shape, base;
  protected void initShape() {
    if(!getBounds().equals(base)) {
      Dimension s = getPreferredSize();
      base = getBounds();
      shape = new Ellipse2D.Float(0, 0, s.width-1, s.height-1);
    }
  }
  @Override
  protected void paintBorder(Graphics g) {
    initShape();
    Graphics2D g2 = (Graphics2D)g;
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                        RenderingHints.VALUE_ANTIALIAS_ON);
    g2.setColor(getBackground());
    //g2.setStroke(new BasicStroke(1.0f));
    g2.draw(shape);
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                        RenderingHints.VALUE_ANTIALIAS_OFF);
  }
  @Override
  public boolean contains(int x, int y) {
    initShape();
    return shape.contains(x, y);
  }
}
}}

**解説 [#x2ead355]
上記のサンプルでは、JButtonに円形の画像を貼り付けてボタンを作成しています。
- 円形、同サイズのPNG画像(円の外側が透過色)を三種類用意してJButtonに設定
-- setIcon
-- setPressedIcon
-- setRolloverIcon
- setContentAreaFilled(false)などを設定して、ボタン自体の描画はしない
- 推奨、最小、最大サイズを画像のサイズに合わせる
-- ただし、縁の線を描画するため、画像サイズより上下左右1pt大きくなるようEmptyBorderを設定している
- containsをオーバーライドして、円の外側をクリックしてもボタンが反応しないようにする
-- 画像の透過色から、円を生成している訳ではなく、画像のサイズから円図形を別途作成している
-- 画像の透過色から、クリック可能な領域を設定する場合は、[[JComponentの形状定義を変更する>Swing/MoveNonRectangularImage]]
- paintBorderをオーバーライドして、元の縁は描画せずにその幅の線で独自に円を描画する
-- containsで使用した図形を利用

----
ボタンの揃えを変更するために、JPanelではなく、Boxを利用しているので、JDK 5 でも JDK 6 と同じように描画するために、Box#paintComponent を以下のようにオーバーライドしています。
- [[Bug ID: 4907674 Box disregards setBackground() even when set Opaque(true)>http://bugs.sun.com/view_bug.do?bug_id=4907674]]

#screenshot(,screenshot1.png)

#code{{
private final Box box = // JDK 6 Box.createHorizontalBox();
  // JDK 5
  new Box(BoxLayout.X_AXIS) {
    protected void paintComponent(Graphics g) {
      if(ui != null) {
        super.paintComponent(g);
      }else if(isOpaque()) {
        g.setColor(getBackground());
        g.fillRect(0, 0, getWidth(), getHeight());
      }
    }
  };
}}

----
以下のようなButtonUIを使って、JButtonをオーバーライドしない方法もあります。
#code{{
JButton button = new JButton(icon);
button.setUI(new RoundImageButtonUI());
}}
#code{{
class RoundImageButtonUI extends BasicButtonUI{
  protected Shape shape, base;
  @Override
  protected void installDefaults(AbstractButton b) {
    super.installDefaults(b);
    Icon icon = b.getIcon();
    if(icon==null) return;
    int iw = Math.max(icon.getIconWidth(), icon.getIconHeight());
    int sw = 1;
    b.setBorder(BorderFactory.createEmptyBorder(sw,sw,sw,sw));
    b.setContentAreaFilled(false);
    b.setFocusPainted(false);
    b.setOpaque(false);
    b.setBackground(Color.BLACK);
    Dimension dim = new Dimension(iw+sw+sw, iw+sw+sw);
    b.setPreferredSize(dim);
    b.setMaximumSize(dim);
    b.setMinimumSize(dim);
    //b.setVerticalAlignment(SwingConstants.TOP);
    b.setAlignmentY(Component.TOP_ALIGNMENT);
    initShape(b);
  }
  @Override
  protected void installListeners(AbstractButton b) {
    BasicButtonListener listener = new BasicButtonListener(b) {
      @Override public void mousePressed(MouseEvent e) {
        AbstractButton b = (AbstractButton) e.getSource();
        initShape(b);
        if(shape.contains(e.getX(), e.getY())) {
          super.mousePressed(e);
        }
      }
      @Override public void mouseEntered(MouseEvent e) {
        if(shape.contains(e.getX(), e.getY())) {
          super.mouseEntered(e);
        }
      }
      @Override public void mouseMoved(MouseEvent e) {
        if(shape.contains(e.getX(), e.getY())) {
          super.mouseEntered(e);
        }else{
          super.mouseExited(e);
        }
      }
    };
    if(listener != null) {
      b.addMouseListener(listener);
      b.addMouseMotionListener(listener);
      b.addFocusListener(listener);
      b.addPropertyChangeListener(listener);
      b.addChangeListener(listener);
    }
  }
  @Override
  public void paint(Graphics g, JComponent c) {
    super.paint(g, c);
    Graphics2D g2 = (Graphics2D)g;
    initShape(c);
    //Border
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                        RenderingHints.VALUE_ANTIALIAS_ON);
    g2.setColor(c.getBackground());
    //g2.setStroke(new BasicStroke(1.0f));
    g2.draw(shape);
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                        RenderingHints.VALUE_ANTIALIAS_OFF);
  }
  private void initShape(JComponent c) {
    if(!c.getBounds().equals(base)) {
      Dimension s = c.getPreferredSize();
      base = c.getBounds();
      shape = new Ellipse2D.Float(0, 0, s.width-1, s.height-1);
    }
  }
}
}}

**参考リンク [#bbf23178]
- [[アクア風の球体の描き方(GIMPチュートリアル) > ロゴ・ボタン | GIMP思い込みチュートリアル(GIMPの使い方)>http://gimp.blog.shinobi.jp/Entry/18/]]
- [[Bug ID: 4907674 Box disregards setBackground() even when set Opaque(true)>http://bugs.sun.com/view_bug.do?bug_id=4907674]]
- [[JButtonの形を変更>Swing/RoundButton]]

**コメント [#kcfc5225]
#comment