Swing 中的绘图
最后修改日期:2024 年 7 月 10 日
Swing 的绘图系统能够渲染矢量图形、图像和基于轮廓字体的文本。
当我们需要更改或增强现有的控件,或者从头开始创建一个自定义控件时,需要在应用程序中使用绘图。为了进行绘图,我们使用 Swing 工具包提供的绘图 API。
绘图在 paintComponent
方法中完成。在绘图过程中,我们使用 Graphics2D
对象。
Swing 2D 矢量图形
有两种不同的计算机图形:矢量图形和光栅图形。光栅图形将图像表示为像素的集合。矢量图形是使用几何图元(如点、线、曲线或多边形)来表示图像。这些图元是使用数学方程式创建的。
两种类型的计算机图形都有优点和缺点。矢量图形相对于光栅图形的优点是
- 尺寸更小
- 可以无限缩放
- 移动、缩放、填充或旋转不会降低图像的质量
图元类型
- 点
- 线
- 折线
- 多边形
- 圆形
- 椭圆
- 样条曲线
Swing 绘制点
最简单的图形图元是点。它是在窗口上的一个单点。Swing 中没有绘制点的方法。要绘制点,我们使用 drawLine
方法。我们将一个点使用两次。
package com.zetcode; import javax.swing.JFrame; import javax.swing.JPanel; import java.awt.Color; import java.awt.EventQueue; import java.awt.Graphics; import java.awt.Graphics2D; import java.util.Random; class DrawPanel extends JPanel { private void doDrawing(Graphics g) { var g2d = (Graphics2D) g; g2d.setColor(Color.blue); for (int i = 0; i <= 1000; i++) { var size = getSize(); var insets = getInsets(); int w = size.width - insets.left - insets.right; int h = size.height - insets.top - insets.bottom; var r = new Random(); int x = Math.abs(r.nextInt()) % w; int y = Math.abs(r.nextInt()) % h; g2d.drawLine(x, y, x, y); } } @Override public void paintComponent(Graphics g) { super.paintComponent(g); doDrawing(g); } } public class PointsEx extends JFrame { public PointsEx() { initUI(); } private void initUI() { var drawPanel = new DrawPanel(); add(drawPanel); setSize(350, 250); setTitle("Points"); setLocationRelativeTo(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String[] args) { EventQueue.invokeLater(() -> { var ex = new PointsEx(); ex.setVisible(true); }); } }
一个点很难观察到;因此,我们在面板表面随机绘制一千个点。
class DrawPanel extends JPanel {
我们正在一个自定义绘图面板上绘图,该面板是一个 JPanel
组件。稍后,将绘图面板添加到 JFrame
组件。
@Override public void paintComponent(Graphics g) { super.paintComponent(g); doDrawing(g); }
自定义绘图在 paintComponent
方法内部执行,我们对其进行覆盖。super.paintComponent
方法调用父类的方法。它做了一些必要的工作来为绘图准备组件。实际的绘图委托给 doDrawing
方法。
var g2d = (Graphics2D) g;
Swing 中的绘图是在 Graphics2D
对象上完成的。
g2d.setColor(Color.blue);
我们将用蓝色绘制我们的点。
var size = getSize(); var insets = getInsets();
窗口的大小包括边框和标题栏。我们不在那里绘图。
int w = size.width - insets.left - insets.right; int h = size.height - insets.top - insets.bottom;
在这里,我们计算将有效绘制我们的点的区域。
var r = new Random(); int x = Math.abs(r.nextInt()) % w; int y = Math.abs(r.nextInt()) % h;
我们在上面计算的区域大小范围内获得一个随机数。
g2d.drawLine(x, y, x, y);
这里我们绘制点。正如我们已经说过的那样,我们使用 drawLine
方法。我们指定同一个点两次。

Swing 绘制线
线是一个简单的图形图元。它使用两个点绘制。
package com.zetcode; import java.awt.BasicStroke; import java.awt.EventQueue; import java.awt.Graphics; import java.awt.Graphics2D; import javax.swing.JFrame; import javax.swing.JPanel; class DrawPanel extends JPanel { private void doDrawing(Graphics g) { var g2d = (Graphics2D) g; float[] dash1 = {2f, 0f, 2f}; float[] dash2 = {1f, 1f, 1f}; float[] dash3 = {4f, 0f, 2f}; float[] dash4 = {4f, 4f, 1f}; g2d.drawLine(20, 40, 250, 40); var bs1 = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 1.0f, dash1, 2f); var bs2 = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 1.0f, dash2, 2f); var bs3 = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 1.0f, dash3, 2f); var bs4 = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 1.0f, dash4, 2f); g2d.setStroke(bs1); g2d.drawLine(20, 80, 250, 80); g2d.setStroke(bs2); g2d.drawLine(20, 120, 250, 120); g2d.setStroke(bs3); g2d.drawLine(20, 160, 250, 160); g2d.setStroke(bs4); g2d.drawLine(20, 200, 250, 200); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); doDrawing(g); } } public class LinesEx extends JFrame { public LinesEx() { initUI(); } private void initUI() { var drawPanel = new DrawPanel(); add(drawPanel); setSize(280, 270); setTitle("Lines"); setLocationRelativeTo(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String[] args) { EventQueue.invokeLater(() -> { var ex = new LinesEx(); ex.setVisible(true); }); } }
在这个例子中,我们绘制了五条线。第一条线使用默认值绘制。其他线将具有不同的“笔触”。笔触是使用 BasicStroke
类创建的。它定义了图形图元轮廓的基本渲染属性集。
float[] dash1 = { 2f, 0f, 2f };
这里我们创建一个虚线,我们在笔触对象中使用它。
var bs1 = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 1.0f, dash1, 2f )
此代码创建一个笔触。笔触定义了线宽、端盖、线连接、斜接限制、虚线和虚线相位。

