首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

自訂 Java LayoutManager 實作教學:打造垂直流式佈局 VFlowLayout

     編輯:Java 相關知識

Java 內建 LayoutManager 雖涵蓋常見需求,但特殊場景 (如垂直流式排列) 仍需自訂。本文詳細解析 LayoutManager 介面契約,並完整實作一個 VFlowLayout (垂直 FlowLayout),可直接用於 Applet/Swing 專案。

LayoutManager 介面核心契約

所有 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())。

VFlowLayout 設計邏輯

  • 元件垂直堆疊,寬度取所有元件最大 preferredWidth。
  • 元件間垂直間距 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);

關鍵實作細節

  1. 同步鎖:所有方法使用 synchronized(parent.getTreeLock()),確保 AWT 事件執行緒安全。
  2. Insets 處理:正確計算容器邊框內縮,避免元件被邊框遮蓋。
  3. 可見性檢查:c.isVisible() 過濾隱藏元件,不參與排版。
  4. 換欄邏輯:moveComponents 獨立方法處理欄位內元件定位,主排版邏輯只負責決定換欄時機。

掌握此範本,即可依需求開發各種客製化 LayoutManager (如圓形排列、瀑布流、響應式網格等)。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved