原文: https://zetcode.com/gfx/java2d/effects/

在 Java 2D 编程教程的这一部分中,我们将展示一些效果。

泡泡

在第一个示例中,我们将看到不断增长的彩色气泡,它们在屏幕上随机出现和消失。 该示例来自 Java 2D 演示。

BubblesEx.java

  1. package com.zetcode;
  2. import java.awt.BasicStroke;
  3. import java.awt.Color;
  4. import java.awt.Dimension;
  5. import java.awt.EventQueue;
  6. import java.awt.Graphics;
  7. import java.awt.Graphics2D;
  8. import java.awt.RenderingHints;
  9. import java.awt.event.ActionEvent;
  10. import java.awt.event.ActionListener;
  11. import java.awt.geom.Ellipse2D;
  12. import javax.swing.JFrame;
  13. import javax.swing.JPanel;
  14. import javax.swing.Timer;
  15. class Surface extends JPanel
  16. implements ActionListener {
  17. private final Color colors[] = {
  18. Color.blue, Color.cyan, Color.green,
  19. Color.magenta, Color.orange, Color.pink,
  20. Color.red, Color.yellow, Color.lightGray, Color.white
  21. };
  22. private Ellipse2D.Float[] ellipses;
  23. private double esize[];
  24. private float estroke[];
  25. private double maxSize = 0;
  26. private final int NUMBER_OF_ELLIPSES = 25;
  27. private final int DELAY = 30;
  28. private final int INITIAL_DELAY = 150;
  29. private Timer timer;
  30. public Surface() {
  31. initSurface();
  32. initEllipses();
  33. initTimer();
  34. }
  35. private void initSurface() {
  36. setBackground(Color.black);
  37. ellipses = new Ellipse2D.Float[NUMBER_OF_ELLIPSES];
  38. esize = new double[ellipses.length];
  39. estroke = new float[ellipses.length];
  40. }
  41. private void initEllipses() {
  42. int w = 350;
  43. int h = 250;
  44. maxSize = w / 10;
  45. for (int i = 0; i < ellipses.length; i++) {
  46. ellipses[i] = new Ellipse2D.Float();
  47. posRandEllipses(i, maxSize * Math.random(), w, h);
  48. }
  49. }
  50. private void initTimer() {
  51. timer = new Timer(DELAY, this);
  52. timer.setInitialDelay(INITIAL_DELAY);
  53. timer.start();
  54. }
  55. private void posRandEllipses(int i, double size, int w, int h) {
  56. esize[i] = size;
  57. estroke[i] = 1.0f;
  58. double x = Math.random() * (w - (maxSize / 2));
  59. double y = Math.random() * (h - (maxSize / 2));
  60. ellipses[i].setFrame(x, y, size, size);
  61. }
  62. private void doStep(int w, int h) {
  63. for (int i = 0; i < ellipses.length; i++) {
  64. estroke[i] += 0.025f;
  65. esize[i]++;
  66. if (esize[i] > maxSize) {
  67. posRandEllipses(i, 1, w, h);
  68. } else {
  69. ellipses[i].setFrame(ellipses[i].getX(), ellipses[i].getY(),
  70. esize[i], esize[i]);
  71. }
  72. }
  73. }
  74. private void drawEllipses(Graphics2D g2d) {
  75. for (int i = 0; i < ellipses.length; i++) {
  76. g2d.setColor(colors[i % colors.length]);
  77. g2d.setStroke(new BasicStroke(estroke[i]));
  78. g2d.draw(ellipses[i]);
  79. }
  80. }
  81. private void doDrawing(Graphics g) {
  82. Graphics2D g2d = (Graphics2D) g.create();
  83. RenderingHints rh
  84. = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
  85. RenderingHints.VALUE_ANTIALIAS_ON);
  86. rh.put(RenderingHints.KEY_RENDERING,
  87. RenderingHints.VALUE_RENDER_QUALITY);
  88. g2d.setRenderingHints(rh);
  89. Dimension size = getSize();
  90. doStep(size.width, size.height);
  91. drawEllipses(g2d);
  92. g2d.dispose();
  93. }
  94. @Override
  95. public void paintComponent(Graphics g) {
  96. super.paintComponent(g);
  97. doDrawing(g);
  98. }
  99. @Override
  100. public void actionPerformed(ActionEvent e) {
  101. repaint();
  102. }
  103. }
  104. public class BubblesEx extends JFrame {
  105. public BubblesEx() {
  106. initUI();
  107. }
  108. private void initUI() {
  109. add(new Surface());
  110. setTitle("Bubbles");
  111. setSize(350, 250);
  112. setLocationRelativeTo(null);
  113. setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  114. }
  115. public static void main(String[] args) {
  116. EventQueue.invokeLater(new Runnable() {
  117. @Override
  118. public void run() {
  119. BubblesEx ex = new BubblesEx();
  120. ex.setVisible(true);
  121. }
  122. });
  123. }
  124. }

