如果希望在 block 中,每个元素都可以指定一些横向和纵向的留白,则可以定义一个 block_with_margin 布局样式,其实现如下:
public Component block_with_margin(Component[] cps, int N, int M,
float hRatio, float vRatio) {
Component[] ncps = new Component[cps.length];
for(int i=0; i<cps.length; i++) {
ncps[i] = center(cps[i], hRatio, vRatio);
}
return block(ncps, N, M);
}
好了,现在我们来看一个稍微复杂一些的例子,我们将使用前面制作的一些布局样式构建一个迷你计算器的外观,如下图所示:
对应的描述代码如下:
Component[] cs = new Component[]{
Button().title("0"),
Button().title("1"),
Button().title("2"),
Button().title("+"),
Button().title("3"),
Button().title("4"),
Button().title("5"),
Button().title("-"),
Button().title("6"),
Button().title("7"),
Button().title("8"),
Button().title("*"),
Button().title("9"),
Button().title("="),
Button().title("%"),
Button().title("/")
};
Component opLayout = block(cs,4,4);
above( above( TextField(),
beside( Button().title("Backspace"), Button().title("C"),0.5), 0.5),
block(cs,4,4), 0.3).at(0,0,300,200).in(C);
如果我们现在希望将所有数字以及操作按钮按照横向和纵向各 2% 进行留白,我们所要做的仅仅是一行的改动,就是把:
Component opLayout = block(cs,4,4);
更改为:
Component opLayout = block_with_margin(cs, 4, 4, 0.02, 0.02);
这意味着什么呢?这意味着我们可以直接使用布局语言进行界面制作,我们可以直接针对布局进行编程,我们所写出来的界面代码就是我们的布局规格说明。
从上面的介绍中,读者可以看出,我们的界面布局语言可以非常方便地定义出一些常见的布局样式,还可以把这些样式组合成更为复杂的一些高阶布局样式,并且这种组合是没有任何限制的。此外,这些布局样式的定义描述方式是和界面设计者头脑中所使用的一些布局词汇和规则贴近的。通过使用界面布局语言,界面设计者完全可以摆脱那些呆板、机械又难以定制和扩展的布局管理器,可以轻松地把头脑中的布局创意直接描述出来,逐步形成自己的布局样式库,充分享受这种创造性的工作所带来的乐趣。
界面布局语言设计与实现
在本小节中,我们会对上面介绍的界面布局语言的一些设计和实现细节进行介绍。我们这里所讲解的是基于 Java Swing 的实现。读者可以根据自己的需要在其他的语言和界面开发工具包上去实现该界面布局语言。
界面布局语言的主要设计思路有两点:
在接口中遵循《Domain Driven Desing》作者 Eric Evans 提出的 FluentInterface 的概念;
语言的层次化设计。
界面布局语言所提供的接口不是 Java 语言层面上的对象接口,也不是使用基于 Java 的语法来使用这些接口构建复杂的界面。相反,我们提供了一个面向界面设计规格描述的接口,接口的语义、规则以及命名完全和界面设计中的规则、概念相符,这样就可以直接使用代码来清晰、直接地表达出界面设计中的布局概念。
在界面布局语言的设计上,我们没有采用定制的面向对象的设计,而是由一组处于不同层次的语言组成,每个层次都是通过对该层的基本原子进行组合构造而来,每个层次所构造出来的实体,则可以作为上一层语言的基本原子使用。这样,我们就在通用的 Java 语言之上,逐步构建出了一种专用于表达界面布局的语言。比起传统的对象设计,这种方法具有更高的抽象层次和通用性。
我们来看一下界面布局语言中基本原子的实现细节,先来看一下 Component 的定义:
public interface Component {
public Component at(int x, int y, int width, int height);
public Component in(Container);
……
}
Button 的实现如下:
public class Button implements Component{
public JButton btn = new JButton();
public Component title(String t){
btn.setText(t);
return this;
}
public Component at(int x, int y, int width, int height) {
Rectangle rect = new Rectangle(x,y,width,height);
btn.setBounds(rect);
return this;
}
public Component in(Container parent){
parent.add(btn);
return this;
}
……
}
从上面的代码中,读者会发现这种写法和传统的 API 写法风格的不同。在这种风格中,为了能够将调用形成一个句子,每个调用在结束时都返回了 this。另外,在给方法起名时也有不同的考虑,不只是关注于该方法的职责和功能,而是更关注于该方法名在整个句子这个上下文中是否通顺、是否更富表达力。
随着更多基本原子组件的编写,会发现 in 和 at 方法在很多组件中都重复出现,此时可以把它们提取到一个抽象基类中。这里这样写是为了清楚起见。