Java 內建 LayoutManager 雖涵蓋常見需求,但特殊場景 (如垂直流式排列) 仍需自訂。本文詳細解析 LayoutManager 介面契約,並完整實作一個 VFlowLayout (垂直 FlowLayout),可直接用於 Applet/Swing 專案。
所有 LayoutManager 必須實作 5 個方法:
| 方法 | 職責 | 呼叫時機 |
|---|---|---|
addLayoutComponent(String, Component) | 處理 container.add(name, comp) 的附加資訊 (如 BorderLayout 區域名) | 元件加入容器時 |
removeLayoutComponent(Component) | 清理元件移除時的狀態 | 元件移除時 |
preferredLayoutSize(Container) | 核心:計算容器建議大小 (依據子元件 preferredSize) | container.pack() 或 setSize(preferredLayoutSize()) |
minimumLayoutSize(Container) | 計算容器最小大小 (依據子元件 minimumSize) | 視窗縮放下限 |
layoutContainer(Container) | 核心:實際設定每個子元件的位置與大小 (setBounds) | 容器首次顯示、大小改變、validate() |
流程:setLayout() → 加入元件 → 系統呼叫 layoutContainer() 排版 → 呼叫 preferredLayoutSize() 決定容器大小 (若 pack())。
vgap,水平邊距 hgap。Serializable 支援序列化。package render_account;
import java.awt.*;
import java.io.Serializable;
public class VFlowLayout implements LayoutManager, Serializable {
private int hgap, vgap;
public VFlowLayout() { this(5, 5); }
public VFlowLayout(int hgap, int vgap) { this.hgap = hgap; this.vgap = vgap; }
public void addLayoutComponent(String name, Component comp) {}
public void removeLayoutComponent(Component comp) {}
// 計算建議大小:寬度=最大元件寬+邊距,高度=所有元件高總和+間距+邊距
public Dimension preferredLayoutSize(Container parent) {
synchronized (parent.getTreeLock()) {
Dimension dim = new Dimension(0, 0);
int n = parent.getComponentCount();
for (int i = 0; i < n; i++) {
Component c = parent.getComponent(i);
if (c.isVisible()) {
Dimension d = c.getPreferredSize();
dim.width = Math.max(dim.width, d.width);
if (i > 0) dim.height += vgap;
dim.height += d.height;
}
}
Insets in = parent.getInsets();
dim.width += in.left + in.right + hgap * 2;
dim.height += in.top + in.bottom + vgap * 2;
return dim;
}
}
// 最小大小邏輯同 preferred,改用 getMinimumSize()
public Dimension minimumLayoutSize(Container parent) {
synchronized (parent.getTreeLock()) {
Dimension dim = new Dimension(0, 0);
int n = parent.getComponentCount();
for (int i = 0; i < n; i++) {
Component c = parent.getComponent(i);
if (c.isVisible()) {
Dimension d = c.getMinimumSize();
dim.width = Math.max(dim.width, d.width);
if (i > 0) dim.height += vgap;
dim.height += d.height;
}
}
Insets in = parent.getInsets();
dim.width += in.left + in.right + hgap * 2;
dim.height += in.top + in.bottom + vgap * 2;
return dim;
}
}
// 實際排版:垂直堆疊,空間不足則換欄
public void layoutContainer(Container parent) {
synchronized (parent.getTreeLock()) {
Insets in = parent.getInsets();
int vSpace = parent.getHeight() - (in.top + in.bottom + vgap * 2);
int left = in.left + hgap;
int top = in.top + vgap;
int width = 0;
int usedHeight = 0;
int colStart = 0;
int n = parent.getComponentCount();
for (int i = 0; i < n; i++) {
Component c = parent.getComponent(i);
if (!c.isVisible()) continue;
Dimension d = c.getPreferredSize();
c.setSize(d.width, d.height);
if (usedHeight == 0 || usedHeight + d.height <= vSpace) {
if (usedHeight > 0) usedHeight += vgap;
usedHeight += d.height;
width = Math.max(width, d.width);
} else {
// 換欄:移動前一欄元件到正確位置
moveComponents(parent, top, left, width, colStart, i);
left += width + hgap;
width = d.width;
usedHeight = d.height;
colStart = i;
}
}
// 處理最後一欄
moveComponents(parent, top, left, width, colStart, n);
}
}
// 將指定範圍元件移動到 (left, top) 起始位置垂直排列
private void moveComponents(Container parent, int top, int left, int width, int start, int end) {
synchronized (parent.getTreeLock()) {
for (int i = start; i < end; i++) {
Component c = parent.getComponent(i);
if (c.isVisible()) {
c.setLocation(left, top);
top += c.getPreferredSize().height + vgap;
}
}
}
}
// Getter/Setter
public void setHgap(int h) { hgap = h; }
public void setVgap(int v) { vgap = v; }
public int getHgap() { return hgap; }
public int getVgap() { return vgap; }
}
JPanel panel = new JPanel(new VFlowLayout(10, 10));
for (int i = 1; i <= 20; i++) {
panel.add(new JButton("Button " + i));
}
JScrollPane scroll = new JScrollPane(panel);
frame.add(scroll, BorderLayout.CENTER);
synchronized(parent.getTreeLock()),確保 AWT 事件執行緒安全。c.isVisible() 過濾隱藏元件,不參與排版。moveComponents 獨立方法處理欄位內元件定位,主排版邏輯只負責決定換欄時機。掌握此範本,即可依需求開發各種客製化 LayoutManager (如圓形排列、瀑布流、響應式網格等)。