ZetCode

效果

最后修改于 2023 年 7 月 17 日

在本部分 Java 2D 编程教程中,我们介绍了一些效果。

气泡

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

com/zetcode/BubblesEx.java
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 方法将椭圆随机放置在窗口上。esizeestroke 数组被填充了值。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 方法会重绘面板组件。

Bubbles
图:气泡

星形

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

com/zetcode/StarDemoEx.java
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 动画或电影片头中很常见。文本在屏幕上逐渐变大,一段时间后,它会慢慢消失。

com/zetcode/PuffEx.java
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 教程中,我们实现了一些视觉效果。