Java学习笔记之GUI编程
GUI编程
1 AWT 类
1.1 AWT 介绍
-
包含了很多的类和接口
-
元素:窗口、标签、按钮、文本框 ...
-
最基本的类
-
AWT
类关系图
1.2 Frame
public class Info {
public static void main(String[] args) {
//标题
Frame frame = new Frame("My GUI");
//可见性
frame.setVisible(true);
//尺寸
frame.setSize(300, 200);
//背景
frame.setBackground(new Color(253, 247, 200));
//弹出位置
frame.setLocation(500, 500);
//尺寸是否可变
frame.setResizable(false);
}
}
//问题:窗口无法关闭,必须结束程序进程
public class MyFrame extends Frame {
public static void main(String[] args) {
int id = 0;
MyFrame myFrame1 = new MyFrame(id, 100, 100, 500, 200, Color.blue);
MyFrame myFrame2 = new MyFrame(++id, 600, 100, 500, 200, Color.red);
MyFrame myFrame3 = new MyFrame(++id, 100, 300, 500, 200, Color.CYAN);
MyFrame myFrame4 = new MyFrame(++id, 600, 300, 500, 200, Color.YELLOW);
}
public MyFrame(int id, int x, int y, int width, int height, Color color){
super("MyFrame " + id);
setBackground(color);
setVisible(true);
setBounds(x, y, width, height);
}
}
1.3 Panel
public class MyPanel {
public static void main(String[] args) {
Frame frame = new Frame();
Panel panel = new Panel();
//不设置的话,窗口默认置顶
frame.setLayout(null);
frame.setBounds(200, 200, 500, 500);
frame.setBackground(Color.blue);
panel.setBackground(Color.YELLOW);
panel.setBounds(50, 50, 400, 400);
frame.add(panel);
frame.setVisible(true);
//解决关闭 Frame 问题
//使用监听器来控制面板的关闭
//适配器模式 new WindowAdapter()
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
}
1.4 布局管理器
1 流式布局
public class MyLayout {
public static void main(String[] args) {
Frame frame = new Frame();
frame.setVisible(true);
frame.setSize(500,500);
new MyLayout().flowLayout(frame);
}
//流式布局
public void flowLayout(Frame frame){
Button button1 = new Button("Button1");
Button button2 = new Button("Button2");
Button button3 = new Button("Button3");
frame.add(button1);
frame.add(button2);
frame.add(button3);
frame.setLayout(new FlowLayout(FlowLayout.RIGHT));
}
}
2 东南西北中布局
public class MyLayout {
public static void main(String[] args) {
Frame frame = new Frame();
frame.setVisible(true);
frame.setSize(500,500);
new MyLayout().borderLayout(frame);
}
//东南西北中布局
public void borderLayout(Frame frame){
Button west = new Button("west");
Button east = new Button("east");
Button north = new Button("north");
Button south = new Button("south");
Button center = new Button("center");
frame.add(west, BorderLayout.WEST);
frame.add(east, BorderLayout.EAST);
frame.add(north, BorderLayout.NORTH);
frame.add(south, BorderLayout.SOUTH);
frame.add(center, BorderLayout.CENTER);
}
}
3 网格布局
public class MyLayout {
public static void main(String[] args) {
Frame frame = new Frame();
frame.setVisible(true);
frame.setSize(500,500);
new MyLayout().gridLayout(frame);
}
//网格布局
public void gridLayout(Frame frame){
Button button1 = new Button("button1");
Button button2 = new Button("button2");
Button button3 = new Button("button3");
Button button4 = new Button("button4");
Button button5 = new Button("button5");
Button button6 = new Button("button6");
frame.setLayout(new GridLayout(2, 3)); //行数和列数
frame.add(button1);
frame.add(button2);
frame.add(button3);
frame.add(button4);
frame.add(button5);
frame.add(button6);
}
}
1.5 GUI布局练习
尝试布局出如下图的面板
public class MyGUITest {
public static void main(String[] args) {
Frame frame = new Frame("MyGUITest");
frame.setVisible(true);
frame.setBounds(50, 50, 600,400);
frame.setResizable(false);
frame.setLayout(new GridLayout(2,1));
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
super.windowClosing(e);
System.exit(0);
}
});
new MyGUITest().initGUI(frame);
}
public void initGUI(Frame frame){
//Panel
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("Button-1"), BorderLayout.WEST);
p1.add(new Button("Button-3"), BorderLayout.EAST);
p2.add(new Button("Button-2"));
p2.add(new Button("Button-4"));
p1.add(p2, BorderLayout.CENTER);
p3.add(new Button("Button-5"), BorderLayout.WEST);
p3.add(new Button("Button-10"), BorderLayout.EAST);
for (int i = 6; i < 10; i++)
p4.add(new Button("Button-" + i));
p3.add(p4, BorderLayout.CENTER);
frame.add(p1);
frame.add(p3);
}
}
程序结果
1.6 事件监听器
- 一个组件可以同时监听多个事件
- 多个组件也可以同时监听一个事件
public class ListenerInfo {
public static void main(String[] args) {
Frame frame = new Frame();
Button button1 = new Button("Button-1");
Button button2 = new Button("Button-2");
Button button3 = new Button("Button-3");
frame.setBounds(100,100,200,100);
frame.setVisible(true);
frame.setLayout(new GridLayout(2,1));
frame.add(button1);
frame.add(button2);
frame.add(button3);
windowClose(frame);
//多个按钮可以监听同一个事件
MyListener1 myListener1 = new MyListener1();
button1.addActionListener(myListener1);
button2.addActionListener(myListener1);
//一个按钮可以同时监听多个事件
MyListener2 myListener2 = new MyListener2();
button3.addActionListener(myListener1);
button3.addActionListener(myListener2);
}
private static void windowClose(Frame frame) {
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
}
//监听器
class MyListener1 implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Hello World!");
}
}
class MyListener2 implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Hello Java!");
}
}
- 使用
setActionCommand()
和getActionCommand()
可以方便的实现监听(不用重复写监听器的实现)
public class ListenerTest {
public static void main(String[] args) {
Frame frame = new Frame();
frame.setVisible(true);
frame.setBounds(100,100,200,200);
frame.setLayout(new FlowLayout());
Button bStart = new Button("button-start");
Button bStop = new Button("button-stop");
frame.add(bStart);
frame.add(bStop);
//设置监听器的对应的命令
MyListener myListener = new MyListener();
bStart.addActionListener(myListener);
bStart.setActionCommand("start");
bStop.addActionListener(myListener);
bStop.setActionCommand("stop");
new ListenerTest().windowClose(frame);
}
private void windowClose(Frame frame){
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
}
class MyListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand().equals("start"))
System.out.println("program is start");
else if (e.getActionCommand().equals("stop"))
System.out.println("program is stop");
}
}
1.7 文本框 TextField
public class TextFiledInfo {
public static void main(String[] args) {
new MyFrameTest();
}
}
class MyFrameTest extends Frame {
public MyFrameTest(){
Frame frame = new Frame();
//TextField 只能输入一行,TextArea 可以输入多行
TextField textField = new TextField();
add(textField);
setVisible(true);
TextFiledListener textFiledListener = new TextFiledListener();
textField.addActionListener(textFiledListener);
//设置文本框中替换的内容
textField.setEchoChar('*');
windowClose();
}
public void windowClose(){
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
}
class TextFiledListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
TextField text = (TextField)e.getSource();
System.out.println(text.getText());
text.setText(""); //用户输入完毕按回车后,清空文本框
}
}
1.7 简易计算器实现
题目:输入两个数,计算出两数之和并返回
思路:
- CalculatorTest 类:程序启动类
- MyCalculator 类:计算器的图形化界面
- CalculatorListener 类:文本框的监听器
public class CalculatorTest {
public static void main(String[] args) {
new MyCalculator().loadFrame();
}
}
class MyCalculator extends Frame {
TextField num1, num2, num3;
public void loadFrame() {
setBounds(200, 200, 500, 150);
setVisible(true);
setLayout(new FlowLayout());
pack();
this.num1 = new TextField(5);
this.num2 = new TextField(5);
this.num3 = new TextField(10);
Label label = new Label("+");
Button button = new Button("=");
button.addActionListener(new CalculatorListener(this));
add(num1);
add(label);
add(num2);
add(button);
add(num3);
windowClosed();
}
private void windowClosed() {
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
}
class CalculatorListener implements ActionListener {
MyCalculator myCalculator;
public CalculatorListener(MyCalculator myCalculator) {
this.myCalculator = myCalculator;
}
@Override
public void actionPerformed(ActionEvent e) {
int number1 = Integer.parseInt(myCalculator.num1.getText());
int number2 = Integer.parseInt(myCalculator.num2.getText());
int number3 = number1 + number2;
myCalculator.num1.setText("");
myCalculator.num2.setText("");
myCalculator.num3.setText("" + number3);
}
}
代码优化:修改外部类 CalculatorListener 为内部类:降低编码成本,可以直接访问外部类的所有属性
public class CalculatorTest {
public static void main(String[] args) {
new MyCalculator().loadFrame();
}
}
class MyCalculator extends Frame {
TextField num1, num2, num3;
public void loadFrame() {
setVisible(true);
setLayout(new FlowLayout());
pack();
this.num1 = new TextField(5);
this.num2 = new TextField(5);
this.num3 = new TextField(10);
Label label = new Label("+");
Button button = new Button("=");
button.addActionListener(new CalculatorListener());
add(num1);
add(label);
add(num2);
add(button);
add(num3);
windowClosed();
}
private void windowClosed() {
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
private class CalculatorListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
int number1 = Integer.parseInt(num1.getText());
int number2 = Integer.parseInt(num2.getText());
int number3 = number1 + number2;
num1.setText("");
num2.setText("");
num3.setText("" + number3);
}
}
}
1.8 Paint
1 自动绘制图案
public class PaintTest {
public static void main(String[] args) {
new MyPaint().loadFrame();
}
}
class MyPaint extends Frame{
public void loadFrame(){
setVisible(true);
setBounds(200,200,500,400);
}
@Override
public void paint(Graphics g) {
g.setColor(Color.lightGray);
g.drawOval(50,50,20,30);
g.setColor(Color.GREEN);
g.fillRect(200,200,100,50);
}
}
2 鼠标监听
public class MyPaintPoints {
public static void main(String[] args) {
new PointFrame("画图");
}
}
class PointFrame extends Frame{
//使用集合来存储鼠标点击的点
ArrayList points;
public PointFrame(String title){
super(title);
setBounds(200,200,500,400);
setVisible(true);
//存储鼠标点击的点
points = new ArrayList<>();
//鼠标监听器
this.addMouseListener(new MyMouseListener());
}
@Override
public void paint(Graphics g) {
Iterator iterator = points.iterator();
while (iterator.hasNext()){
Point point = (Point) iterator.next();
g.setColor(Color.blue);
g.fillOval(point.x, point.y, 10,10);
}
}
//添加点到点集合中
public void addPoint(Point point){
points.add(point);
}
}
class MyMouseListener extends MouseAdapter{
@Override
public void mouseClicked(MouseEvent e) {
PointFrame pointFrame = (PointFrame)e.getSource();
pointFrame.addPoint(new Point(e.getX(), e.getY()));
pointFrame.repaint();
}
}
3 窗口监听
public class MyWindowListener {
public static void main(String[] args) {
new MyWindowListener();
}
public MyWindowListener(){
Frame frame = new Frame();
frame.setVisible(true);
frame.setBounds(200,200,200,100);
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowOpened(WindowEvent e) {
System.out.println("windowOpened");
}
@Override
public void windowClosing(WindowEvent e) {
System.out.println("windowClosing");
}
@Override //常用
public void windowClosed(WindowEvent e) {
System.out.println("windowClosed");
}
@Override //常用
public void windowActivated(WindowEvent e) {
System.out.println("windowActivated");
}
});
}
}
4 键盘监听
public class MyKeyListener {
public static void main(String[] args) {
new MyKey();
}
}
class MyKey extends Frame{
public MyKey(){
setBounds(100,100,200,200);
setVisible(true);
addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_UP)
System.out.println("key UP Pressed");
}
@Override
public void keyReleased(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_UP)
System.out.println("key UP Released");
}
});
}
}
2 Swing 类
2.1 Swing 介绍
- 可以做更高级的操作,是 AWT 的子类
2.2 窗口、面板
public class JFrameInfo {
public static void main(String[] args) {
new MyJFrame().init();
}
}
class MyJFrame extends JFrame{
public void init(){
setVisible(true);
setBounds(100,100,200,200);
//窗体关闭方法
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
JLabel jLabel = new JLabel("Label");
add(jLabel);
//设置水平居中
jLabel.setHorizontalAlignment(SwingConstants.CENTER);
//将标签装进容器(没有 Container, 所有的设置不会生效)
Container container = getContentPane();
container.setBackground(Color.blue);
}
}
2.3 弹窗 Dialog
public class DialogInfo {
public static void main(String[] args) {
new MyDialog();
}
}
class MyDialog extends JFrame{
public MyDialog(){
this.setBounds(200,200,300,250);
this.setVisible(true);
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
Container container = this.getContentPane();
container.setLayout(null); //绝对定位,常用
JButton button = new JButton("点击按钮弹出弹窗");
button.setBounds(20,20,100,50);
button.setHorizontalAlignment(SwingConstants.CENTER);
button.setVerticalAlignment(SwingConstants.CENTER);
container.add(button);
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
new newDialog();
}
});
}
}
class newDialog extends JDialog{
public newDialog() {
//弹窗默认有关闭事件,不用重写
this.setVisible(true);
this.setBounds(300,300,100,100);
this.setLayout(null);
JLabel jLabel = new JLabel("这是弹窗");
Container container = new Container();
container.add(jLabel);
jLabel.setHorizontalAlignment(SwingConstants.CENTER);
container.setBackground(Color.CYAN);
}
}
2.4 标签
1 Icon 标签
public class MyJLabel extends JFrame implements Icon {
int width, height;
public MyJLabel (){};
public MyJLabel(int width, int height){
this.width = width;
this.height = height;
}
public void init(){
this.setBounds(200,200,300,300);
this.setVisible(true);
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
MyJLabel myJLabel = new MyJLabel(50, 50);
JLabel jLabel = new JLabel("iconTest", myJLabel, SwingConstants.CENTER);
Container container = getContentPane();
container.add(jLabel);
}
@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;
}
public static void main(String[] args) {
new MyJLabel().init();
}
}
2 Image 标签
public class ImageLabelTest extends JFrame{
public static void main(String[] args) {
new ImageLabelTest();
}
public ImageLabelTest(){
JLabel label = new JLabel("ImageIcon");
//获取当前类所在目录下的文件
URL url = ImageLabelTest.class.getResource("tt.jpg");
ImageIcon imageIcon = new ImageIcon(url);
label.setIcon(imageIcon);
label.setHorizontalAlignment(SwingConstants.CENTER);
Container container = getContentPane();
container.add(label);
setVisible(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
}
2.5 面板
1 JPanel
public class MyJPanelInfo extends JFrame {
public MyJPanelInfo(){
Container container = getContentPane();
container.setLayout(new GridLayout(2,1,10,10));
JPanel panel1 = new JPanel(new GridLayout(1,3));
JPanel panel2 = new JPanel(new GridLayout(2,2));
JButton button1 = new JButton("1");
JButton button2 = new JButton("2");
JButton button3 = new JButton("3");
JButton button4 = new JButton("4");
JButton button5 = new JButton("5");
JButton button6 = new JButton("6");
JButton button7 = new JButton("7");
panel1.add(button1);
panel1.add(button2);
panel1.add(button3);
panel2.add(button4);
panel2.add(button5);
panel2.add(button6);
panel2.add(button7);
container.add(panel1);
container.add(panel2);
setVisible(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setSize(500,500);
}
public static void main(String[] args) {
new MyJPanelInfo();
}
}
2 JScrollPane
public class JScrollPanelInfo extends JFrame {
public JScrollPanelInfo(){
Container container = getContentPane();
this.setVisible(true);
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
JTextArea text = new JTextArea(300,200);
text.setText("Hello Java!");
JScrollPane panel = new JScrollPane(text);
container.add(panel);
}
public static void main(String[] args) {
new JScrollPanelInfo();
}
}
2.6 按钮
1 图片按钮
public class ImageButtonInfo extends JFrame {
public ImageButtonInfo(){
setVisible(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setBounds(200,200,500,300);
Container container = getContentPane();
URL url = ImageButtonInfo.class.getResource("tt.jpg");
Icon myImage = new ImageIcon(url);
JButton button = new JButton();
button.setIcon(myImage);
button.setToolTipText("图片按钮");
container.add(button);
}
public static void main(String[] args) {
new ImageButtonInfo();
}
}
2 单选框
public class RadioButtonInfo extends JFrame {
public static void main(String[] args) {
new RadioButtonInfo();
}
public RadioButtonInfo(){
setVisible(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setBounds(200,200,500,300);
Container container = getContentPane();
JRadioButton radioButton1 = new JRadioButton("radioButton1");
JRadioButton radioButton2 = new JRadioButton("radioButton2");
JRadioButton radioButton3 = new JRadioButton("radioButton3");
//单选框只能选择一个,因此要将所有的按钮分成一个组
ButtonGroup buttonGroup = new ButtonGroup();
buttonGroup.add(radioButton1);
buttonGroup.add(radioButton2);
buttonGroup.add(radioButton3);
container.add(radioButton1, BorderLayout.CENTER);
container.add(radioButton2, BorderLayout.NORTH);
container.add(radioButton3, BorderLayout.SOUTH);
}
}
3 复选框
public class CheckBoxInfo extends JFrame {
public static void main(String[] args) {
new CheckBoxInfo();
}
public CheckBoxInfo(){
setVisible(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setBounds(200,200,500,300);
Container container = getContentPane();
JCheckBox checkBox1 = new JCheckBox("checkBox1");
JCheckBox checkBox2 = new JCheckBox("checkBox2");
container.add(checkBox1, BorderLayout.NORTH);
container.add(checkBox2, BorderLayout.SOUTH);
}
}
2.7 列表
1 下拉列表框
public class JComboBoxInfo extends JFrame {
public static void main(String[] args) {
new JComboBoxInfo();
}
public JComboBoxInfo(){
setVisible(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setBounds(200,200,500,300);
Container container = getContentPane();
JComboBox status = new JComboBox();
status.addItem(null);
status.addItem("喜剧");
status.addItem("校园剧");
status.addItem("推理剧");
container.add(status);
}
}
2 列表框
public class JListInfo extends JFrame {
public static void main(String[] args) {
new JListInfo();
}
public JListInfo(){
setVisible(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setBounds(200,200,500,300);
Container container = getContentPane();
//静态列表
String[] str = {"Item-1", "Item-2", "Item-3"};
//动态列表
Vector str = new Vector();
str.add("A");
str.add("B");
str.add("C");
JList jList = new JList(str);
container.add(jList);
}
}
2.8 文本框
1 文本框
public class JTextFieldInfo extends JFrame {
public static void main(String[] args) {
new JTextFieldInfo();
}
public JTextFieldInfo(){
setVisible(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setBounds(200,200,500,300);
Container container = getContentPane();
JTextField text = new JTextField("Hello", 5);
JTextField text2 = new JTextField("Hello", 5);
container.add(text, BorderLayout.SOUTH);
container.add(text2, BorderLayout.NORTH);
}
}
2 密码框
public class JTextFieldInfo2 extends JFrame {
public static void main(String[] args) {
new JTextFieldInfo2();
}
public JTextFieldInfo2(){
setVisible(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setBounds(200,200,500,300);
Container container = getContentPane();
//默认显示 *
JPasswordField jPasswordField = new JPasswordField();
jPasswordField.setEchoChar('@'); //可以设置别的
container.add(jPasswordField);
}
}
3 文本域
public class JTextAreaInfo extends JFrame {
public static void main(String[] args) {
new JTextAreaInfo();
}
public JTextAreaInfo(){
setVisible(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setBounds(200,200,500,300);
Container container = getContentPane();
JTextArea text = new JTextArea("Hello");
container.add(text);
}
}
练习:贪吃蛇
1 界面设计
- 主界面大小:900 * 720
- 蛇身大小:25 * 25
- 蛇头大小:25 * 25
- 食物大小:25 * 25
2 静态页面绘制
2.1 项目结构
2.2 初始化界面
- 面板类
public class GamePanel extends JPanel{
public GamePanel(){
}
//面板绘制方法
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g); //清屏
setBackground(Color.WHITE);
//头部计分板
GameSources.info.paintIcon(this, g, 20,5);
//游戏区域
g.fillRect(20,65,850,600);
}
}
- 资源加载类
public class GameSources extends JFrame{
//头部计分板
public static URL INFO_URL = GameSources.class.getResource("../statics/header.png");
//蛇身体
public static URL HEAD_UP_URL = GameSources.class.getResource("../statics/up.png");
public static URL HEAD_DOWN_URL = GameSources.class.getResource("../statics/down.png");
public static URL HEAD_LEFT_URL = GameSources.class.getResource("../statics/left.png");
public static URL HEAD_RIGHT_URL = GameSources.class.getResource("../statics/right.png");
public static URL BODY_URL = GameSources.class.getResource("../statics/body.png");
//食物
public static URL FOOD_URL = GameSources.class.getResource("../statics/food.png");
public static ImageIcon info = new ImageIcon(INFO_URL);
public static ImageIcon up = new ImageIcon(HEAD_UP_URL);
public static ImageIcon down = new ImageIcon(HEAD_DOWN_URL);
public static ImageIcon left = new ImageIcon(HEAD_LEFT_URL);
public static ImageIcon right = new ImageIcon(HEAD_RIGHT_URL);
public static ImageIcon body = new ImageIcon(BODY_URL);
public static ImageIcon food = new ImageIcon(FOOD_URL);
}
- 主启动类
public class Application {
public static void main(String[] args) {
//主界面初始化
JFrame frame = new JFrame("悟道九霄 — 贪吃蛇 1.0.0");
frame.setBounds(450,160,910,720);
frame.setResizable(false);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.add(new GamePanel());
frame.setVisible(true);
}
}
3 让蛇动起来
3.1 静态小蛇和提示语
public class GamePanel extends JPanel implements KeyListener {
int length = 0;
//蛇头坐标
int[] snakeX = new int[800];
int[] snakeY = new int[800];
String fx;
boolean isStart = false; //游戏是否开始
public GamePanel(){
init();
//获取键盘的监听事件
this.addKeyListener(this);
this.setFocusable(true);
}
public void init() {
//蛇初始化
fx = "RIGHT"; //蛇头默认朝右
length = 3;
snakeX[0] = 100; snakeY[0] = 100;
snakeX[1] = 75; snakeY[1] = 100;
snakeX[2] = 50; snakeY[2] = 100;
}
//面板绘制方法
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g); //清屏
setBackground(Color.WHITE);
//头部计分板
GameSources.info.paintIcon(this, g, 20,5);
//游戏区域
g.fillRect(20,65,850,600);
//蛇头 + 蛇身
if (fx.equals("RIGHT"))
GameSources.right.paintIcon(this,g,snakeX[0], snakeY[0]);
else if (fx.equals("LEFT"))
GameSources.left.paintIcon(this,g,snakeX[0], snakeY[0]);
else if (fx.equals("UP"))
GameSources.up.paintIcon(this,g,snakeX[0], snakeY[0]);
else if (fx.equals("DOWN"))
GameSources.down.paintIcon(this,g,snakeX[0], snakeY[0]);
for (int i = 1; i < length; i++) //通过 length 来控制蛇的长度
GameSources.body.paintIcon(this, g, snakeX[i], snakeY[i]);
//是否开始游戏提示
if (!isStart){
/*【重点】:可能是 JPanel 版本更新的问题,在构造函数中将 setFocusable 设置为 true 之后
以前不用再次激活,便可实现实时监听键盘
但是现在,需要在“适当的时候”再次激活,才可以实现键盘监听
此处因为 isStart 改变,所以要重新激活焦点才可以再次监听
*/
this.requestFocus();
g.setColor(Color.WHITE);
g.setFont(new Font("微软雅黑", Font.BOLD, 40));
g.drawString("按下空格 开始/暂停 游戏",220, 350);
}
}
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
if (keyCode == KeyEvent.VK_SPACE){
isStart = !isStart;
repaint(); //重复刷新,小蛇才会动起来
}
}
@Override
public void keyReleased(KeyEvent e) {
}
@Override
public void keyTyped(KeyEvent e) {
}
}
【遇到的问题】
问:明明在构造方法里添加了 keyListener,键盘焦点 setFocusable 也设置为 true ,但是按键盘就是没反应?
答:这是一个很容易忽略的小细节(导致我当初 Debug 了一下午,各种 Google...),解决此问题有两种方法
法一:在 isStart 改变的时候,这个时候键盘已经失去焦点,需要再次激活,因此需要在重新绘画之前,再次激活键盘焦点
if (!isStart){ this.requestFocus(); //再次激活焦点 ...... }
法二:在 Application 启动类中,需要将主窗体 JFrame 的可见性设置放在调用 GamePanel 的构造方法之后,若写在构造方法之前,可见性已经为 true,窗体已经显示,因此 GamePanel 构造方法中的 setFocusable 就算设置为 true,也只能在内存中生效,所以如【法一】 所述,使用前需要再次激活。为了一劳永逸,将构造方法写在窗体可见性的前边即可。即:
//错误写法 public class Application { public static void main(String[] args) { frame.setVisible(true); frame.add(new GamePanel()); } } //正确写法 public class Application { public static void main(String[] args) { frame.add(new GamePanel()); frame.setVisible(true); } }
3.2 静态初始界面
- 暂停时
- 开始后(点击空格后)
3.3 添加定时器
public class GamePanel extends JPanel implements KeyListener, ActionListener {
int length = 0;
//蛇头坐标
int[] snakeX = new int[800];
int[] snakeY = new int[800];
String fx;
boolean isStart = false; //游戏是否开始
//通过改变 delay 来控制游戏的速率大小
Timer timer = new Timer(100, this);
public GamePanel(){
init();
//获取键盘的监听事件
this.setFocusable(true);
this.addKeyListener(this);
timer.start(); //一定要在构造方法里启动定时器,否则没用
}
public void init() {
//蛇初始化
fx = "RIGHT"; //蛇头默认朝右
length = 3;
snakeX[0] = 100; snakeY[0] = 100;
snakeX[1] = 75; snakeY[1] = 100;
snakeX[2] = 50; snakeY[2] = 100;
}
//面板绘制方法
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g); //清屏
setBackground(Color.WHITE);
//头部计分板
GameSources.info.paintIcon(this, g, 20,5);
//游戏区域
g.fillRect(20,65,850,600);
//蛇头 + 蛇身
if (fx.equals("RIGHT"))
GameSources.right.paintIcon(this,g,snakeX[0], snakeY[0]);
else if (fx.equals("LEFT"))
GameSources.left.paintIcon(this,g,snakeX[0], snakeY[0]);
else if (fx.equals("UP"))
GameSources.up.paintIcon(this,g,snakeX[0], snakeY[0]);
else if (fx.equals("DOWN"))
GameSources.down.paintIcon(this,g,snakeX[0], snakeY[0]);
for (int i = 1; i < length; i++) //通过 length 来控制蛇的长度
GameSources.body.paintIcon(this, g, snakeX[i], snakeY[i]);
//是否开始游戏提示
if (!isStart){
g.setColor(Color.WHITE);
g.setFont(new Font("微软雅黑", Font.BOLD, 40));
g.drawString("按下空格 开始/暂停 游戏",220, 350);
}
}
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
//游戏的暂停和开始
if (keyCode == KeyEvent.VK_SPACE){
isStart = !isStart;
repaint();
}
//蛇的走向
if (keyCode == KeyEvent.VK_RIGHT){
fx = "RIGHT";
}else if (keyCode == KeyEvent.VK_UP){
fx = "UP";
}else if (keyCode == KeyEvent.VK_LEFT){
fx = "LEFT";
}else if (keyCode == KeyEvent.VK_DOWN){
fx = "DOWN";
}
}
@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("RIGHT")){
snakeX[0] += 25;
if (snakeX[0] > 845){snakeX[0] = 25;}
}else if (fx.equals("LEFT")){
snakeX[0] -= 25;
if (snakeX[0] < 25){snakeX[0] = 845;}
}else if (fx.equals("UP")){
snakeY[0] -= 25;
if (snakeY[0] < 75) {snakeY[0] = 630;}
}else if (fx.equals("DOWN")){
snakeY[0] += 25;
if (snakeY[0] > 630) {snakeY[0] = 75;}
}
repaint();
}
timer.start();
}
@Override
public void keyReleased(KeyEvent e) {
}
@Override
public void keyTyped(KeyEvent e) {
}
}
4 吃食物
public class GamePanel extends JPanel implements KeyListener, ActionListener {
int length = 0;
//蛇头坐标
int[] snakeX = new int[800];
int[] snakeY = new int[800];
int foodX, foodY;
Random random = new Random();
String fx;
boolean isStart = false; //游戏是否开始
//通过改变 delay 来控制游戏的速率大小
Timer timer = new Timer(100, this);
public GamePanel(){
init();
//获取键盘的监听事件
this.setFocusable(true);
this.addKeyListener(this);
timer.start();
}
public void init() {
//蛇初始化
fx = "RIGHT"; //蛇头默认朝右
length = 3;
snakeX[0] = 100; snakeY[0] = 100;
snakeX[1] = 75; snakeY[1] = 100;
snakeX[2] = 50; snakeY[2] = 100;
foodX = 25 + 25 * random.nextInt(33);
foodY = 75 + 25 * random.nextInt(23);
}
//面板绘制方法
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g); //清屏
setBackground(Color.WHITE);
//头部计分板
GameSources.info.paintIcon(this, g, 20,5);
//游戏区域
g.fillRect(20,65,850,600);
//蛇头 + 蛇身
if (fx.equals("RIGHT"))
GameSources.right.paintIcon(this, g, snakeX[0], snakeY[0]);
else if (fx.equals("LEFT"))
GameSources.left.paintIcon(this, g, snakeX[0], snakeY[0]);
else if (fx.equals("UP"))
GameSources.up.paintIcon(this, g, snakeX[0], snakeY[0]);
else if (fx.equals("DOWN"))
GameSources.down.paintIcon(this, g, snakeX[0], snakeY[0]);
for (int i = 1; i < length; i++) //通过 length 来控制蛇的长度
GameSources.body.paintIcon(this, g, snakeX[i], snakeY[i]);
//绘制食物
GameSources.food.paintIcon(this, g, foodX, foodY);
//是否开始游戏提示
if (!isStart){
g.setColor(Color.WHITE);
g.setFont(new Font("微软雅黑", Font.BOLD, 40));
g.drawString("按下空格 开始/暂停 游戏",220, 350);
}
}
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
//游戏的暂停和开始
if (keyCode == KeyEvent.VK_SPACE){
isStart = !isStart;
repaint();
}
//蛇的走向
if (keyCode == KeyEvent.VK_RIGHT){
fx = "RIGHT";
}else if (keyCode == KeyEvent.VK_UP){
fx = "UP";
}else if (keyCode == KeyEvent.VK_LEFT){
fx = "LEFT";
}else if (keyCode == KeyEvent.VK_DOWN){
fx = "DOWN";
}
}
@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("RIGHT")){
snakeX[0] += 25;
if (snakeX[0] > 845){snakeX[0] = 25;}
}else if (fx.equals("LEFT")){
snakeX[0] -= 25;
if (snakeX[0] < 25){snakeX[0] = 845;}
}else if (fx.equals("UP")){
snakeY[0] -= 25;
if (snakeY[0] < 75) {snakeY[0] = 630;}
}else if (fx.equals("DOWN")){
snakeY[0] += 25;
if (snakeY[0] > 630) {snakeY[0] = 75;}
}
//添加食物
//若直接使用两坐标相等判断,会因为像素的问题导致吃不到,因此两坐标差值在一定范围内,就判定为迟到了食物
if (Math.abs(snakeX[0] - foodX) < 15 && Math.abs(snakeY[0] - foodY) < 15){
length++;
//解决每次生成食物左上角 body 会闪一下
snakeX[length - 1] = snakeX[length - 2] * 2 - snakeX[length-3];
snakeY[length - 1] = snakeY[length - 2] * 2 - snakeY[length-3];
//随机生成食物
foodX = 25 + 25 * random.nextInt(33);
foodY = 75 + 25 * random.nextInt(23);
}
repaint();
}
timer.start();
}
@Override
public void keyReleased(KeyEvent e) {
}
@Override
public void keyTyped(KeyEvent e) {
}
}
5 失败判定
public class GamePanel extends JPanel implements KeyListener, ActionListener {
int length = 0;
//蛇头坐标
int[] snakeX = new int[800];
int[] snakeY = new int[800];
int foodX, foodY;
int speed = 100;
Random random = new Random();
String fx;
boolean isStart = false; //游戏是否开始
//通过改变 delay 来控制游戏的速率大小
Timer timer = new Timer(speed, this);
//失败判定
boolean isFail = false;
public GamePanel(){
init();
//获取键盘的监听事件
this.setFocusable(true);
this.addKeyListener(this);
timer.start();
}
public void init() {
//蛇初始化
fx = "RIGHT"; //蛇头默认朝右
length = 3;
snakeX[0] = 100; snakeY[0] = 100;
snakeX[1] = 75; snakeY[1] = 100;
snakeX[2] = 50; snakeY[2] = 100;
foodX = 25 + 25 * random.nextInt(33);
foodY = 75 + 25 * random.nextInt(23);
}
//面板绘制方法
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g); //清屏
setBackground(Color.WHITE);
//头部计分板
GameSources.info.paintIcon(this, g, 20,5);
//游戏区域
g.fillRect(20,65,850,600);
//蛇头 + 蛇身
if (fx.equals("RIGHT"))
GameSources.right.paintIcon(this, g, snakeX[0], snakeY[0]);
else if (fx.equals("LEFT"))
GameSources.left.paintIcon(this, g, snakeX[0], snakeY[0]);
else if (fx.equals("UP"))
GameSources.up.paintIcon(this, g, snakeX[0], snakeY[0]);
else if (fx.equals("DOWN"))
GameSources.down.paintIcon(this, g, snakeX[0], snakeY[0]);
for (int i = 1; i < length; i++) //通过 length 来控制蛇的长度
GameSources.body.paintIcon(this, g, snakeX[i], snakeY[i]);
//绘制食物
GameSources.food.paintIcon(this, g, foodX, foodY);
//是否开始游戏提示
if (!isStart){
g.setColor(Color.WHITE);
g.setFont(new Font("微软雅黑", Font.BOLD, 40));
g.drawString("按下空格 开始/暂停 游戏",220, 350);
}
//失败提醒
if (isFail){
g.setColor(Color.RED);
g.setFont(new Font("微软雅黑", Font.BOLD, 40));
g.drawString("游戏失败!",365, 330);
g.drawString("按下 空格 重新开始",280, 380);
}
}
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
//游戏的暂停和开始
if (keyCode == KeyEvent.VK_SPACE){
if (isFail){ //重新开始
isFail = false;
init();
}else{ //游戏暂停
isStart = !isStart;
}
repaint();
}
//蛇的走向
if (keyCode == KeyEvent.VK_RIGHT){
fx = "RIGHT";
}else if (keyCode == KeyEvent.VK_UP){
fx = "UP";
}else if (keyCode == KeyEvent.VK_LEFT){
fx = "LEFT";
}else if (keyCode == KeyEvent.VK_DOWN){
fx = "DOWN";
}
}
@Override
public void actionPerformed(ActionEvent e) {
if (isStart && !isFail){ //游戏开始且没有失败
for (int i = length - 1; i > 0; i--){
snakeX[i] = snakeX[i-1];
snakeY[i] = snakeY[i-1];
}
//头部移动
if (fx.equals("RIGHT")){
snakeX[0] += 25;
if (snakeX[0] > 845){snakeX[0] = 25;}
}else if (fx.equals("LEFT")){
snakeX[0] -= 25;
if (snakeX[0] < 25){snakeX[0] = 845;}
}else if (fx.equals("UP")){
snakeY[0] -= 25;
if (snakeY[0] < 75) {snakeY[0] = 630;}
}else if (fx.equals("DOWN")){
snakeY[0] += 25;
if (snakeY[0] > 630) {snakeY[0] = 75;}
}
//添加食物
//若直接使用两坐标相等判断,会因为像素的问题导致吃不到,因此两坐标差值在一定范围内,就判定为迟到了食物
if (Math.abs(snakeX[0] - foodX) < 20 && Math.abs(snakeY[0] - foodY) < 20){
length++;
//解决每次生成食物左上角 body 会闪一下
snakeX[length - 1] = snakeX[length - 2] * 2 - snakeX[length-3];
snakeY[length - 1] = snakeY[length - 2] * 2 - snakeY[length-3];
//随机生成食物
foodX = 25 + 25 * random.nextInt(32);
foodY = 75 + 25 * random.nextInt(22);
}
//判定是否咬到自己
for (int i = 1; i < length; i++){
if (snakeX[i] == snakeX[0] && snakeY[i] == snakeY[0]){
isFail = true;
}
}
repaint();
}
timer.start();
}
@Override
public void keyReleased(KeyEvent e) {
}
@Override
public void keyTyped(KeyEvent e) {
}
}
6 积分系统
package com.jiuxiao.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.util.Random;
public class GamePanel extends JPanel implements KeyListener, ActionListener {
int length = 0;
//蛇头坐标
int[] snakeX = new int[800];
int[] snakeY = new int[800];
int foodX, foodY;
int speed = 100;
Random random = new Random();
String fx;
boolean isStart = false; //游戏是否开始
//通过改变 delay 来控制游戏的速率大小
Timer timer = new Timer(speed, this);
//失败判定
boolean isFail = false;
//积分系统
int score;
public GamePanel(){
init();
//获取键盘的监听事件
this.setFocusable(true);
this.addKeyListener(this);
timer.start();
}
public void init() {
//蛇初始化
fx = "RIGHT"; //蛇头默认朝右
length = 3;
score = 0;
snakeX[0] = 100; snakeY[0] = 100;
snakeX[1] = 75; snakeY[1] = 100;
snakeX[2] = 50; snakeY[2] = 100;
foodX = 25 + 25 * random.nextInt(33);
foodY = 75 + 25 * random.nextInt(23);
}
//面板绘制方法
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g); //清屏
setBackground(Color.WHITE);
//头部计分板
GameSources.info.paintIcon(this, g, 20,5);
//游戏区域
g.fillRect(20,65,850,600);
//蛇头 + 蛇身
if (fx.equals("RIGHT"))
GameSources.right.paintIcon(this, g, snakeX[0], snakeY[0]);
else if (fx.equals("LEFT"))
GameSources.left.paintIcon(this, g, snakeX[0], snakeY[0]);
else if (fx.equals("UP"))
GameSources.up.paintIcon(this, g, snakeX[0], snakeY[0]);
else if (fx.equals("DOWN"))
GameSources.down.paintIcon(this, g, snakeX[0], snakeY[0]);
for (int i = 1; i < length; i++) //通过 length 来控制蛇的长度
GameSources.body.paintIcon(this, g, snakeX[i], snakeY[i]);
//绘制食物
GameSources.food.paintIcon(this, g, foodX, foodY);
//是否开始游戏提示
if (!isStart){
g.setColor(Color.WHITE);
g.setFont(new Font("微软雅黑", Font.BOLD, 40));
g.drawString("按下空格 开始/暂停 游戏",220, 350);
}
//绘制积分系统
g.setColor(Color.WHITE);
g.setFont(new Font("微软雅黑", Font.BOLD, 16));
g.drawString("总长度 : " + length, 700, 23);
g.drawString("总得分 : " + score, 700, 45);
//失败提醒
if (isFail){
g.setColor(Color.CYAN);
g.setFont(new Font("微软雅黑", Font.BOLD, 40));
g.drawString("游戏失败!",350, 290);
g.drawString("按下空格重新开始",270, 380);
g.setColor(Color.green);
g.setFont(new Font("微软雅黑", Font.BOLD, 20));
g.drawString("得分 : " + score + "分",380, 330);
}
}
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
//游戏的暂停和开始
if (keyCode == KeyEvent.VK_SPACE){
if (isFail){ //重新开始
isFail = false;
init();
}else{ //游戏暂停
isStart = !isStart;
}
repaint();
}
//蛇的走向
if (keyCode == KeyEvent.VK_RIGHT){
fx = "RIGHT";
}else if (keyCode == KeyEvent.VK_UP){
fx = "UP";
}else if (keyCode == KeyEvent.VK_LEFT){
fx = "LEFT";
}else if (keyCode == KeyEvent.VK_DOWN){
fx = "DOWN";
}
}
@Override
public void actionPerformed(ActionEvent e) {
if (isStart && !isFail){ //游戏开始且没有失败
for (int i = length - 1; i > 0; i--){
snakeX[i] = snakeX[i-1];
snakeY[i] = snakeY[i-1];
}
//头部移动
if (fx.equals("RIGHT")){
snakeX[0] += 25;
if (snakeX[0] > 845){snakeX[0] = 25;}
}else if (fx.equals("LEFT")){
snakeX[0] -= 25;
if (snakeX[0] < 25){snakeX[0] = 845;}
}else if (fx.equals("UP")){
snakeY[0] -= 25;
if (snakeY[0] < 75) {snakeY[0] = 630;}
}else if (fx.equals("DOWN")){
snakeY[0] += 25;
if (snakeY[0] > 630) {snakeY[0] = 75;}
}
//添加食物
//若直接使用两坐标相等判断,会因为像素的问题导致吃不到,因此两坐标差值在一定范围内,就判定为迟到了食物
if (Math.abs(snakeX[0] - foodX) < 20 && Math.abs(snakeY[0] - foodY) < 20){
length++;
score = (length - 3) * 5;
//解决每次生成食物左上角 body 会闪一下
snakeX[length - 1] = snakeX[length - 2] * 2 - snakeX[length-3];
snakeY[length - 1] = snakeY[length - 2] * 2 - snakeY[length-3];
//随机生成食物
foodX = 40 + 25 * random.nextInt(32);
foodY = 90 + 25 * random.nextInt(22);
}
//判定是否咬到自己
for (int i = 1; i < length; i++){
if (snakeX[i] == snakeX[0] && snakeY[i] == snakeY[0]){
isFail = true;
}
}
repaint();
}
timer.start();
}
@Override
public void keyReleased(KeyEvent e) {
}
@Override
public void keyTyped(KeyEvent e) {
}
}
7 打包发布
- 点击右上角的 【项目结构图标】
- 依次点击【Artifacts】、【JAR】、【From moduls with ...】
- 点击右上角文件夹图标,选择程序主启动类,依次点击【OK】
- 依次点击【apply】、【OK】
- 依次点击【Build】、【Build Artifacts...】
- 再点击【Bulid】,等待项目打包结束
- 打包好之后,会在项目【out】路径下生成一个 【xxx.jar】包,这就是打包好的 jar 包
- 验证是否打包成功,右击 jar 包,选择在文件管理器中打开,可以看到文件夹中已经有了一个 jar 包,在此打开 cmd,运行 jar 包
- 至此,项目打包完成,若要继续转为 exe 可执行文件,参考教程 [作者:李逍遥~](IDEA导出jar打包成exe应用程序的小结_java_脚本之家 (jb51.net))
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!