如何为显示回显字符的JPasswordField设置TextAttribute.TRACKING字间距
问题根因
你遇到的问题是Swing默认JPasswordField的UI实现逻辑导致的:当设置了非0的回显字符时,默认的密码框UI类在绘制回显圆点时,不会应用你给字体设置的TextAttribute.TRACKING字间距属性,而是固定按照默认字符宽度逐个绘制回显符号,只有当你设置回显字符为0显示明文时,才会走普通JTextField的文本绘制逻辑,应用字体的字间距属性。该问题和具体外观实现无关,Metal、Windows等主流外观都遵循这套逻辑。
可行解决方案
我们可以通过自定义BasicPasswordFieldUI的子类,重写回显字符的绘制逻辑,让绘制时主动应用字间距设置,以下是完整可运行的修改后代码:
import javax.swing.*; import javax.swing.plaf.basic.BasicPasswordFieldUI; import java.awt.*; import java.awt.font.TextAttribute; import java.awt.event.*; import java.util.HashMap; import java.util.Map; public class PasswordFieldLetterSpacingFix { public static void main(String[] args) { SwingUtilities.invokeLater(() -> { JPasswordField field = new JPasswordField(15); // 应用自定义UI解决回显时字间距不生效问题 field.setUI(new SpacingPasswordUI()); Map<TextAttribute, Object> attributes = new HashMap<>(); attributes.put(TextAttribute.TRACKING, 0.5); field.setFont(field.getFont().deriveFont(attributes)); JLabel showPassLabel = new JLabel("move mouse here"); showPassLabel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); showPassLabel.addMouseListener(new MouseAdapter() { @Override public void mouseEntered(MouseEvent ev) { field.setEchoChar((char) 0); } @Override public void mouseExited(MouseEvent ev) { field.setEchoChar((char) UIManager.get("PasswordField.echoChar")); } }); JFrame frame = new JFrame("密码框字间距修复"); frame.setLayout(new FlowLayout()); frame.add(field); frame.add(showPassLabel); frame.pack(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setLocationByPlatform(true); frame.setVisible(true); }); } // 自定义支持字间距的密码框UI static class SpacingPasswordUI extends BasicPasswordFieldUI { @Override protected void paintSafely(Graphics g) { JPasswordField passwordField = (JPasswordField) getComponent(); // 明文模式走默认绘制逻辑 if (passwordField.getEchoChar() == 0) { super.paintSafely(g); return; } // 回显模式自定义绘制,主动应用字间距 Graphics2D g2d = (Graphics2D) g.create(); g2d.setColor(passwordField.getForeground()); g2d.setFont(passwordField.getFont()); FontMetrics fm = g2d.getFontMetrics(); char echoChar = passwordField.getEchoChar(); int dotWidth = fm.charWidth(echoChar); // 读取字体设置的tracking值计算额外字间距 float tracking = (Float) passwordField.getFont().getAttributes().getOrDefault(TextAttribute.TRACKING, 0f); int extraSpacing = (int) (dotWidth * tracking); int x = passwordField.getInsets().left; int y = passwordField.getInsets().top + fm.getAscent(); int charCount = passwordField.getPassword().length; for (int i = 0; i < charCount; i++) { g2d.drawString(String.valueOf(echoChar), x, y); x += dotWidth + extraSpacing; } g2d.dispose(); } } }
注意事项
- 该实现兼容所有主流Swing外观,无需额外适配
- 回显模式和明文模式的字间距会自动和你设置的
TextAttribute.TRACKING值保持一致 - 如果需要全局生效,可以通过
UIManager.put("PasswordFieldUI", "你的类全路径.SpacingPasswordUI")设置密码框默认UI
内容的提问来源于stack exchange,提问作者George Z.
相关产品推荐
相关产品推荐

