目录
空布局
在实现GUI界面的过程中,我们有一个非常重要的代码,那就是每个组件在放入到容器之前,要给出该组件的大小以及位置(setBounds方法)。容器就会根据我们设置的大小位置来放置组件,这样的布局方式我们把它叫做空布局。也就是说要想有这样的效果,我们必须把设置容器的布局管理器为null -- setLayout(null)。
其实在Java的GUI当中,Java提供了一些自带的布局管理器,容器也默认使用了这些布局管理器。如果我们不把它设置为null,那么就会按照容器默认的布局管理器来布局,我们设置的大小位置将不起作用。
空布局在使用的过程中,我们发现在简单一些的界面上,我们使用它还凑合。但是如果是一个复杂页面,特别是组件很多的情况下,那么每个组件都要去设置会极大的增加我们的工作量。
所谓的面板嵌套,就是把一个复杂界面拆分成多个简单的,然后再组合到一起。那么在学习的过程中,必然会引入两个概念: 1、面板 -- 中间容器; 2、布局管理器 -- 在一起特定的场景中减少我们布局的工作量。
在Java的GUI中除了使用空布局以外,它还提供了多种自带的布局方式,其中最著名也是最常用的是: 1、流布局 2、边界布局 3、网格布局 4、卡片布局
流布局
流布局的实现类类名叫做FlowLayout。
其实不仅仅是流布局,所有的布局管理器使用都很简单,就是在容器的setLayout方法中传入它的对象就可以了。
演示
public class FlowFrame extends JFrame {
private Container contentP;
private JButton btn1;
private JButton btn2;
private JButton btn3;
private JButton btn4;
private JTextField inputTxt;
public FlowFrame(){
this.setSize(300,400);
this.setLocationRelativeTo(null);
this.setTitle("流布局窗体");
this.setResizable(false);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.addContent();
this.setVisible(true);
}
private void addContent() {
//准备窗体中真正放置组件的容器 -- 内容面板
this.contentP = this.getContentPane();
this.contentP.setBackground(Color.WHITE);
this.contentP.setLayout(new FlowLayout()); //设置内容面板为流布局
this.btn1 = new JButton("按钮1");
this.btn2 = new JButton("按钮2");
this.btn3 = new JButton("按钮3");
this.btn4 = new JButton("按钮4");
this.inputTxt = new JTextField();
// this.inputTxt.setSize(100,30);//这句代码无效
this.inputTxt.setColumns(20);//要使用特有代码setColumns
this.contentP.add(this.btn1);
this.contentP.add(this.btn2);
this.contentP.add(this.btn3);
this.contentP.add(this.btn4);
this.contentP.add(this.inputTxt);
}
}
效果
1、组件的大小由组件的内容自动设置; 问题:如果一个组件在初始化的时候没有内容,那么在流布局中,这个组件将变得很小,不适用。
2、组件的位置是按从左往右,从上往下,由中间开始的方式进行排列。 问题:如果在运行过程中,窗体的尺寸发生了变化,那么界面上的组件会重新按照流布局的排列规范重新布局。
使用场景
1、不要让容器大小在运行时发生改变;
2、最好应用于小范围的容器,单行的最好。
边界布局管理器
类名 -- BorderLayout
演示
public class BorderFrame extends JFrame {
private Container contentP;
private JButton btn1;
private JButton btn2;
private JButton btn3;
private JButton btn4;
private JButton btn5;
public BorderFrame(){
this.setSize(300,400);
this.setLocationRelativeTo(null);
this.setTitle("边界布局窗体");
this.setResizable(false);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.addContent();
this.setVisible(true);
}
private void addContent() {
this.contentP = this.getContentPane();
this.contentP.setLayout(new BorderLayout());
this.btn1 = new JButton("按钮1按钮1按钮1");
this.btn2 = new JButton("按钮2");
this.btn3 = new JButton("按钮3");
this.btn3.setFont(new Font("宋体",Font.ITALIC,30));
this.btn4 = new JButton("按钮4");
this.btn5 = new JButton("按钮5");
this.contentP.add(this.btn1,BorderLayout.EAST);
this.contentP.add(this.btn2,BorderLayout.NORTH);
this.contentP.add(this.btn3,BorderLayout.SOUTH);
this.contentP.add(this.btn4,BorderLayout.WEST);
// this.contentP.add(this.btn5,BorderLayout.CENTER);//默认放中间
}
}
效果
1、BorderLayout的布局,它是按打麻将的方式,把整个容器划分成了“东西南北中”5个区域,如果在添加组件的时候不指定区域,那么就放到中间;
2、放入该区域的组件会自动填充整个区域。
3、当周边区域不存在的时候,中间区域会占领周边;反过来,中间如果不在,那么周边是不会占领中间的。
4、周边区域的大小是可以根据放入该区域组件的内容的大小决定的。
总结:边界布局的特点是 东西南北中,南北要贯通,中间最大。这里的中间最大不是指中间的位置,而是指中间的权利最大。
使用场景
1、边界布局的特点决定了它就不是用来放置某个组件的,而是用来在容器中给中间容器划分位置的;
2、常见的只有一种组件能够独立占据某个区域,就是图片;
3、JFrame的内容面板默认使用的就是边界布局管理器。
网格布局管理器
类名: GridLayout
其实通过类名我们就知道了,它是把整个容器按行列划分为等大的几行几列的网格状区域。
演示
public class GridFrame extends JFrame {
private Container contentP;
private JButton btn1;
private JButton btn2;
private JButton btn3;
private JButton btn4;
private JButton btn5;
public GridFrame(){
this.setSize(300,400);
this.setLocationRelativeTo(null);
this.setTitle("网格布局窗体");
this.setResizable(false);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.addContent();
this.setVisible(true);
}
private void addContent() {
this.contentP = this.getContentPane();
this.contentP.setLayout(new GridLayout(3,2));
this.btn1 = new JButton("按钮1");
this.btn2 = new JButton("按钮2");
this.btn3 = new JButton("按钮3");
this.btn4 = new JButton("按钮4");
this.btn5 = new JButton("按钮5");
this.contentP.add(this.btn1);
this.contentP.add(this.btn2);
this.contentP.add(this.btn3);
// this.contentP.add(this.btn4);
// this.contentP.add(this.btn5);
// this.contentP.add(new JButton("按钮6"));
// this.contentP.add(new JButton("按钮7"));
}
}
效果
1、网格布局管理器会根据new的时候给出的行列,把整个容器划分为等的几个网格;
2、放入的顺序是根据add的顺序,排列的时候是先放满行中所有的列,然后再换行;
3、假如划分的网格的数目与add的组件数目不一致:那么它会保证行不变,通过变化最小的列来装入所有add进来的组件。
4、放入某个网格的组件会自动充满整个区域。
适用场景
网格布局的主要任务也是在容器上划分空间,然后在空间中放入中间容器,很少直接放一个组件。
卡片布局管理器
类名:CardLayout
卡片布局管理器是一个非常实用的布局方式,并且没有替代。其它的布局管理其实都可以用null布局搞定,只要你不怕麻烦。
卡片布局的效果是null搞不定的,它是一种贴层的方式,每次显示一层。
演示
public class CardFrame extends JFrame {
private Container contentP;//内容面板
private MainPanel mainP;//主体内容
private ButtonPanel btnP;//按钮
public CardFrame(){
this.setSize(400,400);//窗口大小
this.setLocationRelativeTo(null);
this.setTitle("卡片布局演示");
this.setResizable(false);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.addContent();
this.setVisible(true);
}
private void addContent() {
this.contentP = this.getContentPane();//获取内容面板
this.contentP.setLayout(new BorderLayout());//边界布局
this.mainP = new MainPanel();//new出主体内容
this.btnP = new ButtonPanel(this);//new出按钮
this.contentP.add(this.mainP);//主体内容放进内容面板
this.contentP.add(this.btnP,BorderLayout.SOUTH);
}
//get、set方法
public Container getContentP() {
return contentP;
}
public void setContentP(Container contentP) {
this.contentP = contentP;
}
public MainPanel getMainP() {
return mainP;
}
public void setMainP(MainPanel mainP) {
this.mainP = mainP;
}
public ButtonPanel getBtnP() {
return btnP;
}
public void setBtnP(ButtonPanel btnP) {
this.btnP = btnP;
}
}
public class MainPanel extends JPanel {
//四张卡牌 四个类
private SpringPanel springP;
private SummerPanel summerP;
private AutumnPanel autumnP;
private WinterPanel winterP;
public MainPanel(){
this.setBackground(Color.BLACK);
this.setLayout(new CardLayout());//主体内容mainP 是放置4张卡片的,所以设置为卡片布局
this.springP = new SpringPanel();//new出四张卡牌内容
this.summerP = new SummerPanel();
this.autumnP = new AutumnPanel();
this.winterP = new WinterPanel();
/*
往设置为卡片布局的容器中放入卡片,必须对每张卡片起一个别名;
别名不能重复。
第一个被add进去的卡片是被默认显示的,也就是最上面。
*/
this.add("spring",this.springP);
this.add("summer",this.summerP);
this.add("autumn",this.autumnP);
this.add("winter",this.winterP);
}
}
public class ButtonPanel extends JPanel {
private JButton firstBtn;
private JButton preBtn;
private JButton nextBtn;
private JButton lastBtn;
private JButton fallBtn; //固定跳秋天
public ButtonPanel(CardFrame cardFrame){
this.setBackground(Color.WHITE);
this.setLayout(new FlowLayout());
this.firstBtn = new JButton("|<");
this.firstBtn.addActionListener(new ActionListener() {//匿名对象new出监听器
@Override
public void actionPerformed(ActionEvent e) {//点击按钮时
MainPanel mainPanel = cardFrame.getMainP();//窗体的主题内容 赋值给变量
CardLayout cardLayout = (CardLayout) mainPanel.getLayout();//窗体的主题内容 强转为卡牌管理
cardLayout.first(mainPanel);
}
});
this.preBtn = new JButton("<<");
this.preBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
MainPanel mainPanel = cardFrame.getMainP();
CardLayout cardLayout = (CardLayout) mainPanel.getLayout();
cardLayout.previous(mainPanel);
}
});
this.nextBtn = new JButton(">>");
this.nextBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//1、首先获取到被设置卡片布局管理的那个容器;
MainPanel mainPanel = cardFrame.getMainP();
//2、通过这个容器获取到设置在它身上的卡片布局管理器对象。
CardLayout cardLayout = (CardLayout) mainPanel.getLayout();
//3、调用这个卡片布局管理器对象的方法完成翻页
cardLayout.next(mainPanel);
}
});
this.lastBtn = new JButton(">|");
this.lastBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
MainPanel mainPanel = cardFrame.getMainP();
CardLayout cardLayout = (CardLayout) mainPanel.getLayout();
cardLayout.last(mainPanel);
}
});
this.fallBtn = new JButton("秋天");
this.fallBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
MainPanel mainPanel = cardFrame.getMainP();
CardLayout cardLayout = (CardLayout) mainPanel.getLayout();
cardLayout.show(mainPanel,"autumn");//根据卡片别名跳指定卡片
}
});
this.add(this.firstBtn);
this.add(this.preBtn);
this.add(this.nextBtn);
this.add(this.lastBtn);
this.add(this.fallBtn);
}
}
综述
1、空布局 -- 这是一种万能的布局,既可以通过设置大小和位置指定放入组件,也可以通过设置大小和位置指定放入中间容器;-- 唯一的缺点就是麻烦,但好处是我们的自由度很大;
2、流布局 -- 它是在单行容器中可以快速的放入组件。
3、边界布局 -- 它主要是在容器中划分区域放入中间容器的,而且它划分区域应该呈现出东西南北中的效果;
4、网格布局 -- 它也是主要在容器中划分区域放入中间容器,只是它划分的区域必须是等大的,而且是按行列来排的网格状。
5、卡片布局 -- 它主要是对一个区域进行贴层的。
中间容器
常用的中间容器是两个:JPanel、JLable。
这两个的区别是:
1、如果中间容器是纯色背景的,请使用JPanel;
2、如果中间容器是有背景图片的,请使用JLable。
事件处理
在所有编程语言当中,用户在GUI的组件或窗体上完成了某个动作,然后我们的程序要做出相应的响应。这个现象实现有一个专业名词叫做“事件处理”。
相当于,我们认为这种现象是在界面上发生了某个事件,然后程序捕获到这个事件的发生,然后调用我们预先定义针对于这个事件响应代码。
在大多数语言当中,这个事件的处理需要2方共同完成。
1、发生事件的组件或容器 -- 事件源对象;
2、事件的触发对象 -- 程序之外的用户。
这些编程语言在代码级别只需要在事件源对象身上预先定义如果发生了什么事情,执行什么代码。
但是Java不一样,Java在设计事件处理的时候提出来的是“委托事件模型”。
委托事件模型
在Java当中的事件处理不是两方,而是三方:
1、事件源对象
2、事件触发对象(人,用户)
3、监听器对象
监听器的类型在JDK中已经给出了,只不过是接口类型。我们要书写它的实现类。
细节:
1、监听器对象写好以后必须要和事件源对象进行绑定;
2、一个监听器对象可以监听多个事件源对象;
3、一个事件源对象也可以被多个监听器对象监听;不同的监听器监听该事件源对象发生的不同事件。(监听器是分职责的,不同的职责的监听器负责不同的事件)。
所以,我们看到在JDK当中设计了很多的监听器接口,比如: 1、ActionListener -- 动作监听器 -- 它是点击事件专用的;
2、KeyListener -- 键盘监听器
3、MouseListener -- 鼠标监听器
Java事件处理实现步骤
1、根据你要处理的事件,选择正确的监听器类型;
2、书写监听器接口的实现类,把要实现的代码写到的对应的方法当中;
3、产生监听器实现类对象;
4、调用事件源的绑定方法,把该监听器对象绑定在该事件源对象身上。
三种监听器的实现方式
我们期望的是: a、满足职责单一; b、Java文件的数目要少; c、尽量少的传参。
1、书写一个独立的监听器类,让它实现监听器接口。
2、让容器类自己实现监听器接口,充当监听器。
3、用匿名内部实现监听器接口。
其它的监听器
所有的监听器使用方法都是一致的。 找到事件源对象,在它new出来之后,调用它的addXxxxListener方法,在参数中以匿名内部类的形式书写上我们的监听器对象。
大多数监听器接口都不止有一个抽象方法(ActionListener是一个特例)。那么如果我们只想实现里面的某一个,但是未了满足Java实现接口的语法,却又不得不把其它不用的抽象方法给空实现了。这些空方法虽然不会起作用,但是留在源代码中也会给我们造成困扰。
基于这种情况,Java采用了一种设计模式 -- 适配器模式。 在JDK中,针对具有多个方法的监听器接口,它都还设计了一个与之对应的类。比如: WindowListener -- WindowAdapter
KeyListener -- KeyAdapter
所有的适配器类都是主动已经实现了监听器接口,同时把接口中的抽象方法已经空实现了。所以,我们只需要new 适配器对象,然后重写我们需要用到的那个(或那几个)事件方法就可以了,其它方法不管了。
另外,适配器还有一个重要作用,就是它把几个相关的监听器合并在了一起,让我们可以一次性完成重写。