文章目录
- Swing自定义标题栏
- 需求
- 最终效果如图
- 步骤
- 额外需求
Swing自定义标题栏
需求
想要实现IDEA类似的标题栏效果,菜单栏放在标题栏同一行,标题居中,右侧为按钮。如图:
最终效果如图
步骤
使用依赖FlatLaf
<!-- https://mvnrepository.com/artifact/com.formdev/flatlaf --> <dependency> <groupId>com.formdev</groupId> <artifactId>flatlaf</artifactId> <version>3.2.5</version> </dependency>
-
取消系统标题栏
frame.setUndecorated(true);
即可实现。 -
自定义标题栏
class CustomTitlePane extends JMenuBar { public CustomTitlePane(JFrame frame) { // 创建一个空白面板来添加左侧间距 JLabel blankLbl = new JLabel(" "); add(blankLbl); // 添加图标 Image image = ImageManager.getImage("/images/logo.png"); ImageIcon icon = new ImageIcon(image); Image scaledImage = icon.getImage().getScaledInstance(16, 16, Image.SCALE_SMOOTH); JLabel iconLabel = new JLabel(new ImageIcon(scaledImage)); add(iconLabel); // 创建一个空白面板来添加左侧间距 JLabel blankLbl2 = new JLabel(" "); add(blankLbl2); JMenuBar menuBar = ToolkitUtilities.getMenuBar(); add(menuBar); // 创建并添加标题标签,居中对齐 JLabel titleLabel = new JLabel(frame.getTitle()); titleLabel.setHorizontalAlignment(SwingConstants.CENTER); add(Box.createHorizontalGlue()); add(titleLabel); add(Box.createHorizontalGlue()); // 添加最小化按钮 JButton minimizeButton = new JButton("-"); minimizeButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { frame.setExtendedState(JFrame.ICONIFIED); } }); add(minimizeButton); // 添加最大化按钮 JButton maximizeButton = new JButton("[]"); maximizeButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (frame.getWidth() == screenSize.width) { frame.setSize(new Dimension(1280, 768)); frame.setLocationRelativeTo(null); } else { frame.setBounds(0, workArea.y, screenSize.width, workArea.height); } } }); add(maximizeButton); // 添加关闭按钮 JButton closeButton = new JButton("X"); closeButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { frame.dispose(); } }); add(closeButton); } @Override public void updateUI() { super.updateUI(); // 设置菜单栏的外观,使用FlatMenuBarUI以匹配FlatLaf主题 setUI(new FlatMenuBarUI()); } }
-
自定义窗口拖放与调整大小
- 首先提供
getResizeCursor
方法,根据鼠标当前位置来判断指针当前应该是什么形状,这里针对需求来说只有两种:默认指针和调整大小的指针。
private static int getResizeCursor(JFrame frame, Point p) { Insets insets = frame.getInsets(); int x = p.x; int y = p.y; int width = frame.getWidth(); int height = frame.getHeight(); if (x < BORDER_SIZE && y < BORDER_SIZE) { return Cursor.NW_RESIZE_CURSOR; } if (x < BORDER_SIZE && y > height - BORDER_SIZE) { return Cursor.SW_RESIZE_CURSOR; } if (x > width - BORDER_SIZE && y < BORDER_SIZE) { return Cursor.NE_RESIZE_CURSOR; } if (x > width - BORDER_SIZE && y > height - BORDER_SIZE) { return Cursor.SE_RESIZE_CURSOR; } if (x < BORDER_SIZE) { return Cursor.W_RESIZE_CURSOR; } if (x > width - BORDER_SIZE) { return Cursor.E_RESIZE_CURSOR; } if (y < BORDER_SIZE) { return Cursor.N_RESIZE_CURSOR; } if (y > height - BORDER_SIZE) { return Cursor.S_RESIZE_CURSOR; } return Cursor.DEFAULT_CURSOR; }
-
这里针对鼠标添加四类事件监听器来实现:
-
鼠标移动 mouseMoved
鼠标移动时根据上面的方法判断当前指针所处位置,及时更新鼠标指针图标。
-
鼠标点击 mousePressed
鼠标点击时,根据当前指针图标来判断:
如果是默认图标则认为是拖动操作,需要记录当前的坐标;
如果是其它图标则认为是调整大小操作。
-
鼠标释放 mouseReleased
鼠标释放时需要变回默认指针。
-
鼠标拖动 mouseDragged
鼠标拖动时,根据当前指针图标判断可以分为两种情况:
如果是默认图标,则认为是移动位置操作,通过
setLocation
方法修改frame位置; 如果不是默认图标,则认为是调整大小操作,对于调整大小操作,需要区分当前指针具体类型来判断是哪个方向上的位移,同时需要计算当前指针坐标点与原始指针坐标点之间的距离,因为在调整大小的过程中除了
size
改变还有location
的改变。 同时,如果要限定窗口最小的
size
,我也是在这里通过MIN_WIDTH
和MIN_HEIGHT
做的限制。
-
private static void addResizeWindowSupport(JFrame frame) { frame.addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { // 获取当前指针形状 cursor = getResizeCursor(frame, e.getPoint()); // 如果是默认指针,可能是要做拖动操作 if(cursor == Cursor.DEFAULT_CURSOR) { mouseAtX = e.getPoint().x; mouseAtY = e.getPoint().y; } } @Override public void mouseReleased(MouseEvent e) { // 释放指针,变回默认指针 cursor = Cursor.DEFAULT_CURSOR; } }); frame.addMouseMotionListener(new MouseAdapter() { @Override public void mouseDragged(MouseEvent e) { // 指针拖动过程中,如果不是默认指针,则是在变大小 if (cursor != Cursor.DEFAULT_CURSOR) { Point p = e.getPoint(); SwingUtilities.convertPointToScreen(p, frame); Point newLocation = frame.getLocation(); Dimension newSize = frame.getSize(); switch (cursor) { case Cursor.N_RESIZE_CURSOR: newSize.height = frame.getHeight() - p.y + frame.getLocation().y; newLocation.y = p.y; break; case Cursor.S_RESIZE_CURSOR: newSize.height = p.y - frame.getLocation().y; break; case Cursor.W_RESIZE_CURSOR: newSize.width = frame.getWidth() - p.x + frame.getLocation().x; newLocation.x = p.x; break; case Cursor.E_RESIZE_CURSOR: newSize.width = p.x - frame.getLocation().x; break; case Cursor.NW_RESIZE_CURSOR: newSize.height = frame.getHeight() - p.y + frame.getLocation().y; newLocation.y = p.y; newSize.width = frame.getWidth() - p.x + frame.getLocation().x; newLocation.x = p.x; break; case Cursor.NE_RESIZE_CURSOR: newSize.height = frame.getHeight() - p.y + frame.getLocation().y; newLocation.y = p.y; newSize.width = p.x - frame.getLocation().x; break; case Cursor.SW_RESIZE_CURSOR: newSize.height = p.y - frame.getLocation().y; newSize.width = frame.getWidth() - p.x + frame.getLocation().x; newLocation.x = p.x; break; case Cursor.SE_RESIZE_CURSOR: newSize.height = p.y - frame.getLocation().y; newSize.width = p.x - frame.getLocation().x; break; } frame.setBounds(newLocation.x, newLocation.y, Math.max(100, newSize.width), Math.max(100, newSize.height)); } else { // 如果是默认指针,则是拖动 frame.setLocation(e.getXOnScreen() - mouseAtX, e.getYOnScreen() - mouseAtY); } } }); frame.addMouseMotionListener(new MouseAdapter() { @Override public void mouseMoved(MouseEvent e) { frame.setCursor(Cursor.getPredefinedCursor(getResizeCursor(frame, e.getPoint()))); } }); }
- 首先提供
额外需求
-
默认窗口最大化且不占用任务栏空间
如果不做操作,默认的最大化会导致任务栏被遮盖。这里上点手段:
// 获取屏幕尺寸 screenSize = Toolkit.getDefaultToolkit().getScreenSize(); // 获取屏幕工作区域的尺寸 workArea = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds(); // 设置窗口的初始位置和尺寸,确保底部不占用任务栏空间 frame.setBounds(0, workArea.y, screenSize.width, workArea.height);