一、简介
图形用户界面(Graphical User Interface,简称 GUI,又称图形用户接口)是指采用图形方式显示的计算机操作用户界面。
GUI核心:Swing、AWT
JavaGUI不流行的原因:
- 需要jre环境
- 界面不美观
既然不流行为什么要学习?
- 可以写出自己心中想要的一些小工具
- 工作时候,也可能需要维护Swing界面,概率不大
- 了解MVC框架,了解监听
二、AWT
2.1 AWT介绍
AWT(Abstract Window Toolkit),中文译为抽象窗口工具包,该包提供了一套与本地图形界面进行交互的接口,是Java提供的用来建立和设置Java的图形用户界面的基本工具。
2.2 组件和容器
1. Frame
package com.kongbai.gui_study;
import java.awt.*;
/**
* @author kongbai
* @version 1.0.0
* @ClassName TestFrame.java
* @Description TODO
* @createTime 2022年03月17日 16:03:00
* GUI的第一个界面
*/
public class TestFrame {
public static void main(String[] args) {
//Frame
Frame frame = new Frame("我的第一个图像界面窗口");
//设置可见性
frame.setVisible(true);
//设置窗口大小
frame.setSize(400, 400);
//设置颜色
frame.setBackground(new Color(113, 170, 146));
//弹出的初始位置
frame.setLocation(200, 200);
//设置大小固定
frame.setResizable(false); //默认为true,即可改变
}
}
显示效果如下,弹出位置在屏幕左上角;大小无法改变且无法通过关闭按钮关闭窗口,只能关闭程序
回顾一下封装,使用封装就可以方便的显示多个窗口
package com.kongbai.gui_study;
import java.awt.*;
/**
* @author kongbai
* @version 1.0.0
* @ClassName TestFrame02.java
* @Description TODO
* @createTime 2022年03月17日 16:17:00
*/
public class TestFrame02 {
public static void main(String[] args) {
//展示多个窗口
MyFrame myFrame1 = new MyFrame(100, 100, 200, 200, Color.blue);
MyFrame myFrame2 = new MyFrame(300, 100, 200, 200, Color.red);
MyFrame myFrame3 = new MyFrame(100, 300, 200, 200, Color.black);
MyFrame myFrame4 = new MyFrame(300, 300, 200, 200, Color.green);
}
}
class MyFrame extends Frame {
static int id = 0; //可能存在多个窗口,需要一个计数器
public MyFrame(int x, int y, int w, int h, Color color) {
super("MyFrame" + (++id));
setVisible(true); //设置可见性
setBounds(x, y, w, h); //设置初始位置及大小
setBackground(color); //设置颜色
setResizable(false); //设置大小固定
}
}
四个不可改变大小的窗口,整齐排列
2. 面板Panel
package com.kongbai.gui_study;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
/**
* @author kongbai
* @version 1.0.0
* @ClassName TestPanel.java
* @Description TODO
* @createTime 2022年03月17日 16:39:00
* panel,在容器中的,可以看出是一个空间,但不能单独存在
*/
public class TestPanel {
public static void main(String[] args) {
Frame frame = new Frame();
Panel panel = new Panel(); //布局的概念
frame.setLayout(null); //设置布局
//坐标
frame.setBounds(300, 300, 500, 500);
frame.setBackground(new Color(255, 255, 0));
//设置panel坐标,相对于frame
panel.setBounds(50, 50, 400, 400);
panel.setBackground(new Color(1, 1, 1));
frame.add(panel); //在frame中添加panel
frame.setVisible(true); //设置可见性
//监听事件,监听窗口关闭事件 System.exit(0);
//这里要使用“适配器模式(23种)”;会出现弹窗选择,而不会像new WindowListener()那样全部重写
frame.addWindowListener(new WindowAdapter() {
//点击窗口关闭按钮时要做的事情
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
}
测试下关闭按钮是否可用
![image.png](https://i-blog.csdnimg.cn/blog_migrate/6a1c53be3aa560349cecf0e0c55f6cca.png)
3. 布局管理器
- 流式布局 -->左右
package com.kongbai.gui_study;
import java.awt.*;
/**
* @author kongbai
* @version 1.0.0
* @ClassName TestFlowLayout.java
* @Description FlowLayout
* @createTime 2022年03月17日 17:03:00
*/
public class TestFlowLayout {
public static void main(String[] args) {
Frame frame = new Frame();
//组件-->按钮
Button button1 = new Button("button1");
Button button2 = new Button("button2");
Button button3 = new Button("button3");
/*
1则是居中;或者FlowLayout.CENTER
frame.setLayout(new FlowLayout(0)); //或者将0FlowLayout.LEFT;按钮靠左
frame.setLayout(new FlowLayout(2)); //或者将2FlowLayout.RIGHT;按钮靠右
*/
frame.setLayout(new FlowLayout()); //设置为流式布局,默认居中
frame.setSize(200, 200);
//添加按钮
frame.add(button1);
frame.add(button2);
frame.add(button3);
frame.setVisible(true);
//关闭按钮作用
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
}
没指定初始坐标,所以默认在最左上角出现;可以调整大小,查看按钮位置
![image.png](https://i-blog.csdnimg.cn/blog_migrate/8be0bb410f5eca28c9cb11f5ad1d72ee.png)
- 表格布局 -->行列
package com.kongbai.gui_study;
import java.awt.*;
/**
* @author kongbai
* @version 1.0.0
* @ClassName TestGridLayout.java
* @Description TODO
* @createTime 2022年03月17日 17:57:00
*/
public class TestGridLayout {
public static void main(String[] args) {
Frame frame = new Frame("TestGridLayout");
Button btn1 = new Button("btn1");
Button btn2 = new Button("btn2");
Button btn3 = new Button("btn3");
Button btn4 = new Button("btn4");
Button btn5 = new Button("btn5");
Button btn6 = new Button("btn6");
frame.setLayout(new GridLayout(3, 2));
frame.add(btn1);
frame.add(btn2);
frame.add(btn3);
frame.add(btn4);
frame.add(btn5);
frame.add(btn6);
frame.pack(); //Java函数,可以自动适配大小
frame.setVisible(true);
}
}
不管怎么拖到窗口,按钮大小会自动适配
![image.png](https://i-blog.csdnimg.cn/blog_migrate/d47dd565e213acc1adfd712f76a28086.png)
- 东西南北中 -->上下
![image.png](https://i-blog.csdnimg.cn/blog_migrate/d6362248a424ab8e40febcaeeed56675.png)
package com.kongbai.gui_study;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
/**
* @author kongbai
* @version 1.0.0
* @ClassName TestBorderLayout.java
* @Description TODO
* @createTime 2022年03月17日 17:52:00
*/
public class TestBorderLayout {
public static void main(String[] args) {
Frame frame = new Frame("TestBorderLayout");
Button east = new Button("East");
Button west = new Button("west");
Button south = new Button("south");
Button north = new Button("north");
Button center = new Button("center");
frame.add(east, BorderLayout.EAST);
frame.add(west, BorderLayout.WEST);
frame.add(south, BorderLayout.SOUTH);
frame.add(north, BorderLayout.NORTH);
frame.add(center, BorderLayout.CENTER);
frame.setSize(300, 300);
frame.setVisible(true);
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
}
![image.png](https://i-blog.csdnimg.cn/blog_migrate/9a2726d8a0ea880206b4a697f3529037.png)
3.1 小作业
实现一个这样的界面
![image.png](https://i-blog.csdnimg.cn/blog_migrate/e724515a1b2c90e699c19f50c23770b8.png)
package com.kongbai.gui_study;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
/**
* @author kongbai
* @version 1.0.0
* @ClassName Operation.java
* @Description 小作业
* @createTime 2022年03月17日 20:59:00
*/
public class Operation {
public static void main(String[] args) {
Frame frame = new Frame("Operation");
frame.setSize(400, 400);
frame.setLocation(300, 300);
frame.setBackground(new Color(178, 229, 255));
frame.setVisible(true);
frame.setLayout(new GridLayout(2, 1));
//4个面板
Panel panel1 = new Panel(new BorderLayout());
Panel panel2 = new Panel(new GridLayout(2, 1));
Panel panel3 = new Panel(new BorderLayout());
Panel panel4 = new Panel(new GridLayout(2, 1));
//上面
panel1.add(new Button("East-1"), BorderLayout.EAST);
panel1.add(new Button("West-1"), BorderLayout.WEST);
panel2.add(new Button("CENTER-1"));
panel2.add(new Button("CENTER-2"));
panel1.add(panel2, BorderLayout.CENTER);
//下面
panel3.add(new Button("East-2"), BorderLayout.EAST);
panel3.add(new Button("West-2"), BorderLayout.WEST);
//中间4个
for (int i = 1; i < 5; i++) {
panel4.add(new Button("for-" + i));
}
panel3.add(panel4, BorderLayout.CENTER);
//这里只需添加两个即可,因为前面已经添加过了
frame.add(panel1);
frame.add(panel3);
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
}
![image.png](https://i-blog.csdnimg.cn/blog_migrate/002034553eb9adc29346322c0d8910ff.png)
3.2 小总结:
- Frame是一个顶级窗口
- Panel无法单独显示,必须添加到某个容器中
- 布局管理器
- 流式
- 表格
- 东西南北中
- 大小、定位、颜色、可见性、监听
4. 事件监听
4.1 按钮点击事件
package com.kongbai.gui_study;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
/**
* @author kongbai
* @version 1.0.0
* @ClassName TestActionEvent.java
* @Description 按钮触发事件
* @createTime 2022年03月18日 15:12:00
*/
public class TestActionEvent {
public static void main(String[] args) {
Frame frame = new Frame();
Button button = new Button("button");
//因为addActionListener()需要一个ActuionListener,所以需要构造一个ActionListener
MyActionListener myActionListener = new MyActionListener();
button.addActionListener(myActionListener);
frame.add(button, BorderLayout.CENTER);
frame.setBounds(200, 200, 400, 400);
windowClose(frame); //关闭窗口
frame.setVisible(true);
}
//关闭窗体的事件
private 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("按钮点击了");
}
}
![image.png](https://i-blog.csdnimg.cn/blog_migrate/79cecbd9029ed56c535b287771aa47dd.png)
4.2 两个按钮共用一事件
package com.kongbai.gui_study;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/**
* @author kongbai
* @version 1.0.0
* @ClassName TestActionEvent02.java
* @Description 双按钮实现同一监听
* @createTime 2022年03月18日 15:32:00
*/
public class TestActionEvent02 {
public static void main(String[] args) {
Frame frame = new Frame("开始-停止");
Button button1 = new Button("start");
Button button2 = new Button("stop");
/*
可以显示的定义触发会返回的命令,如果不定义则会走默认的值
可以多个按钮只写一个监听类
*/
button2.setActionCommand("button2-stop");
MyMonitor myMonitor = new MyMonitor();
button1.addActionListener(myMonitor);
button2.addActionListener(myMonitor);
frame.add(button1, BorderLayout.NORTH);
frame.add(button2, BorderLayout.SOUTH);
frame.setVisible(true);
frame.setBounds(200, 200, 300, 300);
}
}
class MyMonitor implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("按钮被点击了:" + e.getActionCommand());
}
}
分别点击,同一事件,触发效果不同
![image.png](https://i-blog.csdnimg.cn/blog_migrate/419cb682f124f274118343289436269d.png)
4.3 输入框TextField监听
package com.kongbai.gui_study.text;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/**
* @author kongbai
* @version 1.0.0
* @ClassName TestText.java
* @Description 文本框监听
* @createTime 2022年03月18日 15:48:00
*/
public class TestText {
public static void main(String[] args) {
//main方法中只有启动,没有其它多余代码
new MyFrame();
}
}
class MyFrame extends Frame {
public MyFrame() {
TextField textField = new TextField();
add(textField);
MyActionListener myActionListener = new MyActionListener(); //监听这个文本框输入的文字
textField.addActionListener(myActionListener); //按下Enter,就会触发事件
//设置替换编码,将输入的内容替换为‘*’号,控制台还是正常显示
textField.setEchoChar('*');
setVisible(true);
pack();
}
}
//事件监听
class MyActionListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
//这样一条代码也能实现:System.out.println(e.getActionCommand());
TextField field = (TextField) e.getSource(); //获得资源,返回对象
System.out.println(field.getText()); //获得输入框的文本
field.setText(""); //回车后清空
}
}
![image.png](https://i-blog.csdnimg.cn/blog_migrate/77320a0f274f832a84044df90fd56bf6.png)
4.4 简易计算器,组合+内部类回顾
OOP原则:组合>继承
- 实现类的复用除了继承外,还可以使用组合的方式,把该类当成另一个类的组合成分,从而允许新类直接复用该类的public方法。
- 不管继承还是组合,都允许在新类(对于继承就是子类)中直接复用旧类的方法。
- 组合是把旧类对象作为新类的成员变量组合起来,用以实现新类的功能,用户看到的是新类的方法,而不能看到被组合对象的方法。因此,通常要在新类里使用private修饰被组合的旧类对象。
class A extends B {
}
class A {
public B b;
}
简易计算器:
需要显示效果如下,点击等于按钮输出结果,同时清空前面两个文本框的内容
- 面向过程写法
package com.kongbai.gui_study.text;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/**
* @author kongbai
* @version 1.0.0
* @ClassName TestClac.java
* @Description 简易计算器
* @createTime 2022年03月18日 16:37:00
*/
public class TestClac {
public static void main(String[] args) {
new Calculator();
}
}
//计算器类
class Calculator extends Frame {
public Calculator() {
//3个文本框
TextField num1 = new TextField(10);//后面的数字表示字符数
TextField num2 = new TextField(10);
TextField num3 = new TextField(20);
//1个按钮
Button button = new Button("=");
button.addActionListener(new MyCalculatorListener(num1, num2, num3));
//1个标签
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) {
//1.获得加数和被加数
int n1 = Integer.parseInt(num1.getText());
int n2 = Integer.parseInt(num2.getText());
//2.将值运算后放到第三个文本框
num3.setText(String.valueOf((n1 + n2))); //或者前面加个空字符"";必须字符才行
//3.清空前两个文本框
num1.setText("");
num2.setText("");
}
}
- 面向对象写法
package com.kongbai.gui_study.text;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/**
* @author kongbai
* @version 1.0.0
* @ClassName TestClac.java
* @Description 简易计算器
* @createTime 2022年03月18日 16:37:00
*/
public class TestClac {
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(20);
Label label = new Label("+"); //标签
Button button = new Button("="); //按钮
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(String.valueOf(n1 + n2)); //将值运算后放到第三个文本框
calculator.num1.setText(""); //清空前两个文本框
calculator.num2.setText("");
}
}
-
内部类写法(推荐)
好处:1. 更好的包装
2. 可以方便的访问外部的属性和方法;这样写能大量降低代码成本
package com.kongbai.gui_study.text;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/**
* @author kongbai
* @version 1.0.0
* @ClassName TestClac.java
* @Description 简易计算器
* @createTime 2022年03月18日 16:37:00
*/
public class TestClac {
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(20);
Label label = new Label("+"); //标签
Button button = new Button("="); //按钮
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(String.valueOf(n1 + n2)); //将值运算后放到第三个文本框
num1.setText(""); //清空前两个文本框
num2.setText("");
}
}
}
以上三个代码实现效果:
2.2 画笔Paint
package com.kongbai.gui_study;
import java.awt.*;
/**
* @author kongbai
* @version 1.0.0
* @ClassName TestPaint.java
* @Description 画笔
* @createTime 2022年03月21日 20:33:00
*/
public class TestPaint {
public static void main(String[] args) {
new MyPaint().loadFrame();
}
}
class MyPaint extends Frame {
public void loadFrame() {
setBounds(200, 200, 600, 500);
setVisible(true);
}
//画笔
@Override
public void paint(Graphics g) {
//画笔,需要有颜色,画笔可以画画
g.setColor(Color.red);
g.drawOval(100, 100, 100, 100); //圆
g.fillOval(200, 100, 100, 100); //实心圆
g.setColor(Color.green);
g.fillRect(100, 200, 100, 100);
//养成习惯,画笔用完,将他还原到最初的颜色(默认黑色,将颜色注释掉再运行查看);否则颜色会继承到下一个图形中
}
}
![image.png](https://i-blog.csdnimg.cn/blog_migrate/ecbca66fd863956b19d808e6a1eb5512.png)
2.3 鼠标监听
用鼠标在屏幕上画点
package com.kongbai.gui_study.monitor;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Iterator;
/**
* @author kongbai
* @version 1.0.0
* @ClassName TestMouseListener.java
* @Description 鼠标监听
* @createTime 2022年03月21日 20:51:00
*/
public class TestMouseListener {
public static void main(String[] args) {
new MyFrame("drawing");
}
}
class MyFrame extends Frame {
//画画需要画笔,需要监听鼠标当前的位置,需要集合来存储这个点
ArrayList points;
public MyFrame(String title) {
super(title);
setBounds(200, 200, 400, 400);
//存鼠标点击的点
points = new ArrayList<>();
setVisible(true);
//鼠标监听器,针对这个窗口
this.addMouseListener(new MyMouserListener());
}
@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);
}
}
//适配器模式
private class MyMouserListener extends MouseAdapter {
//鼠标单击
@Override
public void mousePressed(MouseEvent e) {
MyFrame frame = (MyFrame) e.getSource();
//点击的时候界面就会产生一个点
frame.points.add(new Point(e.getX(), e.getY()));
//每次点击鼠标都需要重新写一遍
frame.repaint(); //刷新
}
}
}
![image.png](https://i-blog.csdnimg.cn/blog_migrate/eafb53e14b202c4aba5515a7ab68cdeb.png)
2.4 窗口监听
package com.kongbai.gui_study.monitor;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
/**
* @author kongbai
* @version 1.0.0
* @ClassName TestWindow.java
* @Description TODO
* @createTime 2022年03月21日 21:31:00
*/
public class TestWindow {
public static void main(String[] args) {
new WindowFrame();
}
}
class WindowFrame extends Frame {
public WindowFrame() {
setBackground(Color.blue);
setBounds(100, 100, 200, 200);
setVisible(true);
addWindowListener(new MyWindowListener());
}
class MyWindowListener extends WindowAdapter {
@Override
public void windowClosing(WindowEvent e) {
setVisible(false); //隐藏窗口
//System.exit(0); //正常退出;非正常将0改为1
}
}
}
程序运行,点击关闭后,程序依旧没有停止,只是窗口隐藏了
用匿名内部类写最好
package com.kongbai.gui_study.monitor;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
/**
* @author kongbai
* @version 1.0.0
* @ClassName TestWindow.java
* @Description TODO
* @createTime 2022年03月21日 21:31:00
*/
public class TestWindow {
public static void main(String[] args) {
new WindowFrame();
}
}
class WindowFrame extends Frame {
public WindowFrame() {
setBackground(Color.blue);
setBounds(100, 100, 200, 200);
setVisible(true);
this.addWindowListener(
//匿名内部类
new WindowAdapter() {
//关闭窗口
@Override
public void windowClosing(WindowEvent e) {
System.out.println("windowClosing");
System.exit(0);
}
//激活窗口
@Override
public void windowActivated(WindowEvent e) {
WindowFrame source = (WindowFrame) e.getSource();
source.setTitle("窗口被激活了"); //刚开始没有名字,激活后状态栏上显示
System.out.println("windowActivated");
}
}
);
}
}
1.刚运行;2.将窗口隐藏再打开就会发送变化;3.点击关闭后的显示
![image.png](https://i-blog.csdnimg.cn/blog_migrate/50c1ea98e8a5e294985ee58e061dc032.png)
![image.png](https://i-blog.csdnimg.cn/blog_migrate/d4a02ba0dd0ecc47d9c0d931e382f57b.png)
![image.png](https://i-blog.csdnimg.cn/blog_migrate/f3dbcb12ae0f090c75965e116eea53ea.png)
2.5 键盘监听
package com.kongbai.gui_study.monitor;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
/**
* @author kongbai
* @version 1.0.0
* @ClassName TextKeyListener.java
* @Description TODO
* @createTime 2022年03月22日 20:05:00
*/
public class TextKeyListener {
public static void main(String[] args) {
new KeyFrame();
}
}
class KeyFrame extends Frame {
public KeyFrame() {
setBounds(100, 100, 300, 400);
setVisible(true);
this.addKeyListener(new KeyAdapter() {
//键盘按下
@Override
public void keyPressed(KeyEvent e) {
//获得键盘所按的按键;获取键盘码
int keyCode = e.getKeyCode();
if (keyCode == KeyEvent.VK_UP) {
System.out.println("你按下了上键");
}
}
});
}
}
按下上键打印出字符
![image.png](https://i-blog.csdnimg.cn/blog_migrate/d2f56b42168936ac989ce4f93ac14f95.png)
三、Swing
1. 窗口、面板
基本上和Frame差不多,直接使用setBackground设置颜色是不行的,因为swing也需要容器,需要将容器实例化并设置颜色才行
package com.kongbai.gui_study.swing;
import javax.swing.*;
import java.awt.*;
/**
* @author kongbai
* @version 1.0.0
* @ClassName JFrameDemo.java
* @Description TODO
* @createTime 2022年03月23日 20:30:00
*/
public class JFrameDemo {
//init() 初始化
public void init() {
JFrame jf = new JFrame("这是一个JFrame窗口");
jf.setVisible(true);
jf.setBounds(100, 100, 200, 200);
//jf.setBackground(Color.cyan); //直接设置无效
//设置文字
JLabel jlable = new JLabel("欢迎来到祖安", SwingConstants.CENTER); //让文本标签居中
jf.add(jlable);
//容器实例化
jf.getContentPane().setBackground(Color.cyan);
//关闭事件
jf.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
//建立一个窗口
new JFrameDemo().init();
}
}
![image.png](https://i-blog.csdnimg.cn/blog_migrate/b2955d51b02557b9cbe685b3cd7aa22d.png)
2. 弹窗
package com.kongbai.gui_study.swing;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/**
* @author kongbai
* @version 1.0.0
* @ClassName DialogDemo.java
* @Description TODO
* @createTime 2022年03月23日 21:02:00
*/
//主窗口
public class DialogDemo extends JFrame {
public DialogDemo() {
this.setVisible(true);
this.setSize(700, 500);
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
//JFrame容器
Container container = this.getContentPane();
//绝对布局
container.setLayout(null);
//按钮
JButton jButton = new JButton("点击弹出一个对话框");
jButton.setBounds(30, 30, 200, 50); //设置绝对布局后可以直接设置按钮位置
//点击按钮时弹出一个窗口;需要用到监听
jButton.addActionListener(new ActionListener() { //监听器
@Override
public void actionPerformed(ActionEvent e) {
//弹窗
new MyDialog();
}
});
container.add(jButton);
}
public static void main(String[] args) {
new DialogDemo();
}
}
//弹窗(弹窗本身就可以关闭,所以无需重复写关闭代码,否则会报异常)
class MyDialog extends JDialog {
public MyDialog() {
this.setVisible(true);
this.setBounds(100, 100, 500, 500);
Container container = this.getContentPane();
container.setLayout(null);
//因为设置的绝对布局,想要标签显示也要设置setBounds才行
JLabel jLabel = new JLabel("好好学习,天天向上");
jLabel.setBounds(50, 50, 200, 200);
container.add(jLabel);
}
}
![image.png](https://i-blog.csdnimg.cn/blog_migrate/02af5985ac48a3451fa0f2dfa5a09f59.png)
3. 标签
先将图片复制到swing包中
3.1 图标ICON
package com.kongbai.gui_study.swing;
import javax.swing.*;
import java.awt.*;
/**
* @author kongbai
* @version 1.0.0
* @ClassName Icondome.java
* @Description TODO
* @createTime 2022年03月26日 15:25:00
*/
//图标,需要实现类,继承Frame
public class IconDome extends JFrame implements Icon {
private int width;
private int height;
public IconDome() {} //无参构造
public IconDome(int width, int height) {
this.width = width;
this.height = height;
}
public void init() {
IconDome iconDome = new IconDome(30, 30);
//图标放在标签上,也可以放在按钮上
JLabel jLabel = new JLabel("iconDome", iconDome, SwingConstants.CENTER);
Container container = getContentPane();
container.add(jLabel);
this.setVisible(true);
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
this.setBounds(100, 100, 300, 300);
}
public static void main(String[] args) {
new IconDome().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;
}
}
3.2 图片ICON
在图标上放上图片
package com.kongbai.gui_study.swing;
import javax.swing.*;
import java.awt.*;
import java.net.URL;
/**
* @author kongbai
* @version 1.0.0
* @ClassName ImageIcon.java
* @Description TODO
* @createTime 2022年03月26日 15:39:00
*/
public class ImageIconDemo extends JFrame {
public ImageIconDemo() {
//获取图片地址
JLabel label = new JLabel("ImageIcon");
URL url = ImageIconDemo.class.getResource("kongbai.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);
setBounds(100, 100, 300, 300);
}
public static void main(String[] args) {
new ImageIconDemo();
}
}
![image.png](https://i-blog.csdnimg.cn/blog_migrate/83f3309c9050aee355eae24a3062fe19.png)
4. 面板
package com.kongbai.gui_study.swing.jpanel;
import javax.swing.*;
import java.awt.*;
/**
* @author kongbai
* @version 1.0.0
* @ClassName JPanelDemo.java
* @Description TODO
* @createTime 2022年03月26日 16:02:00
*/
public class JPanelDemo extends JFrame {
public JPanelDemo() {
Container container = this.getContentPane();
container.setLayout(new GridLayout(2, 1, 10, 10)); //10代表间距
JPanel panel1 = new JPanel(new GridLayout(1, 3));
JPanel panel2 = new JPanel(new GridLayout(2, 1));
JPanel panel3 = new JPanel(new GridLayout(1, 2));
JPanel panel4 = new JPanel(new GridLayout(2, 3));
panel1.add(new JButton("1"));
panel1.add(new JButton("1"));
panel1.add(new JButton("1"));
panel2.add(new JButton("2"));
panel2.add(new JButton("2"));
panel3.add(new JButton("3"));
panel3.add(new JButton("3"));
panel4.add(new JButton("4"));
panel4.add(new JButton("4"));
panel4.add(new JButton("4"));
panel4.add(new JButton("4"));
panel4.add(new JButton("4"));
panel4.add(new JButton("4"));
container.add(panel1);
container.add(panel2);
container.add(panel3);
container.add(panel4);
this.setVisible(true);
this.setSize(500, 500);
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
new JPanelDemo();
}
}
按钮间有间距的
![image.png](https://i-blog.csdnimg.cn/blog_migrate/3af3a1049dc4df08d73f08c49a71fb2c.png)
JScroll
做一个滚动条效果
package com.kongbai.gui_study.swing.jpanel;
import javax.swing.*;
import java.awt.*;
/**
* @author kongbai
* @version 1.0.0
* @ClassName JScrollDemo.java
* @Description TODO
* @createTime 2022年03月26日 16:13:00
*/
public class JScrollDemo extends JFrame {
public JScrollDemo() {
Container container = this.getContentPane();
//文本域(可以换行)
JTextArea jTextArea = new JTextArea(20, 50);
jTextArea.setText("妙啊!");
//Scroll面板
JScrollPane jScrollPane = new JScrollPane(jTextArea);
container.add(jScrollPane);
setVisible(true);
setBounds(100, 100, 300, 350);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
new JScrollDemo();
}
}
![image.png](https://i-blog.csdnimg.cn/blog_migrate/ba88cee8dbc6fe41f2fb0f5278b96aa9.png)
5. 按钮
5.1 图片按钮
package com.kongbai.gui_study.swing.JButton;
import javax.swing.*;
import java.awt.*;
import java.net.URL;
/**
* @author kongbai
* @version 1.0.0
* @ClassName JButtonDemo.java
* @Description 按钮
* @createTime 2022年03月26日 16:44:00
*/
public class JButtonDemo extends JFrame {
public JButtonDemo() {
Container container = this.getContentPane();
//将图片变为图标
URL resource = JButtonDemo.class.getResource("1.jpg");
Icon icon = new ImageIcon(resource);
///将图标放在按钮上
JButton jButton = new JButton();
jButton.setIcon(icon);
jButton.setToolTipText("图片按钮"); //提示文本
container.add(jButton);
this.setVisible(true);
this.setSize(500, 500);
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
new JButtonDemo();
}
}
一个大按钮,图标在按钮上,鼠标悬停会显示提示文本
![image.png](https://i-blog.csdnimg.cn/blog_migrate/2e4c01934b303576b4876993a3094605.png)
5.2 单选框
package com.kongbai.gui_study.swing.JButton;
import javax.swing.*;
import java.awt.*;
import java.net.URL;
/**
* @author kongbai
* @version 1.0.0
* @ClassName JButtonDemo02.java
* @Description 单选框
* @createTime 2022年03月26日 16:56:00
*/
public class JButtonDemo02 extends JFrame{
public JButtonDemo02() {
Container container = this.getContentPane();
//将图片变为图标
URL resource = JButtonDemo02.class.getResource("1.jpg");
Icon icon = new ImageIcon(resource);
//单选框(不能同时选择)
JRadioButton jRadioButton01 = new JRadioButton("JRadioButton01");
JRadioButton jRadioButton02 = new JRadioButton("JRadioButton02");
JRadioButton jRadioButton03 = new JRadioButton("JRadioButton03");
//给按钮分组,一个组中只能选择一个
ButtonGroup buttonGroup = new ButtonGroup();
buttonGroup.add(jRadioButton01);
buttonGroup.add(jRadioButton02);
buttonGroup.add(jRadioButton03);
container.add(jRadioButton01,BorderLayout.CENTER);
container.add(jRadioButton02,BorderLayout.NORTH);
container.add(jRadioButton03,BorderLayout.SOUTH);
this.setVisible(true);
this.setSize(500, 500);
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
new JButtonDemo02();
}
}
三个按钮只能选中一个;如果去掉分组,三个按钮都可以选定,类似多选框,但并非相同
![image.png](https://i-blog.csdnimg.cn/blog_migrate/05db42ff32b35d48ce2fa0a695f51258.png)
5.3 复选框
package com.kongbai.gui_study.swing.JButton;
import javax.swing.*;
import java.awt.*;
import java.net.URL;
/**
* @author kongbai
* @version 1.0.0
* @ClassName JButtonDemo03.java
* @Description 复选框
* @createTime 2022年03月26日 17:04:00
*/
public class JButtonDemo03 extends JFrame{
public JButtonDemo03() {
Container container = this.getContentPane();
//将图片变为图标
URL resource = JButtonDemo03.class.getResource("1.jpg");
Icon icon = new ImageIcon(resource);
//多选框
JCheckBox jCheckBox01 = new JCheckBox("jCheckBox01");
JCheckBox jCheckBox02 = new JCheckBox("jCheckBox02");
JCheckBox jCheckBox03 = new JCheckBox("jCheckBox03");
container.add(jCheckBox01, BorderLayout.SOUTH);
container.add(jCheckBox02, BorderLayout.NORTH);
this.setVisible(true);
this.setSize(500, 500);
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
new JButtonDemo03();
}
}
![image.png](https://i-blog.csdnimg.cn/blog_migrate/8b534a059e7e4dfc9c62eeaa24351f02.png)
6. 列表
6.1 下拉框
package com.kongbai.gui_study.swing.combobox;
import javax.swing.*;
import java.awt.*;
/**
* @author kongbai
* @version 1.0.0
* @ClassName ComboboxDemo.java
* @Description 下拉框
* @createTime 2022年03月26日 22:24:00
*/
public class ComboboxDemo extends JFrame {
public ComboboxDemo() {
Container container = this.getContentPane();
JComboBox jComboBox = new JComboBox();
jComboBox.addItem(null);
jComboBox.addItem("正在热映");
jComboBox.addItem("已下架");
jComboBox.addItem("即将上映");
container.add(jComboBox);
this.setVisible(true);
this.setSize(500, 300);
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
new ComboboxDemo();
}
}
![image.png](https://i-blog.csdnimg.cn/blog_migrate/a88644f307b3f1dabe3e4d9af1c9c3ae.png)
6.2 列表框
package com.kongbai.gui_study.swing.combobox;
import javax.swing.*;
import java.awt.*;
import java.util.Vector;
/**
* @author kongbai
* @version 1.0.0
* @ClassName ComboboxDemo02.java
* @Description 列表框
* @createTime 2022年03月26日 22:34:00
*/
public class ComboboxDemo02 extends JFrame {
public ComboboxDemo02() {
Container container = this.getContentPane();
//生产列表内容
//也可以这样:String[] contents = {"11111", "22222", "33333"};就不需要add了
Vector contents = new Vector();
JList jList = new JList(contents);
contents.add("张三");
contents.add("李四");
contents.add("王五");
container.add(jList);
this.setVisible(true);
this.setSize(500, 300);
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
new ComboboxDemo02();
}
}
![image.png](https://i-blog.csdnimg.cn/blog_migrate/4bf18b959793b4e79caf42c1da5f9ca4.png)
7. 文本框
7.1 文本框
package com.kongbai.gui_study.swing.text;
import javax.swing.*;
import java.awt.*;
/**
* @author kongbai
* @version 1.0.0
* @ClassName TextDemo01.java
* @Description 文本框
* @createTime 2022年03月26日 22:44:00
*/
public class TextDemo01 extends JFrame {
public TextDemo01() {
Container container = this.getContentPane();
JTextField jTextField01 = new JTextField("hello");
JTextField jTextField02 = new JTextField("world", 20);
container.add(jTextField01, BorderLayout.NORTH);
container.add(jTextField02, BorderLayout.SOUTH);
this.setVisible(true);
this.setSize(500, 300);
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
new TextDemo01();
}
}
![image.png](https://i-blog.csdnimg.cn/blog_migrate/a2369ced2f4bb1a0ee77f7c2bab8f319.png)
7.2 密码框
package com.kongbai.gui_study.swing.text;
import javax.swing.*;
import java.awt.*;
/**
* @author kongbai
* @version 1.0.0
* @ClassName TextDemo02.java
* @Description 密码框
* @createTime 2022年03月26日 22:48:00
*/
public class TextDemo02 extends JFrame{
public TextDemo02() {
Container container = this.getContentPane();
JPasswordField jPasswordField = new JPasswordField(); //***
jPasswordField.setEchoChar('*');
container.add(jPasswordField);
this.setVisible(true);
this.setSize(500, 300);
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
new TextDemo02();
}
}
![image.png](https://i-blog.csdnimg.cn/blog_migrate/a5b7c74fe09ad0d100bd222bcc4ab7d5.png)
7.3 文本域
package com.kongbai.gui_study.swing.jpanel;
import javax.swing.*;
import java.awt.*;
/**
* @author kongbai
* @version 1.0.0
* @ClassName JScrollDemo.java
* @Description TODO
* @createTime 2022年03月26日 16:13:00
*/
public class JScrollDemo extends JFrame {
public JScrollDemo() {
Container container = this.getContentPane();
//文本域(可以换行)
JTextArea jTextArea = new JTextArea(20, 50);
jTextArea.setText("妙啊!");
//Scroll面板
JScrollPane jScrollPane = new JScrollPane(jTextArea);
container.add(jScrollPane);
setVisible(true);
setBounds(100, 100, 300, 350);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
new JScrollDemo();
}
}
![image.png](https://i-blog.csdnimg.cn/blog_migrate/0beffd1b249ea3aaacab868f518ccd1f.png)
四、贪吃蛇
利用帧数即可实现,一帧一帧就是图片,连起来就是动画
- 键盘监听
- 定时器Timer
1. 静态界面绘制
- 游戏主启动类
package com.kongbai.gui_study.snake;
import javax.swing.*;
/**
* @author kongbai
* @version 1.0.0
* @ClassName StartGame.java
* @Description 游戏主启动类
* @createTime 2022年03月28日 15:31:00
*/
public class StartGame {
public static void main(String[] args) {
JFrame jFrame = new JFrame("贪吃蛇");
//正常游戏界面都应该在面板上
jFrame.add(new GamePanel());
jFrame.setVisible(true);
jFrame.setBounds(10, 10, 900, 720);
jFrame.setResizable(false); //窗口大小不可变
jFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
}
- 游戏面板
package com.kongbai.gui_study.snake;
import javax.swing.*;
import java.awt.*;
/**
* @author kongbai
* @version 1.0.0
* @ClassName GamePanel.java
* @Description 游戏面板
* @createTime 2022年03月28日 15:34:00
*/
public class GamePanel extends JPanel {
//绘制面板,游戏中的所有东西都使用此画笔
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
//绘制静态面板
this.setBackground(Color.WHITE);
Data.header.paintIcon(this, g, 25, 11); //头部广告栏
g.fillRect(25, 75, 850, 600); //默认的游戏界面
}
}
- 数据中心
在贪吃蛇代码包里新建一个包,用来放素材
package com.kongbai.gui_study.snake;
import javax.swing.*;
import java.net.URL;
/**
* @author kongbai
* @version 1.0.0
* @ClassName Data.java
* @Description 数据中心
* @createTime 2022年03月28日 15:49:00
*/
public class Data {
//header
public static URL headerURL = Data.class.getResource("statics/header.png");
public static ImageIcon header = new ImageIcon(headerURL);
//action
public static URL upURL = Data.class.getResource("statics/up.png");
public static ImageIcon up = new ImageIcon(upURL);
public static URL downURL = Data.class.getResource("statics/down.png");
public static ImageIcon down = new ImageIcon(downURL);
public static URL leftURL = Data.class.getResource("statics/left.png");
public static ImageIcon left = new ImageIcon(leftURL);
public static URL rightURL = Data.class.getResource("statics/right.png");
public static ImageIcon right = new ImageIcon(rightURL);
//body
public static URL bodyURL = Data.class.getResource("statics/body.png");
public static ImageIcon body = new ImageIcon(bodyURL);
//food
public static URL foodURL = Data.class.getResource("statics/food.png");
public static ImageIcon food = new ImageIcon(foodURL);
}
整体的游戏界面绘制完成
![image.png](https://i-blog.csdnimg.cn/blog_migrate/b12181fda4d9997547e78a68c3d92873.png)
2. 绘制静态小蛇
游戏面板:
package com.kongbai.gui_study.snake;
import javax.swing.*;
import java.awt.*;
/**
* @author kongbai
* @version 1.0.0
* @ClassName GamePanel.java
* @Description 游戏面板
* @createTime 2022年03月28日 15:34:00
*/
public class GamePanel extends JPanel {
//定义蛇的数据结构
int length; //蛇的长度
int[] snakeX = new int[600]; //蛇的X坐标
int[] snakeY = new int[500]; //蛇的Y坐标
String direction; //初始方向
//游戏当前状态:停止、开始
boolean isStart = false; //默认停止
//构造器
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; //第二节身体的坐标
direction = "R"; //初始方向向右
}
//绘制面板,游戏中的所有东西都使用此画笔
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
//绘制静态面板
this.setBackground(Color.WHITE);
Data.header.paintIcon(this, g, 25, 11); //头部广告栏
g.fillRect(25, 75, 850, 600); //默认的游戏界面
//画上小蛇
if (direction.equals("R")) {
Data.right.paintIcon(this, g, snakeX[0], snakeY[0]); //蛇头初始化向右,通过方向判断
}else if (direction.equals("L")) {
Data.left.paintIcon(this, g, snakeX[0], snakeY[0]);
}else if (direction.equals("U")) {
Data.up.paintIcon(this, g, snakeX[0], snakeY[0]);
}else if (direction.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]); //身体
}
//游戏状态
if (isStart == false) {
g.setColor(Color.WHITE);
g.setFont(new Font("微软雅黑", Font.BOLD, 40)); //设置字体
g.drawString("按下空格开始游戏", 300, 300);
}
}
}
![image.png](https://i-blog.csdnimg.cn/blog_migrate/2d6a242f1372a6b641e685d7544c67e1.png)
3. 让小蛇动起来
游戏面板:
package com.kongbai.gui_study.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;
/**
* @author kongbai
* @version 1.0.0
* @ClassName GamePanel.java
* @Description 游戏面板
* @createTime 2022年03月28日 15:34:00
*/
public class GamePanel extends JPanel implements KeyListener, ActionListener {
//定义蛇的数据结构
int length; //蛇的长度
int[] snakeX = new int[600]; //蛇的X坐标
int[] snakeY = new int[500]; //蛇的Y坐标
String direction; //初始方向
//游戏当前状态:停止、开始
boolean isStart = false; //默认停止
//定时器
Timer timer = new Timer(100, this); //100毫秒执行一次
//构造器
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; //第二节身体的坐标
direction = "R"; //初始方向向右
}
//绘制面板,游戏中的所有东西都使用此画笔
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
//绘制静态面板
this.setBackground(Color.WHITE);
Data.header.paintIcon(this, g, 25, 11); //头部广告栏
g.fillRect(25, 75, 850, 600); //默认的游戏界面
//画上小蛇
if (direction.equals("R")) {
Data.right.paintIcon(this, g, snakeX[0], snakeY[0]); //蛇头初始化向右,通过方向判断
}else if (direction.equals("L")) {
Data.left.paintIcon(this, g, snakeX[0], snakeY[0]);
}else if (direction.equals("U")) {
Data.up.paintIcon(this, g, snakeX[0], snakeY[0]);
}else if (direction.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]); //身体
}
//游戏状态
if (isStart == false) {
g.setColor(Color.WHITE);
g.setFont(new Font("微软雅黑", Font.BOLD, 40)); //设置字体
g.drawString("按下空格开始游戏", 300, 300);
}
}
//键盘监听事件
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode(); //获得按键
if (keyCode == KeyEvent.VK_SPACE) { //如果按下的是空格
isStart = !isStart; //取反,不要写死
repaint();
}
}
//事件监听--需要通过固定事件来刷新
@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] = snakeX[0] + 25;
if (snakeX[0] > 850) { //边界判断
snakeX[0] = 25;
}
repaint(); //重画页面
}
timer.start(); //定时器开启
}
@Override
public void keyReleased(KeyEvent e) {
}
@Override
public void keyTyped(KeyEvent e) {
}
}
目前还不能用鼠标控制方向,初始默认向右
4. 键盘控制小蛇移动
package com.kongbai.gui_study.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;
/**
* @author kongbai
* @version 1.0.0
* @ClassName GamePanel.java
* @Description 游戏面板
* @createTime 2022年03月28日 15:34:00
*/
public class GamePanel extends JPanel implements KeyListener, ActionListener {
//定义蛇的数据结构
int length; //蛇的长度
int[] snakeX = new int[600]; //蛇的X坐标
int[] snakeY = new int[500]; //蛇的Y坐标
String direction; //初始方向
//游戏当前状态:停止、开始
boolean isStart = false; //默认停止
//定时器
Timer timer = new Timer(100, this); //100毫秒执行一次
//构造器
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; //第二节身体的坐标
direction = "R"; //初始方向向右
}
//绘制面板,游戏中的所有东西都使用此画笔
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
//绘制静态面板
this.setBackground(Color.WHITE);
Data.header.paintIcon(this, g, 25, 11); //头部广告栏
g.fillRect(25, 75, 850, 600); //默认的游戏界面
//画上小蛇
if (direction.equals("R")) {
Data.right.paintIcon(this, g, snakeX[0], snakeY[0]); //蛇头初始化向右,通过方向判断
}else if (direction.equals("L")) {
Data.left.paintIcon(this, g, snakeX[0], snakeY[0]);
}else if (direction.equals("U")) {
Data.up.paintIcon(this, g, snakeX[0], snakeY[0]);
}else if (direction.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]); //身体
}
//游戏状态
if (isStart == false) {
g.setColor(Color.WHITE);
g.setFont(new Font("微软雅黑", Font.BOLD, 40)); //设置字体
g.drawString("按下空格开始游戏", 300, 300);
}
}
//键盘监听事件
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode(); //获得按键
if (keyCode == KeyEvent.VK_SPACE) { //如果按下的是空格
isStart = !isStart; //取反,不要写死
repaint();
}
//小蛇移动
if (keyCode == KeyEvent.VK_UP) {
direction = "U";
}else if (keyCode == KeyEvent.VK_DOWN) {
direction = "D";
}else if (keyCode == KeyEvent.VK_LEFT) {
direction = "L";
}else if (keyCode == KeyEvent.VK_RIGHT) {
direction = "R";
}
}
//事件监听--需要通过固定事件来刷新
@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 (direction.equals("R")) {
snakeX[0] = snakeX[0] + 25;
if (snakeX[0] > 850) { //边界判断
snakeX[0] = 25;
}
} else if (direction.equals("L")) {
snakeX[0] = snakeX[0] - 25;
if (snakeX[0] < 25) { //边界判断
snakeX[0] = 850;
}
} else if (direction.equals("U")) {
snakeY[0] = snakeY[0] - 25;
if (snakeY[0] < 75) { //边界判断
snakeY[0] = 650;
}
} else if (direction.equals("D")) {
snakeY[0] = snakeY[0] + 25;
if (snakeY[0] > 650) { //边界判断
snakeY[0] = 75;
}
}
repaint(); //重画页面
timer.start(); //定时器开启
}
}
@Override
public void keyReleased(KeyEvent e) {
}
@Override
public void keyTyped(KeyEvent e) {
}
}
能通过键盘控制了
5. 吃食物长大
package com.kongbai.gui_study.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;
/**
* @author kongbai
* @version 1.0.0
* @ClassName GamePanel.java
* @Description 游戏面板
* @createTime 2022年03月28日 15:34:00
*/
public class GamePanel extends JPanel implements KeyListener, ActionListener {
//定义蛇的数据结构
int length; //蛇的长度
int[] snakeX = new int[600]; //蛇的X坐标
int[] snakeY = new int[500]; //蛇的Y坐标
String direction; //初始方向
//食物的坐标
int foodx;
int foody;
Random random = new Random(); //随机数
//游戏当前状态:停止、开始
boolean isStart = false; //默认停止
//定时器
Timer timer = new Timer(100, this); //100毫秒执行一次
//构造器
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; //第二节身体的坐标
direction = "R"; //初始方向向右
//将食物随机分布到界面上
foodx = 25 + 25 * random.nextInt(34); //(850/25=34),边界,食物不能超过边界位置
foody = 75 + 25 * random.nextInt(24); //(600/25=24),边界,食物不能超过边界位置
}
//绘制面板,游戏中的所有东西都使用此画笔
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
//绘制静态面板
this.setBackground(Color.WHITE);
Data.header.paintIcon(this, g, 25, 11); //头部广告栏
g.fillRect(25, 75, 850, 600); //默认的游戏界面
//画上小蛇
if (direction.equals("R")) {
Data.right.paintIcon(this, g, snakeX[0], snakeY[0]); //蛇头初始化向右,通过方向判断
}else if (direction.equals("L")) {
Data.left.paintIcon(this, g, snakeX[0], snakeY[0]);
}else if (direction.equals("U")) {
Data.up.paintIcon(this, g, snakeX[0], snakeY[0]);
}else if (direction.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);
}
}
//键盘监听事件
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode(); //获得按键
if (keyCode == KeyEvent.VK_SPACE) { //如果按下的是空格
isStart = !isStart; //取反,不要写死
repaint(); //重画页面
}
//小蛇移动
if (keyCode == KeyEvent.VK_UP) {
direction = "U";
}else if (keyCode == KeyEvent.VK_DOWN) {
direction = "D";
}else if (keyCode == KeyEvent.VK_LEFT) {
direction = "L";
}else if (keyCode == KeyEvent.VK_RIGHT) {
direction = "R";
}
}
//事件监听--需要通过固定事件来刷新
@Override
public void actionPerformed(ActionEvent e) {
if (isStart) { //如果游戏是开始状态,游戏动起来
//吃食物
if (snakeX[0] == foodx && snakeY[0] == foody) {
length++; //长度加一
//再次随机食物
foodx = 25 + 25 * random.nextInt(34); //(850/25=34),边界,食物不能超过边界位置
foody = 75 + 25 * random.nextInt(24); //(600/25=24),边界,食物不能超过边界位置
}
//移动
for (int i = length - 1; i > 0; i--) { //后节往前移
snakeX[i] = snakeX[i - 1]; //向前移动一节
snakeY[i] = snakeY[i - 1]; //向前移动一节
}
//走向
if (direction.equals("R")) {
snakeX[0] = snakeX[0] + 25;
if (snakeX[0] > 850) { //边界判断
snakeX[0] = 25;
}
} else if (direction.equals("L")) {
snakeX[0] = snakeX[0] - 25;
if (snakeX[0] < 25) { //边界判断
snakeX[0] = 850;
}
} else if (direction.equals("U")) {
snakeY[0] = snakeY[0] - 25;
if (snakeY[0] < 75) { //边界判断
snakeY[0] = 650;
}
} else if (direction.equals("D")) {
snakeY[0] = snakeY[0] + 25;
if (snakeY[0] > 650) { //边界判断
snakeY[0] = 75;
}
}
repaint(); //重画页面
timer.start(); //定时器开启
}
}
@Override
public void keyReleased(KeyEvent e) {
}
@Override
public void keyTyped(KeyEvent e) {
}
}
通过方向键控制小蛇,吃一个食物长度加一
6. 失败判定及积分系统
游戏面板:
到这里就完整了
package com.kongbai.gui_study.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;
/**
* @author kongbai
* @version 1.0.0
* @ClassName GamePanel.java
* @Description 游戏面板
* @createTime 2022年03月28日 15:34:00
*/
public class GamePanel extends JPanel implements KeyListener, ActionListener {
//定义蛇的数据结构
int length; //蛇的长度
int[] snakeX = new int[600]; //蛇的X坐标
int[] snakeY = new int[500]; //蛇的Y坐标
String direction; //初始方向
//食物的坐标
int foodx;
int foody;
Random random = new Random(); //随机数
int score; //成绩
//游戏当前状态:停止、开始
boolean isStart = false; //默认停止
boolean isFail = false; //游戏失败状态
//定时器
Timer timer = new Timer(100, this); //100毫秒执行一次
//构造器
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; //第二节身体的坐标
direction = "R"; //初始方向向右
//将食物随机分布到界面上
foodx = 25 + 25 * random.nextInt(34); //(850/25=34),边界,食物不能超过边界位置
foody = 75 + 25 * random.nextInt(24); //(600/25=24),边界,食物不能超过边界位置
score = 0; //初始分数
}
//绘制面板,游戏中的所有东西都使用此画笔
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
//绘制静态面板
this.setBackground(Color.WHITE);
Data.header.paintIcon(this, g, 25, 11); //头部广告栏
g.fillRect(25, 75, 850, 600); //默认的游戏界面
//画积分
g.setColor(Color.WHITE);
g.setFont(new Font("微软雅黑", Font.BOLD, 18));
g.drawString("长度 " + length, 750, 30);
g.drawString("分数 " + score, 750, 55);
//画食物
Data.food.paintIcon(this, g, foodx, foody);
//画上小蛇
if (direction.equals("R")) {
Data.right.paintIcon(this, g, snakeX[0], snakeY[0]); //蛇头初始化向右,通过方向判断
}else if (direction.equals("L")) {
Data.left.paintIcon(this, g, snakeX[0], snakeY[0]);
}else if (direction.equals("U")) {
Data.up.paintIcon(this, g, snakeX[0], snakeY[0]);
}else if (direction.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]); //身体
}
//游戏状态
if (isStart == false) {
g.setColor(Color.WHITE);
g.setFont(new Font("微软雅黑", Font.BOLD, 40)); //设置字体
g.drawString("按下空格开始游戏", 300, 300);
}
if (isFail == true) {
g.setColor(Color.RED);
g.setFont(new Font("微软雅黑", Font.BOLD, 40)); //设置字体
g.drawString("游戏失败,按下空格重新开始", 300, 300);
}
//分数越高,速度越快
if (score < 100) {
timer.setDelay(100);
}else if (score < 200) {
timer.setDelay(70);
}else if (score < 300) {
timer.setDelay(50);
}else if (score < 400) {
timer.setDelay(30);
}
}
//键盘监听事件
@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_UP && direction != "D") {
direction = "U";
}else if (keyCode == KeyEvent.VK_DOWN && direction != "U") {
direction = "D";
}else if (keyCode == KeyEvent.VK_LEFT && direction != "R") {
direction = "L";
}else if (keyCode == KeyEvent.VK_RIGHT && direction != "L") {
direction = "R";
}
}
//事件监听--需要通过固定事件来刷新
@Override
public void actionPerformed(ActionEvent e) {
if (isStart && isFail == false) { //如果游戏是开始状态且未失败
//吃食物
if (snakeX[0] == foodx && snakeY[0] == foody) {
length++; //长度加一
//再次随机食物
foodx = 25 + 25 * random.nextInt(34); //(850/25=34),边界,食物不能超过边界位置
foody = 75 + 25 * random.nextInt(24); //(600/25=24),边界,食物不能超过边界位置
score += 10; //吃一个食物分数加10
}
//移动
for (int i = length - 1; i > 0; i--) { //后节往前移
snakeX[i] = snakeX[i - 1]; //向前移动一节
snakeY[i] = snakeY[i - 1]; //向前移动一节
}
//走向
if (direction.equals("R")) {
snakeX[0] = snakeX[0] + 25;
if (snakeX[0] > 850) { //边界判断
snakeX[0] = 25;
}
} else if (direction.equals("L")) {
snakeX[0] = snakeX[0] - 25;
if (snakeX[0] < 25) { //边界判断
snakeX[0] = 850;
}
} else if (direction.equals("U")) {
snakeY[0] = snakeY[0] - 25;
if (snakeY[0] < 75) { //边界判断
snakeY[0] = 650;
}
} else if (direction.equals("D")) {
snakeY[0] = 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(); //重画页面
}
}
@Override
public void keyReleased(KeyEvent e) {
}
@Override
public void keyTyped(KeyEvent e) {
}
}
试玩一下,为了加速效果更明显,将timer间隔调大了一点,可自行调整;当分数达到100,能感觉到明显的加速
五、GUI阶段回顾与总结
![image.png](https://i-blog.csdnimg.cn/blog_migrate/798dd351c18cbccb2139b197d7d9b4ed.png)