Swing 的秘密

前言:如今 B/S 模式大行其道的情况下,桌面程序不太受到关注。但 Browsers 不可能,也没必要完全取缔 Clients。B/S 方便于维护管理,而 C/S 则在应对复杂情况时显得得心应手。

下面是我总结的一些 Java Swing 中一些不太容易触及的点。分为监听、布局、界面和特效四块。Update Irregularly


监听

监听在 Swing 中至关重要,是让其活起来的关键

KeyListener 的使用经验

3 个方法的特性

以下表述中,使用字母代替 3 个方法: P → KeyPressed,T → KeyTyped,R → KeyReleased

非输入法状态

  • 监听顺序是 P、T、R
  • T、P、R 可读取 keyChar (字符),P、R 可读取 KeyCode (键值)
  • T 无法监听 shift、ctrl、alt

输入法状态(搜狗输入法等):

  • P 无效,R 正常监听按键,T 逐个读取输入的字符

使用时的技巧

  • e.consume() 用来拦截输入、拦截复制粘贴

    此方法在 keyTyped 拦截字符,在 KeyPressed 拦截 Ctrl、Shift、Alt。在 KeyReleased 中无效(即已经输入过,反悔不了了~)

    以下代码使之只能输入数字,且不能复制输入

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public void KeyTyped(KeyEvent e){
    char i = e.getKeyChar();
    if(i < 48 || i > 57)
    e.consume();
    }

    public void KeyPressed(KeyEvent e){
    e.consume();
    }
  • 如果需要屏蔽类似 Ctrl+C 这样的组合操作,需要在 KeyPressed 中使用 e.consume()

MouseListener + MouseMotionListener 实现组件拖曳

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// cStart 是组件起始位置,mPre 是鼠标当前位置,mStart 是鼠标按下位置
Point cStart,mPre,mStart;

// c 是被拖动的组件,listen 是接收监听的组件
void drag(Component c, Component listen){
listen.addMouseListener(new MouseAdapter() {
// 监测按下动作
public void mousePressed(MouseEvent e){
cStart = c.getLocation();
mStart = e.getLocationOnScreen();
}
});
listen.addMouseMotionListener(new MouseAdapter() {
// 监测拖曳动作
public void mouseDragged(MouseEvent e){
mPre = e.getLocationOnScreen();
int x = mPre.x - mStart.x + cStart.x,
y = mPre.y - mStart.y + cStart.y;
c.setLocation(new Point(x, y));
}
});
}

这份代码实现了简单的拖曳,如有需要,可以自行扩展

建议将此单独包装成类,可以解决需要全局变量的问题,也方便快速操作

布局

觉得几个好用布局是:GridLayout,FlowLayout,BoxLayoutnull

也用过 GridBagLayout,很强大但操作过于复杂。后来发现它所面对的情况,有更精巧的解决办法

☸ 分割面板 JSplitPane

一个简单粗暴的布局方案,可以轻松完成界面的大体布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 参数使这个 JSplitPane 变为纵向分割,不填就默认为横向分割
JSplitPane spliter = new JSplitPane(JSplitPane.VERTICAL_SPLIT);

// 设置首选大小 (首选大小是组件在所有布局上的大小,包括流式布局等)
spliter.setPreferredSize(new Dimension(width, height));
// 设置分割线位置
spliter.setDividerLocation(location);
// 设置分割线宽度,如果为 0 就不显示
spliter.setDividerSize(2);
// ...

// 以下两个方法,对于横向分割,设置其左右组件;对于纵向分割,设置其上下组件
spliter.setLeftComponent(left);
spliter.setRightComponent(right);

这里需要介绍下完美适配的,让文本域可以滚动的组件:JScrollPane

1
2
3
JTextArea area = new JTextArea();
JScrollPane scroller = new JScrollPane(area);
spliter.setLeftComponent(scroller);

这波操作后,滚动面板的滚动条,就只会在需要它的时候出现

而且你发现,文本域 area 连大小都不用设置,自动布满了分配的空间

界面

JavaSwing 的界面很多人觉得不好看。From my perspective,因为它使用了系统风格的边框,And,金属风格的组件确实很丑。

☸ 取消系统默认风格边框 setUndecorated(true)

设计不同寻常的界面的第一步:

1
setUndercorated(true);

这样做之后,就只剩下一块空白的 Frame,包括关闭按钮等都需要你自己做(这也并不是难题)。也就是说你已经完全掌控这个程序的界面

☸ 改用系统风格的组件

