众所周知,使用 JAVA 开发出来的应用程序在各种平台上有着相同的用户界面,这一切得归功于 SWING 良好的跨平台性(少数 AWT 控件在不同的平台上有着极为细小的差别)。并且, JAVA 还提供了 Look&Feel 和 Theme 让开发者对用户界面上的控件进行观感上的改变,这样可以做出更多更漂亮的用户界面。然而,当你仅仅是需要对某个控件进行特别的观感设置时,使用 L&F 或 Theme 可能过于复杂,也使得程序变得比较“庞大”,因此,我们可以通过控件重绘来完成这部分特殊的要求。
javax.swing.JComponent 是所有 SWING 控件的“祖宗”,在 JComponent 中, paintComponent(Graphics g) 方法是用来进行绘制控件的,因此,如果想要重绘 SWING 控件,只需要覆盖 paintComponent(Graphics g) 方法就可以了。如:
<table class=CodeBlock> <tr> <td>public class CubeMenuBar extends JMenuBar {
void paintComponent(Graphics g) {
// 这里写重绘代码
}
}
</td> </tr> </table>下面,我们将以例子来实现控件的重绘,最终结果如下图:
首先,我们按照上图制作一个简单的小程序,相信这个不是一件太难的事情:
<table cellPadding=5 class=CodeBlock> <tr> <td>//TestMenu.java
public class TestMenu extends JFrame {
JMenuBar cmbMenu = new JMenuBar();
JMenu mFile = new JMenu();
JMenu mEdit = new JMenu();
JMenu mSource = new JMenu();
JMenuItem miNew = new JMenuItem();
JMenuItem miOpen = new JMenuItem();
JMenuItem miSave = new JMenuItem();
JMenuItem miClose = new JMenuItem();
JTextArea taEditor = new JTextArea();
public TestMenu() {
// 创建布局
this.getContentPane().setLayout(new BorderLayout());
// 添加菜单
this.setJMenuBar(cmbMenu);
mFile.setText("File");
cmbMenu.add(mFile);
miNew.setText("New");
mFile.add(miNew);
miOpen.setText("Open");
mFile.add(miOpen);
miSave.setText("Save");
mFile.add(miSave);
miClose.setText("Close");
mFile.add(miClose);
miClose.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
dispose();
System.exit(0);
}
});
mEdit.setText("Edit");
cmbMenu.add(mEdit);
mSource.setText("Source");
cmbMenu.add(mSource);
// 添加编辑区域
JScrollPane sp = new JScrollPane(taEditor);
this.getContentPane().add(sp, BorderLayout.CENTER);
this.setTitle("Notepad - JAVA");
this.setSize(new Dimension(400, 300));
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.show();
}
public static void main(String[] args) {
//JFrame.setDefaultLookAndFeelDecorated(true);
TestMenu testmenu = new TestMenu();
}
}
</td> </tr> </table>( 程序运行如上图 )
为了实现特殊的菜单栏,我们需要自己重写 JMenu 类,假设我们的类叫作 CubeMenuBar ,那么,我们只需要将 TestMenu.java 中的
JMenuBar cmbMenu = new JMenuBar();
改为
CubeMenuBar cmbMenu = new CubeMenuBar();
现在,我们看一看最简单的 CubeMenuBar 应该怎么写:
<table cellPadding=5 class=CodeBlock> <tr> <td>// CubeMenuBar.java
public class CubeMenuBar extends JMenuBar {
protected final void paintComponent(Graphics g) {
super.paintComponent(g);
}
}
</td> </tr> </table>我们将 paintComponent(Graphics g) 申明为 protected final 是不愿继承的类再次改写重绘方法, super.paintComponent(g); 调用的父类的方法来进行重绘 MenuBar ,如果你写掉了这一句,呵呵,自己看看运行结果。
由于我们在这里只是研究如何重绘控件,因此,我提供一个现成的类,这个类负责生成带有过度色块的图像,具体不多说,只帖出代码:
<table cellPadding=5 class=CodeBlock> <tr> <td width="659">// ImageCreator.java
public class ImageCreator {
// 定义颜色
public static final Color mainMidColor = new Color(0, 64, 196);
public static final Color mainUltraDarkColor = new Color(0, 0, 64);
// 定义色块数量 ( 高度 )
public static final int CUBE_DIMENSION = 5;
public ImageCreator() {
}
/**
* 产生过度色块图像
* @param width 图像的宽
* @param height 图像的高
* @param leftColor 色块左侧颜色
* @param rightColor 色块结束的颜色
* @param transitionStart 过度色块开始位置
* @param transitionEnd 过度色块结束位置
* @return 创建好的图像
*/
public static BufferedImage getGradientCubesImage(int width, int height,
Color leftColor, Color rightColor, int transitionStart,
int transitionEnd) {
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = (Graphics2D) image.getGraphics();
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
GradientPaint gradient = new GradientPaint(transitionStart, 0, leftColor, transitionEnd, 0, rightColor);
graphics.setPaint(gradient);
graphics.fillRect(transitionStart, 0, transitionEnd - transitionStart, height);
graphics.setColor(leftColor);
graphics.fillRect(0, 0, transitionStart, height);
graphics.setColor(rightColor);
graphics.fillRect(transitionEnd, 0, width - transitionEnd, height);
int cubeCountY = height / ImageCreator.CUBE_DIMENSION;
int cubeCountX = 1 + (transitionEnd - transitionStart)
/ ImageCreator.CUBE_DIMENSION;
int cubeStartY = (height % ImageCreator.CUBE_DIMENSION) / 2;
int cubeStartX = transitionStart
- (ImageCreator.CUBE_DIMENSION - ((transitionEnd - transitionStart) % ImageCreator.CUBE_DIMENSION));
for (int col = 0; col < cubeCountX; col++) {
for (int row = 0; row < cubeCountY; row++) {
// 随机放置色块
if (Math.random() < 0.5) {
continue;
}
// 使用插值方法产生颜色,结果看起来和随机产生的差不多
double coef = 1.0 - (((double) col / (double) cubeCountX) + 0.9 * (Math
.random() - 0.5));
coef = Math.max(0.0, coef);
coef = Math.min(1.0, coef);
// 计算 RGB
int r = (int) (coef * leftColor.getRed() + (1.0 - coef) * rightColor.getRed());
int g = (int) (coef * leftColor.getGreen() + (1.0 - coef) * rightColor.getGreen());
int b = (int) (coef * leftColor.getBlue() + (1.0 - coef) * rightColor.getBlue());
// 填充色块
graphics.setColor(new Color(r, g, b));
graphics.fillRect(cubeStartX + col * ImageCreator.CUBE_DIMENSION, cubeStartY + row
* ImageCreator.CUBE_DIMENSION, ImageCreator.CUBE_DIMENSION, ImageCreator.CUBE_DIMENSION);
// 绘置色块边框
graphics.setColor(new Color(255 - (int) (0.95 * (255 - r)), 255 - (int) (0.9 * (255 - g)),
255 - (int) (0.9 * (255 - b))));
graphics.drawRect(cubeStartX + col * ImageCreator.CUBE_DIMENSION, cubeStartY + row
* ImageCreator.CUBE_DIMENSION,
ImageCreator.CUBE_DIMENSION,
ImageCreator.CUBE_DIMENSION);
}
}
return image;
}
}
</td> </tr> </table>现在,我们的 CubeMenuBar.java 应该写为如下:
<table cellPadding=5 class=CodeBlock> <tr> <td width="654">public class CubeMenuBar extends JMenuBar {
protected final void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(ImageCreator.getGradientCubesImage(this.getWidth(), this.getHeight(), ImageCreator.mainMidColor,
ImageCreator.mainUltraDarkColor, (int) (0.6 * this.getWidth()), (int) (0.9 * this.getWidth())), 0, 0, null);
}
}
</td> </tr> </table>只通过如此简单的几句代码,我们就可以得到了带和过度色块的菜单栏,是不是很有意思呢?
同样,我们在这个小程序中对 JMenu 和 JMenuItem 进行重绘,就可以得到最终的结果,然而,这个程序还有一点点小缺陷,当重绘的文字过大的时候,文字的边缘出现的锯齿,这是 Graphics 类的通病,为以,我们可以再做修改:
<table cellPadding=5 class=CodeBlock> <tr> <td width="640">// CubeMenu.ajva
public class CubeMenu extends JMenu {
protected final void paintComponent(Graphics g) {
Graphics2D graphics = (Graphics2D) g;
Object oldHint = graphics
.getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING);
graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
super.paintComponent(graphics);
graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, oldHint);
graphics.setColor(ImageCreator.mainMidColor);
graphics.setBackground(ImageCreator.mainMidColor);
graphics.fillRect(0, 0, this.getWidth(), this.getHeight());
int x = (this.getWidth() - graphics.getFontMetrics().stringWidth(this.getText())) / 4;
int y = (int) (graphics.getFontMetrics().getLineMetrics(this.getText(), graphics).getHeight());
graphics.setColor(Color.black);
graphics.drawString(this.getText(), x + 1, y + 1);
graphics.setColor(Color.white);
graphics.drawString(this.getText(), x, y);
}
}
</td> </tr> </table>在上述代码中,我们首先将 Graphics 对象转换为了 Graphics2D 对象,这样可以充分利用 Graphics2D 带来的更多特性。然后使用
Object oldHint = graphics.getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING);
将控件原来绘制的渲染方式进行了保存,通过
graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
来为文字添加反锯齿绘制方式,最后再使用
graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, oldHint);
恢复控件原来的绘制方式。
↑返回目录
前一篇: 看看别人是怎样求质数的!
后一篇: java反射