这是泡泡的示例。

  1. private final Color colors[] = {
  2. Color.blue, Color.cyan, Color.green,
  3. Color.magenta, Color.orange, Color.pink,
  4. Color.red, Color.yellow, Color.lightGray, Color.white
  5. };

这些颜色用于绘制气泡。

  1. private void initSurface() {
  2. setBackground(Color.black);
  3. ellipses = new Ellipse2D.Float[NUMBER_OF_ELLIPSES];
  4. esize = new double[ellipses.length];
  5. estroke = new float[ellipses.length];
  6. }

initSurface()方法为面板设置黑色背景。 我们创建三个数组。 椭圆数组(椭圆是椭圆的一种特殊情况),每个椭圆大小的数组以及椭圆描边的数组。 动画期间,气泡的大小和笔触都会增加。

  1. private void initEllipses() {
  2. int w = 350;
  3. int h = 250;
  4. maxSize = w / 10;
  5. for (int i = 0; i < ellipses.length; i++) {
  6. ellipses[i] = new Ellipse2D.Float();
  7. posRandEllipses(i, maxSize * Math.random(), w, h);
  8. }
  9. }

ellipses数组填充有椭圆对象。 posRandEllipses()方法将椭圆对象随机放置在窗口上。 椭圆的初始大小也是随机选择的。

  1. private void initTimer() {
  2. timer = new Timer(DELAY, this);
  3. timer.setInitialDelay(INITIAL_DELAY);
  4. timer.start();
  5. }

将创建并启动一个计时器对象。 用于创建动画。

  1. private void posRandEllipses(int i, double size, int w, int h) {
  2. esize[i] = size;
  3. estroke[i] = 1.0f;
  4. double x = Math.random() * (w - (maxSize / 2));
  5. double y = Math.random() * (h - (maxSize / 2));
  6. ellipses[i].setFrame(x, y, size, size);
  7. }

posRandEllipses()方法将椭圆随机放置在窗口上。 esizeestroke数组填充有值。 setFrame()方法设置椭圆框架矩形的位置和大小。

  1. private void doStep(int w, int h) {
  2. for (int i = 0; i < ellipses.length; i++) {
  3. estroke[i] += 0.025f;
  4. esize[i]++;
  5. if (esize[i] > maxSize) {
  6. posRandEllipses(i, 1, w, h);
  7. } else {
  8. ellipses[i].setFrame(ellipses[i].getX(), ellipses[i].getY(),
  9. esize[i], esize[i]);
  10. }
  11. }
  12. }

动画包括步骤。 在每个步骤中,我们增加每个椭圆的笔触和大小值。 气泡达到最大大小后,将其重置为最小大小,并在面板上随机重新放置。 否则,将显示增加的值。

  1. private void drawEllipses(Graphics2D g2d) {
  2. for (int i = 0; i < ellipses.length; i++) {
  3. g2d.setColor(colors[i % colors.length]);
  4. g2d.setStroke(new BasicStroke(estroke[i]));
  5. g2d.draw(ellipses[i]);
  6. }
  7. }

drawEllipses()方法从面板上的数组绘制所有椭圆。

  1. Dimension size = getSize();
  2. doStep(size.width, size.height);

doDrawing()方法中,我们计算面板的大小。 如果调整窗口大小,气泡将随机分布在窗口的整个区域。

  1. @Override
  2. public void actionPerformed(ActionEvent e) {
  3. repaint();
  4. }

计时器对象以指定的时间间隔触发动作事件。 repaint()方法重新绘制面板组件。

特效 - 图1

图:泡泡

星星

下一个示例显示了一个旋转和缩放的星星。

