编写目的
借助Java语言中的GUI模块,整合先前学习的Java基础语法知识,了解它们在真实的Java项目中应该如何运用。
界面搭建
主界面分析
组件:
1.JFrame:最外层的窗体
2.JMenuBar:最上层的菜单 Bar:栏目、条目
3.JLabel:管理文字和图片的容器 label:容器
- 布局管理器:
- JFrame 默认使用 BorderLayout 布局管理器,这意味着当您直接调用
add(Component comp)
方法时,组件会被添加到 BorderLayout 的CENTER
区域。由于 BorderLayout 的CENTER
区域只能容纳一个组件,如果您尝试添加多个组件到这个区域,后面的组件会覆盖前面的组件。 - 在您的代码中,您使用了
setBounds
方法来设置 JButton 的位置和大小,但这通常与布局管理器不兼容。如果您想使用setBounds
,您应该调用setLayout(null)
来禁用布局管理器(即使用绝对布局)。
- JFrame 默认使用 BorderLayout 布局管理器,这意味着当您直接调用
- 绝对布局:
- 如果您想继续使用
setBounds
来控制组件的位置和大小,您应该在调用add
方法之前调用setLayout(null)
。
- 如果您想继续使用
简单练习
首先导入Swing包
import javax.swing.*;
JFrame是Swing包中用于描述界面的JavaBean类,它有属性:宽和高;行为:设置界面大小,显示/隐藏界面等等。
1.在idea中创建一个宽603像素、高680像素的游戏主界面
//创建游戏主界面
JFrame gameJframe = new JFrame();
//设置游戏主界面的长度和宽度
gameJframe.setSize(603,680);
//将游戏主界面设置为显示 Visible:看得见的
gameJframe.setVisible(true);
2.在idea中创建一个宽488像素、高430像素的登录界面
//创建登录界面
JFrame loginJframe = new JFrame();
loginJframe.setSize(488,430);
loginJframe.setVisible(true);
3.在idea中创建一个宽488像素、高500像素的注册界面
//创建注册界面
JFrame registerJframe = new JFrame();
registerJframe.setSize(488,500);
registerJframe.setVisible(true);
正式开始
1.新建APP类作为项目的启动入口,main方法中不写任何业务逻辑
public static void main(String[] args) {
//项目启动入口
new LoginJframe();
}
2.新建GameJframe、LoginJframe、RegisterJframe三个类继承JFrame类,描述项目中与游戏主界面、登录、注册有关的逻辑代码,重写空参构造方法对界面进行初始化。
import javax.swing.*;
public class LoginJframe extends JFrame {
public LoginJframe(){
this.setSize(488,430);
this.setVisible(true);
}
}
至此页面搭建完毕
界面设置
- 设置界面宽度和高度
- 设置界面标题
- 设置界面居中
- 设置界面总是置顶
- 设置界面关闭模式:EXIT_ON_CLOSE
- 设置界面显示
//设置界面的长度和宽度
this.setSize(488,430);
//设置界面的标题
this.setTitle("拼图 登录");
//设置界面居中
this.setLocationRelativeTo(null);
//设置界面总是置顶
this.setAlwaysOnTop(true);
//设置界面关闭模式
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
//将界面设置为显示 Visible:看得见的
this.setVisible(true);
菜单制作
JMenuBar:菜单条、菜单栏;JMenu:菜单;JMenuItem:菜单条目
步骤:
- 先创建JMenuBar
- 再创建JMenu
- 最后创建JMenuItem
- 把JMenuItem添加到JMenu中
- 把JMenu添加到JMenuBar中
- 在界面对象中设置菜单
public void initJMenuBar() {
//创建菜单栏对象
JMenuBar jMenuBar = new JMenuBar();
//创建菜单选项对象
JMenu functionJMenu = new JMenu("功能");
JMenu aboutUsJMenu = new JMenu("关于我们");
//创建菜单条目对象
JMenuItem changePictureItem = new JMenuItem("更改图片");
JMenuItem replayItem = new JMenuItem("重新游戏");
JMenuItem reLoginItem = new JMenuItem("重新登录");
JMenuItem closeItem = new JMenuItem("关闭游戏");
JMenuItem accountItem = new JMenuItem("公众号");
//把每个菜单条目对象添加到菜单选项对象中
functionJMenu.add(changePictureItem);
functionJMenu.add(replayItem);
functionJMenu.add(reLoginItem);
functionJMenu.add(closeItem);
aboutUsJMenu.add(accountItem);
//把每一个菜单选项对象添加到菜单栏对象中
jMenuBar.add(functionJMenu);
jMenuBar.add(aboutUsJMenu);
//设置菜单栏
this.setJMenuBar(jMenuBar);
}
添加图片
图片对象在Swing包中是ImageIcon类,ImageIcon对象要放在JLabel中进行管理。在创建图片对象中需要把图片的绝对路径或者相对路径作为参数传递。
ImageIcon icon = new ImageIcon("image/01/1.gif");
创建图片管理容器对象,把图片添加进去,在把图片管理容器添加到界面中,默认情况下图片会居中放置。
改变图片位置:
- 取消JFrame界面的居中布局
- 获取JFrame中的隐藏容器,此时的JFrame界面中有标题、菜单等组件,默认情况下放置图片就是在隐藏容器中放置。
- 设置图片添加时的坐标 注意:在设置坐标时必须取消JFrame的默认布局,否则无效
//取消默认布局,只有取消了才能按照XY轴添加图片
this.setLayout(null);
//指定图片位置
jLabel.setBounds(0,0,240,320);
//使用隐藏容器添加图片管理容器对象
this.getContentPane().add(jLabel);
接下来利用循环把所有的图片添加进去
int count = 1;
//使用循环语句把所有图片添加到界面中
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
//创建图片imageIcom对象
ImageIcon icon = new ImageIcon("image/01/" + count + ".jpg ");
//创建图片管理容器对象
JLabel jLabel = new JLabel(icon);
//指定图片位置
jLabel.setBounds(icon.getIconWidth() * j, icon.getIconHeight() * i, icon.getIconWidth(), icon.getIconHeight());
//使用隐藏容器添加图片管理容器对象
this.getContentPane().add(jLabel);
count++;
}
}
打乱图片
图片是根据文件名添加的,项目中图片的文件名是有序数组1~16,因此可以采用生成一个内容是乱序的0~15的数组,达到打乱图片的目的。
生成乱序数组
- 创建一个长度为16的一维数组,初始化其中的数据
- 打乱一维数组中的数据
- 创建一个4*4的二维数组,把打乱后的一维数组中的数据存放进去
public static int[][] disruptData() {
//创建一个数组
int[] beginData = new int[16];
//添加数据
for (int i = 0; i < 16; i++) beginData[i] = i;
//打乱数据
//生成随机数
Random random = new Random();
//创建中间存储量,随即索引
int temp, index;
//从零索引开始打乱位置
for (int i = 0; i < 16; i++) {
//获取随机索引
index = random.nextInt(16);
//交换数据的位置
temp = beginData[i];
beginData[i] = beginData[index];
beginData[index] = temp;
}
//创建一个二维数组
int[][] resultData = new int[4][4];
//向二维数组中添加数据
/*int number = 0;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++, number++) {
resultData[i][j] = beginData[number];
}
}
*/
//方法2
for (int i = 0; i < beginData.length; i++) {
resultData[i / 4][i % 4] = beginData[i];
}
/* //打印数据
for (int i = 0; i < 4; i++) {
System.out.print("[");
for (int j = 0; j < 4; j++) {
if (j < 3) System.out.print(resultData[i][j] + ",");
else System.out.print(resultData[i][j]);
}
System.out.println("]");
}*/
return resultData;
}
添加乱序的图片
把乱序数组中的元素作为文件名,进行添加。注意:由于图片文件中没有名字是0的文件,因此在添加中,乱序数组中0元素对应的图片永远是空白,这正好可以达到目的。
美化界面
1.调整图片的位置,
添加图片时,给图片加上一个初始x坐标和初始y坐标,最终使图片出现在界面的居中偏下位置,整体看起来更好看,这两个初始坐标需要不断调试。
2.添加背景图片
注意事项:
- 添加图片时,先添加的在上面,后添加的在下面,因此背景图片应该最后再添加。
- 背景图片要注意正好包裹住拼图
3.添加拼图的边框
边框是Swing包下的一个接口类,接口名是Border,它有许多实现类,都是具体的边框类型,通过图片管理容器的相应方法添加。
//为图片添加边框 BevelBorder: 斜面边框 参数:0 代表凸起,1 代表凹下
jLabel.setBorder(new BevelBorder(BevelBorder.LOWERED));
4.优化路径
路径有两种:绝对路径和相对路径,它们都可以达到添加图片的目的,一般相对路径使用更方便
绝对路径:从盘符开始的路径 相对路径:一般不从盘符开始,而是从图片的前几级目录开始
移动图片
所谓移动图片,其实是移动二维数组中的数据,然后重新加载图片,这样看起来就达到了移动图片的效果。注意:移动图片准确的来说是移动空白的位置。
上移: 把空白下方的图片向上移动一格
下移:把空白上方的图片向下移动一格
左移:把空白右边的图片向左移动一格
右移:把空白左边的图片向右移动一格
实现步骤
1、移动二维数组中值为0的元素的位置:
初始化数据时,记录值为0的元素的索引。移动数据就是交换数据。
//找到 0 元素对应的索引
if (beginData[i] == 0) {
g.setX(i / 4);
g.setY(i % 4);
}
//左移: 空白右边的图片向左移动
if (y < 3) {
data[x][y] = data[x][y + 1];
data[x][y + 1] = 0;
y++;
//重新加载图片
initImage();
}
2.绑定监听事件
首先让整个gameJFrame类实现KeyListener键盘监听接口,重写它的keyReleased方法,这样当我们按下键盘上的键位时,监听方法就会被调用。
3.添加键位约束
键盘监听机制不会区分特定的键,只要我们按下键就会响应,因此要编写约束代码,通过KeyReleased方法的参数KeyEvent e调用getCode方法,可以获得按下的键位所对应的值,使用switch语句进行控制便可以达到目的。
public void keyReleased(KeyEvent e) {
//获取按键对应的值, 对上、下、左、右进行判断
int code = e.getKeyCode();
switch (code) {
case 37:
System.out.println("图片左移");
4.重新加载图片
二维数组中的数据改变后,重现调用方法initImage重新加载图片
注意:要修改InitImage方法中的代码,在方法开始要加上清除语句。
//清除先前添加的图片
this.getContentPane().removeAll();
因为在添加图片时,后添加的图片会在下方,如果不先清除图片,在添加时图片就会被盖住,无法实现目的。这个代码清除的是图片管理容器JLabel 的对象,对象被清除,界面不会改变,此时还要添加刷新界面的代码。
//刷新界面,这样后加载的图片才会显示再界面上
this.getContentPane().repaint();
注意事项:在交换二维数组中的数据时,要注意索引越界的问题。
其余功能
查看完整图片
设置一个快捷键,按住A键后,按照顺序加载图片,就是完整图片,这个功能的逻辑代码应该写在重写的KeyListener接口的keyPressed()方法中。
作弊码
按w键后,重新加载一张完整图片,二维数组win是定义好的0~15有序排列的一个二维数组。
case 87:
//作弊码:按下w直接赢,其实是重置了二维数组。
System.out.println("作弊");
data = win;
initImage();
break;
判断胜利
添加一个判断游戏胜利的逻辑,提前定义一个正确排序的二维数组win,把data和作比较,全部相等就说明拼图拼好了。
//判断游戏是否胜利
public boolean voctory() {
for (int i = 0; i < data.length; i++) {
for (int j = 0; j < data[i].length; j++) {
//有一个不对就返回false
if (data[i][j] != win[i][j]) return false;
}
}
return true;
}
使用这个逻辑的地方:
1.加载图片之前,在加载前判断游戏是否胜利,如果胜利就可以弹出游戏胜利的图片。
2.执行键盘监听的代码之前,如果游戏胜利,就不能在改变图片了。
计步
为游戏界面添加一个成员属性step,在移动图片的逻辑代码后面添加一条step++的语句。
在加载图片的方法中,新建一个管理图片和文字的容器stepJLabel,在界面中可以直接添加计步。
//添加步数,JLabel既可以管理图片,也可以管理容器
JLabel stepJLabel = new JLabel("步数:" + step);
stepJLabel.setBounds(25, 50, 100, 100);
this.getContentPane().add(stepJLabel);
菜单业务逻辑实现
为了实现这个逻辑,需要让界面实现动作监听接口,在重写的actionPerformed方法中实现对应逻辑,这样在多个方法中都要用到菜单条目JMenuItem对象,因此要把之前定义的几个菜单条目移动到成员属性当中。
重开游戏
实现逻辑是重新调用一次加载图片的方法,要注意先将步数归零再调用方法。
//步数归零
step = 0;
//重新打乱数据
initData();
//重载图片
initImage();
重新登录
1.把当前游戏界面关闭
2.new一个登录界面
//关闭当前界面
this.setVisible(false);
//打开登陆界面
new LoginJframe();
关闭游戏
直接关闭虚拟机
关于我们
需要使用弹窗界面 JDialog类,它和JFrame界面是一个等级的,使用方法也差不多。
更改图片
在菜单中,更改图片下面还有个二级菜单,因此更改图片不是JMenuItem,而是JMenu,在Java语法中,JMenu中还可以添加JMenu。
业务逻辑分析:
给更改图片下面的每个条目都添加动作监听,事件发生后,要执行以下功能:
1.从特定的图片文件夹中选则一个图片进行加载。
2.按A和W显示的图片需要同步更新。
思路:
首先需要把加载图片时,传递的图片路径进行拆分:
第一部分是图片文件的根目录 image/
第二部分是将图片进行分类后的分类目录 例如 animal/ sports/等等
第三部分是分类目录下的各个图片文件的目录 格式为 0+数字
在加载文件时,调用方法给这三部分赋随机值,实现随机选取图片。需要注意:在移动图片和输入作弊码等操作时,不能调用方法再次赋值了。当点击更改图片下的二级菜单时,相当于给定了第二部分的值,因此再给第二部分赋值前要加上判断。接下来的步骤和重开游戏相同。
登录界面
需要的技术点:
- 用户名文字其实是一张图片,还是用JLabel去管理ImageIcon,输入框:JTextField(明文显示的输入框)
- 密码文字也是一张图片,用JLabel去管理ImageIcon,输入框JPasswordField(密文显示的输入框)
- 验证码文字也是一张图片,原理和用户名一样
- 登录和注册是两个按钮,二者有不同的背景图,点击按钮不松的时候,按钮变灰,其实是换了一张深色的背景图。
public class LoginJframe extends JFrame implements MouseListener {
//添加登录按钮
ImageIcon loginIcon = new ImageIcon("image/login/登录按钮.png");
JLabel loginJlabel = new JLabel(loginIcon);
//添加注册按钮
ImageIcon registerIcon = new ImageIcon("image/login/注册按钮.png");
JLabel registerJlabel = new JLabel(registerIcon);
JTextField userText = new JTextField();
JPasswordField pdText = new JPasswordField();
JTextField vercodeText = new JTextField();
//添加用户
private String username = "zhangsan";
private String password = "123456";
private String code = GetValidateCode.captchasImprove();
public LoginJframe() {
//初始化界面
initJFrame();
//初始化图片
initImage();
//将界面设置为显示 Visible:看得见的
this.setVisible(true);
}
private void initImage() {
//添加用户名图片
ImageIcon uerIcon = new ImageIcon("image/login/用户名.png");
JLabel userJlabel = new JLabel(uerIcon);
userJlabel.setBounds(100, 130, 50, 20);
this.getContentPane().add(userJlabel);
//添加用户名文本框
userText.setBounds(170, 125, 200, 30);
this.getContentPane().add(userText);
//添加密码图片
ImageIcon pdIcon = new ImageIcon("image/login/密码.png");
JLabel pdJlbel = new JLabel(pdIcon);
pdJlbel.setBounds(107, 180, 50, 20);
this.getContentPane().add(pdJlbel);
//添加密码文本框
pdText.setBounds(170, 175, 200, 30);
this.getContentPane().add(pdText);
//添加验证码图片
ImageIcon vercodeIcon = new ImageIcon("image/login/验证码.png");
JLabel vercodeJlabel = new JLabel(vercodeIcon);
vercodeJlabel.setBounds(100, 240, 50, 20);
this.getContentPane().add(vercodeJlabel);
//添加验证码文本框
vercodeText.setBounds(170, 230, 150, 40);
this.getContentPane().add(vercodeText);
//添加验证码提示文字
JLabel vercode = new JLabel(code);
// 创建一个Font实例,指定字体名称、样式和大小
// 这里使用"Serif"字体、普通样式和36号大小
// 注意:字体名称依赖于你的系统和已安装的字体
Font font = new Font("Times New Roman", Font.PLAIN, 20);
//把创建的Font实例应用到JLabel上
vercode.setFont(font);
vercode.setBounds(330, 240, 100, 20);
this.getContentPane().add(vercode);
//把登录添加到界面上
loginJlabel.setBounds(100, 290, 130, 50);
this.getContentPane().add(loginJlabel);
//把注册添加到界面上
registerJlabel.setBounds(250, 290, 130, 50);
this.getContentPane().add(registerJlabel);
//添加背景图片
ImageIcon bg = new ImageIcon("image/login/background.png");
JLabel bgJlabel = new JLabel(bg);
bgJlabel.setBounds(0, 0, 500, 400);
this.getContentPane().add(bgJlabel);
}
private void initJFrame() {
//设置界面的长度和宽度
this.setSize(510, 450);
//设置界面的标题
this.setTitle("拼图 登录");
//设置界面居中
this.setLocationRelativeTo(null);
//设置界面总是置顶
this.setAlwaysOnTop(true);
//禁用界面布局管理器,使用绝对布局
this.setLayout(null);
//设置界面关闭模式
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
//给登录添加鼠标监听
loginJlabel.addMouseListener(this);
//给注册添加鼠标监听
registerJlabel.addMouseListener(this);
}
@Override
public void mouseClicked(MouseEvent e) {
}
@Override
public void mousePressed(MouseEvent e) {
Object source = e.getSource();
//鼠标按住改变图片
if (source == loginJlabel) {
ImageIcon loginPressedIcon = new ImageIcon("image/login/登录按下.png");
loginJlabel.setIcon(loginPressedIcon);
} else if (source == registerJlabel) {
ImageIcon registerPressedIcon = new ImageIcon("image/login/注册按下.png");
registerJlabel.setIcon(registerPressedIcon);
}
}
@Override
public void mouseReleased(MouseEvent e) {
Object source = e.getSource();
//鼠标释放时恢复图片
if (source == loginJlabel) {
loginJlabel.setIcon(loginIcon);
//登录判定逻辑
String userInput = userText.getText(); // 假设 userText 是您的 JTextField 变量名
char[] pdInputChars = pdText.getPassword(); // 获取密码字符数组
String pdInput = new String(pdInputChars); // 将字符数组转换为字符串(注意:这在实际应用中可能不安全)
String codeInput = vercodeText.getText();
if (userInput.isEmpty() || pdInput.isEmpty() || codeInput.isEmpty()) {
System.out.println("用户名、密码、验证码不能为空");
} else if (!username.equals(userInput) || !password.equals(pdInput)) {
System.out.println("用户名或密码错误");
} else if (codeInput.equals(code)) {
System.out.println("验证码错误");
} else {
System.out.println("登陆成功");
this.setVisible(false);
new GameJframe();
}
} else if (source == registerJlabel) {
registerJlabel.setIcon(registerIcon);
}
}
@Override
public void mouseEntered(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
Object source = e.getSource();
//鼠标划出时恢复图片
if (source == loginJlabel) {
loginJlabel.setIcon(loginIcon);
} else if (source == registerJlabel) {
registerJlabel.setIcon(registerIcon);
}
}
}