java图形化界面(GUI)
- 一、AWT编程(抽象窗口工具集)
- 1.AWT继承体系
- 2.Container容器
- 2.1 Container继承体系
- 2.2 常见API
- 2.3 容器演示
- 2.3.1 window
- 2.3.2 panel
- 2.3.2 ScrollPane
- 2.4 LayoutManger布局管理器
- 2.4.1 FlowLayout(流式布局)
- 2.4.2 BorderLayout(边框布局)
- 2.4.3 GridLayout(网格布局)
- 2.4.4 GridBagLayout(网格包布局)
- 2.4.5 CardLayout(卡片布局)
- 2.4.6 BoxLayout(盒子布局)
- 2.5 AWT中常用组件
- 2.5.1 基本组件
- 2.5.2 对话框Dialog
- 2.5.2.1 Dialog
- 2.5.2.1 Dialog
- 2.6 事件处理
- 2.6.1 GUI事件处理机制
- 2.6.2 GUI中常见事件和事件监听器
- 2.6.2.1 事件
- 2.6.2.2 事件监听器
- 2.7 菜单组件
- 2.8 绘图
- 2.8.1 组件绘图原理
- 2.8.2 Graphics类的使用
- 2.8.3 处理位图
- 2.8.4 ImageIO的使用
- 2.8.5 五子棋
- 二、Swing编程
一、AWT编程(抽象窗口工具集)
1.AWT继承体系
所有的AWT编程相关类放在java.awt包以及它的子包下,AWT编程中有两个基类:Component和MenuComponent。
- Component:代表一个能以图形化形式显示出来,并可与用户交互的对象,例如Button代表一个按钮,TextField代表一个文本框等;
- MeanComponent:代表图形界面的菜单组件,包括MeanBar(菜单条)、MeanItem(菜单项)等子类。
其中Container是一种特殊的Compoent,它代表一种容器,可以盛装普通的Compoent。
AWT中还有一个非常重要的接口叫LayoutManager,如果一个容器中有多个组件,那么容器就需要使用LayoutManager来管理这些组件的布局方式。
2.Container容器
2.1 Container继承体系
- Window是可以独立存在的的顶级窗口,默认使用BorderLayout管理其内部组件布局;
- Panel可以容纳其他组件,但不能独立存在,他必须内嵌其他容器中使用,默认使用FlowLayout管理其内部组件布局;
- ScrollPane是一个带滚动条的容器,它也不能独立存在,默认使用BorderLayout管理其内部组件布局。
2.2 常见API
Component作为基类,提供了如下常用的方法来设置组件的大小、位置、可见性等。
方法签名 | 方法功能 |
setLocation(int x,int y) | 设置组件的位置 |
setLSize(int width,int height) | 设置组件的大小 |
setBounds(int x,int y,int width,int height) | 同时设置组件的位置、大小 |
setVisible(BOOlean b) | 设置组件可见性 |
Container作为容器根类,提供了如下常用的方法来访问容器中的组件。
方法签名 | 方法功能 |
Component add(Component comp) | 设置组件的位置 |
Component getComponentAt(int x, int y) | 设置组件的大小 |
int getComponentCount() | 同时设置组件的位置、大小 |
Component[] getComponents() | 设置组件可见性 |
2.3 容器演示
2.3.1 window
import java.awt.*;
public class WindowDemo {
public static void main(String[] args) {
//1.创建一个窗口对象
Frame frame = new Frame("这里测试window窗口");
//2.指定窗口的位置,大小(单位像素px)
frame.setLocation(100,100);
frame.setSize(500,300);
//3.设置窗口对象可见
frame.setVisible(true);
}
}
2.3.2 panel
import java.awt.*;
public class PanelDemo {
public static void main(String[] args) {
//1.创建一个window对象,因为,panel以及其他的容器,都不能独立存在,必须依附于window存在
Frame frame = new Frame("这里演示Panel");
//2.创建一个Panel对象
Panel p = new Panel();
//3.创建一个文本框和一个按钮,并且把他们放入到Panel容器中
p.add(new TextField("这里是一个测试文本"));
p.add(new Button("这里是一个测试按钮"));
//4.把panel放入到window中
frame.add(p);
//5.设置window的位置以及大小
frame.setBounds(100,100,500,300);
//6.设置window可见
frame.setVisible(true);
}
}
2.3.2 ScrollPane
import java.awt.*;
public class ScrollPaneDemo {
public static void main(String[] args) {
Frame frame = new Frame("这里演示ScrollPane");
//1.创建一个ScrollPane对象
ScrollPane sp = new ScrollPane(ScrollPane.SCROLLBARS_ALWAYS);
//2.往ScrollPane中添加内容
sp.add(new TextField("这是测试文本"));//不会显示,因为布局方式
sp.add(new Button("这是测试按钮"));
//3.把ScrollPane添加到Frame中
frame.add(sp);
frame.setBounds(100,100,500,300);
frame.setVisible(true);
}
}
2.4 LayoutManger布局管理器
之前,我们介绍了Component中有一个方法setBounds()可以设置当前容器的位置和大小,但是为我们手动设置位置大小,会造成程序的不通用性,不同操作系统要达到一样效果需要更改像素。
为了解决这个问题,java提供了LayoutManger布局管理器,可以根据运行平台自动调整组件的大小,程序员不用在手动设置组件的大小和位置,只需要选择合适的布局管理器即可。
分别对应网格、流式、卡片、网格包、边框布局。
2.4.1 FlowLayout(流式布局)
在FlowLayout布局管理器中,组件像流水一样向某方向流动(排列),遇到障碍(边界)就折回。重头开始排列。在默认情况下FlowLayout布局管理器从左向右排列所有组件,遇到边界就会折回下一行重新开始。
构造方法 | 方法功能 |
FlowLayout() | 使用默认的对齐方式及默认的垂直间距、水平间距创建FlowLayout布局管理器 |
FlowLayout(int align) | 使用指定的对齐方式及默认的垂直间距、水平间距创建FlowLayout布局管理器 |
FlowLayout(int align, int hgap, int vgap) | 使用指定的对齐方式及指定的垂直间距、水平间距创建FlowLayout布局管理器 |
FlowLayout中组件的排列方向(从左到右、从右到左、从中间向两边等),该参数应该使用FlowLayout类的静态常量:FlowLayout.LEFT、FlowLayout.CENTER、FlowLayout.Right,默认是左对齐。 |
import java.awt.*;
public class FlowLayoutDemo {
public static void main(String[] args) {
Frame frame = new Frame("这里测试FlowLayout");
//1.通过setLayout方法设置容器的布局管理器
// frame.setLayout(new FlowLayout(FlowLayout.LEFT,20,20));
//frame.setLayout(new FlowLayout(FlowLayout.CENTER,20,20));
frame.setLayout(new FlowLayout(FlowLayout.RIGHT,40,20));
//2.添加多个按钮到frame中
for (int i = 1; i <= 100; i++) {
frame.add(new Button("按钮"+i));
}
//3.设置最佳大小,pack方法
frame.pack();
frame.setVisible(true);
}
}
2.4.2 BorderLayout(边框布局)
BorderLayout将容器分为EAST、SOUTH、WEST、NORTH、CENTER五个区域,普通组件可以被放置在这五个区域的任意一个中。BorderLayout布局管理器的布局视图如图所示:
当改变使用BorderLayout的容器大小时,NORTH、SOUTH、和CENTER区域水平调整,而EAST、WEST和CENTER区域垂直调整。使用BorderLayout有如下两个注意点:
1.当向使用BorderLayout布局管理器的容器中添加组件时,需要指定要添加到哪个区域中。如果没有指定添加到哪个区域中,则默认添加到中间区域中;
2.如果向同一个区域中添加多个组件时,后放入的组件会覆盖先放入的组件;
构造方法 | 方法功能 |
BorderLayout() | 使用默认的水平间距、垂直间距创建BorderLayout布局管理器 |
BorderLayout(int hgap, int vgap) | 使用指定的水平间距、垂直间距创建BorderLayout布局管理器 |
import java.awt.*;
public class BorderLayoutDemo1 {
public static void main(String[] args) {
Frame frame = new Frame("这里测试BorderLayout");
//1.给frame设置BorderLayout布局管理器
frame.setLayout(new BorderLayout(30,10));
//2.往frame的指定区域添加组件
frame.add(new Button("北侧按钮"),BorderLayout.NORTH);
frame.add(new Button("南侧按钮"),BorderLayout.SOUTH);
frame.add(new Button("东侧按钮"),BorderLayout.EAST);
frame.add(new Button("西侧按钮"),BorderLayout.WEST);
frame.add(new Button("中间按钮"),BorderLayout.CENTER);
frame.pack();
frame.setVisible(true);
}
}
如果不往某个区域放入组件,那么该区域就不会空白出来,而是会被其他区域占用
import java.awt.*;
public class BorderLayoutDemo2 {
public static void main(String[] args) {
Frame frame = new Frame("这里测试BorderLayout");
//1.给frame设置BorderLayout布局管理器
frame.setLayout(new BorderLayout(30,10));
//2.往frame的指定区域添加组件
frame.add(new Button("北侧按钮"),BorderLayout.NORTH);
frame.add(new Button("南侧按钮"),BorderLayout.SOUTH);
frame.add(new Button("中间按钮"),BorderLayout.CENTER);
// frame.add(new TextField("测试文本框"));//默认是中间区域,替代原来的
//这样就可以都显示出来,嵌套了FlowLayout
Panel p = new Panel();
p.add(new Button("中间按钮"));
p.add(new TextField("测试文本框"));
frame.add(p);
frame.pack();
frame.setVisible(true);
}
}
2.4.3 GridLayout(网格布局)
GridLayout布局管理器将容器分割成横线分割的网格,每个网格所占的区域大小相同,当向使用GridLayout布局管理器的容器中添加组件时,默认是从左向右,从上向下依次添加到每个网格中。与FlowLayout不同的是,放置在GridLayout布局管理器中的各组件的大小由组件所处的区域决定(每个组件自动占满整个区域)。
构造方法 | 方法功能 |
GridLayout() | 默认为在一行中每个组件一列,以及默认的横向间距、纵向间距将容器分割成多个网格 |
GridLayout(int rows, int cols, int hgap, int vgap) | 采用指定的行数、列数,以及指定的横向间距、纵向间距将容器分割成多个网格 |
GridLayout(int rows, int cols) | 采用指定的行数、列数,以及默认的横向间距、纵向间距将容器分割成多个网格 |
import java.awt.*;
public class GridLayoutDemo {
public static void main(String[] args) {
Frame frame = new Frame("计算器");
//1.创建一个Panel对象,里面存放一个TextFiled组件
Panel p = new Panel();
p.add(new TextField(30));
//2.把当前这个Panel添加到frame的北边区域
frame.add(p,BorderLayout.NORTH);
//3.创建一个Panel对象,并且设置它的布局管理器为GridLayout
Panel p2 = new Panel();
p2.setLayout(new GridLayout(3,5,4,4));
//4.往Panel中添加内容
for (int i = 0; i < 10; i++) {
p2.add(new Button(i+""));
}
p2.add(new Button("+"));
p2.add(new Button("-"));
p2.add(new Button("*"));
p2.add(new Button("/"));
p2.add(new Button("."));
//5.把当前Panel添加到frame中
frame.add(p2);
frame.pack();
frame.setVisible(true);
}
}
2.4.4 GridBagLayout(网格包布局)
- GridBagLayout布局管理器的功能最强大,但也最复杂,与 GridLayout布局管理器不同的是,在 GridBagLayout布局管理器中,一个组件可以跨越一个或多个网格,并可以设置网格的大小互不相同,从而增加了布局的灵活性。当窗口的大小发生变化时, GridBagLayout布局管理器也可以准确地控制窗口各部分的拉伸。
- 由于在 GridBagLayout布局中,每个组件可以占用多个网格,此时,我们往容器中添加组件的时候,就需要具体的控制每个组件占用多少个网格,java提供的 GridBagContainers类,与特定的组件绑定,可以完成具体大小和跨越性位置。
import java.awt.*;
public class GridBagLayoutDemo {
public static void main(String[] args) {
//1.创建Frame对象
Frame frame = new Frame("这里是GridBagLayout测试");
//2.创建GridBagLayout对象
GridBagLayout gbl = new GridBagLayout();
//3.把Frame对象的布局管理器设置为GridBagLayout
frame.setLayout(gbl);
//4.创建GridBagConstraints对象
GridBagConstraints gbc = new GridBagConstraints();
//5.创建容量为10的Button数组
Button[] bs = new Button[10];
//6.遍历数组,初始化每一个Button
for (int i = 0; i < bs.length; i++) {
bs[i] = new Button("按钮"+(i+1));
}
//7.设置所有的GridBagConstraints对象的fill属性为GridBagConstraints.BOTH,当有空白区域时,组件自动扩大占满空白区域
gbc.fill=GridBagConstraints.BOTH;
//8.设置GridBagConstraints对象的weightx设置为1,表示横向扩展比例为1
gbc.weightx=1;
//9.往frame中添加数组中的前3个Button
addComponent(frame,bs[0],gbl,gbc);
addComponent(frame,bs[1],gbl,gbc);
addComponent(frame,bs[2],gbl,gbc);
//10.把GridBagConstraints的gridwidth设置为GridBagConstraints.REMAINDER,则表明当前组件是横向最后一个组件
gbc.gridwidth=GridBagConstraints.REMAINDER;
//11.把button数组中第四个按钮添加到frame中
addComponent(frame,bs[3],gbl,gbc);
//12.把GridBagConstraints的weighty设置为1,表示纵向扩展比例为1
gbc.weighty=1;
//13.把button数组中第5个按钮添加到frame中
addComponent(frame,bs[4],gbl,gbc);
//14.把GridBagConstaints的gridheight和gridwidth设置为2,表示纵向和横向会占用两个网格
gbc.gridheight=2;
gbc.gridwidth=2;
//15.把button数组中第6个按钮添加到frame中
addComponent(frame,bs[5],gbl,gbc);
//16.把GridBagConstaints的gridheight和gridwidth设置为1,表示纵向会占用1个网格
gbc.gridwidth=1;
gbc.gridheight=1;
//17.把button数组中第7个按钮添加到frame中
addComponent(frame,bs[6],gbl,gbc);
//18.把GridBagConstraints的gridwidth设置为GridBagConstraints.REMAINDER,则表明当前组件是横向最后一个组件
gbc.gridwidth=GridBagConstraints.REMAINDER;
//19.把button数组中第8个按钮添加到frame中
addComponent(frame,bs[7],gbl,gbc);
//20.把GridBagConstaints的gridwidth设置为1,表示纵向会占用1个网格
gbc.gridwidth=1;
//21.把button数组中第9、10个按钮添加到frame中
addComponent(frame,bs[8],gbl,gbc);
addComponent(frame,bs[9],gbl,gbc);
//22.设置frame为最佳大小
frame.pack();
//23.设置frame可见
frame.setVisible(true);
}
public static void addComponent(Container container,Component c,GridBagLayout gridBagLayout,GridBagConstraints gridBagConstraints){
gridBagLayout.setConstraints(c,gridBagConstraints);
container.add(c);
}
}
2.4.5 CardLayout(卡片布局)
CardLayout布局管理器以时间而非空间来管理它里面的组件,它将加入容器的所有组件看成一叠卡片(每个卡片其实就是一个组件),每次只有最上面的那个Component才可见。就好像一副扑克牌,它们叠在一起,每次只有最上面的一张扑克牌才可见。
方法名称 | 方法功能 |
CardLayout() | 创建默认的CardLayout布局管理器 |
CardLayout(int hgap, int vgap) | 通过指定卡片与容器左右边界的间距(hgap)、上下边界(vgap)的间距来创建CardLayout布局管理器 |
first(Container target) | 显示target容器中的第一张卡片 |
last(Container target) | 显示target容器中的最后一张卡片 |
previous(Container target) | 显示target容器中的前一张卡片 |
next(Container target) | 显示target容器中的后一张卡片 |
show(Container target,String name) | 显示target容器中指定名字的卡片 |
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class CardLayoutDemo {
public static void main(String[] args) {
Frame frame = new Frame("这里测试CardLayout");
//1.创建一个Panel,用来存储多张卡片
Panel p1 = new Panel();
//2.创建CardLayout对象,并且把该对象设置给之前创建的容器
CardLayout cardLayout = new CardLayout();
p1.setLayout(cardLayout);
//3.往panel中存储多个组件
String[] names = {"第一张","第二张","第三张","第四张","第五张"};
for (int i = 0; i < names.length; i++) {
p1.add(names[i],new Button(names[i]));
}
//4.把panel放到frame的中间区域
frame.add(p1);
//5.创建另外一个panel p2,用来存储多个按钮组件
Panel p2 = new Panel();
//6.创建5个按钮组件
Button b1 = new Button("上一张");
Button b2 = new Button("下一张");
Button b3 = new Button("第一张");
Button b4 = new Button("最后一张");
Button b5 = new Button("第三张");
//7.创建一个事件监听器,监听按钮的点击动作
ActionListener listener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String actionCommand = e.getActionCommand();//这个字符串其实就是按钮上的文字
switch (actionCommand){
case "上一张":
cardLayout.previous(p1);
break;
case "下一张":
cardLayout.next(p1);
break;
case "第一张":
cardLayout.first(p1);
break;
case "最后一张":
cardLayout.last(p1);
break;
case "第三张":
cardLayout.show(p1,"第三张");
break;
}
}
};
//8.把当前这个时间监听器和多个按钮绑定到一起
b1.addActionListener(listener);
b2.addActionListener(listener);
b3.addActionListener(listener);
b4.addActionListener(listener);
b5.addActionListener(listener);
//9.把按钮添加到容器p2中
p2.add(b1);
p2.add(b2);
p2.add(b3);
p2.add(b4);
p2.add(b5);
//10.把p2放到frame的南边区域
frame.add(p2,BorderLayout.SOUTH);
frame.pack();
frame.setVisible(true);
}
}
2.4.6 BoxLayout(盒子布局)
为了简化开发,Swing引入了一个新的布局管理器:BoxLayout,BoxLayout可以在垂直和水平两个方向上摆放GUI组件,BoxLayout提功了如下一个简单的构造器:
方法名称 | 方法功能 |
BoxLayout(Container target, int axis) | 指定创建基于target容器的BoxLayout布局管理器,该布局管理器的组件按axis方向排列。其中axis有BoxLayout.X_AXIS(横向)和BoxLayout.Y_AXIS(纵向)两个方向 |
import javax.swing.*;
import java.awt.*;
public class BoxLayoutDemo1 {
public static void main(String[] args) {
Frame frame = new Frame("这里测试BoxLayout");
//1.基于frame容器,创建一个BoxLayout对象,并且,该对象存放组件是垂直存放
//BoxLayout boxLayout = new BoxLayout(frame, BoxLayout.Y_AXIS);
//该对象存放组件是水平存放
BoxLayout boxLayout = new BoxLayout(frame, BoxLayout.X_AXIS);
//2.把BoxLayout对象设置给Frame
frame.setLayout(boxLayout);
//3.往frame中添加两个按钮组件
frame.add(new Button("按钮1"));
frame.add(new Button("按钮2"));
frame.pack();
frame.setVisible(true);
}
}
在java.swing包中,提供了一个新的容器Box,该容器的默认布局管理器就是BoxLayout,大多数情况下,使用Box容器去容纳多个GUI组件,然后再把Box容器作为一个组件,添加到其他容器中,从而形成整体窗口布局。
方法名称 | 方法功能 |
static Box createHorizontalBox() | 创建一个水平排列组件的Box容器 |
static Box createVerticalBox() | 创建一个垂直排列组件的Box容器 |
import javax.swing.*;
import java.awt.*;
public class BoxLayoutDemo2 {
public static void main(String[] args) {
Frame frame = new Frame("这里测试BoxLayout");
//1.创建一个水平排列组件的Box容器
Box hBox = Box.createHorizontalBox();
//2.往当前容器中添加两个按钮
hBox.add(new Button("水平按钮1"));
hBox.add(new Button("水平按钮2"));
//3.创建一个垂直排列组件的Box容器
Box vBox = Box.createVerticalBox();
//4.往当前容器中添加两个按钮
vBox.add(new Button("垂直按钮1"));
vBox.add(new Button("垂直按钮2"));
//5.把两个Box容器添加到Frame中展示
frame.add(hBox,BorderLayout.NORTH);
frame.add(vBox);
frame.pack();
frame.setVisible(true);
}
}
Box类中,提供了5个方便的静态方法来生成这些间隔组件
s方法名称 | 方法功能 |
static Component createGlue() | 创建默认的Glue |
static Component createHorizontalGlue() | 创建一条水平Glue(可在两个方向上同时拉伸的间距) |
static Component createVerticalGlue() | 创建一条垂直Glue(可在两个方向上同时拉伸的间距) |
static Component createHorizontalStrut(int width) | 创建一条指定宽度(宽度固定了,不能拉伸)的水平Struct(可在垂直方向拉伸的间距) |
static Component createVerticalStrut(int height) | 创建一条指定高度(高度固定了,不能拉伸)的垂直Struct(可在水平方向拉伸的间距) |
import javax.swing.*;
import java.awt.*;
public class BoxLayoutDemo3 {
public static void main(String[] args) {
Frame frame = new Frame("这里测试BoxLayout");
//1.创建水平排列的Box容器 hBox
Box hBox = Box.createHorizontalBox();
//2.往hBox容器中添加按钮,还需要在多个按钮之间添加分割
hBox.add(new Button("水平按钮一"));
hBox.add(Box.createHorizontalGlue());//该分割在两个方向上都可以拉伸
hBox.add(new Button("水平按钮二"));
hBox.add(Box.createHorizontalStrut(30));
hBox.add(new Button("水平按钮三"));
//3.创建垂直排列的Box容器 vBox
Box vBox = Box.createVerticalBox();
//4.往vBox容器中添加按钮,还需要在多个按钮之间添加分割
vBox.add(new Button("垂直按钮一"));
vBox.add(Box.createVerticalGlue());//该分割在两个方向上都可以拉伸
vBox.add(new Button("垂直按钮二"));
vBox.add(Box.createVerticalStrut(30));
vBox.add(new Button("垂直按钮三"));
vBox.add(Box.createGlue());//两个方向都可以拉伸
//5.把box容器添加到frame中
frame.add(hBox,BorderLayout.NORTH);//加到北边垂直不能拉伸,所以测试时加到中心测试
//frame.add(hBox);
frame.add(vBox);
frame.pack();
frame.setVisible(true);
}
}
2.5 AWT中常用组件
2.5.1 基本组件
组件名 | 功能 |
Button | 按钮 |
Canvas | 用于绘图的画布 |
Checkbox | 复选框组件(也可以当作单选框组件使用) |
CheckboxGroup | 用于将多个Checkbox组件组合成一组,一组Checkbox组价将只有一个可以被选中,即全部变成单选框组件 |
Choice | 下拉选择框 |
Frame | 窗口,在GUI程序里通过该类创建窗口 |
Label | 标签类,用于放置提示性文本 |
List | 列表框组件,可以添加多项条目 |
Panel | 不能单独存在基本容器类,必须放在其他容器中 |
Scrollbar | 滑动条组件。如果需要用户输入位于某个范围的值,就可以使用滑动条组件,比如调色板中设置RGB的上值所用的滑动条。当创建 一个滑动条时,必须指定它的方向、初始值、滑块的大小,最小值和最大值 |
Scrollpane | 带水平及垂直滚动条的容器组件 |
TextArea | 多行文本域 |
TextField | 单行文本框 |
import javax.swing.*;
import java.awt.*;
public class BasicComponentDemo {
Frame frame = new Frame("这里测试基本组件");
TextArea ta = new TextArea(5,20);//文本域
Choice colorChooser = new Choice();//下拉选择框
CheckboxGroup cbg = new CheckboxGroup();
Checkbox male = new Checkbox("男",cbg,true);
Checkbox female = new Checkbox("女",cbg,false);
Checkbox isMarried = new Checkbox("是否已婚?");
TextField tf = new TextField(50);
Button ok = new Button("确认");
List colorList = new List(6,true);
public void init(){
//组装界面
//组装底部
Box bBox = Box.createHorizontalBox();
bBox.add(tf);
bBox.add(ok);
frame.add(bBox,BorderLayout.SOUTH);
//组装 选择部分
colorChooser.add("红色");
colorChooser.add("蓝色");
colorChooser.add("绿色");
Box cBox = Box.createHorizontalBox();
cBox.add(colorChooser);
cBox.add(male);
cBox.add(female);
cBox.add(isMarried);
//组装文本域和选择部分
Box topLeft = Box.createVerticalBox();
topLeft.add(ta);
topLeft.add(cBox);
//组装顶部左边和列表框
colorList.add("红色");
colorList.add("绿色");
colorList.add("蓝色");
Box top = Box.createHorizontalBox();
top.add(topLeft);
top.add(colorList);
frame.add(top);
//设置frame为最佳大小,并且可见
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
new BasicComponentDemo().init();
}
}
2.5.2 对话框Dialog
2.5.2.1 Dialog
Dialog是WIndow类的子类,是一个容器类,属于特殊组件。对话框是可以独立存在的顶级窗口,因此用法与普通窗口的用法几乎完全一样,但是使用对话框需要注意下面两点:
- 对话框通常依赖于其他窗口,就是通常需要有一个父窗口;
- 对话框有非模式(non-modal)和模式(modal)两种,当某个模式对话框被打开后,该模式对话框总是位于它的父窗口上,在模式对话框被关闭之前,父窗口无法获取焦点。
方法名称 | 方法功能 |
Dialog(Frame owner,String title,boolean modal) | 创建一个对话框的对象: owner:当前对话框的父窗口 title:当前对话框的标题 modal:当前对话框是否是模式对话框,true/false |
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class DialogDemo1 {
public static void main(String[] args) {
Frame frame = new Frame("这里测试Dialog");
//1.创建两个对话框Dialog对象,一个模式的,一个非模式的
Dialog d1 = new Dialog(frame, "模式对话框", true);
Dialog d2 = new Dialog(frame, "非模式对话框", false);
//2.通过setBounds方法设置Dialog的位置以及大小
d1.setBounds(20,30,300,200);
d2.setBounds(20,30,300,200);
//3.创建两个按钮
Button b1 = new Button("打开模式对话框");
Button b2 = new Button("打开非模式对话框");
//4.给这两个按钮添加点击后的行为
b1.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
d1.setVisible(true);
}
});
b2.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
d2.setVisible(true);
}
});
//5.把按钮添加到frame中
frame.add(b1,BorderLayout.NORTH);
frame.add(b2);
//设置frame最佳大小并可见
frame.pack();
frame.setVisible(true);
}
}
在Dialog对话框中,可以根据需求,自定义内容
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class DialogDemo2 {
public static void main(String[] args) {
Frame frame = new Frame("这里测试Dialog");
//1.创建两个对话框Dialog对象,一个模式的,一个非模式的
Dialog d1 = new Dialog(frame, "模式对话框", true);
//创建一个垂直的Box容器,把一个文本框和一个按钮添加到Box容器中
Box vBox = Box.createVerticalBox();
vBox.add(new TextField(20));
vBox.add(new Button("确认"));
//把Box容器添加到Dialog中
d1.add(vBox);
//2.通过setBounds方法设置Dialog的位置以及大小
d1.setBounds(20,30,300,200);
//3.创建两个按钮
Button b1 = new Button("打开模式对话框");
//4.给这两个按钮添加点击后的行为
b1.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
d1.setVisible(true);
}
});
//5.把按钮添加到frame中
frame.add(b1,BorderLayout.NORTH);
//设置frame最佳大小并可见
frame.pack();
frame.setVisible(true);
}
}
2.5.2.1 Dialog
Dialog类还有一个子类:FileDialog,它代表一个文本对话框,用于打开或者保存文件,需要注意的是FileDialog,无法指定模态还是非模态,这是因为FileDialog依赖于运行平台的实现,如果运行平台的对话框是模态的,那么FileDialog也是模态的,否则是非模态的。
方法名称 | 方法功能 |
FileDialog(Frame parent, String title, int mode) | 创建一个文件对话框的对象: parent:当前对话框的父窗口 title:当前对话框的标题 mode:文件对话框类型,如果指定为FileDialog.LOAD,用于打开文件,如果指定为FileDialog.SAVE ,用于保存文件 |
String getDirectory() | 获取被打开或保存文件的绝对路径 |
String getFile() | 获取被打开或保存文件的文件名 |
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class FileDialogTest {
public static void main(String[] args) {
Frame frame = new Frame("这里测试FileDialog");
//1.创建两个FileDialog对象
FileDialog f1 = new FileDialog(frame,"选择要打开的文件",FileDialog.LOAD);
FileDialog f2 = new FileDialog(frame,"选择要保存的路径",FileDialog.SAVE);
//2.创建两个按钮
Button b1 = new Button("打开文件");
Button b2 = new Button("保存文件");
//3.给这两个按钮设置点击后的行为:获取打开或者保存的路劲文件名
b1.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
f1.setVisible(true);//代码会阻塞到这里
//获取选择的路径及文件
String directory = f1.getDirectory();
String file = f1.getFile();
System.out.println("打开的文件路径为:"+directory);
System.out.println("打开的文件名称为:"+file);
}
});
b2.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
f2.setVisible(true);
//获取选择的路径及文件
String directory = f2.getDirectory();
String file = f2.getFile();
System.out.println("保存的文件路径为:"+directory);
System.out.println("保存的文件名称为:"+file);
}
});
//4.把按钮添加到Frame中
frame.add(b1,BorderLayout.NORTH);
frame.add(b2);
//设置frame最佳大小并可见
frame.pack();
frame.setVisible(true);
}
}
2.6 事件处理
前面介绍了如何放置各种组件,从而得到了丰富多彩的图形界面,但这些界面还不能响应用户的任何操作,比如单击前面所有所有窗口右上角的“X”按钮,但窗口依然不会关闭。因为在AWT编程中,所有用户的操作,都必须需要经过一套事件处理机制来完成,而Frame和组件本身并没有事件处理能力。
2.6.1 GUI事件处理机制
定义:
当在某个组件上发生某些操作的时候,会自动的触发一段代码的执行。
在GUI事件处理机制中涉及4个重要的概念需要理解:
事件源(Event Source):操作发生的场所,通常指某个组件,例如按钮、窗口等;
事件(Event):在事件源上发生的操作可以叫做事件,GUI会把事件都封装到一个Event对象中,如果需要知道该事件的详细信息,就可以通过Event对象来获取。
事件监听器(Event Listener):当在某个事件源上发生了某个事件,事件监听器就可以对这个事件进行处理。
注册监听:把某个事件监听器(A)通过某事件(B)绑定到某个事件源(C)上,挡在事件源C上发生了事件B之后,那么事件监听器A的代码就会自动执行。
使用步骤:
1.创建事件源组件对象;
2.自定义类,实现XxxListener接口,重写方法;
3.创建事件监听器对象(自定义类对象)
4.调用事件源组件对象的addXxxListener方法完成注册监听
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class EventDemo1 {
Frame frame = new Frame("这里测试事件处理");
TextField tf = new TextField(30);
//事件源
Button ok = new Button("确定");
public void init(){
//组装视图
//监听器
//MyListener myListener = new MyListener();
//注册监听
ok.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("ok按钮被点击了...");
tf.setText("Hello World");
}
});
//把tf和ok放入到Frame中
frame.add(tf,BorderLayout.NORTH);
frame.add(ok);
frame.pack();
frame.setVisible(true);
}
/*private class MyListener implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
tf.setText("Hello World");
}
}*/
public static void main(String[] args) {
new EventDemo1().init();
}
}
2.6.2 GUI中常见事件和事件监听器
事件监听器必须实现事件监听器接口,AWT提供了大量的事件监听器接口用于实现不同类型的事件监听器,用于监听不同类型的事件。AWT中提供了丰富的事件类,用于封装不同组件上所发生的特定操作,AWT的事件类都是AWTEvent类的子类,AWTEvent是EventObject的子类。
2.6.2.1 事件
AWT把事件分为了两大类:
1.低级事件:这类事件是基于某个特定动作的事件。比如进入、点击、拖放等动作的鼠标事件,再比如得到焦点和失去焦点等焦点事件。
事件 | 触发时机 |
CompoentEvent | 组件事件,当组件尺寸发生变化、位置发生移动、显示/隐藏状态发生改变时触发该事件 |
ContainerEvent | 容器事件,当容器里发生添加组件、删除组件时触发该事件 |
WindowEvent | 窗口事件,当窗口状态发生改变(如:打开、关闭、最大化、最小化时触发该事件) |
FocusEvent | 焦点事件,当组件得到焦点或失去焦点时触发该事件 |
KeyEvent | 键盘事件,当按键被按下、松开、单击时触发该事件 |
MouseEvent | 鼠标事件,当进行单击、按下、松开、移动鼠标等动作时触发该事件 |
PaintEvent | 组件绘制事件,该事件是一个特殊的事件类型,当GUI组件调用update/paint方法来呈现自身时触发该事件,该事件并非专用于事件处理模型 |
2.高级事件:这类事件并不会基于某个特定动作,而是根据功能含义定义的事件 | |
事件 | 触发时机 |
– | – |
ActionEvent | 动作事件,当按钮、菜单项被单击,在TextField中按Enter键时触发 |
AjustmentEvent | 调节事件,在滑动条上移动滑块以调节数值时触发该事件 |
ItemEvent | 选项事件,当用户选中某项,或取消选中某项时触发该事件 |
TextEvent | 文本事件,当文本框、文本域里的文本发生改变时触发该事件 |
2.6.2.2 事件监听器
不同的事件需要使用不同的监听器监听,不同的监听器需要实现不同的监听器接口,当指定事件发生后,事件监听器就会调用所包含的事件处理器(实例方法)来处理事件。
事件类别 | 描述信息 | 监听器接口 |
ActionEvent | 激活组件 | ActionListener |
ItemEvent | 选择了某些项目 | ItemListener |
MouseEvent | 鼠标移动 | MouseMotionListener |
MouseEvent | 鼠标点击等 | MouseListener |
KeyEvent | 键盘输入 | KeyListener |
FocusEvent | 组件收到或失去焦点 | FocusListener |
AjustmentEvent | 鼠标移动 | MouseMotionListener |
CompoentEvent | 对象移动缩放显示隐藏等 | ComponentListener |
WindowEvent | 窗口收到窗口级事件 | WindowListener |
ContainerEvent | 容器增加删除了组件 | ContainerListener |
TextEvent | 文本字段或文本区发生改变 | TextListener |
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class ListenerDemo1 {
public static void main(String[] args) {
Frame frame = new Frame("这里测试监听器");
//创建组件(事件源)
TextField tf = new TextField(30);
Choice names = new Choice();
names.add("柳岩");
names.add("舒淇");
names.add("闫妮");
//给文本域添加TextListener,监听内容的变化
tf.addTextListener(new TextListener() {
@Override
public void textValueChanged(TextEvent e) {
String text = tf.getText();
System.out.println("当前文本框中的内容为:"+text);
}
});
//给下拉选择框添加ItemListener,监听条目选项的变化
names.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
Object item = e.getItem();
System.out.println("当前选中的条目为:"+item);
}
});
//给frame注册ContainerListener监听器,监听容器中组件的添加
frame.addContainerListener(new ContainerListener() {
@Override
public void componentAdded(ContainerEvent e) {
Component child = e.getChild();
System.out.println("frame中添加了:"+child);
}
@Override
public void componentRemoved(ContainerEvent e) {
}
});
//添加到frame中
Box hBox = Box.createHorizontalBox();
hBox.add(names);
hBox.add(tf);
frame.add(hBox);
//设置frame最佳大小并可见
frame.pack();
frame.setVisible(true);
}
}
WindowListener的适配器模式
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class ListenerDemo2 {
public static void main(String[] args) {
Frame frame = new Frame("这里测试WindowListener");
frame.setBounds(200,200,500,300);
//设置WindowListener,监听用户点击X的动作,如果点击X,则关闭窗口
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
//停止当前程序
System.exit(0);
}
});
frame.setVisible(true);
}
}
2.7 菜单组件
前面讲解了如果构建GUI界面,其实就是把一些GUI的组件,按照一定的布局放入容器中展示就可以了。在实际开发中,除了主界面,还有一类比较重要的内容就是菜单相关组件,可以通过菜单相关组件很方便的使用特定的功能,在AWT中,菜单相关组件的使用和之前学习的组件一模一样的,只需要把菜单条、菜单、菜单项组合到一起,按照一定的布局,放入容器中即可。
相关菜单组件:
菜单组件名称 | 功能 |
MenuBar | 菜单条,菜单的容器 |
Menu | 菜单组件,菜单项的容器,它也是MenuItem的子类,所以可作为菜单项使用 |
PopupMenu | 上下文菜单组件(右键菜单组件) |
MenuItem | 菜单项组件 |
CheckboxMenuItem | 复选框菜单项组件 |
菜单相关组件使用: | |
1.准备菜单项组件,这些组件可以是MenuItem及其子类对象 | |
2.准备菜单组件Menu或者PopupMenu(右击弹出子菜单),把第一步中准备好的菜单组件添加进来; | |
3.准备菜单条组件MenuBar,把第二步中准备好的菜单组件Menu添加进来; | |
4.把第三步中准备好的菜单条组件添加到窗口对象中显示。 | |
小技巧: | |
1.如果要把在某个菜单的菜单项之间添加分割线,那么只需要调用Menu的add(new MenuItem(“-”))即可。 | |
2.如果给某个菜单项关联快捷键功能,那么只需要在创建菜单项对象时设置即可,例如给菜单项关联ctrl+shift+Q快捷键,只需要:new MenuItem(“菜单项名字”,new MenuShortcut(KeyEvent.VK_Q,true)); |
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
public class SimpleMenu {
//创建窗口
private Frame frame = new Frame("这里测试菜单相关组件");
//创建菜单条
MenuBar menuBar = new MenuBar();
//创建菜单组件
Menu fileMenu = new Menu("文件");
Menu editMenu = new Menu("编辑");
Menu formatMenu = new Menu("格式");
//菜单项组件
MenuItem auto = new MenuItem("自动换行");
MenuItem copy = new MenuItem("复制");
MenuItem paste = new MenuItem("粘贴");
MenuItem comment = new MenuItem("注释",new MenuShortcut(KeyEvent.VK_Q,true));//关联快捷键 ctrl+shift+Q
MenuItem cancelComment = new MenuItem("取消注释");
TextArea ta = new TextArea(6,40);
public void init(){
//组装视图
comment.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
ta.append("您点击了菜单项: "+e.getActionCommand()+"\n");
}
});
//comment.addActionListener(e -> ta.append("您点击了菜单项: "+e.getActionCommand()+"\n"));
formatMenu.add(comment);
formatMenu.add(cancelComment);
//组装编辑菜单
editMenu.add(auto);
editMenu.add(copy);
editMenu.add(paste);
editMenu.add(formatMenu);
//组装菜单条
menuBar.add(fileMenu);
menuBar.add(editMenu);
//把菜单条放入到Frame中
frame.setMenuBar(menuBar);
frame.add(ta);
//设置frame最佳大小并可见
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
new SimpleMenu().init();
}
}
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
public class PopupMenuTest {
private Frame frame = new Frame("这里测试PopupMenu");
//创建文本域
TextArea ta = new TextArea("我爱中华",6,40);
//创建Panel容器
Panel p = new Panel();
//创建PopupMenu
PopupMenu popupMenu = new PopupMenu();
//创建菜单项
MenuItem comment = new MenuItem("注释");
MenuItem cancelComment = new MenuItem("取消注释");
MenuItem copy = new MenuItem("复制");
MenuItem save = new MenuItem("保存");
public void init(){
//组装视图
ActionListener listener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String actionCommand = e.getActionCommand();
ta.append("您点击了: "+actionCommand+"\n");
}
};
comment.addActionListener(listener);
cancelComment.addActionListener(listener);
copy.addActionListener(listener);
save.addActionListener(listener);
popupMenu.add(comment);
popupMenu.add(cancelComment);
popupMenu.add(copy);
popupMenu.add(save);
p.add(popupMenu);
//设置Panel的大小
p.setPreferredSize(new Dimension(400,300));
//给Panel注册鼠标事件,监听用户释放鼠标的动作,展示菜单
p.addMouseListener(new MouseAdapter() {
@Override
public void mouseReleased(MouseEvent e) {
boolean flag = e.isPopupTrigger();
if (flag){//右击,左击为false
//显示PopupMenu
popupMenu.show(p,e.getX(),e.getY());
}
}
});
//放置ta和p
frame.add(ta);
frame.add(p,BorderLayout.SOUTH);
//设置frame最佳大小,并可视;
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
new PopupMenuTest().init();
}
}
2.8 绘图
很多程序例如游戏开发都需要在窗口绘制各种图形,除此之外,即使在开发javaEE项目时,有时候也必须“动态”地向客户端生成各种图形、图表,比如图形验证码、统计图等等,这都需要利用AWT的绘图功能。
2.8.1 组件绘图原理
之前我们已经学习很多组件,例如Button、Frame、Checkbox等等,不同的组件,展示出来的图形都不一样,其实这些组件暂时出来的图形,其本质就是用AWT的绘图来完成的。
在AWT中,真正提供绘图功能的是Graphics对象,那么Component组件和Graphics对象存在什么关系,才能让Component绘制自身图形?在Component类中,提供了下列三个方法来完成组件图形的绘制与刷新:
方法名称 | 功能 |
void paint(Graphics g) | 绘制组件的外观 |
void update(Graphics g) | 内部调用paint方法,刷新组件外观 |
void repaint() | 调用update()方法,刷新组件外观 |
一般情况下,update和paint方法是由AWT系统负责调用,如果程序希望重新绘制组件,可以调用repaint方法完成。
2.8.2 Graphics类的使用
程序中绘图一样,也需要画布、画笔、颜料等等。AWt提供了Canvas类充当画布,提供了Graphics类来充当画笔,通过Graphics对象的setColor()方法可以给画笔设置颜色。
画图步骤:
1.自定义类,继承Canvas类,重写paint(Graphics g)方法完成画图;
2.在paint方法内部,真正开始画图字之前调用Graphics对象的setColor()、setFont()等方法设置画笔的颜色、字体等属性;
3.调用Graphics画笔的drawXxx()方法开始画图。
- 下表中列出了Graphics类中的常用的一些方法:
方法名称 | 方法功能 |
setColor(Color c) | 设置颜色 |
setFont(Font font) | 设置字体 |
drawLine(int x1, int y1, int x2, int y2) | 绘制直线 |
drawRect(int x, int y, int width, int height) | 绘制矩形 |
drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) | 绘制圆角矩形 |
drawOval(int x, int y, int width, int height) | 绘制椭圆形 |
drawPolygon(int[] xPoints, int[] yPoints, int nPoints) | 绘制多边形 |
drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) | 绘制圆弧 |
drawPolyline(int[] xPoints, int[] yPoints, int nPoints) | 绘制折线 |
fillRect(int x, int y, int width, int height) | 填充矩形区域 |
fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) | 填充圆角矩形区域 |
fillOval(int x, int y, int width, int height) | 填充椭圆区域 |
fillPolygon(int[] xPoints, int[] yPoints, int nPoints) | 填充多边形区域 |
fillArc(int x, int y, int width, int height, int startAngle, int arcAngle) | 填充圆弧对应的扇形区域 |
drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer) | 绘制位图 |
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class SimpleDraw {
private final String RECT_SHAPE="rect";
private final String OVAL_SHAPE="oval";
private Frame frame = new Frame("这里测试绘图");
Button btnRect = new Button("绘制矩形");
Button btnOval = new Button("绘制椭圆");
//定义一个变量,记录当前要绘制椭圆还是矩形
private String shape = "";
//自定义类,继承Canvas类,重写paint(Graphics g)方法完成画图;
private class MyCanvas extends Canvas{
@Override
public void paint(Graphics g) {
//绘制不同的图形
if (shape.equals(RECT_SHAPE)){
//绘制矩形
g.setColor(Color.BLACK);//设置当前画笔的颜色为黑色
g.drawRect(100,100,150,100);
}else if(shape.equals(OVAL_SHAPE)){
//绘制椭圆
g.setColor(Color.RED);
g.drawOval(100,100,150,100);
}
}
}
//创建自定义的画布对象
MyCanvas drawArea = new MyCanvas();
public void init(){
//组装视图
btnRect.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//修改标记的值为rect
shape = RECT_SHAPE;
drawArea.repaint();
}
});
btnOval.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//修改标记的值为oval
shape = OVAL_SHAPE;
drawArea.repaint();
}
});
//创建Panel,承载按钮
Panel p = new Panel();
p.add(btnRect);
p.add(btnOval);
frame.add(p,BorderLayout.SOUTH);
//drawArea的大小需要设置
drawArea.setPreferredSize(new Dimension(300,300));
frame.add(drawArea);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
new SimpleDraw().init();
}
}
java也可用于开发一些动画。所谓动画,就是间隔一定的时间(通常销毁0.1秒)重新绘制的画像,两次绘制的图像之间差异较小,肉眼看起来就成了动画。
为了实现间隔一定时间就重新调用组件的repaint()方法,可以借助于Swing提供的Timer类,Timer类是一个定时器,他有一下构造器:
Timer(int delay,ActionListener listener);每间隔delay毫秒,系统自动触发ActionListener监听器里的事件处理器方法,在方法内部我们就可以调用组件的repaint()方法,完成组件重绘。
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class PinBall {
//创建窗口对象
private Frame frame = new Frame("弹球游戏");
//桌面宽度
private final int TABLE_WIDTH = 300;
//桌面高度
private final int TABLE_HEIGHT = 400;
//球拍的高度和宽度
private final int RACKET_WIDTH = 60;
private final int RACKET_HEIGHT = 20;
//小球的大小
private final int BALL_SIZE = 16;
//定义变量,记录小球的坐标
private int ballX = 120;
private int ballY = 20;
//定义变量,记录小球在x和y方向上分别移动的速度
private int speedY = 10;
private int speedX = 5;
//定义变量,记录球拍的坐标
private int racketX = 120;
private final int racketY = 340;
//定义变量,标识当前游戏是否已结束
private boolean isOver = false;
//声明一个定时器
private Timer timer;
//自定义一个类,继承canvas,充当画布
private class MyCanvas extends Canvas{
@Override
public void paint(Graphics g) {
//TODO 在这里绘制内容
if (isOver){
//游戏结束
g.setColor(Color.BLUE);
// g.setFont(new Font("Times",Font.BOLD,30));
g.setFont(new Font("Times",Font.ITALIC,30));
g.drawString("游戏结束!",50,200);
}else{
//游戏中
//绘制小球
g.setColor(Color.RED);
g.fillOval(ballX,ballY,BALL_SIZE,BALL_SIZE);
//绘制球拍
g.setColor(Color.PINK);
g.fillRect(racketX,racketY,RACKET_WIDTH,RACKET_HEIGHT);
}
}
}
//创建绘画区域
MyCanvas drawArea = new MyCanvas();
public void init(){
//组装视图,游戏逻辑的控制
//完成球拍坐标的变化
KeyListener listener = new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
//获取当前按下的键
int keyCode = e.getKeyCode();
if (keyCode == KeyEvent.VK_LEFT){
//<- 应该向左移动
if (racketX>0){
racketX -=10;
}
}
if (keyCode == KeyEvent.VK_RIGHT){
//-> 应该向右移动
if (racketX < (TABLE_WIDTH-RACKET_WIDTH)){
racketX+=10;
}
}
}
};
//给Frame和drawArea注册监听器
frame.addKeyListener(listener);
drawArea.addKeyListener(listener);
//小球坐标的控制
ActionListener task = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//根据边界范围,修正速度
if (ballX<=0 || ballX >=(TABLE_WIDTH-BALL_SIZE)){
speedX = -speedX;
}
if( ballY <=0 || ( ballY > racketY-BALL_SIZE && ballX > racketX && ballX < racketX+RACKET_WIDTH)){
speedY = -speedY;
}
if (ballY > racketY-BALL_SIZE && ( ballX < racketX || ballX > racketX+RACKET_WIDTH)){
//当前小球超出了球拍的范围,游戏结束
//停止定时器
timer.stop();
//修改游戏是否结束的标记
isOver = true;
//重绘界面
drawArea.repaint();
}
//更新小球的坐标,重绘界面
ballX+=speedX;
ballY+=speedY;
//重绘界面
drawArea.repaint();
}
};
timer = new Timer(100,task);
timer.start();
drawArea.setPreferredSize(new Dimension(TABLE_WIDTH,TABLE_HEIGHT));
frame.add(drawArea);
//设置frame最佳大小,并可视
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
new PinBall().init();
}
}
2.8.3 处理位图
如果仅仅绘制一些简单的几何图形,程序的图形效果依然比较单调。AWT也允许在组件上绘制位图。Graphics提供了drawImage(Image image)方法用于绘制位图,该方法需要一个Image参数——代表位图,通过该方法就可以绘制出指定的图。
位图使用步骤:
1.创建Image的子类对象BufferedImage(int width, int height, int imageType),创建时需要指定位图的宽高及类型属性;此时相当于在内存中生成了一张图片;
2.调用BufferedImage对象的getGraphics()方法获取画笔,此时就可以往内存中的这张图片上绘图了,绘图的方法和之前学习的一模一样;
3.调用组件paint方法中提供的Graphics对象的drawImage()方法,一次性的内存中的图片BufferedImage绘制到特定的组件上。
使用位图绘制组件的好处:
使用位图来绘制组件,相当于实现了图的缓冲区,此时绘图时没有直接把图形绘制到组件上,而是先绘制到内存中的BufferedImage上,等全部绘制完毕,再一次性的图像显示到组件上即可,这样用户体验更好。
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
public class HandDraw {
//定义窗口对象
private Frame frame = new Frame("简单手绘程序");
//定义画图区的宽高
private final int AREA_WIDTH = 500;
private final int AREA_HEIGHT = 400;
//定义一个右键菜单,用于设置画笔的颜色
private PopupMenu colorMenu = new PopupMenu();
private MenuItem redItem = new MenuItem("红色");
private MenuItem greenItem = new MenuItem("绿色");
private MenuItem blueItem = new MenuItem("蓝色");
//定义一个变量,记录当前画笔的颜色
private Color forceColor = Color.BLACK;
//创建一个BufferedImage位图对象
BufferedImage image = new BufferedImage(AREA_WIDTH, AREA_HEIGHT, BufferedImage.TYPE_INT_RGB);
//通过位图,获取关联的Graphics对象
Graphics g = image.getGraphics();
//自定义一个类,继承Canvas
private class MyCanvas extends Canvas {
@Override
public void paint(Graphics g) {
g.drawImage(image, 0, 0, null);
}
}
MyCanvas drawArea = new MyCanvas();
//定义变量,记录鼠标拖动过程中,上一次所处的坐标
private int preX = -1;
private int preY = -1;
public void init() {
//组装视图,逻辑控制
ActionListener listener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String actionCommand = e.getActionCommand();
switch (actionCommand) {
case "红色":
forceColor = Color.RED;
break;
case "绿色":
forceColor = Color.GREEN;
break;
case "蓝色":
forceColor = Color.BLUE;
break;
}
}
};
redItem.addActionListener(listener);
greenItem.addActionListener(listener);
blueItem.addActionListener(listener);
colorMenu.add(redItem);
colorMenu.add(greenItem);
colorMenu.add(blueItem);
//把colorMenu设置给绘图区域
drawArea.add(colorMenu);
drawArea.addMouseListener(new MouseAdapter() {
@Override
public void mouseReleased(MouseEvent e) {//当鼠标键抬起时被调用
boolean popupTrigger = e.isPopupTrigger();
if (popupTrigger) {
colorMenu.show(drawArea, e.getX(), e.getY());
}
//重置preX和preY
preX = -1;
preY = -1;
}
});
//设置位图的背景为白色
g.setColor(Color.white);
g.fillRect(0, 0, AREA_WIDTH, AREA_HEIGHT);
//通过监听鼠标的移动,完成线条绘制
drawArea.addMouseMotionListener(new MouseMotionAdapter() {
//该方法,当鼠标左键按下,并进行拖动时,会被调用
@Override
public void mouseDragged(MouseEvent e) {
if (preX > 0 && preY > 0) {
g.setColor(forceColor);
//画线条 需要两组坐标,分别代表线条的起点和终点 e.getX(),e.getY()可以获取坐标()
g.drawLine(preX, preY, e.getX(), e.getY());
}
//修正preX和preY的值
preX = e.getX();
preY = e.getY();
//重绘组件
drawArea.repaint();
}
});
drawArea.setPreferredSize(new Dimension(AREA_WIDTH,AREA_HEIGHT));
frame.add(drawArea);
//设置frame最佳大小并可见
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
new HandDraw().init();
}
}
2.8.4 ImageIO的使用
在实际生活中,很多软件都支持打开本地磁盘已经存在的图片,然后进行编辑,编辑完毕后,再重新保存在本地磁盘,如果使用AWT要完成这样的功能,那么需要使用到ImageIO这个类,可以操作本地磁盘的图片文件。
方法名称 | 方法功能 |
static BufferedImage read(File input) | 获取本地磁盘图片文件 |
static BufferedImage read(InputStream input) | 读取本地磁盘图片文件 |
static boolean write(RenderedImage im, String formatName, File output) | 往本地磁盘中输出图片文件 |
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
public class ReadAndSaveImage {
private Frame frame = new Frame("图片查看器");
MenuBar menuBar = new MenuBar();
Menu menu = new Menu("文件");
MenuItem open = new MenuItem("打开");
MenuItem save = new MenuItem("另存为");
//声明BufferedImage对象,记录本地存取到内存中的图片
BufferedImage image;
private class MyCanvas extends Canvas{
@Override
public void paint(Graphics g) {
g.drawImage(image,0,0,null);
}
}
MyCanvas drawArea = new MyCanvas();
public void init() throws Exception{
//组装视图
open.addActionListener(e->{
//打开一个文件对话框
FileDialog fileDialog = new FileDialog(frame,"打开图片",FileDialog.LOAD);
fileDialog.setVisible(true);
//获取用户选择的图片路径以及名称
String dir = fileDialog.getDirectory();
String fileName = fileDialog.getFile();
try {
image = ImageIO.read(new File(dir,fileName));
drawArea.repaint();
} catch (IOException ex) {
ex.printStackTrace();
}
});
save.addActionListener(e->{
//展示一个文件对话框
FileDialog fileDialog = new FileDialog(frame,"保存图片",FileDialog.SAVE);
fileDialog.setVisible(true);
//获取用户设置的保存路径以及文件名称
String dir = fileDialog.getDirectory();
String fileName = fileDialog.getFile();
try {
ImageIO.write(image,"JPEG",new File(dir,fileName));
} catch (IOException ex) {
ex.printStackTrace();
}
});
menu.add(open);
menu.add(save);
menuBar.add(menu);
//把菜单条放入到窗口中
frame.setMenuBar(menuBar);
frame.add(drawArea);
frame.setBounds(200,200,740,508);
frame.setVisible(true);
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
public static void main(String[] args) throws Exception {
new ReadAndSaveImage().init();
}
}
2.8.5 五子棋
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.File;
public class Gobang {
//定义五子棋游戏窗口
private JFrame f = new JFrame("五子棋游戏");
//声明四个BufferedImage对象,分别记录四张图片
BufferedImage table;
BufferedImage black;
BufferedImage white;
BufferedImage selected;
//声明棋盘的宽和高
final int TABLE_WIDTH = 535;
final int TABLE_HEIGHT = 536;
//声明棋盘横向和纵向分别可以下多少子,他们的值都为15
final int BOARD_SIZE = 15;
//声明每个棋子占用棋盘的比率
final int RATE = TABLE_WIDTH / BOARD_SIZE;
//声明变量,记录棋子对于x方向和y方向的偏移量
final int X_OFFSET = 5;
final int Y_OFFSET = 6;
//声明一个二维数组,记录棋子, 如果索引[i][j]处的值为 0-没有棋子 1-白棋 2-黑棋
int[][] board = new int[BOARD_SIZE][BOARD_SIZE];
//声明红色选择框的坐标 该坐标其实就是二维数组board中的索引
int selected_X = -1;
int selected_Y = -1;
//自定义类,继承Canvas
private class ChessBoard extends JPanel {
@Override
public void paint(Graphics g) {
//绘图
//绘制棋盘
g.drawImage(table,0,0,null);
//绘制选择框
if (selected_X>0 && selected_Y>0){
g.drawImage(selected,selected_X*RATE+X_OFFSET,selected_Y*RATE+Y_OFFSET,null);
}
//绘制棋子
for (int i = 0; i < BOARD_SIZE; i++) {
for (int j = 0; j < BOARD_SIZE; j++) {
//绘制黑棋
if (board[i][j] == 2){
g.drawImage(black,i*RATE+X_OFFSET,j*RATE+Y_OFFSET,null);
}
//绘制白棋
if (board[i][j] == 1){
g.drawImage(white,i*RATE+X_OFFSET,j*RATE+Y_OFFSET,null);
}
}
}
}
}
ChessBoard chessBoard = new ChessBoard();
//声明变量,记录当前下棋的颜色
int board_type = 2;
//声明底部需要用到的组件
Panel p = new Panel();
Button whiteBtn = new Button("白棋");
Button blackBtn = new Button("黑棋");
Button deleteBtn = new Button("删除");
public void refreshBtnColor(Color whiteBtnColor,Color blackBtnColor,Color deleteBtnColor){
whiteBtn.setBackground(whiteBtnColor);
blackBtn.setBackground(blackBtnColor);
deleteBtn.setBackground(deleteBtnColor);
}
public void init() throws Exception {
//组装视图,编写逻辑
whiteBtn.addActionListener(e->{
// 修改当前要下的棋子的标志为1
board_type = 1;
// 刷新按钮的颜色
refreshBtnColor(Color.GREEN,Color.GRAY,Color.GRAY);
});
blackBtn.addActionListener(e->{
// 修改当前要下的棋子的标志为2
board_type = 2;
// 刷新按钮的颜色
refreshBtnColor(Color.GRAY,Color.GREEN,Color.GRAY);
});
deleteBtn.addActionListener(e->{
// 修改当前要下的棋子的标志为0
board_type = 0;
// 刷新按钮的颜色
refreshBtnColor(Color.GRAY,Color.GRAY,Color.GREEN);
});
p.add(whiteBtn);
p.add(blackBtn);
p.add(deleteBtn);
f.add(p,BorderLayout.SOUTH);
//组装棋盘
//初始化图片
table = ImageIO.read(new File("awt-app\\img\\board.jpg"));
white = ImageIO.read(new File("awt-app\\img\\white.gif"));
black = ImageIO.read(new File("awt-app\\img\\black.gif"));
selected = ImageIO.read(new File("awt-app\\img\\selected.gif"));
//处理鼠标移动
chessBoard.addMouseMotionListener(new MouseMotionAdapter() {
//当鼠标移动时会调用该方法
@Override
public void mouseMoved(MouseEvent e) {
selected_X = (e.getX()-X_OFFSET)/RATE;
selected_Y = (e.getY()-Y_OFFSET)/RATE;
chessBoard.repaint();
}
});
//处理鼠标点击
chessBoard.addMouseListener(new MouseAdapter() {
//当鼠标被点击后 会调用这个方法
@Override
public void mouseClicked(MouseEvent e) {
int xPos = (e.getX()-X_OFFSET)/RATE;
int yPos = (e.getY()-Y_OFFSET)/RATE;
board[xPos][yPos] = board_type;
chessBoard.repaint();
}
@Override
public void mouseExited(MouseEvent e) {
selected_X=-1;
selected_Y=-1;
chessBoard.repaint();
}
});
chessBoard.setPreferredSize(new Dimension(TABLE_WIDTH,TABLE_HEIGHT));
f.add(chessBoard);
//设置frame最佳大小并可见
f.pack();
f.setVisible(true);
}
public static void main(String[] args) throws Exception {
new Gobang().init();
}
}
二、Swing编程
Swing编程