StarDemoEx.java

  1. package com.zetcode;
  2. import java.awt.EventQueue;
  3. import java.awt.Graphics;
  4. import java.awt.Graphics2D;
  5. import java.awt.RenderingHints;
  6. import java.awt.event.ActionEvent;
  7. import java.awt.event.ActionListener;
  8. import java.awt.geom.GeneralPath;
  9. import javax.swing.JFrame;
  10. import javax.swing.JPanel;
  11. import javax.swing.Timer;
  12. class Surface extends JPanel
  13. implements ActionListener {
  14. private final int points[][] = {
  15. {0, 85}, {75, 75}, {100, 10}, {125, 75},
  16. {200, 85}, {150, 125}, {160, 190}, {100, 150},
  17. {40, 190}, {50, 125}, {0, 85}
  18. };
  19. private Timer timer;
  20. private double angle = 0;
  21. private double scale = 1;
  22. private double delta = 0.01;
  23. private final int DELAY = 10;
  24. public Surface() {
  25. initTimer();
  26. }
  27. private void initTimer() {
  28. timer = new Timer(DELAY, this);
  29. timer.start();
  30. }
  31. private void doDrawing(Graphics g) {
  32. int h = getHeight();
  33. int w = getWidth();
  34. Graphics2D g2d = (Graphics2D) g.create();
  35. g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
  36. RenderingHints.VALUE_ANTIALIAS_ON);
  37. g2d.setRenderingHint(RenderingHints.KEY_RENDERING,
  38. RenderingHints.VALUE_RENDER_QUALITY);
  39. g2d.translate(w / 2, h / 2);
  40. GeneralPath star = new GeneralPath();
  41. star.moveTo(points[0][0], points[0][1]);
  42. for (int k = 1; k < points.length; k++) {
  43. star.lineTo(points[k][0], points[k][1]);
  44. }
  45. g2d.rotate(angle);
  46. g2d.scale(scale, scale);
  47. g2d.fill(star);
  48. g2d.dispose();
  49. }
  50. @Override
  51. public void paintComponent(Graphics g) {
  52. super.paintComponent(g);
  53. doDrawing(g);
  54. }
  55. private void step() {
  56. if (scale < 0.01) {
  57. delta = -delta;
  58. } else if (scale > 0.99) {
  59. delta = -delta;
  60. }
  61. scale += delta;
  62. angle += 0.01;
  63. }
  64. @Override
  65. public void actionPerformed(ActionEvent e) {
  66. step();
  67. repaint();
  68. }
  69. }
  70. public class StarDemoEx extends JFrame {
  71. public StarDemoEx() {
  72. initUI();
  73. }
  74. private void initUI() {
  75. add(new Surface());
  76. setTitle("Star");
  77. setSize(420, 250);
  78. setLocationRelativeTo(null);
  79. setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  80. }
  81. public static void main(String[] args) {
  82. EventQueue.invokeLater(new Runnable() {
  83. @Override
  84. public void run() {
  85. StarDemoEx ex = new StarDemoEx();
  86. ex.setVisible(true);
  87. }
  88. });
  89. }
  90. }

在此演示中,我们有一颗星星。 星星旋转生长然后收缩。

  1. private final int points[][] = {
  2. {0, 85}, {75, 75}, {100, 10}, {125, 75},
  3. {200, 85}, {150, 125}, {160, 190}, {100, 150},
  4. {40, 190}, {50, 125}, {0, 85}
  5. };

这些点用于绘制星形。

  1. private double angle = 0;
  2. private double scale = 1;
  3. private double delta = 0.01;

当我们旋转星星时使用anglescale因子确定星星的大小。 最后,delta因子是刻度的变化量。

  1. g2d.translate(w / 2, h / 2);

使用translate()方法将坐标系移到窗口的中间。

  1. GeneralPath star = new GeneralPath();
  2. star.moveTo(points[0][0], points[0][1]);
  3. for (int k = 1; k < points.length; k++) {
  4. star.lineTo(points[k][0], points[k][1]);
  5. }

GeneralPath用于创建星形。 通过moveTo()方法将第一点添加到路径。 通过lineTo()方法添加星星的后续点。

  1. g2d.rotate(angle);
  2. g2d.scale(scale, scale);

我们执行旋转和缩放操作。

  1. g2d.fill(star);

fill()方法填充星形的内部。

  1. if (scale < 0.01) {
  2. delta = -delta;
  3. } else if (scale > 0.99) {
  4. delta = -delta;
  5. }

该代码控制星的收缩和增长量。

泡芙

接下来,我们显示粉扑效果。 这种效果在 Flash 动画或电影介绍中很常见。 文本在屏幕上逐渐增长,一段时间后它逐渐消失。

