TA的每日心情 | 开心 2021-3-12 23:18 |
---|
签到天数: 2 天 [LV.1]初来乍到
|
众所周知,使用 java 开发出来的应用程序在各种平台上有着相同的用户界面,这一切得归功于 SWING 良好的跨平台性(少数 AWT 控件在不同的平台上有着极为细小的差别)。并且, JAVA 还提供了 Look&Feel 和 Theme 让开发者对用户界面上的控件进行观感上的改变,这样可以做出更多更漂亮的用户界面。然而,当你仅仅是需要对某个控件进行特别的观感设置时,使用 L&F 或 Theme 可能过于复杂,也使得程序变得比较“庞大”,因此,我们可以通过控件重绘来完成这部分特殊的要求。 javax.swing.JComponent 是所有 SWING 控件的“祖宗”,在 JComponent 中, paintComponent(Graphics g) 方法是用来进行绘制控件的,因此,如果想要重绘 SWING 控件,只需要覆盖 paintComponent(Graphics g) 方法就可以了。如:
public class CubeMenuBar extends JMenuBar { void paintComponent(Graphics g) { // 这里写重绘代码 } }
下面,我们将以例子来实现控件的重绘,最终结果如下图: 首先,我们按照上图制作一个简单的小程序,相信这个不是一件太难的事情:
//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(); } }
( 程序运行如上图 ) 为了实现特殊的菜单栏,我们需要自己重写 JMenu 类,假设我们的类叫作 CubeMenuBar ,那么,我们只需要将 TestMenu.java 中的 JMenuBar cmbMenu = new JMenuBar(); 改为 CubeMenuBar cmbMenu = new CubeMenuBar(); 现在,我们看一看最简单的 CubeMenuBar 应该怎么写:
// CubeMenuBar.java public class CubeMenuBar extends JMenuBar { protected final void paintComponent(Graphics g) { super.paintComponent(g); } }
我们将 paintComponent(Graphics g) 申明为 protected final 是不愿继承的类再次改写重绘方法, super.paintComponent(g); 调用的父类的方法来进行重绘 MenuBar ,如果你写掉了这一句,呵呵,自己看看运行结果。 由于我们在这里只是研究如何重绘控件,因此,我提供一个现成的类,这个类负责生成带有过度色块的图像,具体不多说,只帖出代码:
// 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; } }
现在,我们的 CubeMenuBar.java 应该写为如下:
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); } }
只通过如此简单的几句代码,我们就可以得到了带和过度色块的菜单栏,是不是很有意思呢? 同样,我们在这个小程序中对 JMenu 和 JMenuItem 进行重绘,就可以得到最终的结果,然而,这个程序还有一点点小缺陷,当重绘的文字过大的时候,文字的边缘出现的锯齿,这是 Graphics 类的通病,为以,我们可以再做修改:
// 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); } }
在上述代码中,我们首先将 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); 恢复控件原来的绘制方式。
源码下载:http://file.javaxxz.com/2014/10/29/235548812.zip |
|