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

Java Swing 實現 QQ 風格好友列表面板(可展開收起分組)完整源碼

     編輯:Java 相關知識

效果預覽

左側面板顯示分組標題(我的好友、黑名單),點擊標題切換展開/收起,好友項顯示頭像、昵稱、個性簽名,鼠標懸停變色、顯示工具提示。

核心類架構

  1. MemberModel:好友項組件(頭像按鈕 + 昵簽標籤),封裝懸停/點擊樣式變化。
  2. TestPane:主面板容器,使用 BoxLayout(Y_AXIS) 垂直堆疊分組標題與好友項,實現展開/收起邏輯。
  3. TestFrame:主窗口,包裹 JScrollPane 支援滾動。

關鍵代碼解析

1. MemberModel:好友項模板

public class MemberModel {
    public JPanel jPanel = new JPanel(); // 容器面板
    public JButton jButton;              // 頭像按鈕
    private JLabel lb_nickName, lb_mood; // 昵稱、心情標籤

    public MemberModel(int picIndex, String nickname, int len) {
        this.nickname = nickname;
        initialize(picIndex);
    }

    private void initialize(int picIndex) {
        jPanel.setLayout(null); // 絕對佈局(教學演示,生產建議 MigLayout/GridBagLayout)
        jPanel.setPreferredSize(new Dimension(185, 60));
        jPanel.setBackground(Color.WHITE);

        // 頭像
        jButton = new JButton(new ImageIcon(picIndex + ".jpg"));
        jButton.setBounds(8, 10, 40, 40);
        jButton.setBackground(new Color(236, 255, 236));
        jButton.setBorderPainted(false);

        // 昵稱
        lb_nickName = new JLabel(nickname);
        lb_nickName.setFont(new Font("Dialog", Font.BOLD, 14));
        lb_nickName.setBounds(52, 10, 80, 20);

        // 個性簽名
        lb_mood = new JLabel("世界上最遙遠的距離不是生與死...");
        lb_mood.setFont(new Font("Dialog", Font.PLAIN, 12));
        lb_mood.setBounds(51, 30, 131, 20);
        lb_mood.setToolTipText(lb_mood.getText()); // 完整顯示

        // 懸停效果:面板變色
        jPanel.addMouseListener(new MouseAdapter() {
            public void mouseEntered(MouseEvent e) { jPanel.setBackground(new Color(192,224,248)); }
            public void mouseExited(MouseEvent e)  { jPanel.setBackground(Color.WHITE); }
        });
        // 同樣為頭像按鈕添加懸停監聽...

        jPanel.add(jButton);
        jPanel.add(lb_nickName);
        jPanel.add(lb_mood);
    }
}

2. TestPane:分組容器與展開邏輯

public class TestPane extends JPanel {
    private JLabel groupLabel, blackLabel; // 分組標題標籤
    private JLabel[] friendLabels;         // 好友項容器標籤
    private JLabel[] blackLabels;          // 黑名單項容器標籤
    private int friendClick = 0, blackClick = 0;

    public TestPane() {
        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
        setPreferredSize(new Dimension(200, 408));
        
        initComponents();
        addComponents();
    }

    private void initComponents() {
        // 分組標題:帶圖標、邊框、點擊監聽
        groupLabel = createGroupLabel("我的好友", "img/ico.jpg", "img/ico2.jpg", e -> toggleGroup());
        blackLabel = createGroupLabel("黑名單", "img/ico2.jpg", "img/ico.jpg", e -> toggleBlack());

        // 好友項:每個 JLabel 包裝一個 MemberModel.jPanel
        friendLabels = new JLabel[4]; // 0: 標題, 1-3: 好友
        friendLabels[0] = groupLabel;
        for (int i = 1; i < 4; i++) {
            friendLabels[i] = new JLabel(new ImageIcon("img/bg.jpg"));
            friendLabels[i].add(new MemberModel(i, "CoolBabY" + i, 200).jPanel);
            friendLabels[i].setBorder(BorderFactory.createEmptyBorder(2,2,2,2));
        }

        // 黑名單項:類似構建,初始隱藏
        blackLabels = new JLabel[7];
        blackLabels[0] = blackLabel;
        for (int i = 1; i < 7; i++) {
            blackLabels[i] = new JLabel(new ImageIcon("img/bg.jpg"));
            blackLabels[i].add(new MemberModel(i+3, "CoolBabY" + (i+3), 200).jPanel);
            blackLabels[i].setVisible(false); // 初始收起
            blackLabels[i].setBorder(BorderFactory.createEmptyBorder(2,2,2,2));
        }
    }

    private JLabel createGroupLabel(String text, String icon1, String icon2, MouseListener listener) {
        JLabel label = new JLabel(text, new ImageIcon(icon1), JLabel.LEFT);
        label.setBorder(BorderFactory.createEmptyBorder(2,2,2,2));
        label.addMouseListener(listener);
        return label;
    }

    private void toggleGroup() {
        friendClick++;
        boolean expand = (friendClick % 2 == 1); // 奇數展開
        for (int i = 1; i < friendLabels.length; i++) 
            friendLabels[i].setVisible(expand);
        groupLabel.setIcon(new ImageIcon(expand ? "img/ico2.jpg" : "img/ico.jpg"));
        revalidate(); repaint(); // 關鍵:重新佈局與重繪
    }
    // toggleBlack() 同理...
}

3. TestFrame:主窗口與滾動

public class TestFrame {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            JFrame frame = new JFrame("模仿 QQ 面板");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setSize(230, 700);
            
            JScrollPane scrollPane = new JScrollPane(new TestPane());
            scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
            
            frame.add(scrollPane, BorderLayout.CENTER);
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        });
    }
}

運行前準備

  1. 建立項目,創建三個類(包名 coolbaby6)。
  2. 專案根目錄創建 img/ 文件夾,放入圖片:1.jpg~9.jpg(頭像)、bg.jpg(項背景)、ico.jpg/ico2.jpg(展開/收起圖標)。
  3. 直接運行 TestFrame.main()。

設計缺陷與改進方向

問題影響改進
絕對佈局 (null layout)跨平台適配差、DPI 縮放失效、維護困難使用 MigLayout、GridBagLayout 或 JavaFX
JLabel 作為容器包裝 JPanel語義不清、事件傳播異常、無法自動適應尺寸直接將 MemberModel.jPanel 加入 BoxLayout 面板
硬編碼好友數據無法動態加載、擴展性差抽象 Friend 數據模型,從服務/數據庫加載,動態創建組件
圖片路徑硬編碼打包部署失效使用 getClass().getResource() 加載類路徑資源
無模型-視圖分離業務邏輯與 UI 耦合MVC/MVP 模式,引入 ListModel / CellRenderer

現代替代方案

  • JavaFX:ListView + CellFactory、TreeView、CSS 樣式、綁定屬性,原生支援動畫、高 DPI、觸控。
  • Swing 進階:JList + DefaultListCellRenderer、JTree + DefaultTreeCellRenderer、GlazedLists 排序過濾。
  • Web 技術:Electron / Tauri / SwingNode 嵌入 WebView,用 HTML/CSS/JS 實現豐富 UI。

總結

本示例展示了 Swing 組件組合、事件監聽、動態顯隱、滾動面板等基礎 GUI 開發技巧。雖然代碼存在佈局硬編碼、組件濫用等問題,但作為理解 Swing 組件樹、事件分發、繪製機制的入門案例具有教學價值。實際項目中請採用成熟的佈局管理器、數據模型分離與現代 UI 框架。

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