基本绘图
最后修改于 2023 年 7 月 17 日
在本篇 Java 2D 教程中,我们将进行一些基础绘图。
点
最简单的图形图元是点。它是窗口上的一个单独的点。有一个 Point 类用于表示坐标空间中的一个点,但没有绘制点的方法。为了绘制一个点,我们使用了 drawLine
方法,并将同一个点作为该方法的两个参数传入。
package com.zetcode; import java.awt.Color; import java.awt.EventQueue; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.util.Random; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.Timer; class Surface extends JPanel implements ActionListener { private final int DELAY = 150; private Timer timer; public Surface() { initTimer(); } private void initTimer() { timer = new Timer(DELAY, this); timer.start(); } public Timer getTimer() { return timer; } private void doDrawing(Graphics g) { Graphics2D g2d = (Graphics2D) g; g2d.setPaint(Color.blue); int w = getWidth(); int h = getHeight(); Random r = new Random(); for (int i = 0; i < 2000; i++) { 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); } @Override public void actionPerformed(ActionEvent e) { repaint(); } } public class PointsEx extends JFrame { public PointsEx() { initUI(); } private void initUI() { final Surface surface = new Surface(); add(surface); addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { Timer timer = surface.getTimer(); timer.stop(); } }); setTitle("Points"); setSize(350, 250); setLocationRelativeTo(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { @Override public void run() { PointsEx ex = new PointsEx(); ex.setVisible(true); } }); } }
该示例在窗口上随机绘制 2000 个点。使用计时器循环绘制点。
private void initTimer() { timer = new Timer(DELAY, this); timer.start(); }
使用 javax.swing.Timer
创建动画。它会在指定的时间间隔触发 ActionEvents
。
g2d.setPaint(Color.blue);
点以蓝色绘制。
int w = getWidth(); int h = getHeight();
我们获取组件的宽度和高度。
Random r = new Random(); int x = Math.abs(r.nextInt()) % w; int y = Math.abs(r.nextInt()) % h;
我们在上面计算出的区域大小范围内获取一个随机数。
g2d.drawLine(x, y, x, y);
这里我们绘制点。如前所述,我们使用 drawLine
方法。我们将同一个点指定了两次。
@Override public void actionPerformed(ActionEvent e) { repaint(); }
每次动作事件,我们调用 repaint
方法。这会导致整个客户区被重绘。
addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { Timer timer = surface.getTimer(); timer.stop(); } });
当窗口即将关闭时,我们检索计时器并使用其 stop
方法将其停止。未显式取消的计时器可能会无限期地占用资源。EXIT_ON_CLOSE
默认关闭操作会关闭 JVM 及其所有线程,因此对我们的示例来说不是必需的。但作为良好的编程习惯和提醒,我们仍然这样做。

线条
线是简单的图形图元。线是连接两个点的对象。线使用 drawLine
方法绘制。
package com.zetcode; import java.awt.EventQueue; import java.awt.Graphics; import java.awt.Graphics2D; import javax.swing.JFrame; import javax.swing.JPanel; class Surface extends JPanel { private void doDrawing(Graphics g) { Graphics2D g2d = (Graphics2D) g; g2d.drawLine(30, 30, 200, 30); g2d.drawLine(200, 30, 30, 200); g2d.drawLine(30, 200, 200, 200); g2d.drawLine(200, 200, 30, 30); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); doDrawing(g); } } public class LinesEx extends JFrame { public LinesEx() { initUI(); } private void initUI() { add(new Surface()); setTitle("Lines"); setSize(350, 250); setLocationRelativeTo(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { @Override public void run() { LinesEx ex = new LinesEx(); ex.setVisible(true); } }); } }
我们绘制一个包含四条线的简单对象。
g2d.drawLine(30, 30, 200, 30);
绘制一条直线。方法的参数是两个点的 x、y 坐标。

BasicStroke
BasicStroke
类定义了一组用于图形图元轮廓的基本渲染属性。这些渲染属性包括宽度、端点样式、连接样式、斜接限制和虚线。
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 Surface extends JPanel { private void doDrawing(Graphics g) { Graphics2D g2d = (Graphics2D) g.create(); 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); BasicStroke bs1 = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 1.0f, dash1, 2f); BasicStroke bs2 = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 1.0f, dash2, 2f); BasicStroke bs3 = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 1.0f, dash3, 2f); BasicStroke 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); g2d.dispose(); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); doDrawing(g); } } public class BasicStrokesEx extends JFrame { public BasicStrokesEx() { initUI(); } private void initUI() { add(new Surface()); setTitle("Basic strokes"); setSize(280, 270); setLocationRelativeTo(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { @Override public void run() { BasicStrokesEx ex = new BasicStrokesEx(); ex.setVisible(true); } }); } }
在此示例中,我们展示了各种虚线类型。虚线属性是一种模式,通过混合不透明和透明部分来创建。
Graphics2D g2d = (Graphics2D) g.create();
我们将更改 Graphics
对象的笔触属性;因此,我们处理的是 Graphics
对象的一个副本。(请记住,如果要更改字体、颜色或渲染提示以外的属性,则必须创建副本。)
float[] dash1 = { 2f, 0f, 2f }; float[] dash2 = { 1f, 1f, 1f }; float[] dash3 = { 4f, 0f, 2f }; float[] dash4 = { 4f, 4f, 1f };
这里我们定义了四种不同的虚线模式。
BasicStroke bs1 = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 1.0f, dash1, 2f );
此行构造一个 BasicStroke
对象。
g2d.setStroke(bs1);
我们使用 setStroke
方法将 BasicStroke
应用于当前图形上下文。
g2d.drawLine(20, 80, 250, 80);
使用 drawLine
方法绘制一条线。
g2d.dispose();
最后,我们释放 Graphics
对象的副本。

