Để có 1 ứng dụng đẹp và thực hiện các công việc tiền xử lý để tăng độ hiệu quả cho ứng dụng thì chúng ta xử dụng Splash Screen khi khởi chạy ứng dụng. Phần lớn hầu hết các ứng dụng hiện đại đều chạy Splash Screen để thực hiện các công việc như kiểm tra cấu hình, kiểm tra bản quyền, khởi tạo môi trường làm việc, hay đóng vai trò bootstrap....ví dụ: Visual Studio, Netbeans, Office, Adobe Photoshop, v.v...

Ở bài viết này, tôi sẽ viết chi tiết cách tạo Splash Screen trong Java Swing và việc chạy các công việc tiền xử lý của ứng dụng. Kỹ thuật làm Splash Screen có rất nhiều cách, không cách này thì cách khác, tùy vào ý tưởng của mỗi người viết. Tôi viết 1 cách đơn giản để mọi người ai cũng có thể làm theo và áp dụng được (làm assignment, làm project, làm tool :D,...).

Đầu tiên, tôi sẽ thiết kế hình ảnh của Splash Screen trong Photoshop rồi export nó ra dạng .PNG

Bạn có thể tự thiết kế lấy hoặc nếu thích Splash Screen của tôi, bạn có thể Download file PSD về và chỉnh sửa. (Đây là bài eProject SEM 2 ở Aptech của tôi ngày xưa).

Đã có được 1 file ảnh PNG làm Splash Screen....Viết code thôi!


I. Chuẩn bị viết code


(Ảnh nhỏ bạn có thể click vào để xem ảnh lớn cho rõ).

*Môi trường làm việc: Java 7, Ubuntu 12.04 LTS.

Tạo Project tên SplashScreen. Sau đó, tạo mới các Class và đặt tên với cấu trúc như sau:


Giải thích:
com.blogspot.code4lifevn.app.StartApp: Lớp này chứa phương thức main, được dùng để chạy ứng dụng.
com.blogspot.code4lifevn.splashscreen.ImagePanel: Lớp này được dùng để vẽ và hiển thị ảnh .png;
com.blogspot.code4lifevn.splashscreen.ProgressBarPainter: Lớp này được dùng để custom progressbar component.
com.blogspot.code4lifevn.splashscreen.SplashScreenDrawer: Lớp này được dùng để xử lý events và vẽ Splash Screen.
com.blogspot.code4lifevn.splashscreen.SplashScreen: Lớp này được dùng để thiết kế layout, hiển thị SplashScreenDrawer và xử lý các công việc tiền xử lý.
com.blogspot.code4lifevn.tasks.ITaskExcutor: Đây là 1 interface được dùng để mô tả cho các lớp thực thi.
com.blogspot.code4lifevn.tasks.CheckConfigFileExecutor,
com.blogspot.code4lifevn.tasks.CheckDatabaseExecutor, com.blogspot.code4lifevn.tasks.PrepareWorkSpaceExecutor: Đây là các lớp implements interface ITaskExcutor, sẽ thực thi chi tiết mã công việc cần xử lý. (Bạn có thể thêm các công việc khác phù hợp với bạn)


II. Bắt đầu viết code



Tôi sẽ xử lý lần lượt các lớp và comment rõ ràng để bạn có thể theo dõi.

+ Mã xử lý class: com.blogspot.code4lifevn.splashscreen.ImagePanel
package com.blogspot.code4lifevn.splashscreen;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.LayoutManager;
import java.net.URL;
import javax.swing.ImageIcon;
import javax.swing.JPanel;

/**
 *
 * @author code4lifevn
 */
public class ImagePanel extends JPanel {
    private String imageURL;
    private Image backgroundImage;
    private Dimension dim;

    public ImagePanel() {
        super();
    }

    public ImagePanel(LayoutManager layout) {
        super(layout);
    }

    public ImagePanel(boolean isDoubleBuffered) {
        super(isDoubleBuffered);
    }

    public ImagePanel(LayoutManager layout, boolean isDoubleBuffered) {
        super(layout, isDoubleBuffered);
    }

    public ImagePanel(String imageURL) {
        this.imageURL = imageURL;
    }

    public ImagePanel(String imageURL, LayoutManager layout) {
        super(layout);
        this.imageURL = imageURL;
    }

    public ImagePanel(String imageURL, boolean isDoubleBuffered) {
        super(isDoubleBuffered);
        this.imageURL = imageURL;
    }

