1. 美化界面
关键逻辑 1:
// 相对路径:直接从项目的 src 目录开始写,不包含 D:\ 和个人名字
ImageIcon bg = new ImageIcon("src/image/background.png");
JLabel background = new JLabel(bg);
这两行代码是 Swing 中加载并显示图片的经典组合,核心是通过两个类的协作完成 “读取图片→展示图片” 的过程。
第一行:
ImageIcon bg = new ImageIcon("src/image/background.png");
(1) ImageIcon
类:
它是 Swing 专门用于处理图像的工具类,主要功能是加载图像文件(支持 png、jpg、gif 等格式)并存储图像数据。你可以把它理解为 “图像的容器”—— 它会帮你完成读取文件、解析图像数据的底层工作,最终提供一个可供 Swing 组件直接使用的图像对象。
(2) new ImageIcon(路径)
:
这是调用 ImageIcon
的构造方法,参数是图片的路径(这里用的是相对路径 src/image/background.png
)。执行这行代码时:
- 程序会根据路径找到
background.png
图片文件; - 读取图片的像素、色彩等数据;
- 创建一个
ImageIcon
对象bg
,这个对象内部就 “装着” 这张图片的所有数据。
第二行:
JLabel background = new JLabel(bg);
作用:创建一个标签组件,将前面加载好的图片设置为标签的内容,让图片能在界面上显示。
(1) JLabel
类:
它是 Swing 中的 “标签” 组件,本质是一个 “小型容器”,可以用来显示文本或图像(或两者同时显示)。它的作用类似网页中的 <img>
标签 —— 本身不存储图像数据,而是作为 “展示载体”。
(2) new JLabel(bg)
:
这是调用 JLabel
的构造方法,参数是前面创建的 ImageIcon
对象 bg
(装着图片数据)。执行这行代码时:
- 创建一个
JLabel
对象background
; - 告诉这个标签:“你的显示内容是
bg
里的图片”; - 此时标签已经 “绑定” 了图片,但还不会直接显示在窗口上(需要后续步骤)。
关键逻辑 2:
this.getContentPane().add(background);
(1) this
:指当前的 JFrame
对象(假设这句代码写在继承自 JFrame
的类中,比如之前的 MyJFrame3
)。
(2) this.getContentPane()
:
getContentPane()
是JFrame
类的一个方法,作用是获取当前窗口的 “内容面板”(ContentPane
对象)。- 这个方法返回的
ContentPane
才是真正能存放组件的容器。
(3) .add(background)
:
- 调用
ContentPane
的add()
方法,将组件(这里是background
标签,也就是包含图片的标签)添加到内容面板中。 - 只有添加到内容面板后,
background
标签(以及它包含的图片)才能在窗口中显示出来。
关键逻辑 3:
jLabel.setBorder(new BevelBorder(1));
这句话的作用是给 JLabel
组件添加一个 “凹陷” 效果的立体边框,让组件(比如显示图片的标签)看起来有 “凹下去” 的视觉层次感。
jLabel
:要添加边框的目标组件(比如显示图片的标签、文本标签等)。setBorder(...)
:JLabel
继承的方法(来自父类JComponent
),作用是给组件设置边框(参数是一个边框对象)。new BevelBorder(1)
:创建一个 “斜面边框” 对象,参数1
对应BevelBorder.LOWERED
,即凹陷效果。
2. 移动图片
关键逻辑 1:为什么需要 else ?
for (int i = 0; i < tempArr.length; i++) { // 遍历一维数组tempArr的每个元素if (tempArr[i] == 0) { // 如果当前元素是0(特殊值)x = i / 4; // 计算0在二维数组中的行索引xy = i % 4; // 计算0在二维数组中的列索引y} else { // 如果当前元素不是0(普通值)data[i / 4][i % 4] = tempArr[i]; // 将值存入二维数组的对应位置}
}
(1) 在拼图游戏中,0
通常代表 “空白块”(没有数字的空位),它的作用和其他带数字的拼图块完全不同 —— 其他数字块需要被显示在界面上,而空白块的核心作用是作为 “可移动的空位”,让周围的数字块可以移动到这里。因此,0
不需要像普通数字那样被 “记录到数据数组 data
中”,而是需要单独记录它的位置(x,y
),这是由拼图游戏的核心逻辑决定的。
(2) 功能定位:0
是 “空位”,而非 “拼图块”
拼图游戏的本质是:通过移动数字块填补空白,最终让所有数字块回到正确位置。
- 普通数字(如
1,2,3...
)是 “需要被显示和移动的拼图块”,它们的位置需要被data
数组记录(data[x][y] = 数字
),用于在界面上显示对应的数字图片。 0
代表 “空白区域”(没有拼图块的位置),它不需要被显示(界面上这里是空的),因此不需要存入data
数组
关键逻辑 2:关于 x-- 的理解、关于 initImage( ); 的理解
System.out.println("向下移动");
data[x][y] = data[x - 1][y];
data[x - 1][y] = 0;
x--; //更新元素当前位置的行索引,记录移动后的新位置
initImage(); //根据修改后的数据重新绘制界面,刷新显示效果
(1) 这里的 x、y 表示空白方块,假设 data
数组中,0
代表空白方块,其他数字代表有内容的方块。现在有一个空白方块在 (x=2, y=1)
位置,它上方(x-1=1, y=1
)有一个值为 5
的方块,我们要让 5
向下移动到空白位置。
(2) 代码逻辑就变成了:
① System.out.println("向下移动");
提示:有元素要移动到下方的空白位置。
② data[x][y] = data[x - 1][y];
这里 x=2, y=1
是空白位置,x-1=1, y=1
是上方的有值元素(5)。
意思是:把上方的 5
放到空白位置 (2,1)
上。
执行后,空白位置被 5
填充,原来的 5
位置还没清空。
③ data[x - 1][y] = 0;
把原来 5
所在的位置 (1,1)
设为 0
(变成新的空白位置)。
现在,5
成功移动到了下方的空白位置,而原来的位置变成了新的空白。
④ x--;
空白位置原本在 x=2
,现在移动到了上方的 x=1
(原来 5
的位置)。
用 x--
更新空白位置的坐标(从 2 变成 1),这样如果还要继续移动(比如 5
还能再往下移),程序就知道 “现在的空白位置在 x=1
”,可以继续判断下方是否还有空白。
⑤ initImage();
刷新界面,让你看到 “5
移动到了下方,原来的位置变成了空白” 的效果。
3. 查看完整图片、作弊码和判断胜利
关键逻辑 1:整体逻辑铺垫
int code = e.getKeyCode();
e
是一个键盘事件对象(比如KeyEvent
),当用户按下键盘时会触发这个事件。getKeyCode()
是事件对象的方法,用于获取用户按下的按键对应的 “键码”(每个按键有唯一的数字编码,比如 65 对应字母 A,40 对应下箭头等)。- 这行代码的作用是:把用户按下的按键编码存到变量
code
中,方便后续判断按的是哪个键。
关键逻辑 2:二维数组的 “静态初始化”
else if (code == 87) {data = new int[][]{{1, 2, 3, 4}, // 拼图第1行(数组索引0行):对应1、2、3、4号拼图块{5, 6, 7, 8}, // 拼图第2行(数组索引1行):对应5、6、7、8号拼图块{9, 10, 11, 12}, // 拼图第3行(数组索引2行):对应9、10、11、12号拼图块{13, 14, 15, 0} // 拼图第4行(数组索引3行):13、14、15号块 + 0(空白块)};initImage(); // 重新绘制界面,让正确的拼图布局和胜利图片显示出来
}
① 首先,这段代码的本质是 创建一个 4 行 4 列的 int 类型二维数组,并直接给每个位置赋值,然后把这个 “正确排列的数组” 赋值给data
变量(覆盖原来打乱的拼图数据)。
② 二维数组的 “行” 和 “列”:
- 里面的每一组
{}
代表 “一行”,比如{1,2,3,4}
是第 1 行;每行里的数字(1、2、3、4)是 “列”,分别对应第 1 列、第 2 列、第 3 列、第 4 列。 - 整个数组是 4 行 4 列,正好对应你游戏里的 “4x4 拼图格子”。
4. 计步和菜单业务实现
关键逻辑 1:应用场景和定位事件源头
Object obj = e.getSource();
① 先铺垫:这行代码的 “上下文”—— 事件处理:
- 当用户做了某个交互操作(比如点击按钮、选择菜单、输入文本),程序会生成一个 “事件对象”(通常用变量
e
表示,比如ActionEvent e
、MouseEvent e
),这个e
里包含了 “这个事件的所有信息”—— 而e.getSource()
就是从这些信息里,找到 “触发这个事件的源头组件”。 - 你的拼图游戏有 “重新游戏”“关闭游戏” 两个菜单按钮(JMenuItem),如果给这两个按钮共用一个事件处理器(比如都绑定到同一个 ActionListener),当用户点击其中一个时,程序需要知道 “到底点的是哪个按钮”—— 这时候就需要
e.getSource()
来定位 “点击的源头”。
② Object obj
:用 Object 类型接收源头,因为 “事件的触发源头” 可能是任何组件(比如 JButton 按钮、JTextField 文本框、JMenuItem 菜单、JLabel 标签等),这些组件都是不同的类,但它们有一个共同的父类 ——Object
。
假设没有多态,我们要接收 e.getSource()
的返回值会面临什么问题?
- 如果源头是
JMenuItem
,就需要写JMenuItem obj = e.getSource();
; - 如果源头是
JButton
,又需要写JButton obj = e.getSource();
; - 如果有 10 种不同的组件触发事件,就要写 10 种不同类型的接收代码 —— 这会导致代码极度冗余,无法兼容多种场景。
关键逻辑 2:模态窗口(Modal Window)
jDialog.setModal(true);
它是 Swing 界面开发中控制 “用户操作流程” 的关键设置,尤其适合像 “登录弹窗”“确认提示框” 这类需要用户 “先处理完当前窗口,才能操作其他窗口” 的场景。反之,如果设置为 setModal(false)
(非模态),弹窗弹出后,用户可以随意切换到其他窗口操作,不用管这个弹窗。