想要实现IDEA类似的标题栏效果,菜单栏放在标题栏同一行,标题居中,右侧为按钮。如图:
使用依赖FlatLaf
<dependency> <groupId>com.formdevgroupId> <artifactId>flatlafartifactId> <version>3.2.5version> 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);