    public ImagePanel(String imageURL, LayoutManager layout, boolean isDoubleBuffered) {
        super(layout, isDoubleBuffered);
        this.imageURL = imageURL;
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponents(g);
        URL url = getClass().getResource(getImageURL());
        backgroundImage = new ImageIcon(url).getImage();
        int w = backgroundImage.getWidth(null);
        int h = backgroundImage.getHeight(null);
        dim = new Dimension(w, h);
        this.setPreferredSize(dim);
        g.drawImage(backgroundImage, 0, 0, null);
    }

    public String getImageURL() {
        return imageURL;
    }

    public void setImageURL(String imageURL) {
        this.imageURL = imageURL;
    }

    public Image getBackgroundImage() {
        return backgroundImage;
    }

    public void setBackgroundImage(Image backgroundImage) {
        this.backgroundImage = backgroundImage;
    }

    public Dimension getDim() {
        return dim;
    }

    public void setDim(Dimension dim) {
        this.dim = dim;
    }
}


+ Mã xử lý class: com.blogspot.code4lifevn.splashscreen.ProgressBarPainter
package com.blogspot.code4lifevn.splashscreen;

import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics2D;
import javax.swing.JProgressBar;
import javax.swing.Painter;

/**
 *
 * @author code4lifevn
 */
public class ProgressBarPainter implements Painter&ltJProgressBar&gt {
    private Color color1;
    private Color color2;

    public ProgressBarPainter(Color color) {
        this.color1 = color;
    }

    public ProgressBarPainter(Color color1, Color color2) {
        this.color1 = color1;
        this.color2 = color2;
    }

    @Override
    public void paint(Graphics2D g, JProgressBar object, int width, int height) {
        if(color2 != null) {
            GradientPaint gp = new GradientPaint(0, 0, color1, 0, height, color2);
            g.setPaint(gp);
        } else {
            g.setColor(color1);
        }
        g.fillRect(0, 0, width - 1, height - 1);
    }

}


+ Mã xử lý class: com.blogspot.code4lifevn.splashscreen.SplashScreenDrawer
package com.blogspot.code4lifevn.splashscreen;

import java.awt.*;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowFocusListener;
import java.util.Date;
import javax.swing.JComponent;
import javax.swing.JDialog;

/**
 *
 * @author code4lifevn
 */
public final class SplashScreenDrawer extends JComponent implements ComponentListener, WindowFocusListener, Runnable {
    private JDialog frame;
    protected Image background;
    private long lastupdate = 0;
    private boolean refreshRequested = true;

    SplashScreenDrawer(JDialog frame) {
        this.frame = frame;
        updateBackground();
        frame.addComponentListener(this);
        //frame.addWindowFocusListener(this);

        new Thread(this).start();
    }

