Java 2D – Tạo hiệu ứng bong bóng, hình sao

Bài này hướng dẫn các bạn tạo hiệu ứng trong Java 2D.

Hiệu ứng bong bóng

Trong ví dụ đầu tiên, chúng ta sẽ thấy các bong bóng màu ngẫu nhiên xuất hiện và biến mất trên màn hình.

BubblesEx.java

package net.vncoding;

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);
            }
        });
    }
}

Giải thích:
Đây là ví dụ tạo hiệu ứng bong bóng.

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

Đây là hằng số màu để vẽ bong bóng.

private void initSurface() {

    setBackground(Color.black);
    ellipses = new Ellipse2D.Float[NUMBER_OF_ELLIPSES];
    esize = new double[ellipses.length];
    estroke = new float[ellipses.length];
}

Phương thức initSurface() set background màu đen cho panel. Chúng ta tạo 3 mảng. 1 mảng lưu hình elip, 1 mảng lưu kích thước mỗi hình elip, 1 mảng lưu nét vẽ (nét liền, nét đứt,…) cho mỗi hình elip.

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);
    }
}  

Mảng “ellipses” lưu các object hình elip. Phương thức posRandEllipses() đặt ngẫu nhiên object elip trên cửa sổ. Kích thước khởi tạo của hình elip cũng được chọn ngẫu nhiên.

private void initTimer() {

    timer = new Timer(DELAY, this);
    timer.setInitialDelay(INITIAL_DELAY);
    timer.start();
}

Object timer được khởi tạo và start.
Bộ timer được sử dụng để tạo hiệu ứng chuyển động.

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);
}

Phương thức posRandEllipses() đặt hình elip ngẫu nhiên trên cửa sổ. Mảng “esize” và “estroke” chứa kích thước và độ dày của đường elip.
Phương thức setFrame() đặt vị trí và kích thước của khung hình chữ nhật của 1 hình elip.

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]);
        }
    }
}

Animation bao gồm nhiều bước. Mỗi bước, chúng ta tăng độ dày và kích thước cho mỗi hình elip. Sau khi bong bóng đạt tới kích thước tối đa, reset lại các tham số (vị trí, kích thước) cho hình elip.

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]);
    }
}

Phương thức drawEllipses() vẽ tất cả hình elip trong mảng “ellipses”

private void doDrawing(Graphics g) {
    ...
    Dimension size = getSize();
    doStep(size.width, size.height);
    ...
}

Trong phương thức doDrawing(), chúng ta tính toán kích thước panel. Nếu cửa sổ bị thay đổi kích thước, bong bóng được phân bố ngẫu nhiên trên toàn bộ diện tích của cửa sổ.

@Override
public void actionPerformed(ActionEvent e) {
    repaint();
}

Đố tượng timer kích hoạt xử lí event sau mỗi khoảng thời gian. Phương thức repaint() vẽ lại các component trên panel.

Kết quả:

Java 2D - Hiệu ứng bong bóng
Java 2D – Hiệu ứng bong bóng

Hiệu ứng sao

Ví dụ này show animation quay và scale hình ngôi sao

package net.vncoding;

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);
            }
        });
    }
}

Giải thích:
Trong ví dụ này, chúng ta vẽ 5 ngôi sao. Ngôi này quay, phóng to và thu nhỏ.

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}
};

Đẩy là các điểm để vẽ hình sao.

private double angle = 0;
private double scale = 1;
private double delta = 0.01;

Biến “angle” được sử dụng khi quay hình sao. Biến “scale” quyết định kích thước của hình sao. Cuối cùng, biến “delta” là hệ số scale

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

Hệ trục tọa độ được di chuyển vào giữa cửa sổ.

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]);
}

Class GeneralPath được sử dụng để tạo hình ngôi sao. Điểm đầu tiên được thêm vào đường vẽ bởi phương thức moveTo(), một chuỗi điểm của ngôi sao được thêm bởi phương thức lineTo().

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

Thực hiện việc quay và scale ảnh

g2d.fill(star);

Fill màu cho hình sao.

if (scale < 0.01) {
    
    delta = -delta;
} else if (scale > 0.99) {
    
    delta = -delta;
}

Đoạn code này kiểm soát hệ số phóng to, thu nhỏ hình sao.

Kết quả:

Java 2D - Hiệu ứng animation hình ngôi sao
Java 2D – Hiệu ứng animation hình ngôi sao

Amination phóng to text

PuffEx.java

package net.vncoding;

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 = "VnCoding";
        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);
            }
        });      
    }
}

Giải thích:
Trong ví dụ này, dòng chữ hiện ra và lớn dần, sau đó trở lên trong suốt cho đến khi không nhìn thấy.

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

Set font sử dụng cho text.

FontMetrics fm = g2d.getFontMetrics();

Phương thức getFontMetrics() trả về class FontMetrics. Class lưu thông tin về việc render font trên màn hình.

int stringWidth = fm.stringWidth(s);

Chúng ta sử dụng phương thức stringWidth() của object FontMetrics để lấy chiều rộng của chuỗi.

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

Set transparent cho text đang được vẽ.

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

Vẽ chuỗi text theo hướng nằm ngang.

if (x > 40)
    alpha -= 0.01;

Khi độ cao của font chữ lớn hơn 40 point, chuỗi text bắt đầu mờ đi.

Kết quả:

Java 2D - Hiệu ứng phóng to chữ
Java 2D – Hiệu ứng phóng to chữ

Be the first to comment

Leave a Reply