前言
相信大家都看过黑客帝国,里面炫酷的代码雨一定给大家留下了深刻的印象,今天,博主就带大家用Java来实现这个特效,提前剧透,博主将在这篇博文里写下超详细的实现思路,相信即便是小白也能够看懂,下面我们开始
准备
- jdk1.8+
- java ide,如idea,或者eclipse
思路
首先确定我们要做的是一个窗体应用程序,在窗体中使用画布,画出我们的代码雨,而java中的窗体应用只需要继承JFrame类即可
JFrame只是单纯的窗体容器,而画布,包含在JPanel组件中,现在我们需要将窗体和画布的大小都设置为电脑屏幕的大小,而除了大小之外,我们还需要对窗体进行一系列的设置,比如不显示标题栏,背景为黑色,鼠标不可见,按esc键退出程序等等,完成这些设置之后,我们的代码是这样的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.MemoryImageSource;
public class Rain extends JFrame {
// 屏幕大小
private Dimension screen;
// 画布容器
private JPanel graphicsPanel;
public Rain() {
// 获取屏幕大小
screen = getToolkit().getScreenSize();
// 设置去掉标题栏
setUndecorated(true);
// 设置光标不可见
Toolkit defaultToolkit = Toolkit.getDefaultToolkit();
Image image = defaultToolkit.createImage(new MemoryImageSource(0, 0, null, 0, 0));
Cursor invisibleCursor = defaultToolkit.createCustomCursor(image, new Point(0, 0), "cursor");
setCursor(invisibleCursor);
// 设置全屏
setSize(screen);
// 设置按esc键退出
addKeyListener(new KeyListener() {
public void keyTyped(KeyEvent e) {
}
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
System.exit(0);
}
}
public void keyReleased(KeyEvent e) {
}
});
graphicsPanel = new GraphicsPanel();
// 设置面板
setContentPane(graphicsPanel);
// 设置界面可显示
setVisible(true);
}
private class GraphicsPanel extends JPanel {
public void paint(Graphics g) {
// 设置背景黑色填充
g.setColor(Color.BLACK);
g.fillRect(0, 0, screen.width, screen.height);
}
}
public static void main(String[] args) {
new Rain();
}
}现在运行这个程序,不必奇怪,也不必害怕,我们会得到一个纯黑色的界面,我们可以通过按esc键退出程序,或者调出任务管理器杀掉该进程
准备工作做好了,现在我们来分析一下字符雨所具有的一些元素,很明显,我们需要在这个页面上画出一些随机字符,于是我们需要设置字符的大小,需要获取随机的字符,并且需要在页面上画出来,所以我们需要划分一下界面的行和列,基于以上分析,我们先画一个充满了随机字符的页面,于是我们将代码改进成这样:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.MemoryImageSource;
import java.util.Random;
public class Rain extends JFrame {
// 屏幕大小
private Dimension screen;
// 画布容器
private JPanel graphicsPanel;
// 字符大小
private int size = 16;
// 划分屏幕区域,行数
private int rows;
// 划分屏幕区域,列数
private int cols;
// 随机
private Random random = new Random();
public Rain() {
// 获取屏幕大小
screen = getToolkit().getScreenSize();
// 设置去掉标题栏
setUndecorated(true);
// 设置光标不可见
Toolkit defaultToolkit = Toolkit.getDefaultToolkit();
Image image = defaultToolkit.createImage(new MemoryImageSource(0, 0, null, 0, 0));
Cursor invisibleCursor = defaultToolkit.createCustomCursor(image, new Point(0, 0), "cursor");
setCursor(invisibleCursor);
// 设置全屏
setSize(screen);
// 设置按esc键退出
addKeyListener(new KeyListener() {
public void keyTyped(KeyEvent e) {
}
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
System.exit(0);
}
}
public void keyReleased(KeyEvent e) {
}
});
graphicsPanel = new GraphicsPanel();
// 设置面板
setContentPane(graphicsPanel);
// 设置界面可显示
setVisible(true);
// 设置行数和列数
rows = screen.width / size;
cols = screen.height / size;
}
/**
* 获取随机ASCII可见字符
*
* @return
*/
private char getRandomChar() {
return (char) (new Random().nextInt(94) + 33);
}
/**
* 画布容器
*/
private class GraphicsPanel extends JPanel {
public void paint(Graphics g) {
// 设置背景黑色填充
g.setColor(Color.BLACK);
g.fillRect(0, 0, screen.width, screen.height);
// 遍历划分的区域,将所有位置填满随机字符
for (int x = 0; x < rows; x++) {
for (int y = 0; y < cols; y++) {
// 设置随机字符的颜色和字体
g.setColor(Color.CYAN);
g.setFont(new Font("黑体", Font.BOLD, size));
// 画
g.drawString(String.valueOf(getRandomChar()), x * size, y * size);
}
}
}
}
public static void main(String[] args) {
new Rain();
}
}现在运行代码,你将会得到类似下面这样的界面,页面是静态的
现在我们来尝试让界面动起来,怎么做呢,很简单,我们可以开一个线程,专门去刷新页面,具体就是定时调用JPanel的repaint方法,于是代码进一步更改成这样了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.MemoryImageSource;
import java.util.Random;
public class Rain extends JFrame {
// 屏幕大小
private Dimension screen;
// 画布容器
private JPanel graphicsPanel;
// 字符大小
private int size = 16;
// 划分屏幕区域,行数
private int rows;
// 划分屏幕区域,列数
private int cols;
// 随机
private Random random = new Random();
public Rain() {
// 获取屏幕大小
screen = getToolkit().getScreenSize();
// 设置去掉标题栏
setUndecorated(true);
// 设置光标不可见
Toolkit defaultToolkit = Toolkit.getDefaultToolkit();
Image image = defaultToolkit.createImage(new MemoryImageSource(0, 0, null, 0, 0));
Cursor invisibleCursor = defaultToolkit.createCustomCursor(image, new Point(0, 0), "cursor");
setCursor(invisibleCursor);
// 设置全屏
setSize(screen);
// 设置按esc键退出
addKeyListener(new KeyListener() {
public void keyTyped(KeyEvent e) {
}
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
System.exit(0);
}
}
public void keyReleased(KeyEvent e) {
}
});
graphicsPanel = new GraphicsPanel();
// 设置面板
setContentPane(graphicsPanel);
// 设置界面可显示
setVisible(true);
// 设置行数和列数
rows = screen.width / size;
cols = screen.height / size;
// 开启定时刷新
new Refresher(100, graphicsPanel).start();
}
/**
* 获取随机ASCII可见字符
*
* @return
*/
private char getRandomChar() {
return (char) (new Random().nextInt(94) + 33);
}
/**
* 画布容器
*/
private class GraphicsPanel extends JPanel {
public void paint(Graphics g) {
// 设置背景黑色填充
g.setColor(Color.BLACK);
g.fillRect(0, 0, screen.width, screen.height);
// 遍历划分的区域,将所有位置填满随机字符
for (int x = 0; x < rows; x++) {
for (int y = 0; y < cols; y++) {
// 设置随机字符的颜色和字体
g.setColor(Color.CYAN);
g.setFont(new Font("黑体", Font.BOLD, size));
// 画
g.drawString(String.valueOf(getRandomChar()), x * size, y * size);
}
}
}
}
/**
* 定时刷新画布容器
*/
private class Refresher extends Thread {
private int sleep;
private JPanel panel;
public Refresher(int sleep, JPanel panel) {
this.sleep = sleep;
this.panel = panel;
}
public void run() {
// 死循环固定休眠,即可达到定时刷新的效果
while (true) {
try {
panel.repaint();
Thread.sleep(sleep);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
new Rain();
}
}现在运行程序,可以明显看到页面动起来了
现在再来仔细分析一下,黑客帝国的代码雨,可以看到,雨滴是一列一列从屏幕最上面,慢慢的下落到最下面;雨滴的头是和雨滴的身体的颜色不一样;雨滴的长度不会很长;重复这个过程;基于这个过程,我们不难想到将代码改写如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171import wiki.zimo.helper.CharHelper;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.MemoryImageSource;
import java.util.Random;
public class Rain extends JFrame {
// 屏幕大小
private Dimension screen;
// 画布容器
private JPanel graphicsPanel;
// 字符大小
private int size = 16;
// 划分屏幕区域,行数
private int rows;
// 划分屏幕区域,列数
private int cols;
// 随机
private Random random = new Random();
// 保存每列字符下落的坐标,雨滴的头的初始坐标——Y坐标
private int[] drops;
// 雨滴的身体最大长度
private int maxLength = 30;
// 一次下落的最大列数
private int maxDrop = 5;
public Rain() {
// 获取屏幕大小
screen = getToolkit().getScreenSize();
// 设置去掉标题栏
setUndecorated(true);
// 设置光标不可见
Toolkit defaultToolkit = Toolkit.getDefaultToolkit();
Image image = defaultToolkit.createImage(new MemoryImageSource(0, 0, null, 0, 0));
Cursor invisibleCursor = defaultToolkit.createCustomCursor(image, new Point(0, 0), "cursor");
setCursor(invisibleCursor);
// 设置全屏
setSize(screen);
// 设置按esc键退出
addKeyListener(new KeyListener() {
public void keyTyped(KeyEvent e) {
}
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
System.exit(0);
}
}
public void keyReleased(KeyEvent e) {
}
});
graphicsPanel = new GraphicsPanel();
// 设置面板
setContentPane(graphicsPanel);
// 设置界面可显示
setVisible(true);
// 设置行数和列数
rows = screen.width / size;
cols = screen.height / size;
drops = new int[rows];
// 随机drops
for (int i = 0; i < drops.length; i++) {
drops[i] = random.nextInt(cols);
}
// 开启定时刷新
new Refresher(100, graphicsPanel).start();
}
/**
* 获取随机ASCII可见字符
*
* @return
*/
private char getRandomChar() {
return (char) (new Random().nextInt(94) + 33);
}
/**
* 画布容器
*/
private class GraphicsPanel extends JPanel {
public void paint(Graphics g) {
// 设置背景黑色填充
Font font = g.getFont();
g.setColor(Color.BLACK);
g.fillRect(0, 0, screen.width, screen.height);
// 按行遍历
for (int x = 0; x < rows; x++) {
// 取出随机的y坐标
int y = drops[x];
// 画出雨滴的头
g.setColor(Color.CYAN);
g.setFont(new Font("黑体", Font.BOLD, size));
g.drawString(String.valueOf(CharHelper.getRandomChar()), x * size, y * size);
// 倒着往回画出雨滴的身体,颜色渐变
int color = 255;
int len = 0;
while (--y > 0) {
len++;
color -= 255 / y;
if (color < 0) {
color = 0;
}
g.setFont(font);
g.setColor(new Color(0, color, 0, color));
g.drawString(String.valueOf(CharHelper.getRandomChar()), x * size, y * size);
if (len >= maxLength) {
break;
}
}
// 随机雨滴下落
drops[x] += random.nextInt(maxDrop);
// 完全坠落
if (drops[x] >= cols + maxLength) {
// 随机开始
// drop[x] = random.nextInt(cols);
// 从头开始
drops[x] = 0;
}
}
}
}
/**
* 定时刷新画布容器
*/
private class Refresher extends Thread {
private int sleep;
private JPanel panel;
public Refresher(int sleep, JPanel panel) {
this.sleep = sleep;
this.panel = panel;
}
public void run() {
// 死循环固定休眠,即可达到定时刷新的效果
while (true) {
try {
panel.repaint();
Thread.sleep(sleep);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
new Rain();
}
}最终效果