UIManager.setLookAndFeel(...) 用来设置组件风格

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
try {
// 使用系统风格
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
e.printStackTrace();
}

// -------- 如无特殊需求,代码到这里就可以结束 ---------

// 获取 Swing 组件的默认属性表
UIDefaults uidefs = UIManager.getLookAndFeelDefaults();
// 设置分割面板的分割线的颜色为 DARK_GRAY
uidefs.put("SplitPane.background", new ColorUIResource(Color.DARK_GRAY)
// ...
);

这样,组件的风格跟系统实现了统一,界面自然美观不少

如分割线以下的代码所示,使用获取到的 UIDefaults 可以设置更多的东西,比如一般方法无法修改的分割面板的分割线颜色

☸ 使用 JLabel 代替 JButton

在不注重界面美观的情况下,可以使用 JButton。如果希望界面还可以美化,建议使用 JLabel

JButton 定位是按钮,外观上的修改,部分被限制(哪部分暂时忘了)。JLabel 是标签,几乎可以随心所欲的修改外观,加上监听就变成了按钮

1
2
3
4
5
6
7
// 设置文字的布局
setHorizontalAlignment(JLabel.CENTER);
// 设置为不透底(默认是透底)
setOpaque(true);
// 设置光标为“手型”
setCursor(new Cursor(Cursor.HAND_CURSOR));
// 添加监听 Bla bla ...

特效

JQuery 可以制作淡入淡出、滑入滑出等简单的特效。这些在 Swing 中可以借助多线程来实现(js 中可以借助 setTimeout 方法),来实现动画

☸ 使用多线程实现动画特效

以移动一个组件为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
new Thread(()-> {
// 获取位置
int p_x = c.getX(), p_y = c.getY();
// 移动位置
while(p_x < 200){
p_x += 5;
c.setLocation(p_x, p_y);
// 睡眠
try {
Thread.sleep(25);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();

如代码所示,1000(ms)/25(ms) = 40,组件将以 40(帧/秒),5(px/帧)的速度,也就是 40 × 5 = 200(px/秒)的速度右移至横坐标 200 的位置

请思考,这些为什么需要写在新的线程里?

一个组件 Component (一般指其子类)上有很多用数字控制的特性,如颜色,位置,图像角度、倾斜度等。可以使用类似方法,控制这些数字,实现颜色淡入淡出、图像旋转等特效。

☸ 实现 liner-gradul 效果

cssliner-gradul 是我比较喜欢的一个效果。Swing 可以使用 Graphics + BufferedImage 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class Liner extends JPanel{
private int height, width;
private BufferedImage bufImg = null;
// 构造方法,创建一个面板
public Liner(int width, int height){
// ... 设置面板大小等
}
// 绘制
public void draw(Color a,Color b){
bufImg = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
Graphics g = bufImg.getGraphics();
// 获取 RGB 值和 “加值”
float[] rgb_a = getRGB(a), rgb_b = getRGB(b), dev = new float[3];
for(int x = 0; x < 3; x++){
dev[x] = (rgb_b[x] - rgb_a[x]) / width;
}
int p=0;

// 逐列绘制
for(int i = 0; i < width; i++){
// 转为整数
int[] show = correct(pre);
g.setColor(new Color(show[0], show[1], show[2]));
g.drawLine(p, 0, p, height);

// 加“加值”
crease(pre, dev);
p++;
}
repaint();
}

// 纠正颜色值,并转换为整数数组
private int[] correct(float[] pre){
// ...
}
// 增减颜色值
private float[] crease(float[] pre, float[] dev){
for(int i = 0; i < 3; i++)
pre[i] += dev[i];
return pre;
}
// 获取颜色值
private float[] getRGB(Color a){
return new float[]{a.getRed(), a.getGreen(), a.getBlue()};
}
@Override
public void paint(Graphics g) {
g.drawImage(bufImg, 0, 0, null);
}
}

以上代码省略部分不重要的代码,而且实现仅仅是两个颜色的渐变,如果需要多个颜色渐变,代码更复杂。所以 —— 你还是不要费脑子想了(我的码云中有写好的代码)


后记

Swing 提供基础但全面的桌面程序支持,是目前 Java 默认支持的 GUI 库。官方说 Swing 将会被 JavaFX 逐渐代替,不过它生不逢时,没有得到十足的发展。作为一个更为“现代化”的开发框架,JF 在各方面自然比 Swing 有优势。不过 JF 自然是没有 Swing 开发时的那种直接来的爽快,运行速度也有待提升

更多的内容,尽在我的码云