1 简介
GUI核心技术:Swing、AWT
2 AWT
2.1 Frame
public class Main {
public static void main(String[] args) {
Frame frame = new Frame("First GUI Frame");
frame.setVisible(true);
frame.setSize(400, 400);
frame.setBackground(Color.green);
frame.setLocation(200, 200);
frame.setResizable(false);
}
}
此时,窗口关闭不了!!
多窗口生成:
import java.awt.*;
public class Main {
public static void main(String[] args) {
MyFrame myframe1 = new MyFrame(100, 100, 200, 200, Color.yellow);
MyFrame myframe2 = new MyFrame(300, 100, 200, 200, Color.green);
MyFrame myframe3 = new MyFrame(100, 300, 200, 200, Color.blue);
MyFrame myframe4 = new MyFrame(300, 300, 200, 200, Color.yellow);
}
}
class MyFrame extends Frame{
static int id = 0;
public MyFrame(int x, int y, int width, int height, Color color){
super("MyFrame-" + (++id));
setBounds(x, y, width, height);
setVisible(true);
setBackground(color);
}
}
2.2 Panel
public class Main {
public static void main(String[] args) {
Frame frame1 = new Frame();
Panel panel = new Panel();
frame1.setLayout(null);
frame1.setBounds(300, 300, 500, 500);
frame1.setBackground(new Color(40, 161, 35));
panel.setBounds(50, 50, 400, 400);
panel.setBackground(new Color(10, 20, 40));
frame1.add(panel);
frame1.setVisible(true);
}
}
关闭窗口?
监听事件,添加监听器
public class Main {
public static void main(String[] args) {
Frame frame1 = new Frame();
Panel panel = new Panel();
frame1.setLayout(null);
frame1.setBounds(300, 300, 500, 500);
frame1.setBackground(new Color(40, 161, 35));
panel.setBounds(50, 50, 400, 400);
panel.setBackground(new Color(10, 20, 40));
frame1.add(panel);
frame1.setVisible(true);
frame1.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
}
2.3 布局管理器
2.3.1 流式布局
public class Main {
public static void main(String[] args) {
Frame frame1 = new Frame();
Button button1 = new Button("Button 1");
Button button2 = new Button("Button 2");
Button button3 = new Button("Button 3");
// 设置流式布局
frame1.setLayout(new FlowLayout());
frame1.setSize(200, 200);
frame1.setVisible(true);
frame1.add(button1);
frame1.add(button2);
frame1.add(button3);
}
}
new FlowLayout()默认为居中对齐
public class Main {
public static void main(String[] args) {
Frame frame1 = new Frame();
Button button1 = new Button("Button 1");
Button button2 = new Button("Button 2");
Button button3 = new Button("Button 3");
// 设置流式布局
frame1.setLayout(new FlowLayout(FlowLayout.LEFT));
frame1.setSize(200, 200);
frame1.setVisible(true);
frame1.add(button1);
frame1.add(button2);
frame1.add(button3);
}
}
改变new FlowLayout的参数可以修改其布局方式
2.3.2 东西南北中 Border
frame1.add(button1, BorderLayout.EAST);
public class Main {
public static void main(String[] args) {
Frame frame1 = new Frame();
Button button1 = new Button("Button 1");
Button button2 = new Button("Button 2");
Button button3 = new Button("Button 3");
Button button4 = new Button("Button 4");
Button button5 = new Button("Button 5");
frame1.setSize(200, 200);
frame1.setVisible(true);
frame1.add(button1, BorderLayout.EAST);
frame1.add(button2, BorderLayout.WEST);
frame1.add(button3, BorderLayout.SOUTH);
frame1.add(button4, BorderLayout.NORTH);
frame1.add(button5, BorderLayout.CENTER);
}
}
2.3.3 表格布局 Grid
public class Main {
public static void main(String[] args) {
Frame frame1 = new Frame();
Button button1 = new Button("Button 1");
Button button2 = new Button("Button 2");
Button button3 = new Button("Button 3");
Button button4 = new Button("Button 4");
Button button5 = new Button("Button 5");
Button button6 = new Button("Button 6");
frame1.setSize(200, 200);
frame1.setLayout(new GridLayout(3, 2));
frame1.setVisible(true);
frame1.add(button1, BorderLayout.EAST);
frame1.add(button2, BorderLayout.WEST);
frame1.add(button3, BorderLayout.SOUTH);
frame1.add(button4, BorderLayout.NORTH);
frame1.add(button5, BorderLayout.CENTER);
}
}
frame.pack()函数可以自动最优布局
frame1.pack();
2.3.3 课后练习
绘制图形:
public class Main {
public static void main(String[] args) {
Frame frame = new Frame();
frame.setLayout(new GridLayout(2, 1));
frame.setSize(400, 300);
frame.setVisible(true);
Panel p1 = new Panel(new BorderLayout());
Panel p2 = new Panel(new GridLayout(2, 1));
Panel p3 = new Panel(new BorderLayout());
Panel p4 = new Panel(new GridLayout(2, 2));
p1.add(new Button("1"), BorderLayout.EAST);
p1.add(new Button("2"), BorderLayout.WEST);
p2.add(new Button("3"));
p2.add(new Button("4"));
p1.add(p2, BorderLayout.CENTER);
p3.add(new Button("5"), BorderLayout.EAST);
p3.add(new Button("6"), BorderLayout.WEST);
for(int i=0; i<4; ++i){
p4.add(new Button(""+i));
}
p3.add(p4, BorderLayout.CENTER);
frame.add(p1);
frame.add(p3);
}
}
总结:
Frame和Panel各自进行布局,Panel要加到frame中
2.4 事件监听
事件发生时做出响应
public class Main {
public static void main(String[] args) {
Frame frame = new Frame();
Button button = new Button();
MyActionListener myActionListener = new MyActionListener();
button.addActionListener(myActionListener);
frame.add(button, BorderLayout.CENTER);
frame.pack();
frame.setVisible(true);
}
}
class MyActionListener implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("aaa");
}
}
因需要一个actionListener,所以,实现了自己的ActionListener类
2.4.1 函数实现事件
多个对象都可调用,减少代码的编写,如下例中的windowClose()
public class Main {
public static void main(String[] args) {
Frame frame = new Frame();
Button button = new Button();
MyActionListener myActionListener = new MyActionListener();
button.addActionListener(myActionListener);
frame.add(button, BorderLayout.CENTER);
frame.pack();
frame.setVisible(true);
// 关闭窗口
windowClose(frame);
}
// 关闭窗体事件
public static void windowClose(Frame frame){
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
}
class MyActionListener implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("aaa");
}
}
2.4.2 共用事件
多个组件共用同一个监听类,在类中可以实现不同的响应,也不必为每一个不同的响应都新写一个监听类,使用一个类即可完成。
public class Main {
public static void main(String[] args) {
Frame frame = new Frame();
Button button1 = new Button("1");
Button button2 = new Button("2");
button2.setActionCommand("666");
MyMonitor myMonitor = new MyMonitor();
button1.addActionListener(myMonitor);
button2.addActionListener(myMonitor);
frame.add(button1, BorderLayout.NORTH);
frame.add(button2, BorderLayout.SOUTH);
frame.setVisible(true);
frame.pack();
}
}
class MyMonitor implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("click : " + e.getActionCommand());
if(e.getActionCommand().equals("1")){
// button 1 operation
//xxxxx
System.out.println("operate button 1 event!");
}else {
// button 2 operation
//xxxxx
System.out.println("operate button 2 event!");
}
}
}
2.4.3 输入框监听
在ActionListener类中,e.getSource()会返回当前对象,然后即可进行当前对象的操作。
public class Main {
public static void main(String[] args) {
MyFrame myFrame = new MyFrame();
}
}
class MyFrame extends Frame{
public MyFrame() throws HeadlessException {
TextField textField = new TextField();
add(textField);
// 监听此文本框的内容
MyActionListener myActionListener = new MyActionListener();
// 按回车键会出发此监听器事件
textField.addActionListener(myActionListener);
setVisible(true);
pack();
}
}
class MyActionListener implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
TextField field = (TextField) e.getSource(); // e.getSource()返回当前文本框对象
System.out.println(field.getText());
}
}
输入内容隐藏为 *
textField.setEchoChar(‘*’);
public class Main {
public static void main(String[] args) {
MyFrame myFrame = new MyFrame();
}
}
class MyFrame extends Frame{
public MyFrame() throws HeadlessException {
TextField textField = new TextField();
add(textField);
// 监听此文本框的内容
MyActionListener myActionListener = new MyActionListener();
// 按回车键会出发此监听器事件
textField.addActionListener(myActionListener);
textField.setEchoChar('*');
setVisible(true);
pack();
}
}
class MyActionListener implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
TextField field = (TextField) e.getSource(); // e.getSource()返回当前文本框对象
System.out.println(field.getText());
}
}
2.4.4 简易计算器
public class Main {
public static void main(String[] args) {
Calculator calculator = new Calculator();
}
}
class Calculator extends Frame{
public Calculator() throws HeadlessException {
TextField num1 = new TextField(10); // 字符数
TextField num2 = new TextField(10); // 字符数
TextField num3 = new TextField(10); // 字符数
Button button = new Button("=");
button.addActionListener(new MyCalculatorListener(num1, num2, num3));
Label label = new Label("+");
setLayout(new FlowLayout());
add(num1);
add(label);
add(num2);
add(button);
add(num3);
pack();
setVisible(true);
}
}
class MyCalculatorListener implements ActionListener{
private TextField num1, num2, num3;
public MyCalculatorListener(TextField num1, TextField num2, TextField num3) {
this.num1 = num1;
this.num2 = num2;
this.num3 = num3;
}
@Override
public void actionPerformed(ActionEvent e) {
int n1 = Integer.parseInt(num1.getText());
int n2 = Integer.parseInt(num2.getText());
num3.setText("" + (n1 + n2));
num1.setText("");
num2.setText("");
}
}
使用组合方法进行优化
核心:将Calculator作为Listener的成员,这样不需要在构造函数中传递数值,直接操作对象即可
public class Main {
public static void main(String[] args) {
new Calculator().loadFrame();
}
}
class Calculator extends Frame{
TextField num1, num2, num3; // 字符数
public void loadFrame(){
num1 = new TextField(10);
num2 = new TextField(10);
num3 = new TextField(10);
Button button = new Button("=");
Label label = new Label("+");
button.addActionListener(new MyCalculatorListener(this));
setLayout(new FlowLayout());
add(num1);
add(label);
add(num2);
add(button);
add(num3);
pack();
setVisible(true);
}
}
class MyCalculatorListener implements ActionListener{
Calculator calculator = null;
public MyCalculatorListener(Calculator calculator) {
this.calculator = calculator;
}
@Override
public void actionPerformed(ActionEvent e) {
int n1 = Integer.parseInt(calculator.num1.getText());
int n2 = Integer.parseInt(calculator.num2.getText());
calculator.num3.setText("" + (n1 + n2));
calculator.num1.setText("");
calculator.num2.setText("");
}
}
使用内部类进一步优化
将Listener类放入Calculator里面,即为Calculator的内部类,此时可以随意访问外部类的属性,进一步不需要传递calculator对象
public class Main {
public static void main(String[] args) {
new Calculator().loadFrame();
}
}
class Calculator extends Frame{
TextField num1, num2, num3; // 字符数
public void loadFrame(){
num1 = new TextField(10);
num2 = new TextField(10);
num3 = new TextField(10);
Button button = new Button("=");
Label label = new Label("+");
button.addActionListener(new MyCalculatorListener());
setLayout(new FlowLayout());
add(num1);
add(label);
add(num2);
add(button);
add(num3);
pack();
setVisible(true);
}
// 内部类,可随意访问外部类的属性和方法
private class MyCalculatorListener implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
int n1 = Integer.parseInt(num1.getText());
int n2 = Integer.parseInt(num2.getText());
num3.setText("" + (n1 + n2));
num1.setText("");
num2.setText("");
}
}
}
2.5 画笔
设置颜色,绘制图形类型等
public class Main {
public static void main(String[] args) {
new MyPaint().loadFrame();
}
}
class MyPaint extends Frame{
public void loadFrame(){
setBounds(200, 200, 600, 400);
setVisible(true);
}
@Override
public void paint(Graphics g) {
g.setColor(Color.red);
g.drawOval(100, 100, 100, 100);// 空心圆
g.fillOval(200, 200, 100, 100);// 实心圆
g.setColor(Color.green);
g.fillRect(300, 100, 20, 40);
}
}
2.6 鼠标监听
鼠标的监听器MouseListener
public class Main {
public static void main(String[] args) {
new MyFrame("画图");
}
}
class MyFrame extends Frame{
private ArrayList points;
public MyFrame(String name) throws HeadlessException {
super(name);
setBounds(200, 200, 400, 400);
points = new ArrayList();
// 鼠标监听器
this.addMouseListener(new MyMouseListener());
setVisible(true);
}
@Override
public void paint(Graphics g) {
Iterator iterator = points.iterator();
while(iterator.hasNext()){
Point point = (Point) iterator.next();
g.setColor(Color.black);
g.fillOval(point.x, point.y, 10, 10);
}
}
public void addPaint(Point point){
points.add(point);
}
private class MyMouseListener extends MouseAdapter{
@Override
public void mousePressed(MouseEvent e) {
MyFrame myFrame = (MyFrame) e.getSource();
// 记录当前坐标
myFrame.addPaint(new Point(e.getX(), e.getY()));
myFrame.repaint();
}
}
}
2.7 窗口监听
public class Main {
public static void main(String[] args) {
WindowFrame windowFrame = new WindowFrame();
}
}
class WindowFrame extends Frame{
public WindowFrame() throws HeadlessException {
setVisible(true);
setBounds(100, 100, 200, 200);
addWindowListener(new MyWindowListener());
}
class MyWindowListener extends WindowAdapter{
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
}
}
使用匿名内部类优化代码
public class Main {
public static void main(String[] args) {
WindowFrame windowFrame = new WindowFrame();
}
}
class WindowFrame extends Frame{
public WindowFrame() throws HeadlessException {
setVisible(true);
setBounds(100, 100, 200, 200);
addWindowListener(
new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.out.println("点击关闭!");
}
}
);
}
}
2.8 键盘监听
添加KeyListener,同样使用适配器Adapter重写接口中的函数
public class Main {
public static void main(String[] args) {
KeyFrame keyFrame = new KeyFrame();
}
}
class KeyFrame extends Frame{
public KeyFrame() throws HeadlessException {
setBounds(100, 100, 300, 300);
setVisible(true);
addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
System.out.println(e.getKeyCode());//获取键盘当前的码
if(e.getKeyCode() == KeyEvent.VK_UP){
System.out.println("上");
}
}
});
}
}
3 Swing
3.1 窗口、面板
3.2 弹窗 Dialog
public class Main extends JFrame{
public Main() throws HeadlessException {
this.setVisible(true);
setSize(700, 500);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
Container contentPane = getContentPane();
// 绝对布局
contentPane.setLayout(null);
JButton jButton = new JButton("创建对话框");
jButton.setBounds(30, 30, 200, 50);
// 按钮点击事件
jButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
new MyDialog();
}
});
contentPane.add(jButton);
}
public static void main(String[] args) {
new Main();
}
}
class MyDialog extends JDialog{
public MyDialog() {
setVisible(true);
this.setBounds(100, 100, 500, 500);
Container contentPane = getContentPane();
contentPane.setLayout(null);
contentPane.add(new Label("Dialog文本"));
}
}
3.3 标签与图标 Label and Icon
public class Main extends JFrame implements Icon{
private int width;
private int height;
public Main() throws HeadlessException {
}
public Main(int width, int height) throws HeadlessException {
this.width = width;
this.height = height;
}
public void init(){
Main main = new Main(15, 15);
JLabel jLabel = new JLabel("icon", main, SwingConstants.CENTER);
Container contentPane = getContentPane();
contentPane.add(jLabel);
setVisible(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
new Main().init();
}
@Override
public void paintIcon(Component c, Graphics g, int x, int y) {
g.fillOval(x, y, width, height);
}
@Override
public int getIconWidth() {
return this.width;
}
@Override
public int getIconHeight() {
return this.height;
}
}
使用图片做Icon??
Main.class.getResource(“1.png”);Main类同级目录下检索资源文件1.png
public class Main extends JFrame{
public Main() throws HeadlessException {
}
public void init(){
JLabel jLabel = new JLabel("icon");
URL url = Main.class.getResource("1.png");
ImageIcon imageIcon = new ImageIcon(url);
jLabel.setIcon(imageIcon);
jLabel.setHorizontalAlignment(SwingConstants.CENTER);
Container contentPane = getContentPane();
contentPane.add(jLabel);
setVisible(true);
setBounds(100, 100, 200, 200);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
new Main().init();
}
}
3.4 面板
public class Main extends JFrame{
public Main() throws HeadlessException {
Container contentPane = getContentPane();
// 后两个参数表示 两个面板之间的间隔
contentPane.setLayout(new GridLayout(2, 1, 10, 10));
JPanel jPanel = new JPanel(new GridLayout(1, 3));
JPanel jPanel2 = new JPanel(new GridLayout(3, 2));
jPanel.add(new Button("1"));
jPanel.add(new Button("2"));
jPanel.add(new Button("3"));
jPanel2.add(new Button("4"));
jPanel2.add(new Button("14"));
jPanel2.add(new Button("14"));
jPanel2.add(new Button("14"));
jPanel2.add(new Button("14"));
jPanel2.add(new Button("14"));
contentPane.add(jPanel);
contentPane.add(jPanel2);
setVisible(true);
setSize(500, 500);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
new Main();
}
}
3.5 滚动条
JScrollPanel 带滚动条的面板
public class Main extends JFrame{
public Main() throws HeadlessException {
Container contentPane = getContentPane();
// 文本域
JTextArea jTextArea = new JTextArea(20, 50);
jTextArea.setText("默认文本。。。");
JScrollPane jScrollPane = new JScrollPane(jTextArea);
contentPane.add(jScrollPane);
setBounds(100, 100, 300, 150);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setVisible(true);
}
public static void main(String[] args) {
new Main();
}
}
3.6 按钮
JButton
3.6.1 图片按钮
public class Main extends JFrame{
public Main() throws HeadlessException {
Container contentPane = getContentPane();
URL url = Main.class.getResource("1.png");
ImageIcon imageIcon = new ImageIcon(url);
JButton jButton = new JButton();
jButton.setIcon(imageIcon);
// 光标悬浮在按钮上,提示文字
jButton.setToolTipText("这是图片按钮");
contentPane.add(jButton);
setVisible(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setBounds(100, 100, 400, 400);
}
public static void main(String[] args) {
new Main();
}
}
3.6.2 单选按钮
JRadioButton
设置ButtonGroup限定按钮只能选择一个
public class Main extends JFrame{
public Main() throws HeadlessException {
Container contentPane = getContentPane();
URL url = Main.class.getResource("1.png");
ImageIcon imageIcon = new ImageIcon(url);
// 单选框
JRadioButton jRadioButton1 = new JRadioButton("1");
JRadioButton jRadioButton2 = new JRadioButton("2");
JRadioButton jRadioButton3 = new JRadioButton("3");
// 单选框分组,实现只能选一个
ButtonGroup buttonGroup = new ButtonGroup();
buttonGroup.add(jRadioButton1);
buttonGroup.add(jRadioButton2);
buttonGroup.add(jRadioButton3);
contentPane.add(jRadioButton1, BorderLayout.CENTER);
contentPane.add(jRadioButton2, BorderLayout.NORTH);
contentPane.add(jRadioButton3, BorderLayout.SOUTH);
setVisible(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setBounds(100, 100, 400, 400);
}
public static void main(String[] args) {
new Main();
}
}
3.6.3 复选按钮
CheckBox,不需要添加ButtonGroup
public class Main extends JFrame{
public Main() throws HeadlessException {
Container contentPane = getContentPane();
URL url = Main.class.getResource("1.png");
ImageIcon imageIcon = new ImageIcon(url);
// 多选框
JCheckBox jCheckBox1 = new JCheckBox("1");
JCheckBox jCheckBox2 = new JCheckBox("2");
JCheckBox jCheckBox3 = new JCheckBox("3");
contentPane.add(jCheckBox1, BorderLayout.CENTER);
contentPane.add(jCheckBox2, BorderLayout.NORTH);
contentPane.add(jCheckBox3, BorderLayout.SOUTH);
setVisible(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setBounds(100, 100, 400, 400);
}
public static void main(String[] args) {
new Main();
}
}
3.7 列表
3.7.1 下拉框
jComboBox
public class Main extends JFrame{
public Main() throws HeadlessException {
Container contentPane = getContentPane();
JComboBox jComboBox = new JComboBox();
jComboBox.addItem(null);
jComboBox.addItem("On");
jComboBox.addItem("Up");
jComboBox.addItem("Down");
jComboBox.addItem("Going");
contentPane.add(jComboBox);
setVisible(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setBounds(100, 100, 400, 400);
}
public static void main(String[] args) {
new Main();
}
}
后面可以根据选项的不同添加监听器进行响应
3.7.2 列表框
JList,生成内容后送入JList即可
public class Main extends JFrame{
public Main() throws HeadlessException {
Container contentPane = getContentPane();
// 生成列表内容
// String[] contents = {"1", "2", "3"};
Vector contents = new Vector();
JList jList = new JList(contents);
contents.add("hahaha");
contents.add("hehehe");
contents.add("gagaga");
contentPane.add(jList);
setVisible(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setBounds(100, 100, 400, 400);
}
public static void main(String[] args) {
new Main();
}
}
应用场景:
- 单选框:选择地区
- 列表:展示信息,可动态扩容
3.8 文本框
3.8.1 密码框
JPasswordField
public class Main extends JFrame{
public Main() throws HeadlessException {
Container contentPane = getContentPane();
JPasswordField jPasswordField = new JPasswordField();
jPasswordField.setEchoChar('*');
contentPane.add(jPasswordField);
setVisible(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setBounds(100, 100, 400, 400);
}
public static void main(String[] args) {
new Main();
}
}
输入任何内容,都显示为星号
3.8.2 文本框
JTextField
3.8.3 文本域
JTextArea
4 贪吃蛇小游戏
4.1 编写游戏主启动类
package com.bin.snake;
import javax.swing.*;
// 游戏主启动类
public class StartGame {
public static void main(String[] args) {
JFrame jFrame = new JFrame();
jFrame.setBounds(10, 10, 900, 720);
jFrame.setResizable(false);
jFrame.setVisible(true);
jFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
}
主窗口已构造完成,剩下的功能将在Panel中实现
4.2 编写游戏面板
package com.bin.snake;
import javax.swing.*;
import java.awt.*;
// 游戏面板
public class GamePanel extends JPanel {
// 使用画笔画游戏界面
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
setBackground(Color.black);
}
}
在GameStart类中加入面板的使用
public class StartGame {
public static void main(String[] args) {
JFrame jFrame = new JFrame();
jFrame.setBounds(10, 10, 900, 720);
jFrame.setResizable(false);
jFrame.setVisible(true);
jFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
jFrame.add(new GamePanel());
}
}
导入图片资源,在snake项目目录下新建statics文件夹,保存图片
4.3 编写数据中心类
本Data类负责生成Icon格式的图片,供其他类调用
package com.bin.snake;
import javax.swing.*;
import java.net.URL;
public class Data {
public static URL headerURL = Data.class.getResource("statics/header.png");
public static URL upURL = Data.class.getResource("statics/up.png");
public static URL downURL = Data.class.getResource("statics/down.png");
public static URL leftURL = Data.class.getResource("statics/left.png");
public static URL rightURL = Data.class.getResource("statics/right.png");
public static URL bodyURL = Data.class.getResource("statics/body.png");
public static URL foodURL = Data.class.getResource("statics/food.png");
public static ImageIcon header = new ImageIcon(headerURL);
public static ImageIcon up = new ImageIcon(upURL);
public static ImageIcon down = new ImageIcon(downURL);
public static ImageIcon left = new ImageIcon(leftURL);
public static ImageIcon right = new ImageIcon(rightURL);
public static ImageIcon body = new ImageIcon(bodyURL);
public static ImageIcon food = new ImageIcon(foodURL);
}
4.4 加载头部广告栏
public class GamePanel extends JPanel {
// 使用画笔画游戏界面
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
setBackground(Color.white);
// 头部广告栏
Data.header.paintIcon(this, g, 25, 11);
g.fillRect(25, 75, 850, 600);
}
}
4.5 绘制静态蛇
4.5.1 初始化蛇的信息
在面板中初始化
int length; // 蛇的身长
int[] snakeX = new int[600]; // x 坐标
int[] snakeY = new int[500]; // y 坐标
public GamePanel(){
init();
}
// 初始化蛇
public void init(){
length = 3;
// 头的初始坐标
snakeX[0] = 100;
snakeY[0] = 100;
snakeX[1] = 75;
snakeY[1] = 100;
snakeX[2] = 50;
snakeY[2] = 100;
}
4.5.2 根据初始信息画蛇
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
setBackground(Color.white);
// 头部广告栏
Data.header.paintIcon(this, g, 25, 11);
g.fillRect(25, 75, 850, 600);
Data.right.paintIcon(this, g, snakeX[0], snakeY[0]);
Data.body.paintIcon(this, g, snakeX[1], snakeY[1]);
Data.body.paintIcon(this, g, snakeX[2], snakeY[2]);
}
优化
此时,paint方法中小蛇的身体长度是静态不变的,考虑使用for循环优化
Data.right.paintIcon(this, g, snakeX[0], snakeY[0]);
for (int i = 1; i < length; i++) {
Data.body.paintIcon(this, g, snakeX[i], snakeY[i]);
}
4.5.3 蛇运动的方向
属性新加fx表示方向
String fx;
init函数中默认方向向右
fx = "R";
改写画笔方法,根据fx属性决定使用哪张方向图片
if(fx.equals("R")){
Data.right.paintIcon(this, g, snakeX[0], snakeY[0]);
}else if(fx.equals("L")){
Data.left.paintIcon(this, g, snakeX[0], snakeY[0]);
}else if(fx.equals("U")){
Data.up.paintIcon(this, g, snakeX[0], snakeY[0]);
}else if(fx.equals("D")){
Data.down.paintIcon(this, g, snakeX[0], snakeY[0]);
}
4.6 空格控制游戏开始与暂停
新增属性位isStart,表示当前是开始状态还是暂停状态
boolean isStart;
init函数做相应初始化
isStart = false;
提示按下空格开始游戏:
// 游戏状态
if(isStart == false){
// 提示文字
g.setColor(Color.white);
g.setFont(new Font("微软雅黑", Font.BOLD, 40));
g.drawString("按下空格开始游戏!", 300, 300);
}
4.7 让蛇动起来
实现键盘监听类,让Panel类实现KeyListener接口,并重写三个方法,关键是重写keyPressed
public class GamePanel extends JPanel implements KeyListener
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
}
@Override
public void keyReleased(KeyEvent e) {
}
当键盘空格键被按下时,游戏状态应当发生改变,而不仅仅是固定为某个值
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();// 获得键盘按键
if(keyCode == KeyEvent.VK_SPACE){
// 空格键
isStart = !isStart;
repaint();
}
}
在类构造函数中添加监听器,参数为this是因为当前类实现了KeyListener接口,所以直接使用自身即可
public GamePanel(){
init();
// 获得焦点事件
this.setFocusable(true);
// 获得键盘监听事件
this.addKeyListener(this);
}
下面要让蛇动起来,即使用定时器,让类实现ActionListener接口
public class GamePanel extends JPanel implements KeyListener, ActionListener
即主要是重写其方法
@Override
public void actionPerformed(ActionEvent e) {
}
类中新增属性timer,设置定时时长和操作对象
// 100ms 1s 10次
Timer timer = new Timer(100, this);
让蛇各部分实现坐标更新:
@Override
public void actionPerformed(ActionEvent e) {
if(isStart){
// 游戏开始状态
// 右移
for (int i = length-1; i > 0; i--) {
snakeX[i] = snakeX[i-1]; // 后一节变成前一节的位置
snakeY[i] = snakeY[i-1];
}
snakeX[0] += 25;
repaint();
}
timer.start();
}
还需要在构造函数处启动定时器,让游戏一开始就定时
public GamePanel(){
init();
// 获得焦点事件
this.setFocusable(true);
// 获得键盘监听事件
this.addKeyListener(this);
timer.start();
}
此时能正常移动,但只能右移,且小蛇飞出屏幕,需要边界判断
snakeX[0] += 25;
//边界判断
if(snakeX[0] > 850){
snakeX[0] = 25;
}
repaint();
定时器的作用就是每100ms调用一次ActionPerformerd函数
然后根据键盘输入,监听键盘,改变fx属性
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();// 获得键盘按键
if(keyCode == KeyEvent.VK_SPACE){
// 空格键
isStart = !isStart;
repaint();
}
// 小蛇移动
if(keyCode == KeyEvent.VK_UP){
fx = "U";
}else if(keyCode == KeyEvent.VK_DOWN){
fx = "D";
}if(keyCode == KeyEvent.VK_LEFT){
fx = "L";
}if(keyCode == KeyEvent.VK_RIGHT){
fx = "R";
}
}
然后,根据fx属性,修改计时器方法,并做相应的边界判断
@Override
public void actionPerformed(ActionEvent e) {
if(isStart){
// 游戏开始状态
// 身体移动
for (int i = length-1; i > 0; i--) {
snakeX[i] = snakeX[i-1]; // 后一节变成前一节的位置
snakeY[i] = snakeY[i-1];
}
if (fx.equals("R")){
snakeX[0] += 25;
// 边界判断
if(snakeX[0] > 850)
snakeX[0] = 25;
}else if(fx.equals("L")){
snakeX[0] -= 25;
// 边界判断
if(snakeX[0] < 25)
snakeX[0] = 850;
}else if(fx.equals("U")){
snakeY[0] -= 25;
// 边界判断
if(snakeY[0] < 75)
snakeY[0] = 650;
}else if(fx.equals("D")){
snakeY[0] += 25;
// 边界判断
if(snakeY[0] > 650)
snakeY[0] = 75;
}
repaint();
}
timer.start();
}
4.8 小蛇吃食物长大
新增食物坐标属性,并在init方法中初始化
int foodX;
int foodY;
Random random = new Random();
foodX = 25 + 25 * random.nextInt(34);
foodY = 75 + 25 * random.nextInt(24);
有了食物坐标后,使用画笔在图上画出食物, 在paint函数中新加:
Data.food.paintIcon(this, g, foodX, foodY);
计时器函数中处理吃食物:
//吃食物
if(snakeX[0] == foodX && snakeY[0] == foodY){
length++;
// 再次生成食物
foodX = 25 + 25 * random.nextInt(34);
foodY = 75 + 25 * random.nextInt(24);
}
现在可以实现小蛇动态吃食物并长大,但没有失误判定
4.9 失败判定
新增标志位:isFail判断是否失败
boolean isFail = false;
操纵画笔,当isFail时,系统提示信息
if(isStart == false){
// 提示文字
g.setColor(Color.white);
g.setFont(new Font("微软雅黑", Font.BOLD, 40));
g.drawString("按下空格开始游戏!", 300, 300);
}
if(isFail){
g.setColor(Color.RED);
g.setFont(new Font("微软雅黑", Font.BOLD, 40));
g.drawString("失败!按下空格重新开始游戏!", 300, 300);
}
优化键盘监听函数,按下空格键时,判断是继续还是重新开始。
if(keyCode == KeyEvent.VK_SPACE){
// 空格键
if(isFail){
// 重新开始
isFail = false;
init();
}else {
isStart = !isStart;
}
repaint();
}
在计时器函数中,判断本次更新位置后是否有失败情况:
// 失败判定,撞到自己
for (int i = 1; i < length; i++) {
if(snakeX[0] == snakeX[i] && snakeY[0] == snakeY[i]){
isFail = true;
}
}
优化小细节:
先画食物再画小蛇,这样小蛇能覆盖食物,不然,食物会出现在小蛇头的上层
4.10 积分系统
新增score属性
int score;
init方法中初始化
score = 0;
在头部广告栏后画提示信息:
//画积分
g.setColor(Color.WHITE);
g.setFont(new Font("微软雅黑", Font.BOLD, 18));
g.drawString("长度:"+length, 750, 35);
g.drawString("分数:"+score, 750, 50);
在吃食物时更新分数
//吃食物
if(snakeX[0] == foodX && snakeY[0] == foodY){
length++;
score += 10;
// 再次生成食物
foodX = 25 + 25 * random.nextInt(34);
foodY = 75 + 25 * random.nextInt(24);
}
在出错时,清空,但此操作由init函数负责完成了
GamePanel全部代码:
package com.bin.snake;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.sql.Time;
import java.util.Random;
// 游戏面板
public class GamePanel extends JPanel implements KeyListener, ActionListener {
// 定义蛇的数据结构
int length; // 蛇的身长
int[] snakeX = new int[600]; // x 坐标
int[] snakeY = new int[500]; // y 坐标
String fx;
boolean isStart;
Timer timer = new Timer(100, this);
int foodX;
int foodY;
Random random = new Random();
int score;
boolean isFail = false;
public GamePanel(){
init();
// 获得焦点事件
this.setFocusable(true);
// 获得键盘监听事件
this.addKeyListener(this);
timer.start();
}
// 初始化蛇
public void init(){
length = 3;
// 头的初始坐标
snakeX[0] = 100;
snakeY[0] = 100;
snakeX[1] = 75;
snakeY[1] = 100;
snakeX[2] = 50;
snakeY[2] = 100;
fx = "R";
isStart = false;
foodX = 25 + 25 * random.nextInt(34);
foodY = 75 + 25 * random.nextInt(24);
score = 0;
}
// 使用画笔画游戏界面
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
setBackground(Color.white);
g.fillRect(25, 75, 850, 600);
// 头部广告栏
Data.header.paintIcon(this, g, 25, 11);
//画积分
g.setColor(Color.WHITE);
g.setFont(new Font("微软雅黑", Font.BOLD, 18));
g.drawString("长度:"+length, 750, 35);
g.drawString("分数:"+score, 750, 50);
if(fx.equals("R")){
Data.right.paintIcon(this, g, snakeX[0], snakeY[0]);
}else if(fx.equals("L")){
Data.left.paintIcon(this, g, snakeX[0], snakeY[0]);
}else if(fx.equals("U")){
Data.up.paintIcon(this, g, snakeX[0], snakeY[0]);
}else if(fx.equals("D")){
Data.down.paintIcon(this, g, snakeX[0], snakeY[0]);
}
for (int i = 1; i < length; i++) {
Data.body.paintIcon(this, g, snakeX[i], snakeY[i]);
}
Data.food.paintIcon(this, g, foodX, foodY);
// 游戏状态
if(isStart == false){
// 提示文字
g.setColor(Color.white);
g.setFont(new Font("微软雅黑", Font.BOLD, 40));
g.drawString("按下空格开始游戏!", 300, 300);
}else if(isFail){
g.setColor(Color.RED);
g.setFont(new Font("微软雅黑", Font.BOLD, 40));
g.drawString("失败!按下空格重新开始游戏!", 300, 300);
}
}
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();// 获得键盘按键
if(keyCode == KeyEvent.VK_SPACE){
// 空格键
if(isFail){
// 重新开始
isFail = false;
init();
isStart = true;
}else {
isStart = !isStart;
}
repaint();
}
// 小蛇移动
if(keyCode == KeyEvent.VK_UP){
fx = "U";
}else if(keyCode == KeyEvent.VK_DOWN){
fx = "D";
}if(keyCode == KeyEvent.VK_LEFT){
fx = "L";
}if(keyCode == KeyEvent.VK_RIGHT){
fx = "R";
}
}
@Override
public void keyReleased(KeyEvent e) {
}
@Override
public void actionPerformed(ActionEvent e) {
//吃食物
if(snakeX[0] == foodX && snakeY[0] == foodY){
length++;
score += 10;
// 再次生成食物
foodX = 25 + 25 * random.nextInt(34);
foodY = 75 + 25 * random.nextInt(24);
}
if(isStart && !isFail){
// 游戏开始状态
// 身体移动
for (int i = length-1; i > 0; i--) {
snakeX[i] = snakeX[i-1]; // 后一节变成前一节的位置
snakeY[i] = snakeY[i-1];
}
if (fx.equals("R")){
snakeX[0] += 25;
// 边界判断
if(snakeX[0] > 850)
snakeX[0] = 25;
}else if(fx.equals("L")){
snakeX[0] -= 25;
// 边界判断
if(snakeX[0] < 25)
snakeX[0] = 850;
}else if(fx.equals("U")){
snakeY[0] -= 25;
// 边界判断
if(snakeY[0] < 75)
snakeY[0] = 650;
}else if(fx.equals("D")){
snakeY[0] += 25;
// 边界判断
if(snakeY[0] > 650)
snakeY[0] = 75;
}
// 失败判定,撞到自己
for (int i = 1; i < length; i++) {
if(snakeX[0] == snakeX[i] && snakeY[0] == snakeY[i]){
isFail = true;
}
}
repaint();
}
timer.start();
}
}