    private void updateBackground() {
        try {
            Robot rbt = new Robot();
            Toolkit tk = Toolkit.getDefaultToolkit();
            Dimension dim = tk.getScreenSize();
            background = rbt.createScreenCapture(new Rectangle(0,0,(int)dim.getWidth(),(int)dim.getHeight()));
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    @Override public void paintComponent(Graphics g) {
        try {
            Point pos = this.getLocationOnScreen();
            Point offset = new Point(-pos.x,-pos.y);
            g.drawImage(background,offset.x,offset.y,null);
        } catch(IllegalComponentStateException ex) {
            ex.getCause();
        }
    }

    @Override public void componentShown(ComponentEvent evt) { repaint(); }
    @Override public void componentResized(ComponentEvent evt) { repaint(); }
    @Override public void componentMoved(ComponentEvent evt) { repaint(); }
    @Override public void componentHidden(ComponentEvent evt) { }

    @Override public void windowGainedFocus(WindowEvent evt) { refresh(); }
    @Override public void windowLostFocus(WindowEvent evt) { refresh(); }

    private void refresh() {
       if(this.isVisible() && frame.isVisible()) {
            repaint();
            refreshRequested = true;
            lastupdate = new Date().getTime();
        }
    }

    @Override public void run() {
        try {
            while(true) {
                Thread.sleep(50);
                long now = new Date().getTime();
                if(refreshRequested &&
                   ((now - lastupdate) > 10)) {
                    if(frame.isVisible()) {
                        Point location = frame.getLocation();
                        frame.setVisible(false);
                        updateBackground();
                        frame.setVisible(true);
                        frame.setLocation(location);
                        refresh();
                    }
                    lastupdate = now;
                    refreshRequested = false;
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}


+ Mã xử lý class: com.blogspot.code4lifevn.splashscreen.SplashScreen
package com.blogspot.code4lifevn.splashscreen;

import com.blogspot.code4lifevn.tasks.CheckConfigFileExecutor;
import com.blogspot.code4lifevn.tasks.CheckDatabaseExecutor;
import com.blogspot.code4lifevn.tasks.ITaskExcutor;
import com.blogspot.code4lifevn.tasks.PrepareWorkSpaceExecutor;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Random;
import javax.swing.GroupLayout;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JProgressBar;
import javax.swing.LayoutStyle;

/**
 *
 * @author code4lifevn
 */
public final class SplashScreen implements Runnable {
    private static final List&ltITaskExcutor&gt taskExecutor = new ArrayList&ltITaskExcutor&gt(3);
    static {
        taskExecutor.add(new CheckConfigFileExecutor());
        taskExecutor.add(new CheckDatabaseExecutor());
        taskExecutor.add(new PrepareWorkSpaceExecutor());
    }
    
    private JProgressBar progressBar;
    private JDialog frame;
    private JLabel lbMsg;

    public SplashScreen() {
        new Thread(this).start();
    }

    @Override
    public void run() {
        //=> Init ProgressBar
        progressBar = new JProgressBar(0, 100);
        progressBar.setPreferredSize(new Dimension(146, 44));
        //progressBar.setStringPainted(true);
        
        //=> Init JDialog
        frame = new JDialog();
        frame.setUndecorated(true);

        //=> Init JLabel
        lbMsg = new JLabel();

        //=> Init SplashScreen
        SplashScreenDrawer splashScreenDrawer = new SplashScreenDrawer(frame);
        splashScreenDrawer.setLayout(new BorderLayout());

        //=> Init ImagePanel
        ImagePanel imagePanel = new ImagePanel("resource/SplashScreen.png");
        imagePanel.setOpaque(false);

        //=> Design Layout
        GroupLayout imagePanelLayout = new GroupLayout(imagePanel);
        imagePanel.setLayout(imagePanelLayout);
        imagePanelLayout.setHorizontalGroup(
                imagePanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING)
                .addGroup(imagePanelLayout.createSequentialGroup()
                .addGap(178, 178, 178)
                .addGroup(imagePanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING)
                .addComponent(lbMsg)
                .addComponent(progressBar, GroupLayout.PREFERRED_SIZE, 350, GroupLayout.PREFERRED_SIZE))
                .addContainerGap(172, Short.MAX_VALUE)));
        imagePanelLayout.setVerticalGroup(
                imagePanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING)
                .addGroup(GroupLayout.Alignment.TRAILING, imagePanelLayout.createSequentialGroup()
                .addContainerGap(288, Short.MAX_VALUE)
                .addComponent(lbMsg)
                .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(progressBar, GroupLayout.PREFERRED_SIZE, 6, GroupLayout.PREFERRED_SIZE)
                .addGap(136, 136, 136)));

        splashScreenDrawer.add("Center", imagePanel);
        frame.getContentPane().add("Center", splashScreenDrawer);
        frame.pack();
        frame.setSize(700, 450);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
        
        handleExcutors();
    }

    private void handleExcutors() {
        //Handling progressbar
        double val = 1;
        int i = 0;
        for (ITaskExcutor task : taskExecutor) {
            try {
                val += (i * 100) / taskExecutor.size();
                progressBar.setValue((int) Math.ceil(val));
                task.execute(this);
                Random rd = new Random();
                int delay = rd.nextInt(100);
                Thread.sleep(delay);
            } catch (Exception ex) {
                JOptionPane.showMessageDialog(null, ex.getMessage(), "Lỗi CMNR", JOptionPane.ERROR_MESSAGE);
                break;
            }
            ++i;
        }
    }

    public void setProgressMessage(String message) {
        this.lbMsg.setText(message);
    }
    
}


+ Mã xử lý interface: com.blogspot.code4lifevn.tasks.ITaskExcutor
package com.blogspot.code4lifevn.tasks;

import com.blogspot.code4lifevn.splashscreen.SplashScreen;

/**
 *
 * @author code4lifevn
 */
public interface ITaskExcutor {
    public void execute(SplashScreen splashScreen) throws Exception;
}



+ Mã xử lý class: com.blogspot.code4lifevn.tasks.CheckConfigFileExecutor
package com.blogspot.code4lifevn.tasks;

import com.blogspot.code4lifevn.splashscreen.SplashScreen;

/**
 *
 * @author code4lifevn
 */
public class CheckConfigFileExecutor implements ITaskExcutor { 

    @Override
    public void execute(SplashScreen splashScreen) throws Exception {
        splashScreen.setProgressMessage("Đang kiểm tra file cấu hình...");
        
        //=> Viết các mã thực hiện việc kiểm tra file và thông số cấu hình của bạn
        //=> TODO: Code for life
    }

}


+ Mã xử lý class: com.blogspot.code4lifevn.tasks.CheckDatabaseExecutor
package com.blogspot.code4lifevn.tasks;

import com.blogspot.code4lifevn.splashscreen.SplashScreen;

/**
 *
 * @author code4lifevn
 */
public class CheckDatabaseExecutor implements ITaskExcutor {

    @Override
    public void execute(SplashScreen splashScreen) throws Exception {
        splashScreen.setProgressMessage("Đang kiểm tra cơ sở dữ liệu...");
        
        //=> Viết các mã thực hiện việc kiểm tra cơ sở dữ liệu 
        //(CSDL đã tồn tại chưa? Cấu trúc CSDL có toàn vẹn không? Nếu không thì thực hiện việc khởi tạo CSDL mới)
        //=> TODO: Code for life
    }

}


+ Mã xử lý class: com.blogspot.code4lifevn.tasks.PrepareWorkSpaceExecutor
package com.blogspot.code4lifevn.tasks;

import com.blogspot.code4lifevn.splashscreen.SplashScreen;

/**
 *
 * @author code4lifevn
 */
public class PrepareWorkSpaceExecutor implements ITaskExcutor {

    @Override
    public void execute(SplashScreen splashScreen) throws Exception {
        splashScreen.setProgressMessage("Đang chuản bị môi trường làm việc cho bạn...");
        
        //=> Viết các khởi tạo các thông số cho môi trường làm việc (VD: kết nối CSDL, Preferences, Settings)
        //=> TODO: Code for life
    }

}


+ Mã xử lý class: com.blogspot.code4lifevn.app.StartApp
package com.blogspot.code4lifevn.app;

import com.blogspot.code4lifevn.splashscreen.ProgressBarPainter;
import com.blogspot.code4lifevn.splashscreen.SplashScreen;
import java.awt.Color;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
import javax.swing.UIManager.LookAndFeelInfo;

/**
 *
 * @author code4lifevn
 */
public class StartApp {

    public static void main(String[] args) {
        try {
            for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    UIManager.setLookAndFeel(info.getClassName());
                    UIDefaults myUI = UIManager.getLookAndFeelDefaults();
                    myUI.put("ProgressBar[Enabled].foregroundPainter", new ProgressBarPainter(
                            new Color(72, 166, 255),
                            new Color(180, 219, 255)));
                    myUI.put("ProgressBar[Enabled+Finished].foregroundPainter", new ProgressBarPainter(
                            new Color(72, 166, 255),
                            new Color(180, 219, 255)));
                    myUI.put("ProgressBar[Enabled].backgroundPainter", new ProgressBarPainter(
                            new Color(216, 216, 216),
                            new Color(216, 216, 216)));
                    break;
                }
            }
        } catch (Exception ex) {
            JOptionPane.showMessageDialog(null, ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
        }
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new SplashScreen();
            }
        });
    }
}



