GUI 编程
Swing和AWT 是java开发GUI常用的技术,但是由于外观不太美观, 组件数量偏少, 并且运行需要JRE环境(动不动就上百M的JRE包….), 所以没有流行起来。但是 ,建议简单的学习和了解。
- 组件(JTable,JList等)很多都是MVC的经典示范,学习GUI可以了解mvc架构。
- 工作时,有可能遇见需要维护N年前awt/swing写的软件 ,虽然可能性很小。
- 可以写一些自己使用用的软件,还是相当的方便。
swing是建立在awt基础上的。有必要学习一下,原因如下:
- 知识的关联性,比如布局、颜色、字体、事件机制等….这些都是awt里的内容,但在swing里也经常使用到。
- 学习成本低,因为awt和swing在编码上区别不大,写法基本一致,组件使用上也差不多(只需要记住少数有区别的地方就可以了)。
- 使用场景存在不同:awt消耗资源少,运行速度快,适合嵌入式等;swing跨平台,组件丰富。
虽然现在用Java做cs的很少,但是对于学习Java基础来说,还是很好的资源,可以利用它把以前的所有知识贯穿起来,做一些小应用、游戏等都可以,可以将自己的一些小想法,做成工具分享出来!
AWT
一、AWT介绍
- AWT(Abstract Window Toolkit,抽象窗口工具)包括了很多类和接口,用于Java Application的GUI(Graphics User Interface 图形用户界面)编程。
- GUI的各种元素(如:窗口、按钮、文本框等)由Java类来实现。
- 使用AWT所涉及的类一般在
Java.AWT包及其子包中。 - Container(容器) 和 Component(组件) 是AWT中的两个核心类。

