61

我相信我们所有人都在 Facebook 状态(或其他地方)上看到了省略号,然后单击“显示更多”并且只有另外 2 个字符左右。我猜这是因为懒惰的编程,因为肯定有一个理想的方法。

我的将苗条的字符[iIl1]视为“半字符”,但是当省略号几乎没有隐藏任何字符时,这并不能避免省略号看起来很傻。

有没有理想的方法?这是我的:

/**
 * Return a string with a maximum length of <code>length</code> characters.
 * If there are more than <code>length</code> characters, then string ends with an ellipsis ("...").
 *
 * @param text
 * @param length
 * @return
 */
public static String ellipsis(final String text, int length)
{
    // The letters [iIl1] are slim enough to only count as half a character.
    length += Math.ceil(text.replaceAll("[^iIl]", "").length() / 2.0d);

    if (text.length() > length)
    {
        return text.substring(0, length - 3) + "...";
    }

    return text;
}

语言并不重要,但标记为 Java,因为这是我最感兴趣的。

4

13 回答 13

86

我喜欢让“瘦”字符算作半个字符的想法。简单且很好的近似值。

然而,大多数省略号的主要问题是(恕我直言)它们在中间切词。这是一个考虑词边界的解决方案(但没有深入研究像素数学和 Swing-API)。

private final static String NON_THIN = "[^iIl1\\.,']";

private static int textWidth(String str) {
    return (int) (str.length() - str.replaceAll(NON_THIN, "").length() / 2);
}

public static String ellipsize(String text, int max) {

    if (textWidth(text) <= max)
        return text;

    // Start by chopping off at the word before max
    // This is an over-approximation due to thin-characters...
    int end = text.lastIndexOf(' ', max - 3);

    // Just one long word. Chop it off.
    if (end == -1)
        return text.substring(0, max-3) + "...";

    // Step forward as long as textWidth allows.
    int newEnd = end;
    do {
        end = newEnd;
        newEnd = text.indexOf(' ', end + 1);

        // No more spaces.
        if (newEnd == -1)
            newEnd = text.length();

    } while (textWidth(text.substring(0, newEnd) + "...") < max);

    return text.substring(0, end) + "...";
}

算法测试如下所示:

在此处输入图像描述

于 2010-09-07T09:41:46.063 回答
73

我很震惊没有人提到Commons Lang StringUtils#abbreviate()

更新:是的,它没有考虑到纤细的字符,但我不同意这样的观点,因为每个人都有不同的屏幕和字体设置,并且大部分登陆此页面的人可能正在寻找一个维护的库,例如以上。

于 2012-09-01T13:57:23.100 回答
28

看起来您可能会从 Java 图形上下文的FontMetrics.

附录:在处理这个问题时,区分模型和视图可能会有所帮助。模型是一个StringUTF-16 代码点的有限序列,而视图是一系列字形,在某些设备上以某种字体呈现。

在 Java 的特殊情况下,可以使用SwingUtilities.layoutCompoundLabel()来实现翻译。下面的例子截取了布局调用BasicLabelUI来演示效果。在其他情况下也可以使用效用方法,但FontMetrics必须根据经验确定合适的方法。

替代文字

import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.GridLayout;
import java.awt.Rectangle;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import javax.swing.plaf.basic.BasicLabelUI;

/** @see http://stackoverflow.com/questions/3597550 */
public class LayoutTest extends JPanel {

    private static final String text =
        "A damsel with a dulcimer in a vision once I saw.";
    private final JLabel sizeLabel = new JLabel();
    private final JLabel textLabel = new JLabel(text);
    private final MyLabelUI myUI = new MyLabelUI();

    public LayoutTest() {
        super(new GridLayout(0, 1));
        this.setBorder(BorderFactory.createCompoundBorder(
            new LineBorder(Color.blue), new EmptyBorder(5, 5, 5, 5)));
        textLabel.setUI(myUI);
        textLabel.setFont(new Font("Serif", Font.ITALIC, 24));
        this.add(sizeLabel);
        this.add(textLabel);
        this.addComponentListener(new ComponentAdapter() {

            @Override
            public void componentResized(ComponentEvent e) {
                sizeLabel.setText(
                    "Before: " + myUI.before + " after: " + myUI.after);
            }
        });
    }