III. Chạy thử


Chạy class StartApp, và đây là kết quả của chúng ta ^^


Do các task của tôi thiết kế chưa viết mã cụ thể xử lý những gì nên ProgressBar chạy hơi nhanh. Bạn có thể sửa Thread.sleep() với giá trị cao cao 1 chút để test. Nếu các task có mã cụ thể thì khoảng thời gian thực thi mã sẽ được cộng vào thời gian delay ngẫu nhiên từ 0 đến 100. Những cách điều hướng này, bạn hoàn toàn có thể tự tùy chỉnh hay thêm bớt để phù hợp với dự án mà bạn đang làm.

Hy vọng, bài viết này đem đến cho bạn 1 chút kinh nghiệm khi làm việc với Swing và tiếp cận với cách phát triển những ứng dụng hiện đại với ngôn ngữ lập trình Java.


Demo Download Source Code

0 nhận xét:

Đăng nhận xét

 

code4lifevn team

Thanh niên nghiêm túc :)

Name: Manh Do

Age: years old

Job: Senior Java and Mobile Developer

Country: VietNam

Name: Hung Nguyen

Age: years old

Job: Android Developer

Country: VietNam

Name: Trung PH

Age: years old

Job: Senior iOS and Android Developer

Country: VietNam

Name: Điệp NT

Age: years old

Job: Senior .Net and Android Developer

Country: VietNam