You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何正确实现带动态文本的可伸缩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'字符宽度计算文本长度限制过于保守,虽可用二分法优化,但会影响性能。
  • 直接对比文本宽度与父容器宽度不合理,父容器可能包含其他子组件,标签实际可用宽度并非父容器全宽。

优化后的实现方案

核心思路

  1. 独立存储原始文本,让getText()直接返回原始值,不再通过父类存储显示文本,而是在paintComponent()中直接绘制裁剪后的内容。
  2. 用二分法实现精准文本裁剪,同时缓存裁剪结果减少重复计算,平衡精度与性能。
  3. 以标签自身的可用宽度(扣除内边距后的宽度)作为判断依据,确保计算准确。

优化代码

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 03:27:04