例如,当我们使用布局语言在一个 JFrame 上坐标位置为 (0,0) 展示一个 width 为 200,height 为 60 的按钮时,我们可以这样来描述(为了简洁起见,后面的代码实例中均略去 Layout 名字空间前缀):
Button().title(“button1”).at(0,0,200,60).in(this.getContentPane());
仅仅提供这样一种原子元素的语言显然无法满足我们前面提到的目标。在我们的界面布局语言中,还提供了两种在布局中非常常用的两种从已有组件构造新组件的组合手段:above 和 beside。其中 above 组合子接收 3 个参数:两个现有 Component 以及一个比例,它会产生出一个新的复合 Component,其中按照给定的比例把第一个 Component 摆放在第二个 Component 之上。Beside 组合子接收同样的 3 个参数,并且也产生出一个新的复合 Component,其中按照给定的比例把第一个 Component 摆放在第二个Component左边。
例如,如果我们希望在一个给定的 Container C 上的 Rectangle(0,0,300,40) 中,平行摆放一个 TextField 和一个 Button,且希望 TextField 占据 80% 的比例时,可以这样来描述:
beside(TextField(), Button().title(“ok”), 0.8).at(0,0,300,40).in(C)
同样,我们可以使用 above 来进行如下描述:
above(TextField(), Button().title(“ok”), 0.5).at(0,0,300,60).in(C)
值得注意的是,在我们的界面布局语言中,Component 在 beside 和 above 操作下是封闭的,也就是说 beside 和 above 操作的结果同样也是 Component,并完全可以作为基本的 Component 来再次进行 beside 和 above 组合。这样我们就可以使用这两个简单的操作生成更加复杂的 Component 来,从而完成复杂的界面布局。比如,我们可以这样来进行描述:
Component L = beside(TextField (), Button().title(“…”), 0.8);
above(L, Button().title(“ok”), 0.5). at(0,0,300,60).in(C)
为了保证界面布局语言的完备性,我们增加了一种特殊的原子元素:Empty。它的作用只是占据一定的布局空间。比如,如果我们希望在一个布局空间中右半边放置一个 Button,左半边空置,就可以这些描述:
beside(Empty(), Button(), 0.5).at(0,0,200,40).in(C)
读者在后面可以看到,正是这个 Empty 以及 beside 和 above 操作的闭包性质为我们描述任意复杂的布局样式提供了可能。
在有了这些基础的布局元素和组合手段后,我们就可以通过组合手段来把一些典型的布局样式抽象出来。在下一小节中读者将会看到,布局语言中的 beside 和 above 组合操作其实就是 Java 中的普通方法,因此我们的布局语言中不需要什么特别的抽象手段。也就是说,我们可以直接使用 Java 中已有的抽象手段。
例如,如果我们希望抽象出这样一种布局样式,其中给定一个布局空间和一个布局组件,我们期望该组件能够按照指定的纵、横留白比例位于该布局空间的中心地带。我们可以把该布局样式抽象出来,并命名它为 center。并可以在更复杂的布局样式中把 center 当作一个基本语素使用。center 的实现如下:
public Component center(Component cp, float hRatio, float vRatio) {
float s1 = (1-2.0* hRatio)/ (1.0 - hRatio);
float s2 = (1-2.0*vRatio)/ (1.0-vRatio);
Component u = above(Empty(), above(cp, Empty(), s2), vRatio);
return beside(Empty(), beside(u,Empty(), s1), hRatio);
}
当我们想把一个按钮放置按照在横向 0.2,纵向 0.1 的留白比例放在布局空间 (0,0,100,30) 中时,我们可以简单的进行如下描述:
center(Button().title(“I am at center.”), 0.1,0.1).at(0,0,300,60).in(C)
我们还可以构建出 h_seq 和 v_seq 这样的布局样式,它们分别为把一组给定的布局元素横向顺序排列和纵向顺序排列,其实现如下:
public Component h_seq(Component[] cps) {
int len = cps.length;
if(len == 1) return cps[0];
return beside(cps[0], h_seq(slice(cps, 1, len)), 1.0/len);
}
public Component v_seq(Component[] cps) {
int len = cps.length;
if(len == 1) return cps[0];
return above(cps[0], v_seq(slice(cps, 1, len)), 1.0/len);
}
其中 slice 方法有 3 个参数,一个为布局元素数组,另外两个为区间的起止位置,该方法把给定布局元素数组中指定起止位置的区间部分作为一个新的布局元素数组返回。这两个方法的实现都比较简单直接。下面是两个应用例子:
Component[] cps = new Component[]
{ Button().title(“1”), Button().title(“2”), Button().title(“3”) };
h_seq(cps).at(0,0,300,60).in(C)
v_seq(cps).at(0,0,150,200).in(C)
在 center、h_seq、v_seq 这些布局样式的基础上,我们可以定义出更加高阶的样式来,比如,给定一布局元素序列,我们希望它们在给定的布局空间中按照 N 行、M 列排列。我们称之为 block,其实现如下:
public Component block (Component[] cps, int N, int M) {
Component[][] fcps = formalize(cps, N, M);
Component[] rows = new Component[fcps.length];
for(int i = 0; i < fcps.length; i++) {
rows[i] = h_seq(fcps[i]);
}
return v_seq(rows);
}
其中 formalize 是一个工具方法,它把一个给定的布局元素数组规范化为 N 行 M 列的形式,如果不足则用 Empty 组件补齐。