如何正确实现带动态文本的可伸缩JLabel?
实现自适应裁剪并显示省略号的JLabel优化方案
我反感滚动条的笨拙设计,因此编写了一个自定义JLabel扩展,在每次paint()调用时动态裁剪标签文本并添加省略号。
原实现代码
StretchableLabel类
package StretchableLabel; import lombok.SneakyThrows; import javax.swing.*; import java.awt.*; public class StretchableLabel extends JLabel { private String originalText; public StretchableLabel(String text) { super(text); this.originalText = text; } @Override public void setText(String text) { this.originalText = text; String displayedText = trimTextIfNecessary(text); super.setText(displayedText); } private String trimTextIfNecessary(String text) { if (getFont() == null) return text; FontMetrics fontMetrics = getFontMetrics(getFont()); int textWidth = fontMetrics.stringWidth(text); int parentWidth = getParent().getWidth(); if (textWidth > parentWidth) { return trimText(text, parentWidth, fontMetrics); } else { return text; } } private String trimText(String text, int parentWidth, FontMetrics fontMetrics) { int textLimit = parentWidth / fontMetrics.charWidth('m'); return text.substring(0, textLimit) + "..."; } @SneakyThrows @Override protected void paintComponent(Graphics g) { String displayedText = trimTextIfNecessary(originalText); super.setText(displayedText); super.paintComponent(g); } }
测试主类Main
package StretchableLabel; import org.apache.commons.lang3.StringUtils; import javax.swing.*; public class Main { public static void main(String[] args) { JFrame frame = new JFrame("StretchableLabel Demo"); JPanel panel = new JPanel(); StretchableLabel label = new StretchableLabel((StringUtils.repeat("long label ", 20))); panel.add(label); frame.add(panel); frame.pack(); frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); frame.setLocationRelativeTo(null); frame.setVisible(true); } }
原实现存在的问题
getText()返回裁剪后的文本而非setText()设置的原始字符串,违背预期;且因paintComponent()会调用getText(),直接重写该方法难度大,还需拦截所有setText()调用防止外部修改文本。- 以'm'字符宽度计算文本长度限制过于保守,虽可用二分法优化,但会影响性能。
- 直接对比文本宽度与父容器宽度不合理,父容器可能包含其他子组件,标签实际可用宽度并非父容器全宽。
优化后的实现方案
核心思路
- 独立存储原始文本,让
getText()直接返回原始值,不再通过父类存储显示文本,而是在paintComponent()中直接绘制裁剪后的内容。 - 用二分法实现精准文本裁剪,同时缓存裁剪结果减少重复计算,平衡精度与性能。
- 以标签自身的可用宽度(扣除内边距后的宽度)作为判断依据,确保计算准确。
优化代码
package StretchableLabel; import javax.swing.*; import java.awt.*; public class StretchableLabel extends JLabel { private String originalText; // 缓存裁剪后的文本,避免每次paint都重复计算 private String cachedDisplayText; public StretchableLabel(String text) { super(text); this.originalText = text; this.cachedDisplayText = text; } @Override public void setText(String text) { this.originalText = text; this.cachedDisplayText = text; repaint(); } @Override public String getText() { // 直接返回原始文本,符合预期 return originalText; } @Override protected void paintComponent(Graphics g) { Graphics2D g2d = (Graphics2D) g.create(); try { // 计算标签实际可用于绘制文本的宽度 int availableWidth = getWidth() - getInsets().left - getInsets().right; if (availableWidth <= 0) { super.paintComponent(g); return; } FontMetrics metrics = g2d.getFontMetrics(getFont()); // 检查是否需要重新裁剪(文本、宽度或字体变化时触发) if (!cachedDisplayText.equals(originalText) || metrics.stringWidth(cachedDisplayText) != availableWidth) { cachedDisplayText = getTruncatedText(originalText, availableWidth, metrics); } // 沿用原标签的内边距和对齐逻辑绘制文本 int x = getInsets().left; int y = getInsets().top + metrics.getAscent(); g2d.drawString(cachedDisplayText, x, y); } finally { g2d.dispose(); } } private String getTruncatedText(String text, int maxWidth, FontMetrics metrics) { int fullTextWidth = metrics.stringWidth(text); if (fullTextWidth <= maxWidth) { return text; } String ellipsis = "..."; int ellipsisWidth = metrics.stringWidth(ellipsis); int availableTextWidth = maxWidth - ellipsisWidth; // 二分法找到最长的可显示子串 int low = 0; int high = text.length(); int bestLength = 0; while (low <= high) { int mid = (low + high) / 2; String subText = text.substring(0, mid); int subWidth = metrics.stringWidth(subText); if (subWidth <= availableTextWidth) { bestLength = mid; low = mid + 1; } else { high = mid - 1; } } return text.substring(0, bestLength) + ellipsis; } // 重写获取首选尺寸的方法,确保布局管理器能正确计算标签大小 @Override public Dimension getPreferredSize() { FontMetrics metrics = getFontMetrics(getFont()); int textWidth = metrics.stringWidth(originalText); int height = metrics.getHeight() + getInsets().top + getInsets().bottom; return new Dimension(textWidth, height); } }
优化点说明
getText()返回原始文本:直接重写该方法返回存储的originalText,彻底避免与显示文本的冲突。- 精准裁剪+缓存优化:二分法保证裁剪精度,缓存机制只有在必要时才重新计算,避免性能损耗。
- 基于自身可用宽度计算:使用标签自身的宽度减去内边距作为文本绘制的最大宽度,计算更准确,不受父容器其他组件影响。
- 保留原标签特性:绘制时沿用原标签的内边距、字体等设置,保持JLabel的原有行为逻辑。
内容的提问来源于stack exchange,提问作者demavi
相关产品推荐
相关产品推荐

