package com.liujintao.frame;
import javax.swing.*;
public class JFrameTest {
public static void main(String[] LiuJinTao) {
// 创建窗体对象
JFrame frame = new JFrame();
// 设置窗体大小
frame.setSize(500, 800);
// 修改窗体的关闭模式(点击× ,将整个程序终止)
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
// 设置窗体的标题
frame.setTitle("hello frame");
// 设置窗体可见(一定要放最后,数据就绪后,在显示)
frame.setVisible(true);
}
}
窗体中添加按钮组件
---------------------------------
JButton构造方法:
1. public JButton():这是一个空白构造
2. public JButton(String text):创建一个带文本的按钮
----------------------------
注意:如果取消了窗体的默认布局,就需要手动指定组件的摆放位置
3. 然后使用:窗体对象.getContentPane().add(按钮组件对象);将按钮组件放到窗体面板中
package com.liujintao.frame.button;
import javax.swing.*;
public class JButtonTest {
public static void main (String[] LiuJinTao) {
// 1. 先有窗体在有组件(组件就是窗体里面的所有元素)
JFrame frame = new JFrame();
frame.setSize(500, 500);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
// 2. 创建Button组件(使用构造方法)
// 取消组件的默认摆放位置
frame.setLayout(null);
JButton btn = new JButton("Button");
// 指定组件在窗体面板中的位置
btn.setBounds(100, 100, 100, 100);
// 将按钮添加到窗体中的面板中(位置取决于默认还是,自定义)
frame.getContentPane().add(btn);
// 将窗体显示出来
frame.setVisible(true);
}
}
主要作用就是在窗体中,开辟空间,然后存放文本图片啥的。
需要注意的:如果我在一个位置区域同时放多张图像,后添加的会放到下面,也就是被覆盖。
使用JLabel实现在窗体中开辟空间,展示文本和图片
JLabel构造方法:
Jlabel(String text) 使用指定的文本创建一个 JLabel对象
JLabel(Icon image) 创建一个具有指定图像的 JLabel 对象
package com.liujintao.frame.label;
import javax.swing.*;
public class JLabelTest {
public static void main(String[] LiuJinTao) {
// 创建窗体
JFrame frame = new JFrame();
frame.setSize(500, 500);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setLayout(null);
// 1. 使用JLabel 对象展示文本
JLabel jb1 = new JLabel("面向对象面向君,");
jb1.setBounds(50, 50, 100, 100);
frame.getContentPane().add(jb1);
JLabel jb2 = new JLabel("不负代码不负君!");
jb2.setBounds(150, 50, 100, 100);
frame.getContentPane().add(jb2);
// 2. 使用 JLabel 对象展示图像
// 使用ImageIcon构造器拿到图片,在将图片对象放到JLabel空间中
JLabel JLabel_image_2png = new JLabel(new ImageIcon("D:\\桌面\\文件夹(临时的)\\image\\10.png"));
JLabel_image_2png.setBounds(100, 300, 100, 100);
// 将JLabel放到面板中
frame.getContentPane().add(JLabel_image_2png);
JLabel JLabel_image_11png = new JLabel(new ImageIcon("D:\\桌面\\文件夹(临时的)\\image\\11.png"));
JLabel_image_11png.setBounds(150, 150, 100, 100);
frame.getContentPane().add(JLabel_image_11png);
frame.setVisible(true);
}
}
图像的展示效果
事件源.addActionListener(new ActionListener() {})
package com.liujintao.frame.listener;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class ActionListenerTest {
public static void main(String [] LiuJinTao) {
JFrame frame = new JFrame();
frame.setSize(500, 500);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setLayout(null);
// 创建事件源
JButton btn = new JButton("我是事件源");
btn.setSize(100, 100);
btn.setBounds(100, 100, 100, 100);
frame.getContentPane().add(btn);
btn.addActionListener(new ActionListener () {
// 重写接口中的抽象内部类
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("事件被触发了");
}
});
frame.setVisible(true);
}
}
package com.liujintao.frame.listener;
import javax.swing.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class KeyListenerTest {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setLayout(null);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setSize(500, 500);
frame.addKeyListener(new KeyListener() {
@Override
public void keyTyped(KeyEvent e) {
// 该方法只能监听部分按键
}
@Override
public void keyPressed(KeyEvent e) {
// 键盘按下触发
// 通过事件对象 e上的getKeyCode方法查看键盘表示的数值
int num = e.getKeyCode();
if (num == 37) {
System.out.println("您按下了左键");
} else if (num == 38) {
System.out.println("您按下了上键");
} else if (num == 39) {
System.out.println("您按下了右键");
} else if (num == 40) {
System.out.println("您按下了下键");
}
}
@Override
public void keyReleased(KeyEvent e) {
// 键盘弹起触发
}
});
frame.setVisible(true);
}
}
事件冲突:就是当我们同时注册了动作监听和键盘监听,此时默认的是触发焦点点击事件。
注意:按钮组件比较特殊,再创建好之后,程序的焦点,默认就停留在了按钮组件上面, —— 但是按钮组件,其实不需要占用程序的焦点。
解决方案:给我们的按钮取消焦点就好了
· 事件源.setFocusable(false)
package com.liujintao.frame.listener;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class Tips {
/*
事件冲突:就是当我们同时注册了动作监听和键盘监听,此时默认的是触发焦点点击事件。
解决方案:给我们的按钮取消焦点就好了
事件源.setFocusable(false)
*/
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setSize(500, 500);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setLayout(null);
// 创建一个按钮
JButton btn = new JButton("看我这");
btn.setBounds(100, 100, 100, 100);
frame.getContentPane().add(btn);
// 给按钮注册动作事件
btn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("我被点击了!");
}
});
// 解决方法,取消按钮的焦点
btn.setFocusable(false);
// 注册一个键盘按下事件
frame.addKeyListener(new KeyListener() {
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
System.out.println("被按下了!");
}
@Override
public void keyReleased(KeyEvent e) {
}
});
frame.setVisible(true);
}
}
适配器设计模式:解决接口与接口实现类之间的矛盾问题
实现步骤:
package com.liujintao.stonepuzzle;
import javax.swing.*;
public class Test {
public static void main(String[] args) {
// 创建资源图片的二维数组
int[][] arr = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
{13, 14, 15, 0},
};
// 设置窗体
JFrame frame = new JFrame();
// 设置窗体大小
frame.setSize(514, 595);
// 设置窗体关闭模式
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
// 设置窗体标题
frame.setTitle("华容道单机版V1.0");
// 窗口处于对上层
frame.setAlwaysOnTop(true);
// 窗口居中显示
frame.setLocationRelativeTo(null);
// 取消默布局
frame.setLayout(null);
// 设置 JLabel 组件 将图片资源放进去
// i 执行一次,j 执行四次,我相信都知道。只是需要注意的是。那个在一次外循环的时候,不需要变化的,我们用i,需要变化的使用j
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
JLabel imagesJlb = new JLabel(new ImageIcon("D:\\java_code\\Advanced-Codes\\day04-code\\image\\" + arr[i][j] + ".png"));
// 起始值都为 0 ,乘以 100 都是为 0 ,然后相加,随着循环次数的增长,自然的得到了每个JLable的宽高距离
imagesJlb.setBounds(50 + j * 100,90 + i * 100,100,100);
frame.getContentPane().add(imagesJlb);
}
}
// 将背景图插入到底部,后插入会往下塞进去
JLabel background = new JLabel(new ImageIcon("D:\\java_code\\Advanced-Codes\\day04-code\\image\\background.png"));
background.setBounds(26, 30, 450, 484);
frame.getContentPane().add(background);
// 显示窗体
frame.setVisible(true);
}
}
代码示例:
package com.liujintao.stonepuzzle;
import javax.swing.*;
public class MainFrame extends JFrame {
// 创建资源图片的二维数组
int[][] arr = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
{13, 14, 15, 0},
};
// 通过构造方法调用各个功能模块,只要我这个类被创建,那么就执行!
public MainFrame() {
// 初始化界面
initFrame();
// 绘制内容
paintView();
// 显示窗体
setVisible(true);
}
/**
* 初始化界面
*/
public void initFrame () {
// 设置窗体
// 设置窗体大小
setSize(514, 595);
// 设置窗体关闭模式
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
// 设置窗体标题
setTitle("华容道单机版V1.0");
// 窗口处于对上层
setAlwaysOnTop(true);
// 窗口居中显示
setLocationRelativeTo(null);
// 取消默布局
setLayout(null);
}
/**
* 此方法用于绘制界面,图片的初始化
*/
public void paintView () {
// 设置 JLabel 组件 将图片资源放进去
// i 执行一次,j 执行四次,我相信都知道。只是需要注意的是。那个在一次外循环的时候,不需要变化的,我们用i,需要变化的使用j
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
JLabel imagesJlb = new JLabel(new ImageIcon("D:\\java_code\\Advanced-Codes\\day04-code\\image\\" + arr[i][j] + ".png"));
// 起始值都为 0 ,乘以 100 都是为 0 ,然后相加,随着循环次数的增长,自然的得到了每个JLable的宽高距离
imagesJlb.setBounds(50 + j * 100,90 + i * 100,100,100);
// 访问父类中的这个方法,然而就一个,super直接省略
getContentPane().add(imagesJlb);
}
}
// 将背景图插入到底部,后插入会往下塞进去
JLabel background = new JLabel(new ImageIcon("D:\\java_code\\Advanced-Codes\\day04-code\\image\\background.png"));
background.setBounds(26, 30, 450, 484);
getContentPane().add(background);
}
}
测试类创建调用MainFrame类构造器
package com.liujintao.stonepuzzle;
import javax.swing.*;
public class Test {
public static void main(String[] args) {
// 调用类构造器
new MainFrame();
}
}
/**
* 初始化数据,打乱二维数组
*/
public void initData () {
Random r = new Random();
// 遍历二维数组
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr.length; j++) {
// arr[i][j]
int randomX = r.nextInt(4);
int randomY = r.nextInt(4);
int temp = arr[i][j];
arr[i][j] = arr[randomX][randomY];
arr[randomX][randomY] = temp;
}
}
}
// 注册事件监听
this.addKeyListener(this);
// 键盘事件处理方法
@Override
public void keyPressed(KeyEvent e) {
// 键盘按下触发
int keycode = e.getKeyCode();
move(keycode);
}
/**
* 此方法处理移动业务
* @param keycode
*/
private static void move(int keycode) {
if (keycode == 37) {
System.out.println("按下了左方向键");
} else if (keycode == 38) {
System.out.println("按下了上方向键");
} else if (keycode == 39) {
System.out.println("按下了右方向键");
}else if (keycode == 40) {
System.out.println("按下了下方向键");
}
}
// 下面两种方法不常用,我们选择上面的按下触发事件即可了。
@Override
public void keyReleased(KeyEvent e) {
// 键盘弹起触发
}
@Override
public void keyTyped(KeyEvent e) {
// 类型触发
}
int column; // 二维数组的 列号
int row; // 二位数组的 行号
// 获取到二维数组中的 0 号元素
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr.length; j++) {
if (arr[i][j] == 0) {
column = i;
row = j;
}
}
}
// !!! 每次绘制界面前,做一次清空。然后移动在绘制,就不会往上一次的下面塞进去了。
super.getContentPane().removeAll(); // super可以省略
// 绘制视图的逻辑
// !!! 刷新操作!
super.getContentPane().repaint();
// 键盘事件处理方法
@Override
public void keyPressed(KeyEvent e) {
// 键盘按下触发
int keycode = e.getKeyCode();
move(keycode);
// 事件触发后,重新绘制界面
paintView();
}
/**
* 此方法处理移动业务
* @param keycode
*/
private void move(int keycode) {
if (keycode == 37) {
// 空白块的位置
int temp = arr[row][column];
arr[row][column] = arr[row][column + 1];
arr[row][column + 1] = temp;
//交换完毕后,我们空白块(0号)右移就得将column加一
column++;
} else if (keycode == 38) {
// 空白块的位置
int temp = arr[row][column];
arr[row][column] = arr[row + 1][column];
arr[row + 1][column] = temp;
row++;
} else if (keycode == 39) {
// 空白块的位置
int temp = arr[row][column];
arr[row][column] = arr[row][column - 1];
arr[row][column - 1] = temp;
column--;
}else if (keycode == 40) {
// 空白块的位置
int temp = arr[row][column];
arr[row][column] = arr[row - 1][column];
arr[row - 1][column] = temp;
row--;
} else if (keycode == 90) {
// z键胜利!
arr = new int[][] {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
{13, 14, 15, 0},
};
}
if (column == 3) {
return;
}
if (row == 3) {
return;
}
if (column == 0) {
return;
}
if (column == 0) {
return;
}
// 创建一个二维数组,用于比对。如果arr 数组等于 win 表示胜利返回true
int[][] win = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
{13, 14, 15, 0},
};
因为我们每次移动的时候,都需要判断。所以为了不造成性能问题,所以我将该数组放到了全局作用域下。
定义一个判断是否胜利的方法,返回值为 boolean类型
/**
* 判断游戏是否胜利
*
*/
public boolean victory () {
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr.length; j++) {
if (arr[i][j] != win[i][j]) {
return false;
}
}
}
// 不跑if 证明都相等!游戏胜利
return true;
}
// 每次移动移动之前都判断一下游戏是否胜利
if (victory()) {
// 返回true,显示胜利!将资源加载到界面中
JLabel jb = new JLabel(new ImageIcon("D:\\java_code\\Advanced-Codes\\day04-code\\image\\win.png"));
jb.setBounds(124, 230, 266, 88);
getContentPane().add(jb);
}
// 游戏胜利,该方法里面的移动逻辑则不能执行了(只有在非胜利状态下,才能执行)
if (victory()) {
return;
}
// 设置步数界面显示
JLabel scoreLabel = new JLabel("当前步数:" + count);
scoreLabel.setBounds(50, 20, 100, 20);
getContentPane().add(scoreLabel);
// 重新游戏界面显示
JButton newStart = new JButton("重新开始");
newStart.setBounds(366, 20, 100, 20);
newStart.setFocusable(false);
getContentPane().add(newStart);
newStart.addActionListener(e -> {
count = 0; // 步数归零
initData(); // 打乱石头方块
paintView(); // 重新加载界面
});
package com.liujintao.stonepuzzle;
public class Test {
public static void main(String[] args) {
new MainFrame();
}
}
package com.liujintao.stonepuzzle;
import javax.swing.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Random;
public class MainFrame extends JFrame implements KeyListener {
// 创建资源图片的二维数组
int[][] arr = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
{13, 14, 15, 0},
};
// 创建一个二维数组,用于比对。如果arr 数组等于 win 表示胜利返回true
int[][] win = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
{13, 14, 15, 0},
};
int column; // 二维数组的 列号
int row; // 二位数组的 行号
int count = 0; // 统计步数变量
// 通过构造方法调用各个功能模块,只要我这个类被创建,那么就执行!
public MainFrame() {
// 初始化界面
initFrame();
// 打乱界面
initData();
// 绘制界面
paintView();
// 注册事件监听
this.addKeyListener(this);
// 显示窗体
setVisible(true);
}
/**
* 初始化数据,打乱二维数组
*/
public void initData () {
Random r = new Random();
// 遍历二维数组
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr.length; j++) {
// arr[i][j]
int randomX = r.nextInt(4);
int randomY = r.nextInt(4);
int temp = arr[i][j];
arr[i][j] = arr[randomX][randomY];
arr[randomX][randomY] = temp;
}
}
// 获取到二维数组中的 0 号元素
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr.length; j++) {
if (arr[i][j] == 0) {
column = i;
row = j;
}
}
}
}
/**
* 初始化界面
*/
public void initFrame () {
// 设置窗体
// 设置窗体大小
setSize(514, 595);
// 设置窗体关闭模式
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
// 设置窗体标题
setTitle("华容道单机版V1.0");
// 窗口处于对上层
setAlwaysOnTop(true);
// 窗口居中显示
setLocationRelativeTo(null);
// 取消默布局
setLayout(null);
}
/**
* 此方法用于绘制界面,图片的初始化
*/
public void paintView () {
// !!! 每次绘制界面前,做一次清空。然后移动在绘制,就不会往上一次的下面塞进去了。
getContentPane().removeAll();
// 每次移动移动之前都判断一下游戏是否胜利
if (victory()) {
// 返回true,显示胜利!将资源加载到界面中
JLabel jb = new JLabel(new ImageIcon("D:\\java_code\\Advanced-Codes\\day04-code\\image\\win.png"));
jb.setBounds(124, 230, 266, 88);
getContentPane().add(jb);
}
// 设置步数界面显示
JLabel scoreLabel = new JLabel("当前步数:" + count);
scoreLabel.setBounds(50, 20, 100, 20);
getContentPane().add(scoreLabel);
// 重新游戏界面显示
JButton newStart = new JButton("重新开始");
newStart.setBounds(366, 20, 100, 20);
newStart.setFocusable(false);
getContentPane().add(newStart);
newStart.addActionListener(e -> {
count = 0; // 步数归零
initData(); // 打乱石头方块
paintView(); // 重新加载界面
});
// 设置 JLabel 组件 将图片资源放进去
// i 执行一次,j 执行四次,我相信都知道。只是需要注意的是。那个在一次外循环的时候,不需要变化的,我们用i,需要变化的使用j
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
JLabel imagesJlb = new JLabel(new ImageIcon("D:\\java_code\\Advanced-Codes\\day04-code\\image\\" + arr[i][j] + ".png"));
// 起始值都为 0 ,乘以 100 都是为 0 ,然后相加,随着循环次数的增长,自然的得到了每个JLable的宽高距离
imagesJlb.setBounds(50 + j * 100,90 + i * 100,100,100);
// 访问父类中的这个方法,然而就一个,super直接省略
getContentPane().add(imagesJlb);
}
}
// !!! 刷新操作!
getContentPane().repaint();
// 将背景图插入到底部,后插入会往下塞进去
JLabel background = new JLabel(new ImageIcon("D:\\java_code\\Advanced-Codes\\day04-code\\image\\background.png"));
background.setBounds(26, 30, 450, 484);
getContentPane().add(background);
}
/**
* 判断游戏是否胜利
*
*/
public boolean victory () {
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr.length; j++) {
if (arr[i][j] != win[i][j]) {
return false;
}
}
}
// 不跑if 证明都相等!游戏胜利
return true;
}
// 键盘事件处理方法
@Override
public void keyPressed(KeyEvent e) {
// 键盘按下触发
int keycode = e.getKeyCode();
move(keycode);
// 事件触发后,重新绘制界面
paintView();
}
/**
* 此方法处理移动业务
* @param keycode
*/
private void move(int keycode) {
// 游戏胜利,该方法里面的移动逻辑则不能执行了(只有在非胜利状态下,才能执行)
if (victory()) {
return;
}
if (keycode == 37) {
if (column == 3) {
return;
}
// 空白块的位置
int temp = arr[row][column];
arr[row][column] = arr[row][column + 1];
arr[row][column + 1] = temp;
//交换完毕后,我们空白块(0号)右移就得将column加一
column++;
count++;
} else if (keycode == 38) {
if (row == 3) {
return;
}
// 空白块的位置
int temp = arr[row][column];
arr[row][column] = arr[row + 1][column];
arr[row + 1][column] = temp;
row++;
count++;
} else if (keycode == 39) {
if (column == 0) {
return;
}
// 空白块的位置
int temp = arr[row][column];
arr[row][column] = arr[row][column - 1];
arr[row][column - 1] = temp;
column--;
count++;
}else if (keycode == 40) {
if (row == 0) {
return;
}
// 空白块的位置
int temp = arr[row][column];
arr[row][column] = arr[row - 1][column];
arr[row - 1][column] = temp;
row--;
count++;
} else if (keycode == 90) {
// z键胜利!覆盖被打乱的二维数组
arr = new int[][] {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
{13, 14, 15, 0},
};
}
}
// 下面两种方法不常用,我们选择上面的按下触发事件即可了。
@Override
public void keyReleased(KeyEvent e) {
// 键盘弹起触发
}
@Override
public void keyTyped(KeyEvent e) {
// 类型触发
}
}