    private static class MyLabelUI extends BasicLabelUI {

        int before, after;

        @Override
        protected String layoutCL(
            JLabel label, FontMetrics fontMetrics, String text, Icon icon,
            Rectangle viewR, Rectangle iconR, Rectangle textR) {
            before = text.length();
            String s = super.layoutCL(
                label, fontMetrics, text, icon, viewR, iconR, textR);
            after = s.length();
            System.out.println(s);
            return s;
        }
    }

    private void display() {
        JFrame f = new JFrame("LayoutTest");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.add(this);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                new LayoutTest().display();
            }
        });
    }
}
于 2010-08-30T03:31:33.987 回答
11

如果您正在谈论一个网站 - 即输出 HTML/JS/CSS,您可以丢弃所有这些解决方案,因为有一个纯 CSS 解决方案。

text-overflow:ellipsis;

它并不像在 CSS 中添加那种样式那么简单,因为它会与其他 CSS 交互;例如,它要求元素具有溢出:隐藏;如果你想让你的文本在一行,white-space:nowrap;也很好。

我有一个看起来像这样的样式表:

.myelement {
  word-wrap:normal;
  white-space:nowrap;
  overflow:hidden;
  -o-text-overflow:ellipsis;
  text-overflow:ellipsis;
  width: 120px;
}

您甚至可以有一个“阅读更多”按钮,该按钮只需运行一个 javascript 函数来更改样式,然后宾果游戏,框将重新调整大小并且全文将可见。(但在我的情况下,我倾向于使用 html 标题属性作为全文,除非它可能会变得很长)

希望有帮助。这是一个更简单的解决方案,试图弄乱计算文本大小并截断它,等等。(当然,如果您正在编写一个非基于 Web 的应用程序,您可能仍然需要这样做)

这个解决方案有一个缺点:Firefox 不支持省略号样式。烦人,但我认为这并不重要——它仍然会正确截断文本,因为这是由溢出处理的:隐藏,它只是不显示省略号。它确实适用于所有其他浏览器(包括 IE,一直到 IE5.5!),所以 Firefox 还没有做到这一点有点烦人。希望新版本的 Firefox 能尽快解决这个问题。

[编辑]
人们仍在对这个答案进行投票,所以我应该编辑它以注意 Firefox 现在确实支持省略号样式。该功能是在 Firefox 7 中添加的。如果您使用的是早期版本(FF3.6 和 FF4 仍然有一些用户),那么您就不走运了,但大多数 FF 用户现在都可以了。这里有更多细节:text-overflow:ellipsis in Firefox 4? (和FF5)

于 2010-09-07T10:09:02.633 回答
6

对我来说,这将是理想的 -

 public static String ellipsis(final String text, int length)
 {
     return text.substring(0, length - 3) + "...";
 }

我不会担心每个字符的大小,除非我真的知道它将在哪里以及以什么字体显示。许多字体是固定宽度字体,其中每个字符都具有相同的尺寸。

即使它是可变宽度的字体,如果你把'i'、'l'算作宽度的一半,那么为什么不把'w'和'm'算作宽度的两倍呢?字符串中这些字符的混合通常会平均它们大小的影响,我宁愿忽略这些细节。明智地选择“长度”的值最重要。

于 2010-09-02T05:11:59.370 回答
6

使用 Guava 的com.google.common.base.Ascii.truncate(CharSequence, int, String)方法:

Ascii.truncate("foobar", 7, "..."); // returns "foobar"
Ascii.truncate("foobar", 5, "..."); // returns "fo..."
于 2017-02-22T06:34:22.840 回答
5

这个怎么样(得到一个 50 个字符的字符串):

text.replaceAll("(?<=^.{47}).*$", "...");
于 2015-03-26T19:07:49.710 回答
4
 public static String getTruncated(String str, int maxSize){
    int limit = maxSize - 3;
    return (str.length() > maxSize) ? str.substring(0, limit) + "..." : str;
 }