PuffEx.java

  1. package com.zetcode;
  2. import java.awt.AlphaComposite;
  3. import java.awt.Dimension;
  4. import java.awt.EventQueue;
  5. import java.awt.Font;
  6. import java.awt.FontMetrics;
  7. import java.awt.Graphics;
  8. import java.awt.Graphics2D;
  9. import java.awt.RenderingHints;
  10. import java.awt.event.ActionEvent;
  11. import java.awt.event.ActionListener;
  12. import javax.swing.JFrame;
  13. import javax.swing.JPanel;
  14. import javax.swing.Timer;
  15. class Surface extends JPanel
  16. implements ActionListener {
  17. private Timer timer;
  18. private int x = 1;
  19. private float alpha = 1;
  20. private final int DELAY = 15;
  21. private final int INITIAL_DELAY = 200;
  22. public Surface() {
  23. initTimer();
  24. }
  25. private void initTimer() {
  26. timer = new Timer(DELAY, this);
  27. timer.setInitialDelay(INITIAL_DELAY);
  28. timer.start();
  29. }
  30. private void doDrawing(Graphics g) {
  31. Graphics2D g2d = (Graphics2D) g.create();
  32. RenderingHints rh =
  33. new RenderingHints(RenderingHints.KEY_ANTIALIASING,
  34. RenderingHints.VALUE_ANTIALIAS_ON);
  35. rh.put(RenderingHints.KEY_RENDERING,
  36. RenderingHints.VALUE_RENDER_QUALITY);
  37. g2d.setRenderingHints(rh);
  38. Font font = new Font("Dialog", Font.PLAIN, x);
  39. g2d.setFont(font);
  40. FontMetrics fm = g2d.getFontMetrics();
  41. String s = "ZetCode";
  42. Dimension size = getSize();
  43. int w = (int) size.getWidth();
  44. int h = (int) size.getHeight();
  45. int stringWidth = fm.stringWidth(s);
  46. AlphaComposite ac = AlphaComposite.getInstance(
  47. AlphaComposite.SRC_OVER, alpha);
  48. g2d.setComposite(ac);
  49. g2d.drawString(s, (w - stringWidth) / 2, h / 2);
  50. g2d.dispose();
  51. }
  52. @Override
  53. public void paintComponent(Graphics g) {
  54. super.paintComponent(g);
  55. doDrawing(g);
  56. }
  57. private void step() {
  58. x += 1;
  59. if (x > 40)
  60. alpha -= 0.01;
  61. if (alpha <= 0.01)
  62. timer.stop();
  63. }
  64. @Override
  65. public void actionPerformed(ActionEvent e) {
  66. step();
  67. repaint();
  68. }
  69. }
  70. public class PuffEx extends JFrame {
  71. public PuffEx() {
  72. initUI();
  73. }
  74. private void initUI() {
  75. setTitle("Puff");
  76. add(new Surface());
  77. setSize(400, 300);
  78. setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  79. setLocationRelativeTo(null);
  80. }
  81. public static void main(String[] args) {
  82. EventQueue.invokeLater(new Runnable() {
  83. @Override
  84. public void run() {
  85. PuffEx ex = new PuffEx();
  86. ex.setVisible(true);
  87. }
  88. });
  89. }
  90. }

该示例在窗口上绘制了一个不断增长的文本,从某个角度看,该文本变得越来越透明,直到看不见为止。

  1. Font font = new Font("Dialog", Font.PLAIN, x);
  2. g2d.setFont(font);

这是我们用于文本的字体。

  1. FontMetrics fm = g2d.getFontMetrics();

getFontMetrics()返回FontMetrics类。 该类存储有关在特定屏幕上呈现特定字体的信息。

  1. int stringWidth = fm.stringWidth(s);

我们使用FontMetrics对象的stringWidth()方法来获取字符串的宽度。

  1. AlphaComposite ac = AlphaComposite.getInstance(
  2. AlphaComposite.SRC_OVER, alpha);
  3. g2d.setComposite(ac);

在这里,我们设置所绘制文本的透明度。

  1. g2d.drawString(s, (w - stringWidth) / 2, h / 2);

此代码行在窗口的(水平)中间绘制字符串。

  1. if (x > 40)
  2. alpha -= 0.01;

琴弦高于 40 点后,琴弦开始褪色。

在 Java 2D 教程的这一部分中,我们做了一些视觉效果。