端点样式
端点样式是应用于未闭合子路径和虚线段末端的装饰。Java 2D 中有三种不同的端点样式:CAP_BUTT
、CAP_ROUND
和 CAP_SQUARE
。
CAP_BUTT
— 未闭合子路径和虚线段的末端没有额外的装饰。CAP_ROUND
— 未闭合子路径和虚线段的末端带有圆形装饰,其半径等于画笔宽度的一半。CAP_SQUARE
— 未闭合子路径和虚线段的末端带有方形投影,该投影延伸到段的末端之外,距离等于线宽的一半。
package com.zetcode; import java.awt.BasicStroke; import java.awt.EventQueue; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; import javax.swing.JFrame; import javax.swing.JPanel; class Surface extends JPanel { private void doDrawing(Graphics g) { Graphics2D g2d = (Graphics2D) g.create(); RenderingHints rh = new RenderingHints( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); rh.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); g2d.setRenderingHints(rh); BasicStroke bs1 = new BasicStroke(8, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL); g2d.setStroke(bs1); g2d.drawLine(20, 30, 250, 30); BasicStroke bs2 = new BasicStroke(8, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL); g2d.setStroke(bs2); g2d.drawLine(20, 80, 250, 80); BasicStroke bs3 = new BasicStroke(8, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_BEVEL); g2d.setStroke(bs3); g2d.drawLine(20, 130, 250, 130); BasicStroke bs4 = new BasicStroke(); g2d.setStroke(bs4); g2d.drawLine(20, 20, 20, 140); g2d.drawLine(250, 20, 250, 140); g2d.drawLine(254, 20, 254, 140); g2d.dispose(); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); doDrawing(g); } } public class CapsEx extends JFrame { public CapsEx() { initUI(); } private void initUI() { add(new Surface()); setTitle("Caps"); setSize(280, 270); setLocationRelativeTo(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { @Override public void run() { CapsEx ex = new CapsEx(); ex.setVisible(true); } }); } }
在我们的示例中,我们展示了所有三种类型的端点样式。
BasicStroke bs1 = new BasicStroke(8, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL); g2d.setStroke(bs1);
创建并应用了具有直角端点的基本笔触。CAP_BUTT
不添加任何装饰。
g2d.drawLine(20, 20, 20, 140); g2d.drawLine(250, 20, 250, 140); g2d.drawLine(254, 20, 254, 140);
我们绘制三条垂直线来解释端点样式之间的区别。具有 CAP_ROUND
和 CAP_SQUARE
的线条比具有 CAP_BUTT
的线条更大。具体大多少取决于线条尺寸。在我们的例子中,线条厚度为 8 像素。线条大了 8 像素——左边 4 像素,右边 4 像素。从图中应该很清楚。

连接样式
线连接样式是应用于两个路径段的交点以及子路径端点的交点的装饰。有三种装饰:JOIN_BEVEL
、JOIN_MITER
和 JOIN_ROUND
。
JOIN_BEVEL
— 通过用直线段连接其宽轮廓的外角来连接路径段。JOIN_MITER
— 通过延伸其外边缘直到它们相遇来连接路径段。JOIN_ROUND
— 通过将拐角圆化为线宽一半的半径来连接路径段。
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 Surface extends JPanel { private void doDrawing(Graphics g) { Graphics2D g2d = (Graphics2D) g.create(); BasicStroke bs1 = new BasicStroke(8, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL); g2d.setStroke(bs1); g2d.drawRect(15, 15, 80, 50); BasicStroke bs2 = new BasicStroke(8, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER); g2d.setStroke(bs2); g2d.drawRect(125, 15, 80, 50); BasicStroke bs3 = new BasicStroke(8, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND); g2d.setStroke(bs3); g2d.drawRect(235, 15, 80, 50); g2d.dispose(); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); doDrawing(g); } } public class JoinsEx extends JFrame { public JoinsEx() { initUI(); } private void initUI() { add(new Surface()); setTitle("Joins"); setSize(340, 110); setLocationRelativeTo(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { @Override public void run() { JoinsEx ex = new JoinsEx(); ex.setVisible(true); } }); } }
此代码示例展示了三种不同的线连接样式。
BasicStroke bs1 = new BasicStroke(8, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL); g2d.setStroke(bs1); g2d.drawRect(15, 15, 80, 50);
这里我们创建了一个带有 JOIN_BEVEL
连接的矩形。

在本篇 Java 2D 教程中,我们进行了一些基础绘图。