Java 8下解决Windows L&F中Swing文本高亮后可读性差的问题
Java 8下解决Windows L&F中Swing文本高亮后可读性差的问题
我完全懂你遇到的这个糟心问题——Windows外观风格下,选中文本的默认背景是白色,要是这段文本还加了黄色高亮,那可读性直接拉胯。核心问题就是Swing原本的前景色逻辑只会盯着组件的默认背景属性(比如你说的蓝色)来计算,完全不管当前绘制的文本区域实际是高亮状态还是选中状态,更没法拿到实际的背景颜色来调整。好在Java 8下有个靠谱的解决方案,咱们来一步步搞定它。
解决思路
要解决这个问题,我们得绕开Swing默认的高亮绘制逻辑,自己写一个智能高亮绘制器:
- 这个绘制器不仅要能画出原来的黄色高亮背景,还要能判断当前绘制的文本区域是否被选中;
- 针对不同的背景(普通高亮黄/Windows选中白),用亮度算法计算出最合适的前景色(浅背景配黑字,深背景配白字,保证足够的对比度);
- 在绘制文本时临时替换前景色,让高亮/选中区域的文本自动适配背景,彻底解决可读性问题。
完整修改后的代码
package demos.text; import com.sun.java.swing.plaf.windows.WindowsLookAndFeel; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JTextArea; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; import javax.swing.WindowConstants; import javax.swing.text.BadLocationException; import javax.swing.text.Highlighter; import javax.swing.text.JTextComponent; import javax.swing.text.Position; import javax.swing.text.View; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Font; import java.awt.Graphics; import java.awt.Rectangle; import java.awt.Shape; public class HighlighterDemo { // 替换成自定义的智能高亮绘制器 private static final SmartHighlightPainter painter = new SmartHighlightPainter(Color.YELLOW); public static void main(String[] args) throws BadLocationException, UnsupportedLookAndFeelException, ClassNotFoundException, InstantiationException, IllegalAccessException { UIManager.setLookAndFeel(WindowsLookAndFeel.class.getName()); Container mainPanel = createMainPanel(); JFrame frame = new JFrame("Highlighter Demo"); frame.setContentPane(mainPanel); frame.setLocationRelativeTo(null); frame.pack(); frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); frame.setVisible(true); } private static JPanel createMainPanel() throws BadLocationException { JPanel panel = new JPanel(new BorderLayout()); panel.add(createTextArea()); return panel; } private static JTextArea createTextArea() throws BadLocationException { JTextArea textArea = new JTextArea(); textArea.setFont(new Font("Arial", Font.PLAIN, 20)); String text = "Some sample text"; textArea.setText(text); // 应用自定义高亮绘制器 textArea.getHighlighter().addHighlight(text.indexOf("sample"), text.indexOf("text"), painter); return textArea; } /** * 根据背景色计算最佳前景色(遵循WCAG国际亮度标准,保证对比度) */ private static Color getOptimalForeground(Color background) { // 计算相对亮度:(0.299*红 + 0.587*绿 + 0.114*蓝) / 255 double luminance = (0.299 * background.getRed() + 0.587 * background.getGreen() + 0.114 * background.getBlue()) / 255; // 浅背景(亮度>0.5)用黑色,深背景用白色 return luminance > 0.5 ? Color.BLACK : Color.WHITE; } /** * 智能高亮绘制器:自动适配前景色的核心类 */ static class SmartHighlightPainter implements Highlighter.LayerPainter { private final Color highlightColor; public SmartHighlightPainter(Color highlightColor) { this.highlightColor = highlightColor; } @Override public void paint(Graphics g, int offs0, int offs1, Shape bounds, JTextComponent c) { // 完全复刻DefaultHighlightPainter的背景绘制逻辑,保证高亮样式和原来一致 Rectangle alloc = null; if (offs0 == offs1) { try { Shape shape = c.modelToView(offs0); alloc = shape.getBounds(); } catch (BadLocationException e) { return; } g.drawLine(alloc.x, alloc.y, alloc.x, alloc.y + alloc.height); return; } if (offs0 > offs1) { int temp = offs0; offs0 = offs1; offs1 = temp; } try { Shape shape = c.modelToView(offs0); alloc = shape.getBounds(); if (offs1 == c.getDocument().getLength()) { int endX = c.getWidth(); g.setColor(highlightColor); g.fillRect(alloc.x, alloc.y, endX - alloc.x, alloc.height); } else { Shape s = c.modelToView(offs1); Rectangle rect = s.getBounds(); g.setColor(highlightColor); g.fillRect(alloc.x, alloc.y, rect.x - alloc.x, alloc.height); } } catch (BadLocationException e) { // 忽略绘制异常,不影响整体功能 } } @Override public Shape paintLayer(Graphics g, int offs0, int offs1, Shape bounds, JTextComponent c, View view) { Color currentBg = highlightColor; // 判断当前高亮区域是否和选中区域重叠 if (c.getSelectionStart() != c.getSelectionEnd()) { int selStart = c.getSelectionStart(); int selEnd = c.getSelectionEnd(); // 检查两个区间是否有交集 if (!(offs1 <= selStart || offs0 >= selEnd)) { // 重叠区域使用Windows L&F的系统选中背景色 currentBg = UIManager.getColor("TextArea.selectionBackground"); if (currentBg == null) { currentBg = Color.WHITE; // 兜底默认值,防止获取失败 } } } // 计算最佳前景色并临时应用到绘制上下文 Color optimalFg = getOptimalForeground(currentBg); Color originalFg = g.getColor(); g.setColor(optimalFg); // 让View用新的前景色绘制文本 try { Shape drawBounds = view.modelToView(offs0, Position.Bias.Forward, offs1, Position.Bias.Backward, bounds); view.paint(g, drawBounds); return drawBounds; } catch (BadLocationException e) { return bounds; } finally { // 恢复原来的颜色,避免影响其他区域的文本绘制 g.setColor(originalFg); } } } }
关键细节说明
- 亮度计算逻辑:用WCAG国际标准的相对亮度公式,比简单取RGB平均值更精准,能保证文本和背景的对比度符合无障碍设计要求;
- 智能背景判断:自动识别当前区域是否被选中,优先使用系统的选中背景色计算前景色,完全贴合Windows L&F的风格;
- 兼容性保障:完全适配Java 8,绘制背景的逻辑和
DefaultHighlightPainter完全一致,不会破坏原来的高亮样式,只是在文本绘制环节动态调整了前景色。
这样修改后,不管是普通的黄色高亮文本,还是被选中的黄色高亮文本,都会自动切换到最适合的前景色,彻底解决可读性差的问题。
内容来源于stack exchange
相关产品推荐
相关产品推荐

