Java 黑马程序员学习笔记(进阶篇18)
斗地主游戏
一、斗地主游戏1 -- 准洗发(控制台版)
1. 静态成员变量 + 静态初始化块
static ArrayList<String> list = new ArrayList<>();static {String[] color = {"♦","♠","♥","♣"};String[] number = {"3","4","5","6","7","8","9","10","J","Q","K","A","2"};for (String c : color) { // c依次表示每一种花色for (String n : number) { // n依次表示每一个数字list.add(c + n);}}list.add("小王");list.add("大王");} // 不太理解这个静态代码块的含义
(1) 为什么扑克牌的 list
要加 static
你的代码是这样的:
static ArrayList<String> list = new ArrayList<>();
- 数据不变:54 张牌的内容是固定的。
- 大家共用:游戏中所有玩家操作的是同一副牌。
- 一次生成:节省内存,提高效率。
(2) 不用 static
会发生什么?
假设不加 static
:
class CardGame {ArrayList<String> list = new ArrayList<>();public CardGame() {list.add("♦3");list.add("♦4");// ... 生成一副牌System.out.println("生成了一副新牌");}
}
测试:
CardGame p1 = new CardGame(); // 生成一副新牌
CardGame p2 = new CardGame(); // 又生成一副新牌
输出:
生成了一副新牌
生成了一副新牌
➡ 结果:每多一个玩家,就多一副牌,浪费空间,而且牌不是同一副,没法玩游戏。
2. lookPoker 代码解析
public void lookPoker(String name, ArrayList<String> list) {System.out.print(name + ":"); // 打印玩家名字,不换行for (String poker : list) { // 遍历牌的集合System.out.print(poker); // 打印每张牌,不换行}System.out.println(); // 所有牌打印完后换行
}
二、斗地主游戏2 -- 给牌排序1(利用序号进行排序)
package demo1;import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.TreeSet;public class PokerGame {static HashMap<Integer,String> hm = new HashMap<>();static ArrayList<Integer> list = new ArrayList<>();static {String[] color = {"♦","♠","♥","♣"};String[] number = {"3","4","5","6","7","8","9","10","J","Q","K","A","2"};int serialNumber = 1;for (String n : number) {for (String c : color) {hm.put(serialNumber,c + n);list.add(serialNumber);serialNumber++;}}hm.put(serialNumber,"小王");list.add(serialNumber);serialNumber++;hm.put(serialNumber,"大王");list.add(serialNumber);} // 不太理解这个静态代码块的含义public PokerGame() {//洗牌Collections.shuffle(list);TreeSet<Integer> lord = new TreeSet<>();TreeSet<Integer> player1 = new TreeSet<>();TreeSet<Integer> player2 = new TreeSet<>();TreeSet<Integer> player3 = new TreeSet<>(); // 为什么使用的是TreeSetfor (int i = 0; i < list.size(); i++) {int serialNumber = list.get(i);if (i <= 2) {lord.add(serialNumber);continue;}if (i % 3 == 0) {player1.add(serialNumber);} else if (i % 3 == 1) {player2.add(serialNumber);} else {player3.add(serialNumber);}}lookPoker("底牌",lord);lookPoker("知更鸟",player1);lookPoker("星期日",player2);lookPoker("阿格莱雅",player3);}public void lookPoker(String name, TreeSet<Integer> ts) { // 不太理解System.out.print(name + ":");for (Integer serialNumber : ts) {String poker = hm.get(serialNumber);System.out.print(poker + " ");}System.out.println();}
}
关键逻辑 1:为什么用 TreeSet
存储牌
(1) TreeSet 的特点
- 有序集合(默认从小到大排序),底层是红黑树实现。
- 存储的元素会自动排序,并且不允许重复。
(2) 为什么这里用 TreeSet
① 斗地主发牌后,每个人手里的牌需要按大小顺序排列方便查看。
② 牌的大小已经通过编号体现:
- 3 的编号最小,然后依次增大,2 最大,小王次之,大王最大。
- 所以只要把牌的编号放入
TreeSet
,它就会自动从小到大排好序。
③ 这样在调用 lookPoker()
输出时,直接按顺序打印即可。
三、斗地主游戏2 -- 给牌排序2(给每一张牌计算价值)
package demo3;import java.util.*;public class PokerGame {static HashMap<String,Integer> hm = new HashMap<>();static ArrayList<String> list = new ArrayList<>();static {String[] color = {"♦","♠","♥","♣"};String[] number = {"3","4","5","6","7","8","9","10","J","Q","K","A","2"};for (String c : color) {for (String n : number) {list.add(c + n);}}list.add(" 小王");list.add(" 大王");hm.put("J",11);hm.put("Q",12);hm.put("K",13);hm.put("A",14);hm.put("2",15);hm.put("小王",50);hm.put("大王",100);}public PokerGame() {//洗牌Collections.shuffle(list);ArrayList<String> lord = new ArrayList<>();ArrayList<String> player1 = new ArrayList<>();ArrayList<String> player2 = new ArrayList<>();ArrayList<String> player3 = new ArrayList<>();for (int i = 0; i < list.size(); i++) {String poker = list.get(i);if (i <= 2) {lord.add(poker);continue;}if (i % 3 == 0 ) {player1.add(poker);} else if (i % 3 == 1 ) {player2.add(poker);} else {player3.add(poker);}}order(lord);order(player1);order(player2);order(player3);lookPoker("底牌",lord);lookPoker("知更鸟",player1);lookPoker("星期日",player2);lookPoker("阿格莱雅",player3);}public void order(ArrayList<String> list) {Collections.sort(list, new Comparator<String>() {@Overridepublic int compare(String o1, String o2) {String color1 = o1.substring(0,1); // 不太理解int value1 = getValue(o1);String color2 = o2.substring(0,1);int value2 = getValue(o2);int i = value1 - value2;return i == 0 ? color1.compareTo(color2) : i;}});}public int getValue(String poker) {String number = poker.substring(1);if (hm.containsKey(number)) {return hm.get(number);} else {return Integer.parseInt(number); // 不太理解}}public void lookPoker(String name,ArrayList<String> list) { // 不太理解System.out.print(name + ":");for (String poker : list) {System.out.print(poker + " ");}System.out.println();}}
四、斗地主游戏3 (页面版)
Poker:
package domain;import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;public class Poker extends JLabel implements MouseListener {//属性//1.牌的名字 格式:数字 - 数字private String name;//2.牌显示正面还是反面private boolean up;//3.是否可以被点击private boolean canClick = false;//4.当前的状态,表示当前的牌是否已经被点击//如果是没有被点击的状态,此时被点击了,会执行弹起的操作//如果是已经被点击的状态,此时被点击了,会执行降落的操作private boolean clicked = false;public Poker(String name,boolean up){this.name = name;this.up = up;//判断当前的牌是显示正面还是反面if(this.up){//显示正面turnFront();}else{//显示反面turnRear();}//设置牌的宽高this.setSize(71,96);//把牌显示出来this.setVisible(true);//给每一张牌添加监听this.addMouseListener(this);}//显示正面public void turnFront(){//给牌设置正面this.setIcon(new ImageIcon("farmerandlord\\image\\poker\\"+name+".png"));//修改成员变量this.up = true;}//显示反面public void turnRear(){//给牌设置反面this.setIcon(new ImageIcon("farmerandlord\\image\\poker\\rear.png"));//修改成员变量this.up = false;}@Overridepublic void mouseClicked(MouseEvent e) {//点击//判断当前的牌是否可以被点击if(canClick){//当牌被点击之后,要么升起,要么降落//表示牌的位移像素int step = 0;if(clicked){//表示当前的牌已经被点击//降落(y 增加 20像素)step = 20;}else{//表示当前的牌还没有被点击//升起 (y 减少 20像素)step = -20;}//需要修改一下clicked变量记录的值clicked = !clicked;//修改一下牌的位置Point from = this.getLocation();//创建一个Point的对象,表示结束位置Point to = new Point(from.x,from.y + step);//把最新的位置设置给牌this.setLocation(to);}}@Overridepublic void mousePressed(MouseEvent e) {}@Overridepublic void mouseReleased(MouseEvent e) {}@Overridepublic void mouseEntered(MouseEvent e) {}@Overridepublic void mouseExited(MouseEvent e) {}/*** 获取* @return name*/public String getName() {return name;}/*** 设置* @param name*/public void setName(String name) {this.name = name;}/*** 获取* @return up*/public boolean isUp() {return up;}/*** 设置* @param up*/public void setUp(boolean up) {this.up = up;}/*** 获取* @return canClick*/public boolean isCanClick() {return canClick;}/*** 设置* @param canClick*/public void setCanClick(boolean canClick) {this.canClick = canClick;}/*** 获取* @return clicked*/public boolean isClicked() {return clicked;}/*** 设置* @param clicked*/public void setClicked(boolean clicked) {this.clicked = clicked;}public String toString() {return "Poker{name = " + name + ", up = " + up + ", canClick = " + canClick + ", clicked = " + clicked + "}";}
}
GameJFrame
package game;import domain.Poker;import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;public class GameJFrame extends JFrame implements ActionListener {//获取界面中的隐藏容器,现在统一获取了,后面直接用就可以了public static Container container = null; // 不太理解//管理抢地主和不抢两个按钮JButton[] landlord = new JButton[2];//管理出牌和不要两个按钮JButton[] publishCard = new JButton[2];//游戏界面中地主的图标JLabel dizhu;//集合嵌套集合//大集合中有三个小集合//小集合中装着每一个玩家当前要出的牌//0索引:左边的电脑玩家//1索引:中间的自己//2索引:右边的电脑玩家ArrayList<ArrayList<Poker>> currentList = new ArrayList<>();//集合嵌套集合//大集合中有三个小集合//小集合中装着每一个玩家手上的牌//0索引:左边的电脑玩家//1索引:中间的自己//2索引:右边的电脑玩家ArrayList<ArrayList<Poker>> playerList = new ArrayList<>();//底牌ArrayList<Poker> lordList = new ArrayList<>();//牌盒,装所有的牌ArrayList<Poker> pokerList = new ArrayList();//三个玩家前方的文本提示//0索引:左边的电脑玩家//1索引:中间的自己//2索引:右边的电脑玩家JTextField time[] = new JTextField[3];public GameJFrame() {//设置任务栏的图标setIconImage(Toolkit.getDefaultToolkit().getImage("farmerandlord\\image\\poker\\dizhu.png"));//设置界面initJframe();//添加组件initView();//界面显示出来//先展示界面再发牌,因为发牌里面有动画,界面不展示出来,动画无法展示this.setVisible(true);//初始化牌//准备牌,洗牌,发牌,排序initCard();//打牌之前的准备工作//展示抢地主和不抢地主两个按钮并且再创建三个集合用来装三个玩家准备要出的牌initGame();}//初始化牌(准备牌,洗牌,发牌,排序)public void initCard() {//准备牌//把所有的牌,包括大小王都添加到牌盒pokerList当中for (int i = 1; i <= 5; i++) {for (int j = 1; j <= 13; j++) {if ((i == 5) && (j > 2)) {break;} else {Poker poker = new Poker(i + "-" + j, false);poker.setLocation(350, 150);pokerList.add(poker);container.add(poker);}}}//洗牌Collections.shuffle(pokerList);//创建三个集合用来装三个玩家的牌,并把三个小集合放到大集合中方便管理ArrayList<Poker> player0 = new ArrayList<>();ArrayList<Poker> player1 = new ArrayList<>();ArrayList<Poker> player2 = new ArrayList<>();//发牌for (int i = 0; i < pokerList.size(); i++) {//获取当前遍历的牌Poker poker = pokerList.get(i);//发三张底牌if (i <= 2) {//把底牌添加到集合中lordList.add(poker);Common.move(poker, poker.getLocation(), new Point(270 + (75 * i), 10));continue;}//给三个玩家发牌if (i % 3 == 0) {//给左边的电脑发牌Common.move(poker, poker.getLocation(), new Point(50, 60 + i * 5));player0.add(poker);} else if (i % 3 == 1) {//给中间的自己发牌Common. move(poker, poker.getLocation(), new Point(180 + i * 7, 450));player1.add(poker);//把自己的牌展示正面poker.turnFront();} else if (i % 3 == 2) {//给右边的电脑发牌Common. move(poker, poker.getLocation(), new Point(700, 60 + i * 5));player2.add(poker);}//把三个装着牌的小集合放到大集合中方便管理playerList.add(player0);playerList.add(player1);playerList.add(player2);//把当前的牌至于最顶端,这样就会有牌依次错开且叠起来的效果container.setComponentZOrder(poker, 0);}//排序for (int i = 0; i < 3; i++) {order(playerList.get(i));Common.rePosition(this,playerList.get(i),i);}}//排序public void order(ArrayList<Poker> list) {//此处可以改为lambda表达式Collections.sort(list, new Comparator<Poker>() {@Overridepublic int compare(Poker o1, Poker o2) {//获取o1的花色和价值int color1 = Integer.parseInt(o1.getName().substring(0, 1));int value1 = getValue(o1);//获取o2的花色和价值int color2 = Integer.parseInt(o2.getName().substring(0, 1));int value2 = getValue(o2);//倒序排列//细节://图形化界面当中,牌倒着摆放int flag = value2 - value1;//如果牌的价值一样,则按照花色排序if (flag == 0){return color2 - color1;}else {return flag;}}});}//获取每一张牌的价值public int getValue(Poker poker) {//获取牌的名字 1-1String name = poker.getName();//获取牌的花色int color = Integer.parseInt(poker.getName().substring(0, 1));//获取牌对应的数字,同时也是牌的价值int value = Integer.parseInt(name.substring(2));//在本地文件中,每张牌的文件名为:数字1-数字2//数字1表示花色,数字2表示牌的数字//其中3~K对应的数字2,可以视为牌的价值//所以,我们单独判断大小王,A,2即可//计算大小王牌的价值if (color == 5){//小王的初始价值为1,在1的基础上加100,小王价值为:101//大王的初始价值为2,在2的基础上加100,大王价值为:102return value += 100;}//计算A的价值if (value == 1){//A的初始价值为1,在1的基础上加20,大王价值为:21return value += 20;}//计算2的价值if (value == 2){//2的初始价值为2,在2的基础上加30,大王价值为:32return value += 30;}//如果不是大小王,不是A,不是2,牌的价值就是牌对应的数字return value;}//打牌之前的准备工作private void initGame() {//创建三个集合用来装三个玩家准备要出的牌for (int i = 0; i < 3; i++) {ArrayList<Poker> list = new ArrayList<>();//添加到大集合中方便管理currentList.add(list);}//展示抢地主和不抢地主两个按钮landlord[0].setVisible(true);landlord[1].setVisible(true);//展示自己前面的倒计时文本for (JTextField field : time) {field.setText("倒计时30秒");field.setVisible(true);}}@Overridepublic void actionPerformed(ActionEvent e) {}//添加组件public void initView() {//创建抢地主的按钮JButton robBut = new JButton("抢地主");//设置位置robBut.setBounds(320, 400, 75, 20);//添加点击事件robBut.addActionListener(this);//设置隐藏robBut.setVisible(false);//添加到数组中统一管理landlord[0] = robBut;//添加到界面中container.add(robBut);//创建不抢的按钮JButton noBut = new JButton("不 抢");//设置位置noBut.setBounds(420, 400, 75, 20);//添加点击事件noBut.addActionListener(this);//设置隐藏noBut.setVisible(false);//添加到数组中统一管理landlord[1] = noBut;//添加到界面中container.add(noBut);//创建出牌的按钮JButton outCardBut = new JButton("出牌");outCardBut.setBounds(320, 400, 60, 20);outCardBut.addActionListener(this);outCardBut.setVisible(false);publishCard[0] = outCardBut;container.add(outCardBut);//创建不要的按钮JButton noCardBut = new JButton("不要");noCardBut.setBounds(420, 400, 60, 20);noCardBut.addActionListener(this);noCardBut.setVisible(false);publishCard[1] = noCardBut;container.add(noCardBut);//创建三个玩家前方的提示文字:倒计时//每个玩家一个//左边的电脑玩家是0//中间的自己是1//右边的电脑玩家是2for (int i = 0; i < 3; i++) {time[i] = new JTextField("倒计时:");time[i].setEditable(false);time[i].setVisible(false);container.add(time[i]);}time[0].setBounds(140, 230, 60, 20);time[1].setBounds(374, 360, 60, 20);time[2].setBounds(620, 230, 60, 20);//创建地主图标dizhu = new JLabel(new ImageIcon("images/dizhu.png"));dizhu.setVisible(false);dizhu.setSize(40, 40);container.add(dizhu);}//设置界面public void initJframe() {//设置标题this.setTitle("斗地主");//设置大小this.setSize(830, 620);//设置关闭模式this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//设置窗口无法进行调节this.setResizable(false);//界面居中this.setLocationRelativeTo(null);//获取界面中的隐藏容器,以后直接用无需再次调用方法获取了container = this.getContentPane();//取消内部默认的居中放置container.setLayout(null);//设置背景颜色container.setBackground(Color.LIGHT_GRAY);}}
关键逻辑 1:成员变量设计
public static Container container = null;
- 作用:保存窗体的内容面板
ContentPane
,方便在其他地方直接添加组件,不用每次都this.getContentPane()
。 - 注意:
public static
意味着这个容器是全局共享的,其他类也能直接访问。
关键逻辑 2:核心方法:initCard()
for (int i = 1; i <= 5; i++) {for (int j = 1; j <= 13; j++) {if ((i == 5) && (j > 2)) break;Poker poker = new Poker(i + "-" + j, false);poker.setLocation(350, 150);pokerList.add(poker);container.add(poker);}
}
i
表示花色(1~4 是普通花色,5 是大小王)。j
表示牌面数字(1~13)。- 大小王只有两张(
i=5
时 j 只到 2)。 - 每张牌是一个
Poker
对象(应该是自定义的JLabel
子类,能显示牌的正反面)。
关键逻辑 3:Poker poker = new Poker(i + "-" + j, false);
① Poker
是自定义的 Swing 组件类(推测继承自JLabel
),用于表示一张扑克牌。
② i + "-" + j
是牌的唯一标识:
i
代表 “花色 / 类型”(比如1~4
对应普通花色,5
对应大小王);j
代表 “牌面数字”(比如1
对应 A,11
对应 J,13
对应 K);- 示例:
"1-1"
可能是 “黑桃 A”,"5-1"
是 “小王”,"5-2"
是 “大王”。
③ 第二个参数false
:控制牌的初始状态(比如 “是否正面朝上”,false
可能表示 “反面朝上”,后续发牌时自己的牌会翻转为正面)。
LoginJFrame
package game;import domain.User;
import domain.User;
import util.CodeUtil;
import game.GameJFrame;
import util.CodeUtil;import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;public class LoginJFrame extends JFrame implements MouseListener {static ArrayList<User> allUsers = new ArrayList<>();static {allUsers.add(new User("zhangsan","123"));allUsers.add(new User("lisi","1234"));}JButton login = new JButton();JButton register = new JButton();JTextField username = new JTextField();JPasswordField password = new JPasswordField();JTextField code = new JTextField();//正确的验证码JLabel rightCode = new JLabel();public LoginJFrame() {//初始化界面initJFrame();//初始化组件,在这个界面中添加内容initView();//让当前界面显示出来this.setVisible(true);}//在这个界面中添加内容public void initView() {//1. 添加用户名文字Font usernameFont = new Font(null,1,16);JLabel usernameText = new JLabel("用户名");usernameText.setForeground(Color.white);usernameText.setFont(usernameFont);usernameText.setBounds(140, 55, 55, 22);this.getContentPane().add(usernameText);//2.添加用户名输入框username.setBounds(223, 46, 200, 30);this.getContentPane().add(username);//3.添加密码文字JLabel passwordText = new JLabel("密码");Font passwordFont = new Font(null,1,16);passwordText.setForeground(Color.white);passwordText.setFont(passwordFont);passwordText.setBounds(197, 95, 40, 22);this.getContentPane().add(passwordText);//4.密码输入框password.setBounds(263, 87, 160, 30);this.getContentPane().add(password);//验证码提示JLabel codeText = new JLabel("验证码");Font codeFont = new Font(null,1,16);codeText.setForeground(Color.white);codeText.setFont(codeFont);codeText.setBounds(215, 142, 55, 22);this.getContentPane().add(codeText);//验证码的输入框code.setBounds(291, 133, 100, 30);this.getContentPane().add(code);//获取正确的验证码String codeStr = CodeUtil.getCode();Font rightCodeFont = new Font(null,1,15);//设置颜色rightCode.setForeground(Color.RED);//设置字体rightCode.setFont(rightCodeFont);//设置内容rightCode.setText(codeStr);//绑定鼠标事件rightCode.addMouseListener(this);//位置和宽高rightCode.setBounds(400, 133, 100, 30);//添加到界面this.getContentPane().add(rightCode);//5.添加登录按钮login.setBounds(123, 310, 128, 47);login.setIcon(new ImageIcon("farmerandlord\\image\\login\\登录按钮.png"));//去除按钮的边框login.setBorderPainted(false);//去除按钮的背景login.setContentAreaFilled(false);//给登录按钮绑定鼠标事件login.addMouseListener(this);this.getContentPane().add(login);//6.添加注册按钮register.setBounds(256, 310, 128, 47);register.setIcon(new ImageIcon("farmerandlord\\image\\login\\注册按钮.png"));//去除按钮的边框register.setBorderPainted(false);//去除按钮的背景register.setContentAreaFilled(false);//给注册按钮绑定鼠标事件register.addMouseListener(this);this.getContentPane().add(register);//7.添加背景图片JLabel background = new JLabel(new ImageIcon("farmerandlord\\image\\login\\background.png"));background.setBounds(0, 0, 633, 423);this.getContentPane().add(background);}//初始化组件,在这个界面中添加内容public void initJFrame() {this.setSize(633, 423);//设置宽高this.setTitle("斗地主游戏 V1.0登录");//设置标题this.setDefaultCloseOperation(3);//设置关闭模式this.setLocationRelativeTo(null);//居中this.setAlwaysOnTop(true);//置顶this.setLayout(null);//取消内部默认布局}//点击@Overridepublic void mouseClicked(MouseEvent e) {Object obj = e.getSource();if (obj == login) {//获取两个文本输入框中的内容String usernameInput = username.getText();String passwordInput = password.getText();//获取用户输入的验证码String codeInput = code.getText();//判断验证码是否为空if (codeInput.length() == 0) {showJDialog("验证码不能为空");return;}//判断用户名和密码是否为空if (usernameInput.length() == 0 || passwordInput.length() == 0) {showJDialog("用户名或者密码为空");return;}//判断验证码是否正确if (!codeInput.equalsIgnoreCase(rightCode.getText())) {showJDialog("验证码输入错误");return;}//判断集合中是否包含当前用户对象//其实就是验证用户名和密码是否相同//contains底层是依赖equals方法判断的,所以需要重写equals方法User userInfo = new User(usernameInput, passwordInput);if (allUsers.contains(userInfo)) {//关闭当前登录界面this.setVisible(false);//打开游戏的主界面new GameJFrame();} else {showJDialog("用户名或密码错误");}} else if (obj == register) {System.out.println("点击了注册按钮");} else if (obj == rightCode) {//获取一个新的验证码String code = CodeUtil.getCode();rightCode.setText(code);}}//展示弹框public void showJDialog(String content) {//创建一个弹框对象JDialog jDialog = new JDialog();//给弹框设置大小jDialog.setSize(200, 150);//让弹框置顶jDialog.setAlwaysOnTop(true);//让弹框居中jDialog.setLocationRelativeTo(null);//弹框不关闭永远无法操作下面的界面jDialog.setModal(true);//创建Jlabel对象管理文字并添加到弹框当中JLabel warning = new JLabel(content);warning.setBounds(0, 0, 200, 150);jDialog.getContentPane().add(warning);//让弹框展示出来jDialog.setVisible(true);}//按下不松@Overridepublic void mousePressed(MouseEvent e) {if (e.getSource() == login) {login.setIcon(new ImageIcon("farmerandlord\\image\\login\\登录按下.png"));} else if (e.getSource() == register) {register.setIcon(new ImageIcon("farmerandlord\\image\\login\\注册按下.png"));}}//松开按钮@Overridepublic void mouseReleased(MouseEvent e) {if (e.getSource() == login) {login.setIcon(new ImageIcon("farmerandlord\\image\\login\\登录按钮.png"));} else if (e.getSource() == register) {register.setIcon(new ImageIcon("farmerandlord\\image\\login\\注册按钮.png"));}}//鼠标划入@Overridepublic void mouseEntered(MouseEvent e) {}//鼠标划出@Overridepublic void mouseExited(MouseEvent e) {}}
关键逻辑 1:类的整体结构
public class LoginJFrame extends JFrame implements MouseListener
- 继承
JFrame
:让这个类本身就是一个窗口。 - 实现
MouseListener
:监听鼠标事件(点击、按下、释放、移入、移出)。 - 这是 Swing 图形界面开发的常见写法,但对于初学者来说,事件监听机制是一个难点:
你点击按钮,程序如何知道要执行哪段代码?→ 就是通过 addMouseListener(this)
把当前窗口对象注册为监听器,然后重写 mouseClicked()
等方法。
关键逻辑 2:按钮图片切换(按下 / 弹起)
@Override
public void mousePressed(MouseEvent e) {if (e.getSource() == login) {login.setIcon(new ImageIcon("farmerandlord\\image\\login\\登录按下.png"));}
}@Override
public void mouseReleased(MouseEvent e) {if (e.getSource() == login) {login.setIcon(new ImageIcon("farmerandlord\\image\\login\\登录按钮.png"));}
}
- 通过鼠标按下 / 释放事件切换按钮图标,实现按钮点击的视觉反馈。
- 这种交互细节初学者容易忽略,但对用户体验很重要。
五、JButton
、JTextField
、JPasswordField
的作用
1. JButton(按钮组件)
JButton
是用于用户交互的按钮组件,核心作用是接收用户的点击操作并触发相应的事件。
- 用户点击按钮后,程序可以通过绑定事件监听器(如
ActionListener
)执行特定逻辑(例如提交表单、打开新窗口、执行计算等)。 - 按钮上可以显示文本(如 “登录”“保存”)或图标,直观提示用户其功能。
2. JTextField
(文本输入框组件)
JTextField
是用于输入和显示单行可见文本的组件,适用于需要用户输入普通文本(非敏感信息)的场景。
- 允许用户直接输入文本(如用户名、搜索关键词、年龄等),也可以通过代码动态设置或获取文本内容。
- 文本内容默认可见(直接显示输入的字符),支持基本的文本编辑(如复制、粘贴、删除)。
3. JPasswordField
(密码输入框组件)
JPasswordField
是专门用于输入和处理敏感文本(如密码) 的组件,本质是 JTextField
的子类,但对文本显示做了特殊处理。
- 输入的文本会被隐藏(通常显示为 “*”“・” 等占位符),避免他人直接看到敏感信息,增强安全性。
- 同样支持单行输入,通过代码获取时需通过
getPassword()
方法(返回字符数组,而非字符串,进一步降低内存中密码泄露的风险)。
典型场景示例
在登录界面中:
JTextField
用于输入 “用户名”(可见文本);JPasswordField
用于输入 “密码”(隐藏文本);JButton
作为 “登录” 按钮,点击后触发验证用户名和密码的逻辑。