所有的可以显示出来的图形元素都称为Component,Component代表了所有的可见的图形元素。
Component里有一种比较特殊的图形元素叫Container,Container(容器)在图形界面里面是一种可以容纳其它Component元素的一种容器,Container本身也是一种Component,Container里面也可以容纳别的Container。
Container里面又分为Window和Pannel,Window是可以独立显示出来的,平时看到的各种各样的应用程序的窗口都可以称为Window,Window作为一个应用程序窗口独立显示出来。
Pannel也可以容纳其它的图形元素,但一般看不见Pannel,Pannel不能作为应用程序的独立窗口显示出来,Pannel要想显示出来就必须得把自己装入到Window里面才能显示出来。Pannel应用比较典型的就是Applet(JAVA的页面小应用程序),现在基本已经不用了,AJAX和JAVASCRIPT完全取代了它的应用。
Window本身又可以分为Frame和Dialog,Frame就是平时看到的一般的窗口,而Dialog则是需要用户进行某些操作(如点击某个下拉菜单的项)才出现的对话框,这种对话框就是Dialog。
二、组件和容器(Component & Container)
- Java的图形用户界面的最基本组成部分是Component,Component类及其子类的对象用来描述以图形化的方式显示在屏幕上并能与用户进行交互的GUI元素。例如,一个按钮,一个标签等。
- 一般的Component对象不能独立地显示出来,必须将 “放在” 某一个Container对象中才可以显示出来。
- Container是Component的子类,Container子类对象可以 “容纳” 别的Component对象。
- Container对象可使用add(…)方法向其中添加其他的Component对象。
- Container是Component的子类,因此Container对象也可以被当作Component对象添加到其他的Container对象中。
- 有两种常用的Container:
- Window:其对象表示自由停泊的顶级窗口。
- Panel:其对象可作为容纳其它Component对象,但不能独立存在,必须被添加到其它Container中(如Window或Applet)
2.1 Fram
- Frame是Window的子类,由Frame或其子类创建的对象为一个窗体。
- Frame的常用构造方法:
- Frame()
- Frame(String s):创建标题栏为字符串s的窗口
- Frame的属性方法
setBounds(int x,int y,int width,int height):设置窗体位置和大小。x、y是左上角坐标,width、height是宽度和高度。setSize(int width,int height):设置窗体的大小,x、y是左上角坐标。setLocation(int x,int y):设置窗体位置,x、y是左上角坐标。setBackground(Color c):设置背景颜色,参数为Color对象。setVisible(boolean b):设置是否可见。setTitle(String name):设置标题栏。setResizable(boolean b):设置窗体是否可以调整大小。
【Frame范例】
package com.kuang;import java.awt.*;//GUI编程编写的第一个图形界面窗口public class TestFrame {public static void main(String[] args) {//只是在内存里面创建了一个窗口对象,还不能真正显示出来Frame frame = new Frame("我的第一个JAVA图形界面窗口");//设置窗体的背景颜色//三个参数分别代表RGBframe.setBackground(new Color(110, 217, 119));//设置窗体是否可见//要想看到在内存里面创建出来的窗口对象,必须调用setVisble()方法,并且把参数true传入才能看得见窗体//如果传入的参数是false,那窗体就是看不见的frame.setVisible(true);//设置窗体的初始大小frame.setSize(400,400);//设置窗体出现时的位置,如果不设置则默认在左上角(0,0)位置显示frame.setLocation(200,200);// 设置窗体能否被改变大小// 默认是true,设置为false后表示不能改变窗体的显示大小// 这里窗体显示的大小设置为200X200,不能再使用鼠标拖大或者缩小frame.setResizable(false);}}
运行结果:
发现问题:关闭不掉。
解决方法:停止Java程序的运行
【演示二:展示多个窗口】
package com.kuang;import java.awt.*;public class TestMultiFrame {public static void main(String[] args) {MyFrame f1 = new MyFrame(100,100,200,200,Color.blue);MyFrame f2 = new MyFrame(300,100,200,200,Color.yellow);MyFrame f3 = new MyFrame(100,300,200,200,Color.red);MyFrame f4 = new MyFrame(300,300,200,200,Color.MAGENTA);}}//自定义一个类MyFrame,并且从Frame类继承,这样自定义类MyFrame类就拥有了Frame类的一切属性和方法,并且MyFrame类还可以自定义属性和方法//使用从Frame类继承而来的自定义类来创建图形窗口比直接使用Frame类来创建图形窗口要灵活,所以一般使用从Frame类继承而来的自定义类创建图形窗口界面比较好,不推荐直接使用Frame类来创建图形窗口界面class MyFrame extends Frame{//定义一个静态成员变量id,用来记录创建出来的窗口的数目static int id = 0;//自定义构成方法,在构造方法体内使用super调用父类Frame的构造方法public MyFrame(int x,int y,int w,int h,Color color){super("MyFrame"+(++id));/*使用从父类Frame继承而来的方法设置窗体的相关属性*/setBackground(color);//设置背景色setLayout(null);//setBounds(x,y,w,h);//设置初始位置、大小setVisible(true);//设置是否可见}}
运行结果:
2.2 Panel
- Panel对象可以堪称可以容纳Component的空间
- Panel对象可以拥有自己的布局管理器
- Panel类拥有从其父类继承来的方法:
setBounds(int x,int y,int width,int height):设置窗体位置和大小。x、y是左上角坐标,width、height是宽度和高度。setSize(int width,int height):设置窗体的大小,x、y是左上角坐标。setLocation(int x,int y):设置窗体位置,x、y是左上角坐标。setBackground(Color c):设置背景颜色,参数为Color对象。setLayout(LayoutManager mgr):设置布局管理器
- Panel的构造方法为:
Panel():使用默认的FlowLayout类布局管理器初始化。Panel(LayoutManager layout):使用指定的布局管理器初始化。
【演示】
package com.wang.gui.awt;import java.awt.*;import java.awt.event.WindowAdapter;import java.awt.event.WindowEvent;/*** @Author wangjin* @Date 2022/04/21 20:30* @Description*///Panel 可以看成是一个空间,但是不能单独存在public class PanelTest {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(83, 218, 84));//panel 设置坐标,相对于Framepanel.setBounds(50,50,400,400);panel.setBackground(new Color(232, 78, 78));//frame.add(panel)frame.add(panel);//设置可见性frame.setVisible(true);//监听事件解决窗口关闭问题//监听窗口关闭事件 System.exit(0)//适配器模式frame.addWindowListener(new WindowAdapter() {//窗口点击关闭时需要做的事情@Overridepublic void windowClosing(WindowEvent e) {//结束程序System.exit(0);}});}}
结果如下:
三、布局管理器
- Java语言中,提供了布局管理器类的对象可以管理
- 管理Component在Container中的布局,不必直接设置Component的位置和大小。
- 每个Container都有一个布局管理器对象,当容器需要对某个组件进行定位或判断其大小尺寸时,就会调用其对应的布局管理器,调用Container的setLayout()方法改变其布局管理器对象。
- AWT提供了5中布局管理器:
- FlowLayout
- BorderLayout
- GridLayout
- CardLayout
- GridBagLayout
3.1 第一种布局管理器——FlowLayout
- FlowLayout时Panel类的默认布局管理器。
- FlowLayout布局管理器对组件逐行定位,行内从左到右,一行排满后换行。
- 不改变组件的大小,按组件原有尺寸显示组件,可设置不同组件间距、行距以及对齐方式。
- FlowLayout布局管理器默认的对齐方式是居中。
- FlowLayout的构造方法
new FlowLayout(FlowLayout.RIGHT,20,40):右对齐,组件之间水平间距20个像素,垂直间距40个像素。new FlowLayout(FlowLayout.LEFT):左对齐,水平和垂直间距为缺省值(5)。new FlowLayout():使用默认的居中对齐方式,水平和垂直间距为缺省值(5)。
【演示】
package com.kuang;import java.awt.*;public class TestFlowLayout {public static void main(String[] args) {Frame frame = new Frame("FlowLayout");//使用Button类创建按钮//按钮类的其中一个构造方法:Button(String label) label为按钮显示的文本Button button1 = new Button("button1");Button button2 = new Button("button2");Button button3 = new Button("button3");// setLayout方法的定义:public void setLayout(LayoutManager mgr)// 使用流式布局管理器 - FlowLayout//默认水平居中frame.setLayout(new FlowLayout());//布局时使用FlowLayout.LEFT常量,将按钮设置为左对齐// frame.setLayout(new FlowLayout(FlowLayout.LEFT));//布局时使用FlowLayout.RIGHT常量,就将按钮设置为右对齐// frame.setLayout(new FlowLayout(FlowLayout.RIGHT));frame.setSize(200,200);frame.add(button1); // 把创建出来的按钮放置到Frame窗体中frame.add(button2); // 这里并没有设置按钮的大小与位置frame.add(button3); // 设置按钮的大小与位置都是由布局管理器来做的frame.setVisible(true);}}
运行结果:
3.2 第二种布局管理器——BorderLayout
- BorderLayout时Frame类的默认布局管理器
- BorderLayout将整个容器的布局划分成:
- 东(EAST)
- 西(WEST)
- 南(SOUTH)
- 北(NORTH)
- 中(CENTER)五个区域,组件只能被添加到指定的区域。
- 如不指定组件的加入部位,则默认加入到CENTER区。
- 每个区域只能加入一个组件,如加入多个,则先前加入的会被覆盖。
- BorderLayout型布局管理器尺寸缩放原则:
- 北、南两个区域在水平方向缩放。
- 东、西两个区域在垂直方向缩放。
- 中部可在两个方向上缩放。
package com.kuang;import java.awt.*;public class BorderLayoutTest{public static void main(String[] args) {Frame frame = new Frame("BorderLayoutTest");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窗体时按照东西南北中五个方向排列好,推荐使用这种方式去排列窗体元素//这样容易检查出错误 因为这样写如果写错了编译器会提示出错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窗体时使用方向定位的字符串指定按钮的放置位置//这种使用方向定位的字符串指定按钮的放置方式不推荐使用 一旦写错了方向字符串就不好检查出来//因为即使是写错了仍然可以编译通过/*frame.add(east,"EAST");frame.add(west,"West");frame.add(south,"South");frame.add(north,"North");frame.add(center,"Center");*/frame.setSize(200,200);frame.setVisible(true);}}
运行结果:
3.3 第三种布局管理器——GridLayout(表格布局管理器)
- GridLayout型布局管理器将空间划分成规则的矩形网格,每个单元格区域大小相等。组件被添加到每个单元格中,先从左到右填满一行后换行,再从上到下。
- 在GridLayout构造方法中指定分割的行数和列数:
- 如:
GridLayout(3,4);
- 如:
【演示】
package com.kuang;import java.awt.*;public class GridLayoutTest {public static void main(String[] args) {Frame frame = new Frame("GridLayoutTest");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("bnt6");// 把布局划分成3行2列的表格布局形式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语言的一个函数// 这个函数的作用就是根据窗口里面的布局及组件的preferredSize来确定frame的最佳大小。frame.pack();frame.setVisible(true);}}
运行结果:
3.4 布局练习

这几种布局管理器可以设置在Frame里面,也可以设置在Panel里面,而Panel本身也可以加入到Frame里面,因此通过Frame与Panel的嵌套就可以实现比较复杂的布局;
【演示】
package com.kuang;import java.awt.*;public class TestTenButtons {public static void main(String[] args) {//对显示窗体进行设置Frame frame = new Frame("布局管理器的嵌套使用");//把整个窗体分成2行1列的表格布局frame.setLayout(new GridLayout(2,1));frame.setLocation(300,400);frame.setSize(400,300);frame.setVisible(true);frame.setBackground(new Color(204,204,255));//对Panel进行布局的设置Panel p1 = new Panel(new BorderLayout());//p2使用2行1列的表格布局Panel p2 = new Panel(new GridLayout(2,1));Panel p3 = new Panel(new BorderLayout());//p4使用2行2列的表格布局Panel p4 = new Panel(new GridLayout(2,2));//这里主要是把按钮元素加入到Panel里面p1.add(new Button("East-1"),BorderLayout.EAST);p1.add(new Button("West-1"),BorderLayout.WEST);p2.add(new Button("p2-btn-1"));p2.add(new Button("p2-btn-2"));//p1里面嵌套p2,把p2里面的按钮作为p的中间部分装入到p1里面//把p2作为元素加入到p1里面p1.add(p2,BorderLayout.CENTER);p3.add(new Button("East-2"),BorderLayout.EAST);p3.add(new Button("West-2"),BorderLayout.WEST);for(int i=0;i<4;i++){p4.add(new Button("p4-btn-"+i));}//p3里面嵌套p4,把p4里面的按钮作为p的中间部分装入到p3里面p3.add(p4,BorderLayout.CENTER);//把Panel装入Frame里面,以便于在Frame窗体中显示出来frame.add(p1);frame.add(p3);//事件监听,实现窗口关闭frame.addWindowListener(new WindowAdapter() {@Overridepublic void windowClosing(WindowEvent e) {System.exit(0);}});}}
运行结果 :
四、布局管理器总结
- Frame是一个顶级窗口,Frame的默认布局管理器为BorderLayout。
- Panel无法单独显示,必须添加到某个容器中。
- Panel的默认布局管理器为FlowLayout
- 布局管理器:
- FlowLayout — 流式布局管理器
- BorderLayout — 东西南北中布局管理器
- GridLayout — 表格布局管理器
- 当把Panel作为一个组件添加到某个容器中后,该Panel仍然可以有自己的布局管理器。
- 使用布局管理器时,布局管理器负责各个组件的大小和位置,因此用户无法在这种情况下设置组件的大小和位置属性,如果试图使用Java语言的
setLocation()、setSize()、setBounds()等方法,则会被布局管理器覆盖。 - 如果用户确实需要亲自设置组件大小或位置,则应取消该容器的布局管理器,方法为:
setLayout(null);
五、事件监听

【测试代码一】
package com.kuang;import java.awt.*;import java.awt.event.*;public class ActionEventTest{public static void main(String[] args) {Frame frame = new Frame("TestActionEvent");Button button = new Button("Press Me");// 创建一个监听对象MyActionListener listener = new MyActionListener();// 把监听加入到按钮里面,监听按钮的动作,// 当按钮触发打击事件时,就会返回一个监听对象e,然后会自动执行actionPerformed方法button.addActionListener(listener);frame.add(button, BorderLayout.CENTER);frame.pack();addWindowClosingEvent(frame);frame.setVisible(true);}//点击窗体上的关闭按钮关闭窗体private static void addWindowClosingEvent(Frame frame){frame.addWindowListener(new WindowAdapter() {@Overridepublic void windowClosing(WindowEvent e) {System.exit(0);}});}}// 自定义Monitor(监听)类实现事件监听接口ActionListener// 一个类要想成为监听类,那么必须实现ActionListener接口class MyActionListener implements ActionListener{//重写ActionListener接口里面的actionPerformed(ActionEvent e)方法@Overridepublic void actionPerformed(ActionEvent e) {System.out.println("A Button has been Pressed");}}

点击窗口Press Me按钮,触发事件,点击窗口关闭按钮可以实现窗口的关闭。
【测试代码二】
多个按钮共用同一个监听事件
package com.kuang;import java.awt.*;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;public class ActionEventTest02 {public static void main(String[] args) {Frame frame = new Frame("ActionEventTest");Button btn1 = new Button("start");Button btn2 = new Button("stop");//创建监听对象MyMonitor monitor = new MyMonitor();//一个监听对象同时监听两个按钮的动作btn1.addActionListener(monitor);btn2.addActionListener(monitor);//可以显示设置btn2的执行单击命令后的返回信息,如果不显示定义,则会返回默认信息//可以多个按钮只写一个事件监听类btn2.setActionCommand("GameOver");frame.add(btn1,BorderLayout.NORTH);frame.add(btn2,BorderLayout.CENTER);frame.pack();frame.setVisible(true);}}class MyMonitor implements ActionListener{@Overridepublic void actionPerformed(ActionEvent e) {//使用返回的监听对象e调用getActionCommand()方法获取两个按钮执行单击命令后的返回信息,根据返回信息的不同区分当前操作的是哪一个按钮//btn1没有使用setActionCommand()方法设置,则btn1返回的信息就是按钮上显示的文本System.out.println("a button has been pressed,"+"the relative info is:\n"+ e.getActionCommand());}}

分别点击start按钮和stop按钮出发监听事件,点击关闭窗口按钮实现窗口的关闭。
六、TexField事件监听
- TexField对象可能发生Action(光标在文本框内敲回车)事件。与该事件对应的事件类是
java.awt.event.ActionEvent - 用来处理 ActionEvent 事件是实现了
java.awt.event.ActionListener接口的类的对象。ActionListener接口定义有方法:public void actionPerformed(ActionEvent e)
- 实现该接口的类要在该方法中添加处理该事件(Action)的语句。
- 实现 addActionListener(ActionListener I)方法为TexField对象注册一个ActionListener对象,当TexField对象发生Action事件时,会生成一个ActionEvent对象,该对象作为参数传递给ActionListener对象的actionPerformed方法在方法中可以获取该对象的信息,并做相应的处理。
package com.kuang;import java.awt.*;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;public class TextFieldTest{public static void main(String[] args) {new MyFrameTextField();}}class MyFrameTextField extends Frame{MyFrameTextField(){TextField textField = new TextField();add(textField);textField.addActionListener(new MyMonitor2());//setEchoChar()方法:设置文本框输入时显示的字符,这里设置为*,//这样输入任何内容就都以*显示出来,不过打印出来时依然可以看到输入的内容textField.setEchoChar('*');setVisible(true);pack();}}class MyMonitor2 implements ActionListener{//接口里面的所有方法都是public(公共的)//从API文档复制void actionPerformed(ActionEvent e)时,要在void前面加上public@Overridepublic void actionPerformed(ActionEvent e) {//事件的相关信息都封装在了对象e里面,通过对象e的相关方法可以获取事件的相关信息//getSource()方法:拿到事件源,获得一些资源,返回一个对象//注意:拿到这个事件源时,把它当作TextField的父类来对待//getSource()方法的定义是:“public Object getSource()”返回值是一个Object对象//所以要强制转换成TextField类型的对象//在一个类里面想访问另一个类的事件源对象可以通过getSource()方法TextField textField = (TextField) e.getSource();// textField.getText()是取得文本框里面的内容System.out.println(textField.getText());// 把文本框里面的内容清空textField.setText("");}}



【使用TextField类实现简单的计算器】
package com.kuang2;import java.awt.*;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;public class TestMath {public static void main(String[] args) {new Calculator();}}//主要是完成计算器元素的布局class Calculator extends Frame{Calculator(){//创建3个文本框,并指定其初始大小分别为10个字符和15个字符的大小//使用的是TextField类的另外一种构造方法:public TextField(int columns)TextField num1 = new TextField(10);TextField num2 = new TextField(10);TextField num3 = new TextField(15);//创建等号按钮Button btnEqual = new Button("=");//给等号按钮加上监听,让点击按钮后有响应事件发生btnEqual.addActionListener(new MyMonitor(num1, num2, num3));//“+”是一个静态文本,所以使用Label类创建一个静态文本对象Label lblPlus = new Label("+");//把Frame默认的BorderLayout布局改成FlowLayout布局setLayout(new FlowLayout());add(num1);add(lblPlus);add(num2);add(btnEqual);add(num3);pack();setVisible(true);}}class MyMonitor implements ActionListener{//为了使对按钮的监听能够对文本框也起作用,在自定义类MyMonitor里面定义三个TextField类型的对象 num1,num2,num3,//并且定义了MyMonitor类的一个构造方法,构造方法带有三个TextField类型的参数,用于接收从TFFrame类传递过来的三个TextField类型的参数//然后把接收到的三个TextField类型的参数赋值给在本类中声明的三个TextField类型的参数num1,num2,num3,再在actionPerformed()方法里面处理num1,num2,num3TextField num1, num2, num3;public MyMonitor(TextField num1, TextField num2, TextField num3) {this.num1 = num1;this.num2 = num2;this.num3 = num3;}//事件的相关信息都封装在了对象e里面,通过对象e的相关方法就可以获取事件的相关信息@Overridepublic void actionPerformed(ActionEvent e) {// num对象调用getText()方法取得显示的文本字符串int n1 = Integer.parseInt(num1.getText());int n2 = Integer.parseInt(num2.getText());//num3对象调用setText()方法设置自己的显示文本//字符串与任意类型的数据使用“+”连接时得到的一定是字符串,这里使用一个空字符串与int类型的数连接,就可以直接把(n1+n2)得到的int类型的数隐式地转换成字符串了,这是一种把别的基本数据类型转换成字符串的一个小技巧。//也可使用“String.valueOf((n1+n2))”把(n1+n2)的和转换成字符串num3.setText("" + (n1 + n2));//num3.setText(String.valueOf((n1+n2)));//计算结束后清空num1,num2文本框里面的内容num1.setText("");num2.setText("");}}


【JAVA里面的经典用法:在一个类里面持有另外一个类的引用】
完全改造成面向对象写法
package com.kuang2;import java.awt.*;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;public class TestMath1 {public static void main(String[] args) {new Calculator().launchFrame();}}//做好计算器的窗体界面class Calculator extends Frame {//把设计计算器窗体的代码封装成一个方法TextField num1, num2, num3;public void launchFrame() {num1 = new TextField(10);num2 = new TextField(10);num3 = new TextField(15);Label lblPlus = new Label("+");Button btnEqual = new Button("=");btnEqual.addActionListener(new MyMonitorbtnEqual(this));setLayout(new FlowLayout());add(num1);add(lblPlus);add(num2);add(btnEqual);add(num3);pack();setVisible(true);}}//通过取得Calculator2类的引用,然后使用这个引用去访问Calculator2类里面的成员变量//这种做法比上一种直接去访问Calculator2类里面的成员变量要好得多//因为现在不需要知道 Calculator2类里面有哪些成员变量了,现在要访问Calculator2类里面的成员变量,直接使用 Calculator2类对象的引用去访问即可//这个Calculator2类的对象好比是一个大管家, 而我告诉大管家,我要访问Calculator2类里面的那些成员变量,大管家的引用就会去帮我找,不再需要我自己去找了。//这种在一个类里面持有另一个类的引用的用法是一种非常典型的用法,使用获取到的引用就可以在一个类里面访问另一个类的所有成员了class MyMonitorbtnEqual implements ActionListener {Calculator2 calculator2 = null;public MyMonitorbtnEqual(Calculator2 calculator2) {this.calculator2 = calculator2;}@Overridepublic void actionPerformed(ActionEvent e) {int n1 = Integer.parseInt(calculator2.num1.getText());int n2 = Integer.parseInt(calculator2.num2.getText());calculator2.num3.setText("" + (n1 + n2));calculator2.num1.setText("");calculator2.num2.setText("");}}
结果:

七、内部类
- 好处:
- 可以方便的访问包装类的成员
- 可以更清楚的组织逻辑,防止不应该被其他类访问的类进行访问
- 何时使用:
- 该类不允许或不需要其它类进行访问时
【内部类的使用范例】
package com.kuang2;import java.awt.*;import java.awt.event.*;public class TestMath3 {public static void main(String args[]) {new MyMathFrame().launchFrame();}}class MyMathFrame extends Frame {TextField num1, num2, num3;public void launchFrame() {num1 = new TextField(10);num2 = new TextField(15);num3 = new TextField(15);Label lblPlus = new Label("+");Button btnEqual = new Button("=");btnEqual.addActionListener(new MyMonitor());setLayout(new FlowLayout());add(num1);add(lblPlus);add(num2);add(btnEqual);add(num3);pack();setVisible(true);}/** 这个MyMonitor类是内部类,它在MyFrame类里面定义MyFrame类称为MyMonitor类的包装类*//** 使用内部类的好处:* 第一个巨大的好处就是可以畅通无阻地访问外部类(即内部类的包装类)的所有成员变量和方法* 如:这里在MyFrame类(外部类)定义的三个成员变量num1,num2,num3,在MyMonitor(内部类)里面就可以直接访问* 这相当于在创建外部类对象时内部类对象默认就拥有了一个外部类对象的引用*/private class MyMonitor implements ActionListener {public void actionPerformed(ActionEvent e) {int n1 = Integer.parseInt(num1.getText());int n2 = Integer.parseInt(num2.getText());num3.setText("" + (n1 + n2));num1.setText("");num2.setText("");}}}
内部类带来的巨大好处是:
- 可以很方便地访问外部类定义的成员变量和方法
- 当某一个类不需要其他类访问的时候就把这个类声明为内部类。
八、Graphics类
每个Component都有一个paint(Graphics g)用于实现绘图目的,每次重画该Component时都自动调用paint方法。
Graphics类中提供了许多绘图方法,如:
【测试代码】
package com.kuang3;import java.awt.*;public class TestPaint {public static void main(String[] args) {new MyPaint().launchFrame();//在main()方法里面并没有显示调用paint(Graphics g)方法,可是当创建出Frame窗体后却可以看到Frame窗体上画出了圆和矩形,这是因为paint()方法是一个比较特殊的方法//在创建Frame窗体时会自动隐式调用,当我们把Frame窗体最小化再次打开时,会再次调用paint()方法重新把圆和矩形在Frame窗体上画出来,即每次需要重画Frame窗体的时候就会自动调用paint()方法}}class MyPaint extends Frame{public void launchFrame(){setBounds(200,200,600,500);setVisible(true);}public void paint(Graphics g){//paint(Graphics g)方法有一个Graphics类型的参数g//可以把这个g当作是一个画家,这个画家手里拿着一只画笔,我们通过设置画笔的颜色与形状来画出我们想要的各种各样的图像/*设置画笔的颜色*/g.setColor(Color.red);g.drawOval(100, 100, 100, 100);//画一个红色空心圆g.fillOval(100, 300, 100, 100);//画一个红色实心圆g.setColor(Color.green);g.fillRect(300,300,100,100);//画一个绿色实心圆//这下面的两行代码是为了写程序的良好编程习惯而写的//前面设置了画笔的颜色,现在就应该把画笔的初始颜色恢复过来//就相当于是画家用完画笔之后把画笔上的颜色清理掉一样Color c = g.getColor();g.setColor(c);}}

九、鼠标事件适配器
- 抽象类
java.awt.event.MouseAdapter实现了MouseListener接口,可以使用其子类作为MouseEvent的监听器,只要重写其相应的方法即可。 - 对于其他的监听器,也有对应的适配器。
- 适用适配器可以避免监听器定义没有必要的空方法。
【测试代码:实现鼠标画点】
package com.wang.gui.awt;import java.awt.*;import java.awt.event.MouseAdapter;import java.awt.event.MouseEvent;import java.util.ArrayList;import java.util.Iterator;//鼠标监听事件public class MouseListenerTest {public static void main(String[] args) {new MyFrame1("画图");}}//自己的类class MyFrame1 extends Frame {//画画需要画笔,需要监听鼠标当前的位置,需要集合来存储这个点ArrayList points;public MyFrame1(String title) {super(title);setBounds(200, 200, 400, 300);//存鼠标点击的点points = new ArrayList<>();setVisible(true);//鼠标监听器,针对这个窗口this.addMouseListener(new MyMouseListener());}@Overridepublic 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);}}//添加一个点到界面上public void addPoint(Point point) {points.add(point);}//适配器模式private class MyMouseListener extends MouseAdapter {//鼠标三种情况:按下、弹起、按住不放@Overridepublic void mousePressed(MouseEvent e) {MyFrame1 myFrame1 = (MyFrame1) e.getSource();//鼠标点击,就会在界面上出现一个点//这个点就是鼠标的点myFrame1.addPoint(new Point(e.getX(), e.getY()));//每次点击鼠标都需要重画一遍,执行一次刷新一遍myFrame1.repaint();}}}

分析:
十、窗口事件
- 窗口事件所对应的事件类为WindowEvent,所对应的事件监听接口为WindowListener。
- WindowListener定义的方法有:
public void windowOpened(WindowEvent e)public void windowClosing(WindowEvent e)public void windowClosed(WindowEvent e)public void windowIconified(WindowEvent e)public void windowDeiconified(WindowEvent e)public void windowActivated(WindowEvent e)public void windowDeactivated(WindowEvent e)
- 与WindowListener对应的适配器为WindowAdapter。 ```java package com.kuang3;
import java.awt.; import java.awt.event.;
public class TestWindowClose{ public static void main(String args[]){ new WindowFrame(“关闭WindowFrame”); } }
class WindowFrame extends Frame{ public WindowFrame(String s){ super(s); setBounds(200,200,400,300); setLayout(null); setBackground(new Color(204,204,255)); setVisible(true); this.addWindowListener(new WindowMonitor()); /监听本窗体的动作,把所有的动作信息封装成一个对象传递到监听类里面/
this.addWindowListener(/*在一个方法里面定义一个类,这个类称为局部类,也叫匿名的内部类,这里的{……代码……}里面的代码很像一个类的类体,只不过这个类没有名字,所以叫匿名类。在这里是把这个匿名类当成WindowAdapter类来使用,这样写的本质意义是相当于这个匿名类从WindowAdapter类继承,现在new了一个匿名类的对象出来,然后把这个对象当成WindowAdapter来使用,这个匿名类出了()就没有人认识了*/new WindowAdapter(){public void windowClosing(WindowEvent e){setVisible(false);System.exit(-1);}});
}
/这里也是将监听类定义为内部类/ class WindowMonitor extends WindowAdapter{ /WindowAdapter(Window适配器)类实现了WindowListener监听接口,重写了WindowListener接口里面的所有方法。 如果直接使用自定义WindowMonitor类去实现WindowListener接口,就得要重写WindowListener接口里面的所有方法,但现在只需要用到这些方法里面的其中一个方法,所以采用继承实现WindowListener监听接口的一个子类并重写这个子类里面需要用到的方法即可。 这种做法比直接实现WindowListener监听接口要重写很多个用不到的方法要简洁方便得多。 /
/*重写需要用到的windowClosing(WindowEvent e)方法*/public void windowClosing(WindowEvent e){setVisible(false);/*将窗体设置为不显示,即可实现窗体关闭*/System.exit(0);/*正常退出*/}
} }
<a name="63ea9634"></a>### 十一、键盘响应事件【键盘响应事件——KeyEvent】```javapackage com.kuang3;import java.awt.*;import java.awt.event.*;public class TestKeyEvent{public static void main(String args[]){new KeyFrame("键盘响应事件");}}class KeyFrame extends Frame{public KeyFrame(String s){super(s);setBounds(200,200,400,300);setLayout(null);setVisible(true);addKeyListener(new KeyMonitor());}/*把自定义的键盘监听类定义为内部类,这个监听类从键盘适配器KeyAdapter类继承。从KeyAdapter类继承是为了简洁方便,只需要重写用到的方法即可。这种做法比直接实现KeyListener接口要简单方便,如果直接实现KeyListener接口,就要把KeyListener接口里面的所有方法重写一遍。但真正用到的只有一个方法,这样重写其他的方法但又用不到难免会做无用功*/class KeyMonitor extends KeyAdapter{public void keyPressed(KeyEvent e){int keycode = e.getKeyCode();/*使用getKeyCode()方法获取按键的虚拟码*//*如果获取到的键的虚拟码等于up键的虚拟码,则表示当前按下的键是up键。KeyEvent.VK_UP表示取得up键的虚拟码键盘中的每一个键都对应一个虚拟码,这些虚拟码在KeyEvent类里被定义为静态常量,所以可以使用“类名.静态常量名”的形式访问得到这些静态常量*/if(keycode == KeyEvent.VK_UP){System.out.println("你按的是up键");}}}}/*键盘的处理事件是这样的:每一个键都对应一个虚拟码,当按下某一个键时,系统就会去找这个键对应的虚拟码,以此来确定当前按下的是哪个键*/
Swing
Swing是GUI(图形用户界面)开发工具包,想深入学习的可查阅有关资料或图书,比如《Java Swing图形界面开发与案例详解》——清华大学出版社。
早期的AWT(抽象窗口工具包)组件开发的图形用户界面,要依赖本地系统,当把AWT组件开发的应用程序移植到其他平台的系统上运行时,不能保证其外观风格,因此AWT是依赖于本地系统平台的。而使用Swing开发的Java应用程序,其界面是不受本地系统平台限制的,即Swing开发的Java应用程序移植到其他系统平台上时,其界面外观是不会改变的。
但要注意的是,虽然Swing提供的组件可以方便开发Java应用程序,但是Swing并不能取代AWT,在开发Swing程序时通常要借助与AWT的一些对象来共同完成应用程序的设计。
一、常用窗体
Swing窗体是Swing的一个组件,同时也是创建图形化用户界面的容器,可以将其它组件放置在窗体容器中。
1.1 JFrame框架窗体
JFrame窗体是一个容器,在Swing开发中经常要用到,它是Swing程序中各个组件的载体。<br /> 语法格式如下:
JFrame jf = new JFrame(title);
当然,在开发中更常用的方式是通过继承`java.swing.JFrame`类创建一个窗体,可通过this关键字调用其方法。<br /> 在JFrame对象创建完成后,需要调用`getContentPane()方法`将窗体转换为容器,然后在容器中添加组件或设置布局管理器,通常这个容器用来包含和显示组件。如果需要将组件添加至容器,可以使用来自Container类的`add()方法`进行设置。
【下面举一个JFrame窗体的例子】
package com.kuang4;import javax.swing.JFrame;import javax.swing.WindowConstants;public class JFrameDemo {public void CreateJFrame() {// 实例化一个JFrame对象JFrame jf = new JFrame("这是一个JFrame窗体");// 设置窗体可视jf.setVisible(true);// 设置窗体大小jf.setSize(100,100,500, 600);// 设置窗体关闭方式jf.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);}public static void main(String[] args) {new JFrameDemo().CreateJFrame(); // 调用CreateJFrame()方法}}
结果:

这就是一个500*600的窗体,用的是setSize()方法;
标题为“这是一个JFrame窗体”,在实例化对象时就可以定义;
窗体关闭方式见窗体右上角为“EXIT_ON_CLOSE”;
窗体可视setVisible()方法中的参数为“false”或不写setVisible()方法时,此窗体不可见。
常用的窗体关闭方式有四种:
DO_NOTHING_ON_CLOSE:什么也不做就将窗体关闭;DISPOSE_ON_CLOSE”:任何注册监听程序对象后会自动隐藏并释放窗体;HIDE_ON_CLOSE”: 隐藏窗口的默认窗口关闭;EXIT_ON_CLOSE:退出应用程序默认窗口关闭。
【下面再举一个用继承JFrame的方式编写的代码,并加入Container容器及JLabel标签(后面会提到),来看一下具体的流程。】
package com.kuang4;import java.awt.Color;import java.awt.Container;import javax.swing.JFrame;import javax.swing.JLabel;import javax.swing.SwingConstants;import javax.swing.WindowConstants;public class JFrameDemo2 extends JFrame{public void init() {// 可视化this.setVisible(true);// 大小this.setSize(500, 350);// 标题this.setTitle("王车车");// 关闭方式this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);// 创建一个JLabel标签JLabel jl = new JLabel("欢迎来到我的世界!");// 使标签文字居中jl.setHorizontalAlignment(SwingConstants.CENTER);// 获取一个容器Container container = this.getContentPane();// 将标签添加至容器container.add(jl);// 设置容器背景颜色container.setBackground(Color.YELLOW);}public static void main(String[] args) {new JFrameDemo2().init();}}
运行结果:
这里继承了JFrame类,所以方法中实现时用this关键字即可(或直接实现,不加this)。
1.2 JDialog弹窗
JDialog窗体是Swing组件中的对话框,继承了AWT组件中的`java.awt.Dialog`类。功能是从一个窗体中弹出另一个窗体。
【下面来看一个实例】
package com.kuang4;import java.awt.Container;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;import javax.swing.JButton;import javax.swing.JDialog;import javax.swing.JFrame;import javax.swing.JLabel;import javax.swing.WindowConstants;// 继承JDialog类public class JDialogDemo extends JDialog {// 实例化一个JDialog类对象,指定其父窗体、窗口标题和类型public JDialogDemo() {super(new MyJFrame(), "这是一个JDialog窗体", true);Container container = this.getContentPane();container.add(new JLabel("王车车爱学习!"));this.setSize(500, 350);}public static void main(String[] args) {new JDialogDemo();}}// 下面这部分内容包含监听器,可自行查阅资料class MyJFrame extends JFrame {public MyJFrame() {this.setVisible(true);this.setSize(700, 500);this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);Container container = this.getContentPane();container.setLayout(null);JButton jb = new JButton("点击弹出对话框"); // 创建按钮jb.setBounds(30, 30, 200, 50); // 按钮位置及大小jb.addActionListener(new ActionListener() { // 监听器,用于监听点击事件@Overridepublic void actionPerformed(ActionEvent e) {new JDialogDemo().setVisible(true);}});container.add(jb);}}
当点击按钮时,触发点击事件,创建一个JDialog的实例化对象,弹出一个窗口。
这里出现了许多之前学过的知识,比如super关键字,相当于使用了JDialog(Frame f, String title,boolean model)形式的构造方法;监听器的实现就是一个匿名内部类,之前也提到过。
二、标签组件
在Swing中显示文本或提示信息的方法是使用标签,它支持文本字符串和图标。上面我们提到的JLabel就是这里的内容。
2.1 标签
标签由JLabel类定义,可以显示一行只读文本、一个图像或带图像的文本。
JLabel类提供了许多构造方法,可查看API选择需要的使用,如显示只有文本的标签、只有图标的标签或包含文本与图标的标签等。因为上面已经出现过了,这里就不再举例了。常用语法格式如下,创建的是一个不带图标和文本的JLabel对象:
JLabel jl = new JLabel();

2.2 图标
Swing中的图标可以放置在按钮、标签等组件上,用于描述组件的用途。图标可以用Java支持的图片文件类型进行创建,也可以使用java.awt.Graphics类提供的功能方法来创建。
在Swing中通过Icon接口来创建图标,可以在创建时给定图标的大小、颜色等特性。
注意,Icon是接口,在使用Icon接口的时候,必须实现Icon接口的三个方法:
public int getIconHeight()public int getIconWidth()public void paintIcon(Component arg0, Graphics arg1, int arg2, int arg3)//前两个方法用于获取图片的长宽//paintIcon()方法用于实现在指定坐标位置画图。
下面看一个用Icon接口创建图标的实例:
package com.kuang4;import java.awt.Component;import java.awt.Container;import java.awt.Graphics;import javax.swing.Icon;import javax.swing.JFrame;import javax.swing.JLabel;import javax.swing.SwingConstants;import javax.swing.WindowConstants;public class IconDemo extends JFrame implements Icon {private int width; // 声明图标的宽private int height; // 声明图标的长public IconDemo() {} // 定义无参构造方法public IconDemo(int width, int height) { // 定义有参构造方法this.width = width;this.height = height;}@Overridepublic int getIconHeight() { // 实现getIconHeight()方法return this.height;}@Overridepublic int getIconWidth() { // 实现getIconWidth()方法return this.width;}@Overridepublic void paintIcon(Component arg0, Graphics arg1, int arg2, int arg3){ // 实现paintIcon()方法arg1.fillOval(arg2, arg3, width, height); // 绘制一个圆形}public void init() { // 定义一个方法用于实现界面IconDemo iconDemo = new IconDemo(15, 15); // 定义图标的长和宽JLabel jb = new JLabel("icontest", iconDemo, SwingConstants.CENTER);// 设置标签上的文字在标签正中间Container container = getContentPane();container.add(jb);this.setVisible(true);this.setSize(500, 350);this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);}public static void main(String[] args) {new IconDemo().init();}}
运行结果如下:
这样如果需要在窗体中使用图标,就可以用如下代码创建图标:
IconDemo iconDemo = new IconDemo(15, 15);
2.3 图片图标
Swing中的图标除了可以绘制之外,还可以使用某个特定的图片创建。利用javax.swing.ImageIcon类根据现有图片创建图标。
下面看一个实例,先在包下放一个图片(注意放置位置,不同位置路径不同),如下:
【下面是实现的代码】
package com.kuang4;import java.awt.Container;import java.net.URL;import javax.swing.Icon;import javax.swing.ImageIcon;import javax.swing.JFrame;import javax.swing.JLabel;import javax.swing.SwingConstants;import javax.swing.WindowConstants;public class ImageIconDemo extends JFrame {public ImageIconDemo() {JLabel jl = new JLabel("这是一个JFrame窗体,旁边是一个图片");URL url = ImageIconDemo.class.getResource("tx-old.jpg"); //获得图片所在URLIcon icon = new ImageIcon(url); // 实例化Icon对象jl.setIcon(icon); // 为标签设置图片jl.setHorizontalAlignment(SwingConstants.CENTER);jl.setOpaque(true); // 设置标签为不透明状态Container container = getContentPane();container.add(jl);setVisible(true);setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);setSize(500, 350);}public static void main(String[] args) {new ImageIconDemo();}}

对于图片标签,我们经常将图片放置在标签上,用JLabel中的setIcon()方法即可,当然也可以在初始化JLabel对象时为标签指定图标,这需要获取一个Icon实例。
而getResource()方法可以获得资源文件的URL路径,这里的路径是相对于前面的那个类的,所以可将该图片与该类放在同一个文件夹下;如果不在同一个文件夹下,需通过其它方法获取路径。
三、布局管理器
Swing中,每个组件在容器中都有一个具体的位置和大小,在容器中摆放各自组件时很难判断其具体位置和大小,这里我们就要引入布局管理器了,它提供了基本的布局功能,可以有效的处理整个窗体的布局。常用的布局管理器包括流布局管理器、边界布局管理器、网格布局管理器等。
3.1 绝对布局
绝对布局在上一篇的例子中已经出现过了,是硬性指定组件在容器中的位置和大小,可以使用绝对坐标的方式来指定组件的位置。步骤如下:
- 使用Container.setLayout(null)方法取消布局管理器
- 使用Container.setBounds()方法设置每个组件的位置和大小
【举一个简单的例子】
Container container = getContentPane(); // 创建容器JButton jb = new JButton("按钮"); // 创建按钮jb.setBounds(10, 30, 100, 30); // 设置按钮位置和大小container.add(jb); // 将按钮添加到容器中
setBounds()方法中,前两个参数是位置的xy坐标,后两个参数是按钮的长和宽。
3.2 流布局管理器
流布局管理器是布局管理器中最基本的布局管理器,使用FlowLayout类,像“流”一样从左到右摆放组件,直到占据了这一行的所有空间,再向下移动一行。组件在每一行的位置默认居中排列,要更改位置可自行设置。
在FlowLayout的有参构造方法中,alignment设置为0时,每一行的组件将被指定左对齐排列;当alignment被设置为2时,每一行的组件将被指定右对齐排列;而为1时是默认的居中排列。
下面举个例子,创建10个按钮并用流布局管理器排列。
package com.kuang5;import java.awt.Container;import java.awt.FlowLayout;import javax.swing.JButton;import javax.swing.JFrame;import javax.swing.WindowConstants;public class FlowLayoutDemo extends JFrame {public FlowLayoutDemo() {Container container = this.getContentPane();// 设置流布局管理器,2是右对齐,后两个参数分别为组件间的水平间隔和垂直间隔this.setLayout(new FlowLayout(2, 10, 10));// 循环添加按钮for(int i=0; i<10; i++) {container.add(new JButton("按钮" + i));}this.setSize(300, 200);this.setVisible(true);this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);}public static void main(String[] args) {new FlowLayoutDemo();}}
第一个参数为2是右对齐,每个按钮间的水平、垂直间隔都为10。后两个图分别为参数为1居中排列和参数为0左对齐。运行结果如下:

3.3 边界布局管理器
在不指定窗体布局时,Swing组件默认的布局管理器是边界布局管理器,使用的是BorderLayout类。在上篇例子中,一个JLabel标签占据了整个空间,实质上是默认使用了边界布局管理器。边界布局管理器还可以容器分为东、南、西、北、中五个区域,可以将组件加入这五个区域中。
【演示】
package com.kuang5;import java.awt.BorderLayout;import java.awt.Container;import javax.swing.JButton;import javax.swing.JFrame;import javax.swing.WindowConstants;public class BorderLayoutDemo extends JFrame {private String[] border = {BorderLayout.CENTER, BorderLayout.NORTH,BorderLayout.SOUTH, BorderLayout.WEST, BorderLayout.EAST}; // 此数组用于存放组件摆放位置private String[] button = {"中", "北", "南", "西", "东"}; // 此数组用于存放按钮名称public BorderLayoutDemo() {Container container = this.getContentPane();this.setLayout(new BorderLayout()); // 设置容器为边界布局管理器// 循环添加按钮for(int i=0; i<button.length ; i++) {container.add(border[i], new JButton(button[i])); // 左参数为设置布局,右参数为创建按钮}this.setVisible(true);this.setSize(300, 200);this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);}public static void main(String[] args) {new BorderLayoutDemo();}}

3.4 网络布局管理器
网格布局管理器将容器划分为网格,组件按行按列排列,使用GridLayout类。在此布局管理器中,每个组件的大小都相同,且会填满整个网格,改变窗体大小,组件也会随之改变。
【演示】
package com.kuang5;import java.awt.Container;import java.awt.GridLayout;import javax.swing.JButton;import javax.swing.JFrame;import javax.swing.WindowConstants;class GirdLayoutDemo extends JFrame {public GirdLayoutDemo() {Container container = this.getContentPane();this.setLayout(new GridLayout(7, 3, 5, 5)); // 前两个参数为7行3列,后两个参数为网格间的间距for(int i=0; i<20; i++) {container.add(new JButton("按钮" + i));}this.setVisible(true);this.setSize(300, 300);this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);}public static void main(String[] args) {new GirdLayoutDemo();}}

四、面板
面板也是一个容器,可作为容器容纳其他组件,但也必须被添加到其他容器中。Swing中常用面板有JPanel面板和JScrollPane面板。
4.1 JPanel
JPanel面板可以聚集一些组件来布局。继承自java.awt.Container类。
【演示】
package com.kuang5;import java.awt.Container;import java.awt.GridLayout;import javax.swing.JButton;import javax.swing.JFrame;import javax.swing.JPanel;import javax.swing.WindowConstants;public class JPanelDemo extends JFrame {public JPanelDemo() {Container container = this.getContentPane();container.setLayout(new GridLayout(2, 1, 10, 10)); // 整个容器为2行1列,后面的参数为间距JPanel p1 = new JPanel(new GridLayout(1, 3)); // 初始化一个面板,设置1行3列的网格布局JPanel p2 = new JPanel(new GridLayout(1, 2)); // 初始化一个面板,设置1行2列的网格布局JPanel p3 = new JPanel(new GridLayout(2, 1)); // 初始化一个面板,设置2行1列的网格布局JPanel p4 = new JPanel(new GridLayout(3, 2)); // 初始化一个面板,设置3行2列的网格布局p1.add(new JButton("1")); // 在JPanel面板中添加按钮p1.add(new JButton("1")); // 在JPanel面板中添加按钮p1.add(new JButton("1")); // 在JPanel面板中添加按钮p2.add(new JButton("2")); // 在JPanel面板中添加按钮p2.add(new JButton("2")); // 在JPanel面板中添加按钮p3.add(new JButton("3")); // 在JPanel面板中添加按钮p3.add(new JButton("3")); // 在JPanel面板中添加按钮p4.add(new JButton("4")); // 在JPanel面板中添加按钮p4.add(new JButton("4")); // 在JPanel面板中添加按钮p4.add(new JButton("4")); // 在JPanel面板中添加按钮p4.add(new JButton("4")); // 在JPanel面板中添加按钮p4.add(new JButton("4")); // 在JPanel面板中添加按钮p4.add(new JButton("4")); // 在JPanel面板中添加按钮container.add(p1); // 在容器中添加面板container.add(p2); // 在容器中添加面板container.add(p3); // 在容器中添加面板container.add(p4); // 在容器中添加面板this.setVisible(true);this.setSize(500, 350);this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);}public static void main(String[] args) {new JPanelDemo();}}
运行结果如下,可自行对比代码与结果理解JPanel。其中,容器的GridLayout布局设置了横纵都为10的间距,JPanel的GridLayout布局没有设置网格间距。
4.2 JScrollPanel
若遇到一个较小的容器窗体中显示一个较大部分内容的情况,可用JScrollPane面板。这是一个带滚动条的面板,就像平时浏览网页,经常遇到的滚动条一样。
如果需要在JScrollPane面板中放置多个组件,需将这多个组件放置在JPanel面板上,然后将JPanel面板作为一个整体组件添加在JScrollPane面板上。
【演示】
package com.kuang5;import java.awt.Container;import javax.swing.JFrame;import javax.swing.JScrollPane;import javax.swing.JTextArea;import javax.swing.WindowConstants;public class JScrollPaneDemo extends JFrame {public JScrollPaneDemo() {Container container = this.getContentPane();// 创建文本区域组件JTextArea textArea = new JTextArea(20, 50);textArea.setText("欢迎学习Java");//ScrollPanel面板JScrollPane sp = new JScrollPane(textArea);container.add(sp);this.setVisible(true);this.setSize(300, 150);this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);}public static void main(String[] args) {new JScrollPaneDemo();}}
结果:
其中JTextArea是创建一个文本区域组件,大小为20*50,setText()方法是给该文本区域填值。
这里在new一个JScrollPane时,就将文本区域组件添加到其上。
五、按钮组件
5.1 提交按钮组件(JButton)
JButton在之前的例子中已经出现多次,是较为常用的组件,用于触发特定动作。可以在按钮上显示文本标签,还可以显示图标,如下:
package com.kuang5;import javax.swing.*;import java.awt.*;public class JButtonDemo extends JFrame {public JButtonDemo() {Container container = this.getContentPane();//将一个图片变成一个图标URL url = JButtonDemo.class.getResource("wj.jpg");Icon icon = new ImageIcon(url);//把这个图标放在按钮上JButton button = new JButton();button.setIcon(icon);button.setToolTipText("图片按钮");//把按钮就在容器上container.add(button);this.setVisible(true);this.setSize(500,300);this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);}public static void main(String[] args) {new JButtonDemo();}}
5.2 单选按钮组件(JRadioButton)
默认情况下,单选按钮显示一个圆形图标,通常在其旁放置一些说明性文字。当用户选中某个单选按钮后,按钮组中其它按钮将被自动取消,这时就需要按钮组(ButtonGroup)来将同组按钮放在一起,该按钮组中的按钮只能选择一个,而不在此按钮中的按钮不受影响。语法格式如下:
package com.kuang5;import javax.swing.*;import java.awt.*;public class JRadioButtonDemo extends JFrame {public JRadioButtonDemo() {Container container = this.getContentPane();//将一个图片变成图标URL resource = JRadioButtonDemo.class.getResource("wj.jpg");Icon icon = new ImageIcon(resource);//单选框JRadioButton radioButton1 = new JRadioButton("JRadioButton1");JRadioButton radioButton2 = new JRadioButton("JRadioButton2");JRadioButton radioButton3 = new JRadioButton("JRadioButton3");//由于单选框只能选择一个,所以进行分组,组内只能选一个ButtonGroup group = new ButtonGroup();group.add(radioButton1);group.add(radioButton2);group.add(radioButton3);container.add(radioButton1,BorderLayout.CENTER);container.add(radioButton2,BorderLayout.NORTH);container.add(radioButton3,BorderLayout.SOUTH);this.setVisible(true);this.setBounds(100,100,300,350);this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);}public static void main(String[] args) {new JRadioButtonDemo();}}

5.3 复选框组件(JCheckBox)
复选框是一个方块图标,外加一段描述性文字,与单选按钮的区别就是可以多选。每一个复选框都提供“选中”与“不选中”两种状态。语法格式如下:
package com.kuang5;import javax.swing.*;import java.awt.*;public class JCheckBoxDemo extends JFrame {public JCheckBoxDemo() {Container container = this.getContentPane();//将一个图片变成图标URL resource = JRadioButtonDemo.class.getResource("wj.jpg");Icon icon = new ImageIcon(resource);//多选框JCheckBox checkBox01 = new JCheckBox("checkBox01");JCheckBox checkBox02 = new JCheckBox("checkBox02");container.add(checkBox01,BorderLayout.NORTH);container.add(checkBox02,BorderLayout.SOUTH);this.setVisible(true);this.setBounds(100,100,300,350);this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);}public static void main(String[] args) {new JCheckBoxDemo();}}

六、列表组件
6.1 下拉列表(JComboBox)
下拉列表框使用JComboBox类对象来表示,如下方代码:
package com.kuang5;import javax.swing.*;import java.awt.*;public class JComboBoxDemo extends JFrame {public JComboBoxDemo() {Container container = this.getContentPane();JComboBox status = new JComboBox();status.addItem(null);status.addItem("正在热映");status.addItem("已下架");status.addItem("即将上映");container.add(status);this.setVisible(true);this.setSize(500,350);this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);}public static void main(String[] args) {new JComboBoxDemo();}}
显示的样式如下:
6.2 列表框(JList)
列表框只是在窗体上占据固定的大小,如果要使列表框具有滚动效果,可以将列表框放入滚动面板中。使用数组初始化列表框的参数如下。
package com.kuang5;import javax.swing.*;import java.awt.*;public class JListDemo extends JFrame {public JListDemo() {Container container = this.getContentPane();//生成列表的内容//使用数组初始化列表框的参数String[] contents = {"1", "2", "3"};//列表中需要放入内容JList jlist = new JList(contents);container.add(jlist);this.setVisible(true);this.setSize(500, 350);this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);}public static void main(String[] args) {new JListDemo();}}

将Vector类型的数据作为初始化JList的参数如下。
package com.kuang5;import javax.swing.*;import java.awt.*;import java.util.Vector;public class JListDemo extends JFrame {public JListDemo() {Container container = this.getContentPane();//生成列表的内容//使用数组初始化列表框的参数//String[] contents = {"1", "2", "3"};//将Vector类型的数据作为初始化JList的参数Vector contents = new Vector();//列表中需要放入内容JList jlist = new JList(contents);contents.add("zhangsan");contents.add("lisi");contents.add("wangwu");container.add(jlist);this.setVisible(true);this.setSize(500, 350);this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);}public static void main(String[] args) {new JListDemo();}}

应用场景:
- 下拉框:选择地区或者一些单个选项。
- 列表:用来展示信息,一般是动态扩容的。
七、文本组件
7.1 文本框(JTexField)
文本框用来显示或编辑一个单行文本,语法格式如下:
JTextField jtext = new JTextField("aaa"); // 创建一个文本框,值为aaaJTextField jtext2 = new JTextField("aaa", 20); // 创建一个长度为20的文本框,值为aaajtext.setText(""); // 将文本框置空
其余构造方法可参考API或源码。
package com.wang.gui.swing;import javax.swing.*;import java.awt.*;public class TextDemo extends JFrame {public TextDemo() {Container container = this.getContentPane();TextField textField = new TextField("hello");//文本框默认显示文字,columns为最多可以放的字符数TextField textField2 = new TextField("world",20);container.add(textField,BorderLayout.NORTH);container.add(textField2,BorderLayout.SOUTH);this.setVisible(true);this.setSize(500, 350);this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);}public static void main(String[] args) {new TextDemo();}}

7.2 密码框(JPasswordField)
密码框与文本框的定义与用法类似,但会使用户输入的字符串以某种符号进行加密。如下方代码:
JPasswordField jpassword = new JPasswordField();jpassword.setEchoChar('#'); // 设置回显符号
代码演示
package com.wang.gui.swing;import javax.swing.*;import java.awt.*;public class JPasswordFieldDemo extends JFrame {public JPasswordFieldDemo() {Container container = this.getContentPane();JPasswordField passwordField = new JPasswordField();passwordField.setEchoChar('*');container.add(passwordField);this.setVisible(true);this.setSize(500, 350);this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);}public static void main(String[] args) {new JPasswordFieldDemo();}}

7.3 文本域(JTexField)
文本域组件在上面的代码中已经出现了,如下方代码所示:
JTextArea textarea = new JTextArea(20, 50); // 创建文本区域组件textarea.setText("欢迎来到西部开源学Java");
代码演示
package com.wang.gui.swing;import javax.swing.*;import java.awt.*;/*** @Author wangjin* @Date 2022/04/23 15:36* @Description*/public class JScrollPanelDemo extends JFrame {public JScrollPanelDemo() {Container container = this.getContentPane();//文本域JTextArea textArea = new JTextArea(20, 50);textArea.setText("欢迎学习java");//ScrollPanel面板JScrollPane scrollPane = new JScrollPane(textArea);container.add(scrollPane);this.setVisible(true);this.setBounds(100,100,300,350);this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);}public static void main(String[] args) {new JScrollPanelDemo();}}

对GUI编程就讲到这里,授人以鱼不如授人以渔,经过这一小段的学习已经能掌握看方法和源码学习的能力了,之后有一些小游戏专题来巩固JavaSE阶段的学习。
小游戏:2048
思路:
使用了4x4的GridLayout作为布局,然后使用16个JLabel作为方块ui。数据上则是使用一个长度为16的int数组储存方块的数值,通过监听上下左右的按键进行相应的数据处理,最后通过刷新函数将数据显示出来并设置颜色。
胜负判定的实现,胜的判定很简单:就是玩家凑出了至少一个2048的方块即为胜利;而失败的判定思路略复杂,主要是通过模拟用户分别按下上、下、左、右键后,判断格子里是否还有空位,如分别向四个方向移动后都无法产生空位,则判负。
【Game类】
import javax.swing.*;import java.awt.*;import java.awt.event.KeyEvent;import java.awt.event.KeyListener;import java.util.ArrayList;import java.util.Arrays;import java.util.HashMap;import java.util.List;public class Game {//用于储存颜色的实体类private static class Color {public Color(int fc, int bgc) {fontColor = fc;//字体颜色bgColor = bgc;//背景颜色}public int fontColor;//字体颜色public int bgColor;//背景颜色}JFrame mainFrame;//主窗口对象JLabel[] jLabels;//方块,用jlabel代替int[] datas = new int[]{0, 0, 0, 0,0, 0, 0, 0,0, 0, 0, 0,0, 0, 0, 0};//每个方块上的数值int[] temp = new int[4];//方块移动算法中抽离的的临时数组int[] temp2 = new int[16];//用于检测方块是否有合并List emptyBlocks = new ArrayList<Integer>(16);//在生成新方块时用到的临时list,用以存放空方块//存放颜色的mapstatic HashMap<Integer, Color> colorMap = new HashMap<Integer, Color>(){{put(0, new Color(0x776e65, 0xCDC1B4));put(2, new Color(0x776e65, 0xeee4da));put(4, new Color(0x776e65, 0xede0c8));put(8, new Color(0xf9f6f2, 0xf2b179));put(16, new Color(0xf9f6f2, 0xf59563));put(32, new Color(0xf9f6f2, 0xf67c5f));put(64, new Color(0xf9f6f2, 0xf65e3b));put(128, new Color(0xf9f6f2, 0xedcf72));put(256, new Color(0xf9f6f2, 0xedcc61));put(512, new Color(0xf9f6f2, 0xe4c02a));put(1024, new Color(0xf9f6f2, 0xe2ba13));put(2048, new Color(0xf9f6f2, 0xecc400));}};public Game() {initGameFrame();initGame();refresh();}//开局时生成两个2的方块和一个4的方块private void initGame() {for (int i = 0; i < 2; i++) {generateBlock(datas, 2);}generateBlock(datas, 4);}//随机生成4或者2的方块private void randomGenerate(int arr[]) {int ran = (int) (Math.random() * 10);if (ran > 5) {generateBlock(arr, 4);} else {generateBlock(arr, 2);}}//随机生成新的方块,参数:要生成的方块数值private void generateBlock(int arr[], int num) {emptyBlocks.clear();for (int i = 0; i < 16; i++) {if (arr[i] == 0) {emptyBlocks.add(i);}}int len = emptyBlocks.size();if (len == 0) {return;}int pos = (int) (Math.random() * 100) % len;arr[(int) emptyBlocks.get(pos)] = num;refresh();}//胜负判定并做终局处理private void judge(int arr[]) {if (isWin(arr)) {JOptionPane.showMessageDialog(null, "恭喜,你已经成功凑出2048的方块", "你赢了", JOptionPane.PLAIN_MESSAGE);System.exit(0);}if (isEnd(arr)) {int max = getMax(datas);JOptionPane.showMessageDialog(null, "抱歉,你没有凑出2048的方块,你的最大方块是:" + max, "游戏结束", JOptionPane.PLAIN_MESSAGE);System.exit(0);}}//判断玩家是否胜利,只要有一个方块大于等于2048即为胜利private boolean isWin(int arr[]) {for (int i : arr) {if (i >= 2048) {return true;}}return false;}//此函数用于判断游戏是否结束,如上下左右移后均无法产生空块,即代表方块已满,则返回真,表示游戏结束private boolean isEnd(int arr[]) {int[] tmp = new int[16];int isend = 0;System.arraycopy(arr, 0, tmp, 0, 16);left(tmp);if (isNoBlank(tmp)) {isend++;}System.arraycopy(arr, 0, tmp, 0, 16);right(tmp);if (isNoBlank(tmp)) {isend++;}System.arraycopy(arr, 0, tmp, 0, 16);up(tmp);if (isNoBlank(tmp)) {isend++;}System.arraycopy(arr, 0, tmp, 0, 16);down(tmp);if (isNoBlank(tmp)) {isend++;}if (isend == 4) {return true;} else {return false;}}//判断是否无空方块private boolean isNoBlank(int arr[]) {for (int i : arr) {if (i == 0) {return false;}}return true;}//获取最大的方块数值private int getMax(int arr[]) {int max = arr[0];for (int i : arr) {if (i >= max) {max = i;}}return max;}//刷新每个方块显示的数据private void refresh() {JLabel j;for (int i = 0; i < 16; i++) {int arr = datas[i];j = jLabels[i];if (arr == 0) {j.setText("");} else if (arr >= 1024) {j.setFont(new Font("Dialog", 1, 42));j.setText(String.valueOf(datas[i]));} else {j.setFont(new Font("Dialog", 1, 50));j.setText(String.valueOf(arr));}Color currColor = colorMap.get(arr);j.setBackground(new java.awt.Color(currColor.bgColor));j.setForeground(new java.awt.Color(currColor.fontColor));}}//初始化游戏窗口,做一些繁杂的操作private void initGameFrame() {//创建JFrame以及做一些设置mainFrame = new JFrame("2048 Game");mainFrame.setSize(500, 500);mainFrame.setResizable(false);//固定窗口尺寸mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);mainFrame.setLocationRelativeTo(null);mainFrame.setLayout(new GridLayout(4, 4));mainFrame.getContentPane().setBackground(newjava.awt.Color(0xCDC1B4));//添加按键监听mainFrame.addKeyListener(new KeyListener() {@Overridepublic void keyTyped(KeyEvent keyEvent) {}@Overridepublic void keyPressed(KeyEvent keyEvent) {System.arraycopy(datas, 0, temp2, 0, 16);//根据按键的不同调用不同的处理函数switch (keyEvent.getKeyCode()) {case KeyEvent.VK_UP:up(datas);break;case KeyEvent.VK_DOWN:down(datas);break;case KeyEvent.VK_LEFT:left(datas);break;case KeyEvent.VK_RIGHT:right(datas);break;}//判断移动后是否有方块合并,若有,生成新方块,若无,不产生新方块if (!Arrays.equals(datas, temp2)) {randomGenerate(datas);}refresh();judge(datas);}@Overridepublic void keyReleased(KeyEvent keyEvent) {}});//使用系统默认的ui风格try {UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());} catch (Exception e) {JOptionPane.showMessageDialog(null, e.getMessage());}//使用16个JLabel来显示16个方块jLabels = new JLabel[16];JLabel j; //引用复用,避免for里创建过多引用for (int i = 0; i < 16; i++) {jLabels[i] = new JLabel("0", JLabel.CENTER);j = jLabels[i];j.setOpaque(true);// 设置边界,参数:上,左,下,右,边界颜色j.setBorder(BorderFactory.createMatteBorder(6, 6, 6, 6, new java.awt.Color(0xBBADA0)));//j.setForeground(new java.awt.Color(0x776E65));j.setFont(new Font("Dialog", 1, 52));mainFrame.add(j);}mainFrame.setVisible(true);}private void left(int arr[]) {moveLeft(arr);combineLeft(arr);moveLeft(arr);//合并完后会产生空位,所以要再次左移}//向左合并方块private void combineLeft(int arr[]) {for (int l = 0; l < 4; l++) {//0 1 2for (int i = 0; i < 3; i++) {if ((arr[l * 4 + i] != 0 && arr[l * 4 + i + 1] != 0) && arr[l * 4 + i] == arr[l * 4 + i + 1]) {arr[l * 4 + i] *= 2;arr[l * 4 + i + 1] = 0;}}}}//方块左移,针对每一行利用临时数组实现左移private void moveLeft(int arr[]) {for (int l = 0; l < 4; l++) {int z = 0, fz = 0;//z(零);fz(非零)for (int i = 0; i < 4; i++) {if (arr[l * 4 + i] == 0) {z++;} else {temp[fz] = arr[l * 4 + i];fz++;}}for (int i = fz; i < 4; i++) {temp[i] = 0;}for (int j = 0; j < 4; j++) {arr[l * 4 + j] = temp[j];}}}private void right(int arr[]) {moveRight(arr);combineRight(arr);moveRight(arr);}private void combineRight(int arr[]) {for (int l = 0; l < 4; l++) {//3 2 1for (int i = 3; i > 0; i--) {if ((arr[l * 4 + i] != 0 && arr[l * 4 + i - 1] != 0) && arr[l * 4 + i] == arr[l * 4 + i - 1]) {arr[l * 4 + i] *= 2;arr[l * 4 + i - 1] = 0;}}}}private void moveRight(int arr[]) {for (int l = 0; l < 4; l++) {int z = 3, fz = 3;//z(零);fz(非零)for (int i = 3; i >= 0; i--) {if (arr[l * 4 + i] == 0) {z--;} else {temp[fz] = arr[l * 4 + i];fz--;}}for (int i = fz; i >= 0; i--) {temp[i] = 0;}for (int j = 3; j >= 0; j--) {arr[l * 4 + j] = temp[j];}}}private void up(int arr[]) {moveUp(arr);combineUp(arr);moveUp(arr);}private void combineUp(int arr[]) {for (int r = 0; r < 4; r++) {for (int i = 0; i < 3; i++) {if ((arr[r + 4 * i] != 0 && arr[r + 4 * (i + 1)] != 0) && arr[r + 4 * i] == arr[r + 4 * (i + 1)]) {arr[r + 4 * i] *= 2;arr[r + 4 * (i + 1)] = 0;}}}}private void moveUp(int arr[]) {for (int r = 0; r < 4; r++) {int z = 0, fz = 0;//z(零);fz(非零)for (int i = 0; i < 4; i++) {if (arr[r + 4 * i] == 0) {z++;} else {temp[fz] = arr[r + 4 * i];fz++;}}for (int i = fz; i < 4; i++) {temp[i] = 0;}for (int j = 0; j < 4; j++) {arr[r + 4 * j] = temp[j];}}}private void down(int arr[]) {moveDown(arr);combineDown(arr);moveDown(arr);}private void combineDown(int arr[]) {for (int r = 0; r < 4; r++) {for (int i = 3; i > 0; i--) {if ((arr[r + 4 * i] != 0 && arr[r + 4 * (i - 1)] != 0) && arr[r + 4 * i] == arr[r + 4 * (i - 1)]) {arr[r + 4 * i] *= 2;arr[r + 4 * (i - 1)] = 0;}}}}private void moveDown(int arr[]) {for (int r = 0; r < 4; r++) {int z = 3, fz = 3;//z(零);fz(非零)for (int i = 3; i >= 0; i--) {if (arr[r + 4 * i] == 0) {z--;} else {temp[fz] = arr[r + 4 * i];fz--;}}for (int i = fz; i >= 0; i--) {temp[i] = 0;}for (int j = 3; j >= 0; j--) {arr[r + 4 * j] = temp[j];}}}}
【StartFrame类】
package com.test2048;import javax.swing.*;import java.awt.*;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;public class StartFrame {JFrame mainFrame;final String gameRule = "2048游戏共有16个格子,开始时会随机生成两个数值为2的方块和一个数值为4的方块,\n" +"玩家可通过键盘上的上、下、左、右方向键来操控方块的滑动方向,\n" +"每按一次方向键,所有的方块会向一个方向靠拢,相同数值的方块将会相加并合并成一个方块,\n" +"此外,每滑动一次将会随机生成一个数值为2或者4的方块,\n" + "玩家需要想办法在这16个格子里凑出2048数值的方块,若16个格子被填满且无法再移动,\n" +"则游戏结束。";public StartFrame() {initFrame();}private void initFrame() {mainFrame = new JFrame("2048 Game");mainFrame.setSize(500, 500);mainFrame.setResizable(false);//固定窗口尺寸mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);mainFrame.setLocationRelativeTo(null);//窗口居中JPanel jPanel = new JPanel();//BoxLayout.Y_AXIS是指定从上到下垂直布置组件。jPanel.setLayout(new BoxLayout(jPanel, BoxLayout.Y_AXIS));jPanel.add(newLine(Box.createVerticalStrut(25)));//添加空白区域JLabel jLabel = new JLabel("2048");jLabel.setForeground(new Color(0x776e65));jLabel.setFont(new Font("Dialog", 1, 92));jPanel.add(newLine(jLabel));/*JLabel author = new JLabel("by xxx");jPanel.add(newLine(author));*/jPanel.add(newLine(Box.createVerticalStrut(50)));JButton btn1 = new JButton("开始游戏");btn1.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent actionEvent) {new Game();mainFrame.dispose();}});jPanel.add(newLine(btn1));jPanel.add(newLine(Box.createVerticalStrut(50)));JButton btn2 = new JButton("游戏规则");btn2.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent actionEvent) {JOptionPane.showMessageDialog(null, gameRule, "游戏规则",JOptionPane.PLAIN_MESSAGE);}});jPanel.add(newLine(btn2));jPanel.add(newLine(Box.createVerticalStrut(50)));JButton btn3 = new JButton("退出游戏");btn3.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent actionEvent) {System.exit(0);}});jPanel.add(newLine(btn3));mainFrame.add(jPanel);mainFrame.setVisible(true);}//添加新一行垂直居中的控件,通过在控件两边填充glue对象实现private JPanel newLine(Component c) {JPanel jp = new JPanel();jp.setLayout(new BoxLayout(jp, BoxLayout.X_AXIS));jp.add(Box.createHorizontalGlue());jp.add(c);jp.add(Box.createHorizontalGlue());jp.setOpaque(false);//设置不透明return jp;}}
【Main】
package com.test2048;public class Main {public static void main(String[] args) {new StartFrame();}}
小游戏:贪吃蛇
开始游戏的窗口,首先引入窗口,然后在窗口画布上进行添加各类动画。
JFrame frame=new JFrame("My SnakeGame");
Jframe 是个类,引入了窗口frame,该窗口的标题是:My SnakeGame
//设置零点坐标,窗口大小frame.setBounds(100,100,900,720);
- 参数1: 窗口的左上角点的横坐标
- 参数2:窗口的左上角点的纵坐标
- 参数3:窗口的宽度
- 参数4:窗口的高度
- 单位为像素
//窗口大小不可随意改变大小frame.setResizable(false);
这样设置后不能够随意改变窗口的大小。主要是牵扯到画面的自动扩展延伸。
//关闭窗口即关闭程序,无后台frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
这样设置后关闭窗口后自动关闭程序,否则关闭窗口后,程序依旧在运行。
frame.setVisible(true); //窗口可被展示
窗口可以被看到
//引入画板frame.add(new GameJPanel());
通过这个语句引入画板,操作都在画板上进行。
画板
定义我们自己的画布类必须继承自JPanel系统画板类,需要重写方法**protected void paintComponent(Graphics g){}**。 在这个方法里编写想要画在画板上的东西,这里的Graphics g参数是画笔g,可以设置画笔的颜色、字体等。
在重写的这个方法里先调用父类的方法 :**super.paintComponent(g);**
需要在画面上进行某些改变的话都要写在这个重写的方法里。
this.setBackground(Color.WHITE); //定义背景颜色
WHITE可选该为其他颜色,其他部分为固定语法。
this表示当前画布,可以理解为当前画布设置背景颜色为白色。
g.setColor(Color.WHITE); //画笔颜色设置g.setFont(new Font("宋体",Font.BOLD,40));
设置画笔的写出的颜色、字体格式、字体类型、字体大小。
g.drawString("按下空格开始",300,300);
语法:**g.drawString("要打印的字体",x,y);**
写入打印的字符串,打印起始点的坐标。
Date.header.paintIcon(this,g,25,11);//把头部广告栏画上去//参数分别为:窗口this(本窗口),画笔g,(25,11)表示坐标距离
语法:**Date.名字.paintIcon(画面,画笔,x,y);**
这里的header是我们在下面的数据引入时引入的图片资源名称。paintIcon();指打印图片的方法。
g.fillRect(25,75,850,600); //填充游戏窗口画面
25,75表示起始的点坐标。850,600表示从那点开始要打印的画面的宽度、高度,这部主要实现游戏窗口的背景打印。
按键监听
这里需要实现两个接口,第一个是键盘监听接口 KeyListener。这个接口依旧在我们自己定义的画板上进行实现,实现了这个接口我们就需要实现三个方法。
- ```java //这个方法主要监听键盘上按键仅按一下的情况下,可以理解为按一下记一下。 public void keyTyped(KeyEvent e) { //按一下
}
2.```java//这个方法监听的是键盘按下后弹起的过程。public void keyReleased(KeyEvent e) { //弹起}
//监听长按的键盘。public void keyPressed(KeyEvent e) { //一直按}
这里的KeyEvent e是接受的参数,e.getKeyCode();表示得到的键盘输入情况
定义个数keyCode接受这个按键,int keyCode=e.getKeyCode();后面都是用keyCode与按键进行比较
KeyEvent.VK_UP //表示上键
语法:KeyEvent.VK_按键;按键部分需要全大写字母。例如LEFT
if(keyCode==KeyEvent.VK_DDWN){//当按键为“下”时做什么,一般为改变一个变量(在这个监听按键的方法里改变),通过这个改变这个变量改变打印的内容(在重写的画板方法里改变打印的内容),从而实现画面里方向的改变。}repaint(); //重新绘制界面的方法
repaint();作用是刷新打印的界面,可以在监听按键的方法里调用,从而实现按键后画面的改变。
一般会定义个初始化变量的方法,用来实现游戏重新加载或第一次加载时的变量初始化。
定时操作
需要实现重写的第二个接口是定时活动操作ActionListener,会随着时间变化而变化,设置定时执行该方法的的方法体。我们需要重写方法
public void actionPerformed(ActionEvent e) {//执行定时操作//随时间变化的操作,如想让蛇头朝一个方向走动只需要其坐标(X或Y)自增。//这里面的操作都会随着我们设置的时间间隔不断重复执行。timer.start(); //计时器开始}
在方法的左后要加上timer.start(); ,计时器开始确保计时器开始执行,即执行完第一次后不断重复执行开始。
数据引入
在这之前需要先建个static包用来存放我们的资源数据,直接将所需要的照片包拷进该文件夹即可。
定义Data类,通过Date类引入我们需要使用的图片、音乐等外部资源。
语法: **修饰符 URL 地址名称=类名.class.getResource("地址");**
地址名称是自己按规则随意取的,地址是文件所在的文件夹和文件名称和类型。
public static URL headerURL=Date.class.getResource("地址");//引入资源的地址
eg:public static URL upURL=Date.class.getResource("static/up.png");
需要注意的是这里只是引入资源的地址,并没有说明这是什么资源类型。
声明引入资源的类型。如图片的声明。
语法:**修饰符 ImageIcon 名称=new ImageIcon(地址名称);**
public static ImageIcon header=new ImageIcon(headerURL);//引入资源为图片
主程序入口
package com.wang.gui.snake;import javax.swing.*;/*** @Author wangjin* @Date 2022/04/23 19:51* @Description*///游戏的主启动类public class StartGame {public static void main(String[] args) {JFrame frame = new JFrame("My SnakeGame");//设置零点坐标,窗口大小frame.setBounds(10, 10, 900, 720);//窗口大小不可变frame.setResizable(false);//关闭窗口即关闭程序,无后台frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);//正常游戏界面都应该在面板上///引入画板frame.add(new GamePanel());//设置窗口可被展示frame.setVisible(true);}}
数据引入
package com.wang.gui.snake;import javax.swing.*;import java.net.URL;/*** @Author wangjin* @Date 2022/04/23 20:23* @Description*///数据中心public class Data {//相对路径 wj.jpg//绝对路径 / 相当于当前的项目//引入数据,引入后才能被直接调用//引入资源的地址public static URL headerURL = Data.class.getResource("statics/header.png");//引入资源为图片public static ImageIcon header = new ImageIcon(headerURL);public static URL upURL = Data.class.getResource("statics/up.png");public static URL downURL = Data.class.getResource("statics/down.png");public static URL leftURL = Data.class.getResource("statics/left.png");public static URL rightURL = Data.class.getResource("statics/right.png");public static ImageIcon up = new ImageIcon(upURL);public static ImageIcon down = new ImageIcon(downURL);public static ImageIcon left = new ImageIcon(leftURL);public static ImageIcon right = new ImageIcon(rightURL);public static URL bodyURL = Data.class.getResource("statics/body.png");public static ImageIcon body = new ImageIcon(bodyURL);public static URL foodURL = Data.class.getResource("statics/food.png");public static ImageIcon food = new ImageIcon(foodURL);}
画板
package com.wang.gui.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 wangjin* @Date 2022/04/23 19:57* @Description*///游戏的面板public class GamePanel extends JPanel implements KeyListener, ActionListener {//JPanel画板类,写入我们需要的东西如图片,背景颜色等,//这里我们自己定义一个画板,继承来自系统的画板//定义小蛇的数据结构//小蛇的长度int length;//小蛇的x坐标 25*25int[] snakeX = new int[600];//小蛇的y坐标 25*25int[] snakeY = new int[500];//小蛇初始方向String fx;//食物的坐标int foodx;int foody;//引入随机方法Random random = new Random();//积分成绩int score;//游戏当前的状态:开始/停止//默认游戏未开始boolean isStart = false;//游戏失败状态boolean isFail = false;//定时器:以毫秒为单位 1000ms=1sTimer timer = new Timer(100, this); //50毫秒执行一次//构造器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;//小蛇初始方向向右fx = "R";//把食物随机分布在界面上foodx = 25 + 25 * random.nextInt(34);foody = 75 + 25 * random.nextInt(24);//积分成绩初始化score = 0;}//控制面板// 游戏中的所有东西,都是用这个画笔来画@Overrideprotected void paintComponent(Graphics g) { //重写方法,画板super.paintComponent(g); //这里的Graphics g就是画笔//绘制静态面板//定义背景颜色this.setBackground(Color.white);//头部广告栏画上去//参数分别为,窗口this(本窗口),画笔g,坐标距离Data.header.paintIcon(this, g, 25, 11);//绘制默认的游戏界面//填充窗口画面g.fillRect(25, 75, 850, 600);//把积分画到界面上g.setColor(Color.white);g.setFont(new Font("微软雅黑", Font.BOLD, 16));g.drawString("长度" + length, 750, 35);g.drawString("积分" + score, 750, 50);//把食物画在界面上Data.food.paintIcon(this, g, foodx, foody);//把小蛇画上去//蛇头初始化向右,需要通过方向来判断//通过监听键盘改变fx的值,改变fx的值后打印不同画面,实现蛇头的转向if (fx.equals("R")) {Data.right.paintIcon(this, g, snakeX[0], snakeY[0]);} else if (fx.equals("L")) {Data.left.paintIcon(this, g, snakeX[0], snakeY[0]);} else if (fx.equals("U")) {Data.up.paintIcon(this, g, snakeX[0], snakeY[0]);} else if (fx.equals("D")) {Data.down.paintIcon(this, g, snakeX[0], snakeY[0]);}for (int i = 1; i < length; i++) {//第一个身体的坐标//初始值为3,两节身体,随着长度增加,可以不断打印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);}}//键盘监听事件@Overridepublic 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) { //由键盘输入的值改变fxfx = "U";} else if (keyCode == KeyEvent.VK_DOWN) {fx = "D";} else if (keyCode == KeyEvent.VK_LEFT) {fx = "L";} else if (keyCode == KeyEvent.VK_RIGHT) {fx = "R";}repaint(); //重新绘制界面的方法}//事件监听 -- 需要通过固定时间来刷新,比如1秒刷新10次@Overridepublic void actionPerformed(ActionEvent e) { //执行定时操作//如果游戏是开始状态,就让小蛇动起来!if (isStart && isFail == false) {//吃食物if (snakeX[0] == foodx && snakeY[0] == foody) {//长度+1length++;//积分:没吃到一次食物,积分+10score += 10;//再次在界面上随机分布食物//食物坐标刷新坐标随机foodx = 25 + 25 * random.nextInt(34);foody = 75 + 25 * random.nextInt(24);}//移动for (int i = length - 1; i > 0; i--) {//遵循上一节的足迹往前走//后一节移到前一节的位置 snakeX[1]= snakeX[0];snakeX[i] = snakeX[i - 1];snakeY[i] = snakeY[i - 1];}//走向if (fx.equals("R")) {//在这里进行打印判断,由fx的值判断蛇头的打印snakeX[0] = snakeX[0] + 25;//边界判断if (snakeX[0] > 850) {snakeX[0] = 25; //超过边界后从另一边出来}} else if (fx.equals("L")) {snakeX[0] = snakeX[0] - 25;//边界判断if (snakeX[0] < 25) {snakeX[0] = 850; //超过边界后从另一边出来}} else if (fx.equals("U")) {snakeY[0] = snakeY[0] - 25;//边界判断if (snakeY[0] < 75) {snakeY[0] = 650; //超过边界后从另一边出来}} else if (fx.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(); //重画页面}timer.start(); //定时器开启}@Override//按一下public void keyReleased(KeyEvent e) {}@Override//弹起public void keyTyped(KeyEvent e) {}}
效果图:
所需要的资源:
left.png
right.png
up.png
down.png
body.png
food.png
header.png
