Swing借鉴Android的做法,利用XML实现自动布局
众所周知,java很强大,其强大性主要体现在成熟的社区组织、各类第三方开源的jar类库。所有的这些第三方的功能包,都极大地拓宽了java的适用范围。自动垃圾回收机制,让编写java应用变得轻松自然。
但桌面应用一直是java的软肋。长久以来,swing需要采用拖沓冗余的代码来实现复杂的界面布局、“重口味”的原生swing界面,均让Swing这一领域一直不愠不火。Swing界面刷新机制EDT里面的坑,均吓倒了一大批欲在这个领域做出点什么的新人。
个人认为,布局代码“不直观”,是摆在swing开发者尤其是面前的第一道大关。但是这个问题在安卓领域却早已拥有成熟的解决方案:采用xml方式布局。那么,比安卓历史更久的Swing技术为何还没有一个简单易用的xml布局工具?
举个例子:
为了实现以下的界面,
我们要写这么长的布局代码:
private void initViews()
{
this.editServerIp = new JTextField(16);
this.editServerPort = new JTextField(5);
this.editServerIp.setForeground(new Color(13, 148, 252));
this.editServerPort.setForeground(new Color(13, 148, 252));
this.editServerIp.setText("rbcore.openmob.net");
this.editServerPort.setText("7901");
this.btnLogin = new JButton("登陆");
this.btnLogin.setUI(new BEButtonUI().setNormalColor(BEButtonUI.NormalColor.blue));
this.btnLogin.setForeground(Color.white);
this.editLoginName = new JTextField(22);
this.editLoginPsw = new JPasswordField(22);
this.btnLogout = new JButton("退出");
this.viewMyid = new JLabel();
this.viewMyid.setForeground(new Color(255, 0, 255));
this.viewMyid.setText("未登陆");
this.btnSend = new JButton("发送消息");
this.btnSend.setUI(new BEButtonUI().setNormalColor(BEButtonUI.NormalColor.green));
this.btnSend.setForeground(Color.white);
this.editId = new JTextField(20);
this.editContent = new JTextField(20);
this.debugPane = new JTextPane();
this.debugPane.setBackground(Color.black);
this.debugPane.setCaretColor(Color.white);
Log.getInstance().setLogDest(this.debugPane);
this.imInfoPane = new JTextPane();
HardLayoutPane authPanel = new HardLayoutPane();
JPanel serverInfoPane = new JPanel(new BorderLayout());
JPanel portInfoPane = new JPanel(new BorderLayout());
portInfoPane.add(new JLabel(":"), "West");
portInfoPane.add(this.editServerPort, "Center");
serverInfoPane.add(this.editServerIp, "Center");
serverInfoPane.add(portInfoPane, "East");
authPanel.addTo(serverInfoPane, 2, true);
authPanel.nextLine();
authPanel.addTo(new JLabel("用户名:"), 1, true);
authPanel.addTo(this.editLoginName, 1, true);
authPanel.nextLine();
authPanel.addTo(new JLabel("密 码:"), 1, true);
authPanel.addTo(this.editLoginPsw, 1, true);
authPanel.nextLine();
authPanel.addTo(this.btnLogin, 1, true);
authPanel.addTo(this.btnLogout, 1, true);
authPanel.nextLine();
authPanel.addTo(new JLabel("我的id:"), 1, true);
JPanel idAndVerPanel = new JPanel();
idAndVerPanel.setLayout(new BoxLayout(idAndVerPanel, 2));
JLabel lbVer = new JLabel("v2.1b151012.1O");
lbVer.setForeground(new Color(184, 184, 184));
idAndVerPanel.add(this.viewMyid);
idAndVerPanel.add(Box.createHorizontalGlue());
idAndVerPanel.add(lbVer);
authPanel.addTo(idAndVerPanel, 1, true);
authPanel.nextLine();
HardLayoutPane toPanel = new HardLayoutPane();
toPanel.addTo(new JLabel("对方ID号:"), 1, true);
toPanel.addTo(this.editId, 1, true);
toPanel.nextLine();
toPanel.addTo(new JLabel("发送内容:"), 1, true);
toPanel.addTo(this.editContent, 1, true);
toPanel.nextLine();
toPanel.addTo(this.btnSend, 4, true);
toPanel.nextLine();
HardLayoutPane oprPanel = new HardLayoutPane();
oprPanel.addTitledLineSeparator("登陆认证");
oprPanel.addTo(authPanel, 1, true);
oprPanel.addTitledLineSeparator("消息发送");
oprPanel.addTo(toPanel, 1, true);
oprPanel.addTitledLineSeparator();
JPanel leftPanel = new JPanel(new BorderLayout());
leftPanel.add(oprPanel, "North");
JScrollPane imInfoSc = new JScrollPane(this.imInfoPane);
imInfoSc.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createEmptyBorder(0, 7, 0, 7), imInfoSc.getBorder()));
imInfoSc.setHorizontalScrollBarPolicy(31);
leftPanel.add(imInfoSc, "Center");
getContentPane().setLayout(new BorderLayout());
getContentPane().add(leftPanel, "West");
JScrollPane sc = new JScrollPane(this.debugPane);
sc.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createEmptyBorder(4, 0, 0, 2), sc.getBorder()));
sc.setHorizontalScrollBarPolicy(31);
getContentPane().add(sc, "Center");
setLocationRelativeTo(null);
setSize(1000, 700);
}
界面很漂亮,但是以上的代码长度让我不由得哼起来“这让我感到绝望,董小姐”。
难道写swing界面的人,真的要变成码农么?
画界面这么低级、无意义、费时费力的事情,为何要我们一遍又一遍地靠写代码完成?
难道就没有一种更好的方式去解决这个问题吗?
安卓早已有更成熟的做法,为何不能直接拿来用?
于是,经过一夜的苦思冥想,我终于找到了下面的方式:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Container>
<Container layout="BorderLayout">
<JPanel id="leftPanel" position="West" layout="BorderLayout">
<HardLayoutPane id="oprPanel" position="North">
<TitledLineSeparator>登陆认证</TitledLineSeparator>
<HardLayoutPane id="authPanel" w="1">
<JPanel id="serverInfoPane" w="2" layout="BorderLayout">
<JTextField id="editServerIp" position="Center" columns="16" foreground="13, 148, 252">rbcore.openmob.net</JTextField>
<JPanel id="portInfoPane" position="East" layout="BorderLayout">
<JLabel position="West">:</JLabel>
<JTextField id="editServerPort" position="Center" columns="5" foreground="13, 148, 252">7901</JTextField>
</JPanel>
</JPanel>
<NextLine />
<JLabel w="1">用户名:</JLabel>
<JTextField id="editLoginName" columns="22" w="1"></JTextField>
<NextLine />
<JLabel w="1">密 码:</JLabel>
<JPasswordField id="editLoginPsw" columns="22" w="1"></JPasswordField>
<NextLine />
<JButton id="btnLogin" w="1" UI="BEButtonUI|NormalColor.blue" foreground="white">登陆</JButton>
<JButton id="btnLogout" w="1">退出</JButton>
<NextLine />
<JLabel w="1">我的id:</JLabel>
<JPanel id="idAndVerPanel" layout="BoxLayout|2" w="1">
<JLabel id="viewMyid" foreground="255, 0, 255">未登陆</JLabel>
<horizontalGlue />
<JLabel id="lbVer" foreground="184, 184, 184">v2.1b151012.1O</JLabel>
</JPanel>
<NextLine />
</HardLayoutPane>
<TitledLineSeparator>消息发送</TitledLineSeparator>
<HardLayoutPane id="toPanel" w="1">
<JLabel w="1">对方ID号:</JLabel>
<JTextField id="editId" columns="38" w="3"></JTextField>
<NextLine />
<JLabel w="1">发送内容:</JLabel>
<JTextField id="editContent" columns="38" w="3"></JTextField>
<NextLine />
<JButton id="btnSend" UI="BEButtonUI|NormalColor.green" w="4" foreground="white">发送消息</JButton>
<NextLine />
</HardLayoutPane>
<TitledLineSeparator>我的自定义</TitledLineSeparator>
<HardLayoutPane id="refPanel" w="1" xmlRef="refPanel.xml" />
<TitledLineSeparator />
</HardLayoutPane>
<JScrollPane id="imInfoSc" position="Center" border="CompoundBorder|0,7,0,7" horizontalScrollBarPolicy="31">
<JTextPane id="imInfoPane" />
</JScrollPane>
</JPanel>
<JScrollPane id="sc" position="Center" border="CompoundBorder|4,0,0,2" horizontalScrollBarPolicy="31">
<JTextPane id="debugPane" background="black" caretColor="white" />
</JScrollPane>
</Container>
我将上述的复杂界面代码,转换成为一个完整的xml描述文件。 所有组件的属性,均可以在xml的节点中*增加。配合自己写的高度定制的布局xml解析器,从此界面布局这一“Boring Zone”终于可以自动化啦!
终于,写Swing应用不再那么费时费力,而布局,将清晰呈现。配合采用反射技术写成的xml解析器,可以实现xml里面的组件ID与后台的组件声明自动绑定。写Swing程序,从此将和开发Web应用一样容易。今后的开发步骤基本为:
1. 写xml描述文件
2. 后台事件绑定
其基本原理,已经和web的jquery开发相当接近!加入组件双向绑定之后,其开发体验将和angularJS接近!
后台要获取一个组件,可以采用类似Jquery的方式:
JButton btnSend = (JButton)X$.get(“btnSend”);
绑定事件照旧这样写:
btnSend.addActionListener(new ActionListener(){
…
});
好了,这样做之后,就真正做到了布局代码XML化,如果需要修改界面布局,只需要轻松地修改XML代码即可。后台强大的XML解析器会自动读取最新的xml布局文件,并重绘整个新的布局文件!
采用XML布局方式只会带来了哪些变化?
《Swing参照Android采用XML的方式自动布局》这样带来了什么变化呢?变化就是,这样的思想可以大大加快Java桌面应用的开发效率。从今往后,用java写桌面应用再也不用枯燥地用手工代码搭积木了,取而代之的,是层次化非常清晰的XML代码。一个XML布局描述文件就是一个JAVA桌面软件的界面!用此法开发桌面程序,省下来的时间将会相当可观!并且XML的树形结构特性很适合做布局文件组件化,这样,我们可以构建出一些XML组件库,然后基于手里的工具箱快速生成新应用的界面,成就感爆棚!
总结:
时间就是金钱,合理地使用框架,可以化繁为简,让以往让人畏惧的繁琐工作不再可怕。