re管理器Java_自定义布局管理器-FormLayout
第二部分:自定义布局管理器
在java.awt包与javax.swing包下有许多现成的布局类,比如BorderLayout、FlowLayout,还有较为复杂的、用于精确定位的布局类GridBagLayout、SpringLayout等。起初我刚刚从事gooey时(06年中),企图依靠JDK自带的布局类进行布局,但是实际不可能或者说很难做到。对于复杂的GridBagLayout、SpringLayout来说又望而生畏,而且使用GridBagLayout、SpringLayout来完成布局的话工作量相当可观,因此当时放弃了布局管理器,采用ComponentListener等尺寸监听事件来布局组件。虽然这种做法没有工具支持、采用手工coding,但是自由度上升了很多,而且熟悉了以后编码效率也大幅其高。与此同时,我开始接触SWT,发现org.eclipse.swt.layout.FormLayout布局很强大、用起来爱不释手、要多好有多好、要多强有多强......。于是当时我用来布局组件的方式是采用ComponentListener监听与FormLayout结合的方式,也是在同期,我领悟到了九宫图这种专业布局,因此之后九宫图的实现也都采用上述两种方法。随着对SWT的不断了解外加IM软件界面的专业性,我发现SWT并不非常适合做专业外观,也因为此我逐渐将精力转向Swing。
在介绍如何编写自定义布局管理器前,我想先把SWT体系下的FormLayout布局(表单布局)特点做个简要介绍。
SWT体系下的FormLayout是非常灵活、精确的布局,FormLayout布局组件的特点是采用百分比+偏移量的方式。前者可以应付容器尺寸变化时内部组件随之等比例调整;后者以应付精确的布局。这一特征是通过org.eclipse.swt.layout.FormData和org.eclipse.swt.layout.FormAttachment两个类来实现。
通常使用FormLayout来定位一个组件要确定4个FormAttachment对象:top、bottom、left、right,即组件的4条边。而且通常是使用FormAttachment(int numerator,int offset)这个构造器,也就是百分比+偏移量。当然FormAttachment不只这一种,但是都是可选的,如果想深入研究FormLayout可以参阅SWT相关的介绍。
下面给出一段SWT示例程序:
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setText("SWT Application");
shell.setLayout(new FormLayout());
final Button button = new Button(shell, SWT.NONE);
button.setText("button");
final FormData formData = new FormData();
formData.top = new FormAttachment(20, 0);
formData.left = new FormAttachment(50, 0);
formData.bottom = new FormAttachment(20, 30);
formData.right = new FormAttachment(50, 50);
button.setLayoutData(formData);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}运行效果如下:
由运行效果可以看出,FormLayout通过指定组件的四条边来完成布局。
FormLayout很强大、灵活,但是AWT、Swing包中却没有,但是不等于说不能实现,学习了上文之后当然可以移植到Swing中来。
SWT中使用FormLayout还要结合FormData(表单数据)与FormAttachment(表单附件)。下面给出这两个移植过来的类实现
public final class FormAttachment {
float percentage; // 这个参数与SWT中的不同,不叫numerator,而是其等价的小数形式
int offset;
public FormAttachment(float percentage, int offset) {
this.percentage = percentage;
this.offset = offset;
}
}
public final class FormData {
public FormAttachment left;
public FormAttachment right;
public FormAttachment top;
public FormAttachment bottom;
}
你应该了解坐标系的概念,Java中的坐标系以向右、向下为正方向。因此对于offset,正值是向右、向下偏移;负值是向左、向上偏移。
与SWT的FormAttachment稍有不同的是,我自定义的构造器第一个参数是float类型,它代表的意思与“FormAttachment(int numerator,int offset)”相同,都是表示百分比,只不过前者用整数表示,后者用小数表示。例如SWT中“new FormAttachment(20,0);”用后者表示就是“new FormAttachment(0.2f,0);”。
在FormLayout布局中,定位一个组件需要最多4个FormAttachment对象,但是可以不必全部指定,稍后可以看到缺省的行为。
如果你的布局管理器比较简单,可以实现LayoutManager接口。但是正如上文所述,LayoutManager的addLayoutComponent(String name, Component comp)方法是必须通过java.awt.Container类的“Component add(String name, Component comp)”方法触发调用,其中的字符串参数指定了布局信息。但是字符串表达方式很有限,因此应当采用LayoutManager2接口,这样,addLayoutComponent(Component comp, Object constraints)方法被调用时,“Object constraints”可以是任何类型的对象,很方便。下面逐步实现这个类。
首先搭建的原型如下
public final class FormLayout implements LayoutManager2 {
public void addLayoutComponent(Component comp, Object constraints) {}
public float getLayoutAlignmentX(Container target) {
return 0;
}
public float getLayoutAlignmentY(Container target) {
return 0;
}
public void invalidateLayout(Container target) {}
public Dimension maximumLayoutSize(Container target) {
return null;
}
public void addLayoutComponent(String name, Component comp) {}
public void layoutContainer(Container parent) {}
public Dimension minimumLayoutSize(Container parent) {
return null;
}
public Dimension preferredLayoutSize(Container parent) {
return null;
}
public void removeLayoutComponent(Component comp) {}
}
再声明一个保存组件与布局信息对应关系的映射:private final Map componentConstraints = new HashMap();
接着完成addLayoutComponent方法的实现。在完成编写之前我们看一下怎样去使用FormLayout以做到心中有数。下面的一段代码是调用FormLayout示例:
getContentPane().setLayout(new FormLayout());
JButton button = new JButton();
button.setText("button");
FormData formData = new FormData();
formData.top = new FormAttachment(0.2f, 0);
formData.left = new FormAttachment(0.5f, 0);
formData.bottom = new FormAttachment(0.2f, 30);
formData.right = new FormAttachment(0.5f, 50);
getContentPane().add(button,formData);
如上所示,当调用“getContentPane().add(button,formData);”时,布局类的 public void addLayoutComponent(Component comp, Object constraints)方法便会调用,constraints参数就是FormData对象。所以在addLayoutComponent方法中需要做的就是把组件与布局信息关联起来。下面是完整实现:
public void addLayoutComponent(Component comp, Object constraints) {
if (constraints == null) {
throw new IllegalArgumentException("constraints can't be null");
} else if (!(constraints instanceof FormData)) {
throw new IllegalArgumentException("constraints must be a " + FormData.class.getName() + " instance");
} else {
synchronized (comp.getTreeLock()) {
FormData formData = (FormData) constraints;
if (formData.left == null || formData.top == null) {
throw new IllegalArgumentException("left FormAttachment and top FormAttachment can't be null");
}
componentConstraints.put(comp, (FormData) constraints);
}
}
}
前面的合法性检查是必需的,你懂的。然后比较重要的就是“synchronized (comp.getTreeLock()) ”,这是保障在多线程的环境下能安全执行,如果你察看JDK源码布局类的实现,会发现这个同步多次用到,我这么用也是参考JDK的实现。关于getTreeLock的实现在JDK6.0源码中是这样实现的。
public abstract class Component implements ImageObserver, MenuContainer,Serializable {
...
static final Object LOCK = new AWTTreeLock();
static class AWTTreeLock {}...
public final Object getTreeLock() {
return LOCK;
}...
}
还要注意的是传入的FormData实例的left、top FormAttachment必须要给出,因为这两个FormAttachment代表的是Location(位置)信息,因此必须指定。对于right、bottom可以不指定,但是如果不指定的话,必须能从getPreferredSize()中得到信息,否则组件的尺寸将无法确定。
对于addLayoutComponent(String name, Component comp)方法,由于通过查看源码发现“实现了LayoutManager2 接口的布局类该方法永远不会被调用”(未来的JDK版本如何实现不能保证),所以该方法空实现,并在注视上作@deprecated标记。
/**
* @deprecated
*/
public void addLayoutComponent(String name, Component comp) {}
除了layoutContainer方法,其余方法均很简单。一并给出:
public float getLayoutAlignmentX(Container target) {
return 0.5f;
}
public float getLayoutAlignmentY(Container target) {
return 0.5f;
}
public void invalidateLayout(Container target) {}
public Dimension maximumLayoutSize(Container target) {
return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
}
public Dimension minimumLayoutSize(Container target) {
return new Dimension(0, 0);
}
public Dimension preferredLayoutSize(Container target) {
return new Dimension(0, 0);
}
public void removeLayoutComponent(Component comp) {
synchronized (comp.getTreeLock()) {
componentConstraints.remove(comp);
}
}
根据上文所述,这些方法不难理解。其实对于FormLayout来说,...LayoutSize(Container target)、getLayoutAlignmentX等方法不是很重要。重要的是public void layoutContainer(Container target)的实现,也是所有布局类最重要的一个类。
首先该方法的第一步也要套上 synchronized (target.getTreeLock()) {},所有的代码放入同步块中。接下来是:
final int w = parent.getWidth();
final int h = parent.getHeight();
final Component[] components = parent.getComponents();
for (final Component comp : components) {}
不难理解,是要首先获取容器当前的长、高,然后遍历容器内的所有组件逐一进行布局。下面的工作就是在for循环体中做文章了。循环体第一段是
final FormData formData = componentConstraints.get(comp);
if (formData == null) {
continue;
}
因为在addLayoutComponent(Component comp, Object constraints)方法中已经关联了组件与布局信息,所以可以通过componentMap.get(comp)这一行得到组件的布局信息,加上空值判断确保代码万无一失。
接下来取出4个FormAttachment对象,表示组件的四条边。
final FormAttachment left = formData.left;
final FormAttachment right = formData.right;
final FormAttachment top = formData.top;
final FormAttachment bottom = formData.bottom;
然后计算Location信息(组件左上角的坐标)x、y:
final int x = (int) (left.percentage * w) + left.offset; // 左边的x坐标
final int y = (int) (top.percentage * h) + top.offset; // 上边的y坐标
计算方法就是FormLayout的布局方式:(百分比*容器尺寸)+像素偏移量。
然后计算组件的长、高,width、height:
final int width;
final int height;
if (right == null || bottom == null) {
final Dimension size = comp.getPreferredSize();
if (size == null) {
throw new RuntimeException("If right FormAttachment or bottom FormAttachment is null,the component must have preferred-size");
} else {
width = size.width;
height = size.height;
}
} else {
final int x2 = (int) (right.percentage * w) + right.offset; // 右边的x坐标
final int y2 = (int) (bottom.percentage * h) + bottom.offset; // 下边的y坐标
width = x2 - x;
height = y2 - y;
}
计算时根据给出right与bottom布局分为两种情况,如果未给出,那么根据组件的getPreferredSize方法得到组件的最佳大小,以这个大小决定组件的尺寸。作为规范,使用布局管理器布局不是参照组件的getSize而是参照getPreferredSize来最终决定组件的尺寸,所有布局管理器也都是这么实现的。所以如果你企图
设置组件的setSize()方法来达到在布局管理器中布局的目的是不可能的,所以你应该视图调用组件的setPreferredSize方法。接上,如果right和bottom都不是null,那么计算组件尺寸将忽略getPreferredSize,计算x2和y2的坐标,然后两坐标相减得到长宽。最后调用组件的setBounds进行最终定位。
comp.setBounds(x, y, width, height);可见对于布局管理器,其布局原理与使用绝对布局一样,调用setBounds实现,没什么特别之处。只不过是把布局单独抽出成一个类来实现罢了。
layoutContainer的完整代码如下:
public void layoutContainer(final Container parent) {
synchronized (parent.getTreeLock()) {
final int w = parent.getWidth();
final int h = parent.getHeight();
final Component[] components = parent.getComponents();
for (final Component comp : components) {
final FormData formData = componentConstraints.get(comp);
if (formData == null) {
continue;
}
final FormAttachment left = formData.left;
final FormAttachment right = formData.right;
final FormAttachment top = formData.top;
final FormAttachment bottom = formData.bottom;
final int x = (int) (left.percentage * w) + left.offset;
final int y = (int) (top.percentage * h) + top.offset;
final int width;
final int height;
if (right == null || bottom == null) {
final Dimension size = comp.getPreferredSize();
if (size == null) {
throw new RuntimeException("If right FormAttachment or bottom FormAttachment is null,the component must have preferred-size");
} else {
width = size.width;
height = size.height;
}
} else {
final int x2 = (int) (right.percentage * w) + right.offset;
final int y2 = (int) (bottom.percentage * h) + bottom.offset;
width = x2 - x;
height = y2 - y;
}
comp.setBounds(x, y, width, height);
}
}
}
作为FormLayout需要补充的是,在进行最终布局“component.setBounds(x, y, width, height);”之前,未进行逻辑判断,所以x、y可能会超出了容器的范围而width、height也可能是负值,这都会导致组件“莫名其妙”地不可见,这都不是布局管理器的问题。例如以下两行代码:
formData.left = new FormAttachment(0.5f, 30);
formData.right = new FormAttachment(0.5f, 20);
就会使组件永远不能显示,因为对于left的定位,是位于容器50%处向右30像素处,而right是位于容器50%处向右20像素处,这样组件的长度就是-10,怎么能显示出来呢?
FormLayout就介绍到这里,因为发帖只能在周末,加上最近一段时间还有别的事,能挤出一点时间真不容易。请关注下一篇CenterLayout的实现。
re管理器Java_自定义布局管理器-FormLayout相关推荐
- Swing布局管理器--BorderLayout(边框布局管理器)
概要 在向容器中添加组件时,需要考虑组件的大小和位置.如果不使用布局管理器,则需要先在纸上画好各个组件的位置并计算组件间的距离,再向容器中添加,这样虽然可以控制组件的位置,实现起来却十分麻烦. 为此j ...
- java常用布局管理器(流布局管理器、边界布局管理器、网格布局管理器)
在Swing中,每个组件在容器中都有一个具体的位置大小.而在容器中摆放各种组件时很难判断其具体位置和大小,使用布局管理器比程序员直接在容器中控制Swing组件的位置和大小方便得多,可以更加有效地处理整 ...
- Swing布局管理器--流式布局管理器
流式布局管理器 FlowLayout流式布局管理器,是JPanel和JApplet的默认布局管理 FlowLayout会将组件从上到下,从左到右的放置规律逐渐进行定位,直到占据这一行所在的空间,才会向 ...
- java中什么是布局管理器_Java中布局管理器
布局管理器种类 FlowLayout: 组件在一行中从左至右水平排列,排满后折行,它是Panel.Applet的默认布局管理器. BorderLayout:北.南.东.西.中,它是Window.Fra ...
- Django实现adminx自定义布局管理编辑界面
出于强迫症,默认的Django的Adminx后台不美化的化,使用效果欠佳.在许多Web应用程序中,用户管理是一个重要的功能,而Django提供了用户继承功能,可以轻松地扩展和定制用户模型.使用相关的插 ...
- kafka自定义生产者分区器、自定义消费者分区器
目录 1 默认分区 1.1 键key的作用 1.2 键的分区 2 生产者自定义分区 2.1 使用场景分析 2.2 自定义分区器要实现Partitioner接口 2.3 生产者使用分区器 3 消费者自定 ...
- 源码包安装和快捷管理nginx,自定义命令管理服务
安装线上的生产服务器软件包时大多会用源码安装,这是因为源码安装可以选择最新的软件包,而Linux系统自带的软件包一般都是最稳定的版本,但不能保证是最新的.源码安装还可以自行调整编译参数,最大化地定制安 ...
- 怪物猎人ol配装器java_怪物猎人ol配装器
游戏标签: 怪物猎人 多玩MHO配装器是为怪物猎人ol玩家打造的搭配游戏装备的辅助工具,是玩家的最佳配装伴侣.这款配装器集合了国内与国外配装器的优点,采用多玩原创的搜索计算公式令配装结果更快显示,让玩 ...
- 【RecyclerView】 三、RecyclerView 布局 ( 线性布局管理器 LinearLayoutManager )
文章目录 一.线性布局 1.线性布局管理器 LinearLayoutManager 2.垂直不翻转代码示例 3.水平翻转代码示例 二.完整代码示例 三.RecyclerView 相关资料 本篇博客主要 ...
最新文章
- 深度学习调参tricks总结!
- python和java一样吗-Python和Java的区别
- 国资委:九月份常州光伏出口大增 新兴市场增幅亮眼
- Java游戏地下城_地下城与勇士DNF-鬼剑士
- Ubuntu16.04彻底删除旧内核
- 网络安全-防火墙与入侵检测系统
- 2019ICPC(沈阳) (回文自动机+Palindrome Series优化dp)
- Java创建一个学生类
- Sinkhorn算法,正矩阵与双随机矩阵之间的关系
- 30岁以后的人生,如何来逆袭?
- macos 字体_巧用 iTerm2 zsh oh-my-zsh 打造炫酷的 MacOS 终端环境
- 《Xcode实战开发》——1.1节下载
- DOS常用命令(和Linux对比)
- WORD-如何解除WORD文档的锁定
- python中def _init_是什么意思_python中的__init__(self)是什么意思呢
- 艺赛旗(RPA)国家企业信用信息公示系统验证码破解(二)
- 大学计算机组装作业台式,组装一部单机作业基本型计算机的50步骤
- 项目 6 统计雇员薪水
- 引用 《大明宫词》经典台词89句
- 操作系统-AOSOA