于 2012-05-31T12:48:26.540 回答
3

如果您担心省略号仅隐藏了极少数字符,为什么不检查该条件呢?

public static String ellipsis(final String text, int length)
{
    // The letters [iIl1] are slim enough to only count as half a character.
    length += Math.ceil(text.replaceAll("[^iIl]", "").length() / 2.0d);

    if (text.length() > length + 20)
    {
        return text.substring(0, length - 3) + "...";
    }

    return text;
}
于 2010-09-02T04:52:01.500 回答
3

我会选择类似于您拥有的标准模型的东西。我不会为字符宽度的事情而烦恼-正如@Gopi所说,最终可能会全部平衡。我要做的是新的是有另一个参数,叫做“minNumberOfhiddenCharacters”(可能不那么冗长)。然后在进行省略号检查时,我会执行以下操作:

if (text.length() > length+minNumberOfhiddenCharacters)
{
    return text.substring(0, length - 3) + "...";
}

这意味着如果您的文本长度为 35,您的“长度”为 30,并且您要隐藏的最小字符数为 10,那么您将获得完整的字符串。如果您要隐藏的最小字符数是 3,那么您将得到省略号而不是这三个字符。

需要注意的主要事情是我已经颠覆了“长度”的含义,使其不再是最大长度。输出字符串的长度现在可以是 30 个字符(当文本长度大于 40 时)到 40 个字符(当文本长度为 40 个字符时)。实际上,我们的最大长度变为 length+minNumberOfhiddenCharacters。当原始字符串小于 30 时,字符串当然可以短于 30 个字符,但这是我们应该忽略的无聊情况。

如果您希望长度是一个硬而快速的最大值,那么您需要更多类似的东西:

if (text.length() > length)
{
    if (text.length() - length < minNumberOfhiddenCharacters-3)
    {
        return text.substring(0, text.length() - minNumberOfhiddenCharacters) + "...";
    }
    else
    {
        return text.substring(0, length - 3) + "...";
    }
}

所以在这个例子中,如果 text.length() 是 37,length 是 30 并且 minNumberOfhiddenCharacters = 10 那么我们将进入内部 if 的第二部分,得到 27 个字符 + ... 得到 30。这实际上是一样的就好像我们进入了循环的第一部分(这表明我们的边界条件是正确的)。如果文本长度为 36,我们将得到 26 个字符 + 省略号,给我们 29 个字符,其中 10 个隐藏。

我在争论重新排列一些比较逻辑是否会使其更直观,但最终决定保持原样。你可能会发现这text.length() - minNumberOfhiddenCharacters < length-3让你在做什么变得更加明显。

于 2010-09-02T16:02:52.997 回答
3

在我看来,没有像素数学就无法获得好的结果。

因此,当您在 Web 应用程序上下文(如 facebook)中时,Java 可能是解决此问题的错误方法。

我会选择 javascript。由于 Javascript 不是我感兴趣的主要领域,我无法真正判断是否是一个好的解决方案,但它可能会给你一个指导。

于 2010-09-03T15:30:08.283 回答
1

大多数这些解决方案都没有考虑字体指标,这是我多年来使用的 java swing 的一个非常简单但有效的解决方案。

private String ellipsisText(String text, FontMetrics metrics, Graphics2D g2, int targetWidth) {
   String shortText = text;
   int activeIndex = text.length() - 1;

   Rectangle2D textBounds = metrics.getStringBounds(shortText, g2);
   while (textBounds.getWidth() > targetWidth) {
      shortText = text.substring(0, activeIndex--);
      textBounds = metrics.getStringBounds(shortText + "...", g2);
   }
   return activeIndex != text.length() - 1 ? shortText + "..." : text;
}
于 2018-11-21T10:03:57.727 回答
-2

您也可以像这样简单地实现:

mb_strimwidth($string, 0, 120, '...')

谢谢。

于 2020-05-14T01:24:47.173 回答