swing自定义组件之BUTTON(精)
Swing自定义组件之Button
GUI组件要完成的任务有2个,展现与业务。对于按钮来说,文本、图标、边框、背景属于展现层,而这些元素在按钮不同状态下会不尽相同,一般来说至少有4种状态下的展现:普通、鼠标放在其上、被按下、被禁用,也就是按钮应该具备这4种状
态。
下面给出的示例是swing实现的自定义按钮。
通常swing自定义组件继承javax.swing.JComponent并重写protected void
paintComponent(Graphics g方法实现自定义绘制。重写paintComponent方法时通常要先去掉super.paintComponent(g,因为父方法调用会绘制背景色。不妨先看一下源代码中的调用过程。
在JComponent.java中paintComponent(Graphics g方法定义如下: protected void paintComponent(Graphics g {
if (ui != null {
Graphics scratchGraphics = (g == null ? null : g.create(;
try {
ui.update(scratchGraphics, this;
}
finally {
scratchGraphics.dispose(;
}
}
}
其中ui的声明如下
protected transient ComponentUI ui;
然后转向ComponentUI的update(Graphics g, JComponent c方法:
public void update(Graphics g, JComponent c {
if (c.isOpaque( {
g.setColor(c.getBackground(;
g.fillRect(0, 0, c.getWidth(,c.getHeight(;
}
paint(g, c;
}
可见如果发现组件是非透明的,就绘制背景,可以看出swing组件的setBackground 方法如何绘制背景的。
一般简单的自定义组件,你可以只通过重写paintComponent方法来实现绘制,对于一般的组件这已经足够。对于自定义按钮一般的原则是准备4张背景图对应上述4种状态,这4种状态都可通过鼠标****来感知,当状态改变时,调用repaint(使Button重绘。除了背景,按钮文本、图标等的改变一样也必须调用repaint(来刷新。
然后重要的一点是你必须重写public Dimension getPreferredSize(来获得按钮的最佳尺寸。getPreferredSize方法对于布局管理器来说至关重要,布局管理器会通过getPreferredSize的判断组件的最佳大小,并进行布局。而对于本范例而言,getPreferredSize的大小只和背景图片大小有关。
对于业务,尽量做到前台界面与后来业务分离。你可以自定义按钮动作****器来实现,本例是沿用swing的Action实现,当鼠标抬起时,构造一个ActionEvent对象,然后交给Action成员的actionPerformed(ActionEvent e处理。
代码如下:
/*
* ImageButton.java
*
* Created on 2007年11月11日, 下午6:30
*
* To change this template, choose Tools | Template Manager * and open the template in the editor.
*/
package demo.swing;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.util.HashMap;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.MouseInputListener;
/**
* 为需要具备专业外观的按钮提供方便的实现。通过此类提供的若干方法可轻松实现专业外观。
* @author 电玩
*/
public final class ImageButton extends JComponent implements
MouseInputListener {
/**
* 通常状态下的背景图片
*/
private Icon backgroundImage;
/**
* 通常状态下的最佳尺寸
*/
private Dimension preferredSize;
/**
* 鼠标光标在上方时的背景图片
*/
private Icon rolloverBackgroundImage;
/**
* 鼠标光标在上方时的最佳尺寸
*/
private Dimension rolloverPreferredSize; /**
* 按钮被按下时的背景图片
*/
private Icon pressedBackgroundImage;
/**
* 按钮被按下时的最佳尺寸
*/
private Dimension pressedPreferredSize; /**
* 按钮被禁止时的背景图片
*/
private Icon disabledBackgroundImage;
/**
* 按钮被禁止时的最佳尺寸
*/
private Dimension disabledPreferredSize; /**
* 当前按钮的最佳尺寸
*/
private volatile Dimension currentSize;
/**
* 通常状态下按钮的图标
*/
private Icon icon;
/**
* 按钮被禁止时的图标
*/
private Icon disabledIcon;
/**
* 按钮图标出现的位置
*/
private IconOrientation iconOrientation = IconOrientation.WEST;
/**
* 按钮的默认尺寸
*/
private static final Dimension DEFAULT_SIZE = new Dimension(100, 25;
/**
* 水平偏移量
*/
private int horizontalOffset = DEFAULT_HORIZONTAL_OFFSET;
/**
* 默认的水平偏移量
*/
private static final int DEFAULT_HORIZONTAL_OFFSET = 4;
/**
* 垂直偏移量
*/
private int verticalOffset = DEFALUT_VERTICAL_OFFSET;
/**
* 默认的垂直偏移量
*/
private static final int DEFALUT_VERTICAL_OFFSET = 2;
/**
* 显示在按钮上的文字
*/
private String text;
/**
* 按钮文本的字体
*/
private Font font;
/**
* 按钮文本的默认字体
*/
private static final Font DEFAULT_FONT = Font.decode("Dialog-Plain-14";
/**
* 按钮被禁止时候文字的颜色
*/
private Color disabledForeground;
/**
* 默认的按钮被禁止时文本的颜色
*/
private final Color DEFAULT_DISABLED_FOREGROUND = new Color(192, 192, 192;
/**
* 按钮的状态
*/
private volatile Status status = Status.DEFAULT;
/**
* 按钮的颜色透明度
*/
private volatile float alpha = 1.0f;
private static HashMap< RenderingHints.Key, Object> renderingHints;
/**
* 按钮执行的动作
*/
private Action action;
static {
renderingHints = new HashMap(;
renderingHints.put(RenderingHints.KEY_ALPHA_INTERPOLATION,
RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY; renderingHints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON;
renderingHints.put(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY; renderingHints.put(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE;
renderingHints.put(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON; renderingHints.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC ; renderingHints.put(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE;
renderingHints.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON;
}
/** 创建一个ImageButton按钮的实例 */
public ImageButton( {
currentSize = DEFAULT_SIZE;
addMouseListener(this;
addMouseMotionListener(this;
}
/**
* 设置常规状态下按钮的背景。调用该方法会影响到按钮在常规状态下的最佳尺寸,
* 由传入的图标参数对应的图标尺寸决定
* @param backgroundImage 常规状态下背景图标对象
* @throws https://www.360docs.net/doc/e312502923.html,ng.IllegalArgumentException 如果传入的参数为null
*/
public void setBackgroundImage(Icon backgroundImage throws IllegalArgumentException {
if (backgroundImage == null {
throw new IllegalArgumentException("backgroundImage can't be null";
}
this.backgroundImage = backgroundImage;
preferredSize = new Dimension(backgroundImage.getIconWidth(,
backgroundImage.getIconHeight(;
currentSize = preferredSize;
}
/**
* 设置被禁用状态下按钮的背景。调用该方法会影响到按钮在禁用状态下的最佳尺寸,
* 由传入的图标参数对应的图标尺寸决定
* @param disabledBackgroundImage 禁用状态下背景图标对象
* @throws https://www.360docs.net/doc/e312502923.html,ng.IllegalArgumentException 如果传入的参数为null
*/
public void setDisabledBackgroundImage(Icon disabledBackgroundImage throws IllegalArgumentException {
if (disabledBackgroundImage == null {
throw new IllegalArgumentException(
"disabledBackgroundImage can't be null";
}
this.disabledBackgroundImage = disabledBackgroundImage;
disabledPreferredSize = new Dimension(disabledBackgroundImage
.getIconWidth(, disabledBackgroundImage.getIconHeight(;
}
/**
* 设置在被按下状态时按钮的背景。调用该方法会影响到按钮在被按下状态时的最佳尺寸,
* 由传入的图标参数对应的图标尺寸决定
* @param pressedBackgroundImage 被按下时的背景图标对象
* @throws https://www.360docs.net/doc/e312502923.html,ng.IllegalArgumentException 如果传入的参数为null
*/
public void setPressedBackgroundImage(Icon pressedBackgroundImagethrows IllegalArgumentException {
if (pressedBackgroundImage == null {
throw new IllegalArgumentException(
"pressedBackgroundImage can't be null";
}
this.pressedBackgroundImage = pressedBackgroundImage;
pressedPreferredSize = new Dimension(pressedBackgroundImage
.getIconWidth(, pressedBackgroundImage.getIconHeight(;
}
/**
* 设置鼠标指针在其上方时按钮的背景。调用该方法会影响到按钮在鼠标指针在其上方
* 时的最佳尺寸,由传入的图标参数对应的图标尺寸决定
* @param rolloverBackgroundImage 鼠标指针在其上方时的背景图标对象
* @throws https://www.360docs.net/doc/e312502923.html,ng.IllegalArgumentException 如果传入的参数为null
*/
public void setRolloverBackgroundImage(Icon rolloverBackgroundImage throws IllegalArgumentException {
if (rolloverBackgroundImage == null {
throw new IllegalArgumentException(
"rolloverBackgroundImage can't be null";
}
this.rolloverBackgroundImage = rolloverBackgroundImage;
rolloverPreferredSize = new Dimension(rolloverBackgroundImage
.getIconWidth(, rolloverBackgroundImage.getIconHeight(;
}
/**
* 设置水平偏移量。
* @param horizontalOffset 水平偏移量
* @throws https://www.360docs.net/doc/e312502923.html,ng.IllegalArgumentException 如果参数小于0,抛出此异常
*/
public void setHorizontalOffset(int horizontalOffset throws IllegalArgumentException{
if(horizontalOffset < 0 {
throw new IllegalArgumentException("horizontalOffset must >=0";
}
this.horizontalOffset = horizontalOffset;
}
/**
* 设置垂直偏移量
* @param verticalOffset 垂直偏移量
* @throws https://www.360docs.net/doc/e312502923.html,ng.IllegalArgumentException 如果参数小于0,抛出此异常
*/
public void setVerticalOffset(int verticalOffset throws IllegalArgumentException{
if(verticalOffset < 0 {
throw new IllegalArgumentException("verticalOffset must >=0";
}
this.verticalOffset = verticalOffset;
}
/**
* 设置按钮的图标
* @param icon 图标对象
* @throws https://www.360docs.net/doc/e312502923.html,ng.IllegalArgumentException 如果传入的参数为null,抛出此异常
*/
public synchronized void setIcon(Icon icon throws IllegalArgumentException {
if(icon == null {
throw new IllegalArgumentException(
"icon can't be null";
}
this.icon = icon;
}
/**
* 设置按钮在被禁用时显示的图标
* @param disabledIcon 图标对象
* @throws https://www.360docs.net/doc/e312502923.html,ng.IllegalArgumentException 如果传入的参数为null,抛出此异常
*/
public synchronized void setDisabledIcon(Icon disabledIcon throws IllegalArgumentException {
if(disabledIcon == null {
throw new IllegalArgumentException(
"disabledIcon can't be null";
}
this.disabledIcon = disabledIcon;
}
public void setIconOrientation(IconOrientation iconOrientation throws IllegalArgumentException{
if(iconOrientation == null {
throw new IllegalArgumentException(
"iconOrientation can't be null";
}
this.iconOrientation = iconOrientation;
}
public void setDisabledForeground(Color disabledForeground {
this.disabledForeground = disabledForeground;
}
/**
* 设置按钮的透明度
* @param alpha 透明度。范围在0.0f~1.0f之间。0.0f为完全透明,1.0f 为完全显示
* @throws https://www.360docs.net/doc/e312502923.html,ng.IllegalArgumentException 如果不在0.0f~1.0f之间会抛出此异常
*/
public synchronized void setAlpha(float alphathrows IllegalArgumentException{
if (alpha < 0f || alpha > 1.0f {
throw new IllegalArgumentException(
"alpha value must between 0.0 and 1.0";
}
this.alpha = alpha;
repaint(;
}
* 设置按钮显示的文本
* @param text 显示的文本字符串
*/
public synchronized void setText(String text {
if (text != null {
this.text = text;
repaint(;
}
}
/**
* 返回按钮的当前字体,如果之前没有设置字体,则返回默认字体* @return 按钮的当前字体
*/
public Font getFont( {
if(font == null {
return DEFAULT_FONT;
}
return font;
}
/**
* 设置按钮的当前字体
* @param font 字体实例
public void setFont(Font font {
this.font = font;
super.setFont(font;
}
/**
* 指定这个按钮的动作
* @param action 按钮的动作
*/
public void setAction(Action action {
this.action = action;
}
/**
* 覆盖JComponent.setEnabled(boolean enabled,设置是否按钮可用或被禁止
* @param enabled 如果按钮可用则为true,否则为false
* @see https://www.360docs.net/doc/e312502923.html,ponent#isEnabled
*/
@Override
public void setEnabled(boolean enabled {
super.setEnabled(enabled;
if (isEnabled( != enabled {
if (disabledPreferredSize != null
&& !disabledPreferredSize.equals(currentSize {
currentSize = disabledPreferredSize;
revalidate(;
}
}
}
/**
* 绘制按钮边框
* @param g 图形上下文
* @deprecated 此方法只在内部被调用,外部不要显式调用*/
@Override
protected void paintBorder(Graphics g {
}
/**
* 绘制子组件
* @param g 图形上下文
* @deprecated 此方法只在内部被调用,外部不要显式调用*/
@Override
protected void paintChildren(Graphics g {
}
/**
* 绘制按钮
* @param g 图形上下文
* @deprecated 此方法只在内部被调用,外部不要显式调用
*/
@Override
protected void paintComponent(Graphics g {
Graphics2D g2d = (Graphics2D g.create(;
g2d.addRenderingHints(renderingHints;
if(alpha < 1.0f {
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVE R,
alpha;
}
drawBackgroundImage(g2d;
drawIcon(g2d;
drawText(g2d;
g2d.dispose(;
}
private void drawIcon(final Graphics2D g2d {
Icon currentIcon = isEnabled( ? icon : disabledIcon;
if(currentIcon == null {
return;
}
int w = currentIcon.getIconWidth(;
int h = currentIcon.getIconHeight(;
int offsetX = 0;
int offsetY = 0;
switch (iconOrientation {
case WEST:
offsetX = horizontalOffset;
offsetY = getHeight( / 2 - h / 2;
break;
case EAST:
offsetX = getWidth( - horizontalOffset - w; offsetY = getHeight( / 2 - h / 2;
break;
case NORTH:
offsetX = getWidth( / 2 - w / 2;
offsetY = verticalOffset;
break;
case SOUTH:
offsetX = getWidth( / 2 - w / 2;
offsetY = getHeight( - verticalOffset - h; break;
case NORTH_WEST:
offsetX = horizontalOffset;
offsetY = verticalOffset;
break;