效果
最后修改于 2023 年 7 月 17 日
在本部分 Java 2D 编程教程中,我们介绍了一些效果。
气泡
在第一个示例中,我们看到了在屏幕上随机出现和消失的、不断变大的彩色气泡。该示例来自 Java 2D 演示。
package com.zetcode; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.geom.Ellipse2D; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.Timer; class Surface extends JPanel implements ActionListener { private final Color colors[] = { Color.blue, Color.cyan, Color.green, Color.magenta, Color.orange, Color.pink, Color.red, Color.yellow, Color.lightGray, Color.white }; private Ellipse2D.Float[] ellipses; private double esize[]; private float estroke[]; private double maxSize = 0; private final int NUMBER_OF_ELLIPSES = 25; private final int DELAY = 30; private final int INITIAL_DELAY = 150; private Timer timer; public Surface() { initSurface(); initEllipses(); initTimer(); } private void initSurface() { setBackground(Color.black); ellipses = new Ellipse2D.Float[NUMBER_OF_ELLIPSES]; esize = new double[ellipses.length]; estroke = new float[ellipses.length]; } private void initEllipses() { int w = 350; int h = 250; maxSize = w / 10; for (int i = 0; i < ellipses.length; i++) { ellipses[i] = new Ellipse2D.Float(); posRandEllipses(i, maxSize * Math.random(), w, h); } } private void initTimer() { timer = new Timer(DELAY, this); timer.setInitialDelay(INITIAL_DELAY); timer.start(); } private void posRandEllipses(int i, double size, int w, int h) { esize[i] = size; estroke[i] = 1.0f; double x = Math.random() * (w - (maxSize / 2)); double y = Math.random() * (h - (maxSize / 2)); ellipses[i].setFrame(x, y, size, size); } private void doStep(int w, int h) { for (int i = 0; i < ellipses.length; i++) { estroke[i] += 0.025f; esize[i]++; if (esize[i] > maxSize) { posRandEllipses(i, 1, w, h); } else { ellipses[i].setFrame(ellipses[i].getX(), ellipses[i].getY(), esize[i], esize[i]); } } } private void drawEllipses(Graphics2D g2d) { for (int i = 0; i < ellipses.length; i++) { g2d.setColor(colors[i % colors.length]); g2d.setStroke(new BasicStroke(estroke[i])); g2d.draw(ellipses[i]); } } 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); Dimension size = getSize(); doStep(size.width, size.height); drawEllipses(g2d); g2d.dispose(); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); doDrawing(g); } @Override public void actionPerformed(ActionEvent e) { repaint(); } } public class BubblesEx extends JFrame { public BubblesEx() { initUI(); } private void initUI() { add(new Surface()); setTitle("Bubbles"); setSize(350, 250); setLocationRelativeTo(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { @Override public void run() { BubblesEx ex = new BubblesEx(); ex.setVisible(true); } }); } }
这是气泡示例。
private final Color colors[] = { Color.blue, Color.cyan, Color.green, Color.magenta, Color.orange, Color.pink, Color.red, Color.yellow, Color.lightGray, Color.white };
这些颜色用于绘制气泡。
private void initSurface() { setBackground(Color.black); ellipses = new Ellipse2D.Float[NUMBER_OF_ELLIPSES]; esize = new double[ellipses.length]; estroke = new float[ellipses.length]; }
initSurface
方法为面板设置了黑色背景。我们创建了三个数组。一个用于椭圆(圆是椭圆的特例),一个用于每个椭圆的大小,还有一个用于椭圆笔触。在动画期间,气泡的大小和笔触都会增长。
private void initEllipses() { int w = 350; int h = 250; maxSize = w / 10; for (int i = 0; i < ellipses.length; i++) { ellipses[i] = new Ellipse2D.Float(); posRandEllipses(i, maxSize * Math.random(), w, h); } }
ellipses
数组填充了椭圆对象。posRandEllipses
方法将椭圆对象随机放置在窗口上。椭圆的初始大小也是随机选择的。
private void initTimer() { timer = new Timer(DELAY, this); timer.setInitialDelay(INITIAL_DELAY); timer.start(); }
创建并启动了一个计时器对象。它用于创建动画。
private void posRandEllipses(int i, double size, int w, int h) { esize[i] = size; estroke[i] = 1.0f; double x = Math.random() * (w - (maxSize / 2)); double y = Math.random() * (h - (maxSize / 2)); ellipses[i].setFrame(x, y, size, size); }
posRandEllipses
方法将椭圆随机放置在窗口上。esize
和 estroke
数组被填充了值。setFrame
方法设置了椭圆的框架矩形的位置和大小。
private void doStep(int w, int h) { for (int i = 0; i < ellipses.length; i++) { estroke[i] += 0.025f; esize[i]++; if (esize[i] > maxSize) { posRandEllipses(i, 1, w, h); } else { ellipses[i].setFrame(ellipses[i].getX(), ellipses[i].getY(), esize[i], esize[i]); } } }
动画由多个步骤组成。在每个步骤中,我们增加每个椭圆的笔触和大小值。当气泡达到最大尺寸后,它将被重置为最小尺寸,并在面板上随机重新定位。否则,它将以增加的值显示。
private void drawEllipses(Graphics2D g2d) { for (int i = 0; i < ellipses.length; i++) { g2d.setColor(colors[i % colors.length]); g2d.setStroke(new BasicStroke(estroke[i])); g2d.draw(ellipses[i]); } }
drawEllipses
方法在面板上绘制数组中的所有椭圆。
Dimension size = getSize(); doStep(size.width, size.height);
在 doDrawing
方法中,我们计算面板的大小。如果窗口被调整大小,气泡将随机分布在整个窗口区域。
@Override public void actionPerformed(ActionEvent e) { repaint(); }
计时器对象以指定的时间间隔触发动作事件。repaint
方法会重绘面板组件。