Swing 绘制矩形
要绘制矩形,我们使用 drawRect
方法。要用当前颜色填充矩形,我们使用 fillRect
方法。
package com.zetcode; import java.awt.Color; import java.awt.EventQueue; import java.awt.Graphics; import java.awt.Graphics2D; import javax.swing.JFrame; import javax.swing.JPanel; class DrawPanel extends JPanel { private void doDrawing(Graphics g) { var g2d = (Graphics2D) g; g2d.setColor(new Color(212, 212, 212)); g2d.drawRect(10, 15, 90, 60); g2d.drawRect(130, 15, 90, 60); g2d.drawRect(250, 15, 90, 60); g2d.drawRect(10, 105, 90, 60); g2d.drawRect(130, 105, 90, 60); g2d.drawRect(250, 105, 90, 60); g2d.drawRect(10, 195, 90, 60); g2d.drawRect(130, 195, 90, 60); g2d.drawRect(250, 195, 90, 60); g2d.setColor(new Color(125, 167, 116)); g2d.fillRect(10, 15, 90, 60); g2d.setColor(new Color(42, 179, 231)); g2d.fillRect(130, 15, 90, 60); g2d.setColor(new Color(70, 67, 123)); g2d.fillRect(250, 15, 90, 60); g2d.setColor(new Color(130, 100, 84)); g2d.fillRect(10, 105, 90, 60); g2d.setColor(new Color(252, 211, 61)); g2d.fillRect(130, 105, 90, 60); g2d.setColor(new Color(241, 98, 69)); g2d.fillRect(250, 105, 90, 60); g2d.setColor(new Color(217, 146, 54)); g2d.fillRect(10, 195, 90, 60); g2d.setColor(new Color(63, 121, 186)); g2d.fillRect(130, 195, 90, 60); g2d.setColor(new Color(31, 21, 1)); g2d.fillRect(250, 195, 90, 60); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); doDrawing(g); } } public class RectanglesEx extends JFrame { public RectanglesEx() { initUI(); } private void initUI() { var drawPanel = new DrawPanel(); add(drawPanel); setSize(360, 300); setTitle("Rectangles"); setLocationRelativeTo(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String[] args) { EventQueue.invokeLater(() -> { var ex = new RectanglesEx(); ex.setVisible(true); }); } }
在这个例子中,我们绘制了九个彩色矩形。
g2d.setColor(new Color(212, 212, 212)); g2d.drawRect(10, 15, 90, 60); ...
我们将矩形轮廓的颜色设置为柔和的灰色,以免干扰填充颜色。要绘制矩形的轮廓,我们使用 drawRect
方法。前两个参数是 x 和 y 值。第三个和第四个是宽度和高度。
g2d.fillRect(10, 15, 90, 60);
要用颜色填充矩形,我们使用 fillRect
方法。

Swing 使用纹理
纹理是应用于形状的位图图像。要在 Java 2D 中使用纹理,我们使用 TexturePaint
类。
package com.zetcode; import javax.imageio.ImageIO; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.JPanel; import java.awt.EventQueue; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.TexturePaint; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; class DrawingPanel extends JPanel { private BufferedImage slate; private BufferedImage java; private BufferedImage pane; public DrawingPanel() { loadImages(); } private void loadImages() { try { slate = ImageIO.read(new File("src/resources/slate.png")); java = ImageIO.read(new File("src/resources/java.png")); pane = ImageIO.read(new File("src/resources/pane.png")); } catch (IOException ex) { JOptionPane.showMessageDialog(this, "Could not load images", "Error", JOptionPane.ERROR_MESSAGE); System.exit(1); } } private void doDrawing(Graphics g) { var g2d = (Graphics2D) g.create(); var slateTp = new TexturePaint(slate, new Rectangle(0, 0, 90, 60)); var javaTp = new TexturePaint(java, new Rectangle(0, 0, 90, 60)); var paneTp = new TexturePaint(pane, new Rectangle(0, 0, 90, 60)); g2d.setPaint(slateTp); g2d.fillRect(10, 15, 90, 60); g2d.setPaint(javaTp); g2d.fillRect(130, 15, 90, 60); g2d.setPaint(paneTp); g2d.fillRect(250, 15, 90, 60); g2d.dispose(); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); doDrawing(g); } } public class TexturesEx extends JFrame { public TexturesEx() { initUI(); } private void initUI() { var drawingPanel = new DrawingPanel(); add(drawingPanel); setTitle("Textures"); setSize(360, 120); setLocationRelativeTo(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String[] args) { EventQueue.invokeLater(() -> { var ex = new TexturesEx(); ex.setVisible(true); }); } }
在代码示例中,我们用三种不同的纹理填充三个矩形。
private BufferedImage slate; private BufferedImage java; private BufferedImage pane;
BufferedImage
是存储在内存中的像素矩形。它是 Swing 中最重要的图像类型之一。许多 Swing 方法返回一个 BufferedImage
来使用。
slate = ImageIO.read(new File("src/resources/slate.png"));
在这里,我们使用 ImageIO.read
方法将图像读入缓冲图像。它接受一个 File
对象并返回一个 BufferedImage
。
slateTp = new TexturePaint(slate, new Rectangle(0, 0, 90, 60));
我们从缓冲图像创建一个 TexturePaint
类。
g2d.setPaint(slateTp); g2d.fillRect(10, 15, 90, 60);
我们用纹理填充一个矩形。

使用渐变
在计算机图形学中,渐变是从亮到暗或从一种颜色到另一种颜色的平滑混合。在 2D 绘图程序和绘画程序中,渐变用于创建彩色背景和特殊效果,以及模拟光和阴影。
package com.zetcode; import java.awt.Color; import java.awt.EventQueue; import java.awt.GradientPaint; import java.awt.Graphics; import java.awt.Graphics2D; import javax.swing.JFrame; import javax.swing.JPanel; class DrawPanel extends JPanel { private void doDrawing(Graphics g) { var g2d = (Graphics2D) g; var gp1 = new GradientPaint(5, 5, Color.red, 20, 20, Color.black, true); g2d.setPaint(gp1); g2d.fillRect(20, 20, 300, 40); var gp2 = new GradientPaint(5, 25, Color.yellow, 20, 2, Color.black, true); g2d.setPaint(gp2); g2d.fillRect(20, 80, 300, 40); var gp3 = new GradientPaint(5, 25, Color.green, 2, 2, Color.black, true); g2d.setPaint(gp3); g2d.fillRect(20, 140, 300, 40); var gp4 = new GradientPaint(25, 25, Color.blue, 15, 25, Color.black, true); g2d.setPaint(gp4); g2d.fillRect(20, 200, 300, 40); var gp5 = new GradientPaint(0, 0, Color.orange, 0, 20, Color.black, true); g2d.setPaint(gp5); g2d.fillRect(20, 260, 300, 40); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); doDrawing(g); } } public class GradientsEx extends JFrame { public GradientsEx() { initUI(); } private void initUI() { var drawPanel = new DrawPanel(); add(drawPanel); setSize(350, 350); setTitle("Gradients"); setLocationRelativeTo(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String[] args) { EventQueue.invokeLater(() -> { var ex = new GradientsEx(); ex.setVisible(true); }); } }
我们的代码示例展示了五个带有渐变的矩形。
var gp4 = new GradientPaint(25, 25, Color.blue, 15, 25, Color.black, true);
要使用渐变,我们使用 Java Swing 的 GradientPaint
类。通过操纵颜色值和开始和结束点,我们可以获得不同类型的渐变。
g2d.setPaint(gp5);
渐变通过调用 setPaint
方法来激活。

绘制文本
绘图使用 drawString
方法完成。我们指定要绘制的字符串以及文本在窗口区域中的位置。
package com.zetcode; import javax.swing.JFrame; import javax.swing.JPanel; import java.awt.EventQueue; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; class DrawPanel extends JPanel { private void doDrawing(Graphics g) { var g2d = (Graphics2D) g; var rh = new RenderingHints( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); rh.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); g2d.setRenderingHints(rh); var font = new Font("URW Chancery L", Font.BOLD, 21); g2d.setFont(font); g2d.drawString("Not marble, nor the gilded monuments", 20, 30); g2d.drawString("Of princes, shall outlive this powerful rhyme;", 20, 60); g2d.drawString("But you shall shine more bright in these contents", 20, 90); g2d.drawString("Than unswept stone, besmear'd with sluttish time.", 20, 120); g2d.drawString("When wasteful war shall statues overturn,", 20, 150); g2d.drawString("And broils root out the work of masonry,", 20, 180); g2d.drawString("Nor Mars his sword, nor war's quick " + "fire shall burn", 20, 210); g2d.drawString("The living record of your memory.", 20, 240); g2d.drawString("'Gainst death, and all oblivious enmity", 20, 270); g2d.drawString("Shall you pace forth; your praise shall still " + "find room", 20, 300); g2d.drawString("Even in the eyes of all posterity", 20, 330); g2d.drawString("That wear this world out to the ending doom.", 20, 360); g2d.drawString("So, till the judgment that yourself arise,", 20, 390); g2d.drawString("You live in this, and dwell in lovers' eyes.", 20, 420); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); doDrawing(g); } } public class DrawingTextEx extends JFrame { public DrawingTextEx() { initUI(); } private void initUI() { var drawPanel = new DrawPanel(); add(drawPanel); setSize(500, 470); setTitle("Sonnet55"); setLocationRelativeTo(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String[] args) { EventQueue.invokeLater(() -> { var ex = new DrawingTextEx(); ex.setVisible(true); }); } }
在我们的示例中,我们在面板组件上绘制十四行诗。
var rh = new RenderingHints( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); rh.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); g2d.setRenderingHints(rh);
此代码用于使我们的文本看起来更好。我们使用 RenderingHints
应用一种称为“抗锯齿”的技术。
var font = new Font("URW Chancery L", Font.BOLD, 21); g2d.setFont(font);
我们为我们的文本选择一种特定的字体。
g2d.drawString("Not marble, nor the gilded monuments", 20, 30);
这是绘制文本的代码。
绘制图像
工具包最重要的功能之一是显示图像的能力。图像是像素的数组。每个像素代表给定位置的颜色。我们可以使用 JLabel
等组件来显示图像,或者我们可以使用*Java 2D API*来绘制它。
package com.zetcode; import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.JPanel; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; class DrawPanel extends JPanel { private Image myImage; public DrawPanel() { initPanel(); } private void initPanel() { loadImage(); var dm = new Dimension(myImage.getWidth(null), myImage.getHeight(null)); setPreferredSize(dm); } private void loadImage() { myImage = new ImageIcon("src/resources/icesid.jpg").getImage(); } private void doDrawing(Graphics g) { var g2d = (Graphics2D) g; g2d.drawImage(myImage, 0, 0, null); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); doDrawing(g); } } public class DrawImageEx extends JFrame { public DrawImageEx() { initUI(); } private void initUI() { var drawPanel = new DrawPanel(); add(drawPanel); setTitle("Image"); pack(); setLocationRelativeTo(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String[] args) { EventQueue.invokeLater(() -> { var ex = new DrawImageEx(); ex.setVisible(true); }); } }
此示例将在面板上绘制一个图像。该图像将适合 JFrame
窗口。
private void initPanel() { loadImage(); var dm = new Dimension(img.getWidth(null), img.getHeight(null)); setPreferredSize(dm); }
在 initPanel
方法中,我们调用 loadImage
方法。我们确定图像尺寸并设置面板组件的首选大小。这将与 pack
方法一起显示将完全适合窗口的图像。
private void loadImage() { myImage = new ImageIcon("src/resources/icesid.jpg").getImage(); }
该方法从磁盘加载图像。我们使用 ImageIcon
类。这个类简化了 Java Swing 中图像的处理。
g2d.drawImage(this.img, 0, 0, null);
图像使用 drawImage
方法绘制。
在本章中,我们在 Java Swing 中做了一些绘图。请访问 ZetCode 的 Java 2D 教程,获取有关 Swing 中绘图的更多信息。