星形
下一个示例显示了一个旋转和缩放的星星。
package com.zetcode; import java.awt.EventQueue; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.geom.GeneralPath; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.Timer; class Surface extends JPanel implements ActionListener { private final int points[][] = { {0, 85}, {75, 75}, {100, 10}, {125, 75}, {200, 85}, {150, 125}, {160, 190}, {100, 150}, {40, 190}, {50, 125}, {0, 85} }; private Timer timer; private double angle = 0; private double scale = 1; private double delta = 0.01; private final int DELAY = 10; public Surface() { initTimer(); } private void initTimer() { timer = new Timer(DELAY, this); timer.start(); } private void doDrawing(Graphics g) { int h = getHeight(); int w = getWidth(); Graphics2D g2d = (Graphics2D) g.create(); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); g2d.translate(w / 2, h / 2); GeneralPath star = new GeneralPath(); star.moveTo(points[0][0], points[0][1]); for (int k = 1; k < points.length; k++) { star.lineTo(points[k][0], points[k][1]); } g2d.rotate(angle); g2d.scale(scale, scale); g2d.fill(star); g2d.dispose(); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); doDrawing(g); } private void step() { if (scale < 0.01) { delta = -delta; } else if (scale > 0.99) { delta = -delta; } scale += delta; angle += 0.01; } @Override public void actionPerformed(ActionEvent e) { step(); repaint(); } } public class StarDemoEx extends JFrame { public StarDemoEx() { initUI(); } private void initUI() { add(new Surface()); setTitle("Star"); setSize(420, 250); setLocationRelativeTo(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { @Override public void run() { StarDemoEx ex = new StarDemoEx(); ex.setVisible(true); } }); } }
在此演示中,我们有一个星星。星星会旋转、变大然后变小。
private final int points[][] = { {0, 85}, {75, 75}, {100, 10}, {125, 75}, {200, 85}, {150, 125}, {160, 190}, {100, 150}, {40, 190}, {50, 125}, {0, 85} };
这些点用于绘制星形。
private double angle = 0; private double scale = 1; private double delta = 0.01;
angle
用于旋转星星。scale
因子决定了星星的大小。最后,delta
因子是缩放量的变化。
g2d.translate(w / 2, h / 2);
使用 translate
方法将坐标系移动到窗口中央。
GeneralPath star = new GeneralPath(); star.moveTo(points[0][0], points[0][1]); for (int k = 1; k < points.length; k++) { star.lineTo(points[k][0], points[k][1]); }
GeneralPath
用于创建星形。路径的第一个点使用 moveTo
方法添加。星形的后续点使用 lineTo
方法添加。
g2d.rotate(angle); g2d.scale(scale, scale);
我们执行旋转和缩放操作。
g2d.fill(star);
fill
方法填充星形的内部。
if (scale < 0.01) { delta = -delta; } else if (scale > 0.99) { delta = -delta; }
此代码控制星星的收缩和生长量。
喷雾
接下来我们展示一个烟雾效果。这种效果在 Flash 动画或电影片头中很常见。文本在屏幕上逐渐变大,一段时间后,它会慢慢消失。
package com.zetcode; import java.awt.AlphaComposite; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.Timer; class Surface extends JPanel implements ActionListener { private Timer timer; private int x = 1; private float alpha = 1; private final int DELAY = 15; private final int INITIAL_DELAY = 200; public Surface() { initTimer(); } private void initTimer() { timer = new Timer(DELAY, this); timer.setInitialDelay(INITIAL_DELAY); timer.start(); } 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); Font font = new Font("Dialog", Font.PLAIN, x); g2d.setFont(font); FontMetrics fm = g2d.getFontMetrics(); String s = "ZetCode"; Dimension size = getSize(); int w = (int) size.getWidth(); int h = (int) size.getHeight(); int stringWidth = fm.stringWidth(s); AlphaComposite ac = AlphaComposite.getInstance( AlphaComposite.SRC_OVER, alpha); g2d.setComposite(ac); g2d.drawString(s, (w - stringWidth) / 2, h / 2); g2d.dispose(); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); doDrawing(g); } private void step() { x += 1; if (x > 40) alpha -= 0.01; if (alpha <= 0.01) timer.stop(); } @Override public void actionPerformed(ActionEvent e) { step(); repaint(); } } public class PuffEx extends JFrame { public PuffEx() { initUI(); } private void initUI() { setTitle("Puff"); add(new Surface()); setSize(400, 300); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLocationRelativeTo(null); } public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { @Override public void run() { PuffEx ex = new PuffEx(); ex.setVisible(true); } }); } }
该示例在窗口中绘制一个不断增长的文本,从某个点开始,文本变得越来越透明,直到看不见。
Font font = new Font("Dialog", Font.PLAIN, x); g2d.setFont(font);
这是我们用于文本的字体。
FontMetrics fm = g2d.getFontMetrics();
getFontMetrics
返回 FontMetrics
类。该类存储特定屏幕上特定字体渲染的信息。
int stringWidth = fm.stringWidth(s);
我们使用 FontMetrics
对象的 stringWidth
方法来获取字符串的宽度。
AlphaComposite ac = AlphaComposite.getInstance( AlphaComposite.SRC_OVER, alpha); g2d.setComposite(ac);
这里我们设置了要绘制的文本的透明度。
g2d.drawString(s, (w - stringWidth) / 2, h / 2);
这行代码在窗口的(水平)中间绘制字符串。
if (x > 40) alpha -= 0.01;
当字符串的高度超过 40 点后,它开始淡出。
在本部分 Java 2D 教程中,我们实现了一些视觉效果。