如何查找安卓TextView每行的字符数?

25
在Android中,我有一个TextView,它的宽度与屏幕长度相同,内边距为dip 5。如何计算在屏幕上适合单行的字符数?换句话说,我想知道textview的列数是多少?
我考虑手动计算,取决于文本大小和宽度,但1)我不知道它们之间的相关性,2)由于dip单位的填充,不同的屏幕将使用不同数量的实际像素来填充。
总体问题:我试图解决这个问题:如果给定一个字符串,如何手动编辑字符串,以便当textview逐个字符打印字符串时,我将知道何时开始一个不适合一行的单词。请注意:我知道textview会自动将不适合的单词放到下一行,但是由于我正在逐个字符地打印,就像输入动画一样,textview在打印出该单词的溢出字符之前不知道该单词不适合。
已经到处搜索了这个问题...
谢谢!
添加的解决方案:
一个可能的解决方案:
public String measure2 (TextView t, String s) {
    String u = "";
    int start = 0;
    int end = 1;
    int space = 0;
    boolean ellipsized = false;
    float fwidth = t.getMeasuredWidth();
    for(;;) {
        //t.setText(s.substring(start, end));
        float twidth = t.getPaint().measureText(s.substring(start, end));
        if (twidth < fwidth){
            if (end < s.length())
                end++;
            else {
                if (!ellipsized)
                    return s;
                return u + s.subSequence(start, end);
            }
        }
        else {
            ellipsized = true;
            space = (u + s.substring(start, end)).lastIndexOf(" ");
            if (space == -1)
                space = end - 1;
            u += s.subSequence(start, space) + "\n";
            start = space + 1;
            end = start + 1;
        }
    }
}

解决方案2,但有时仍然使用解决方案1。
public String measure3 (TextView t, String s) {
    List<String> wlist = Arrays.asList(s.split(" "));
    if (wlist.size() == 1)
        return measure2(t, s);
    String u = "";
    int end = 1;
    float fwidth = t.getMeasuredWidth();
    for(;;) {
        //t.setText(s.substring(start, end));
        if (wlist.isEmpty())
            return u;
        String temp = listStr(wlist, end);
        float twidth = t.getPaint().measureText(temp);
        if (twidth < fwidth){
            if (end < wlist.size())
                end++;
            else {
                return u + temp;
            }
        }
        else {
            temp = listStr(wlist, end-1);
            if (end == 1)
                temp = measure2(t, temp);
            if (wlist.isEmpty())
                return u + temp;
            else
                u = u + temp + "\n";
            wlist = wlist.subList(end - 1, wlist.size());
            end = 1;
        }
    }
}

public String listStr (List<String> arr, int end) {
    String s = "";
    for (String e : arr.subList(0, end) ){
        s = s + e + " ";
    }
    return s.trim();
}

我使用了上面的代码来生成一个原始字符串s,然后生成一个将被打印出来的字符串u。但是,我认为这种方法非常低效。是否有另一种方法或更好的算法?注意:measure3中有一些错误,但我懒得编辑。


2
我认为默认字体不是等宽的。因此,相对于 I 字母,它可以容纳的 M 字母数量会更少。如果您正在包含一个等宽字体,那么找出方法之一是不断将字符串添加到视图中,然后手动在屏幕上计数。但是,这也会因不同的屏幕而异,可能会对您的使用造成问题。 - FoamyGuy
我会看一下这个问题:https://dev59.com/l2025IYBdhLWcg3wAg7p - codeskraps
@Derek 能否更新 measure3 函数。我想使用这个解决方案。谢谢 - VIISHRUT MAVANII
@Derek 最终我得到了解决方案,你也应该尝试这个 https://stackoverflow.com/a/56753731/6676310 - VIISHRUT MAVANII
3个回答

35

试试这个:

private boolean isTooLarge (TextView text, String newText) {
    float textWidth = text.getPaint().measureText(newText);
    return (textWidth >= text.getMeasuredWidth ());
}
检测有多少个字符符合要求是不可能的,因为字符的宽度是可变的。上面的函数将测试特定字符串是否适合在TextView中。newText的内容应该是特定行中的所有字符。如果为真,则开始新行(并使用新字符串作为参数)。

回复评论:

  1. 因为该应用程序可以在许多系统上运行,所以正是需要测量它。
  2. 这是解决您“总体问题”的方法。使用str.size()>numCol与太大有何区别?您需要实现动画(提示#1:插入换行符)。
  3. 正如我之前所说,当您开始新行时,就会开始一个新字符串(提示#2:如果扩展TextView,则可以通过覆盖setText来实现所有这些)。 (提示3:使用static int lines;跟踪创建的行,并使用newString.split("\\r?\\n")[lines-1]检查长度)。

我正在设置字体大小,但由于我喜欢在多个系统上使用该应用程序,因此我不会手动计算长度。 此外,您的isTooLarge可以在第一次溢出发生时获取字符串的长度,但它仍将使第一行受到textview省略动画的影响。 感谢您的帮助,但我需要一些东西,可以让我知道每行文本视图何时省略。 - Derek
好的,我在编辑后的问题中发布了我的解决方案,有更有效的方法吗? - Derek
@Derek - 首先,我看到你是逐个字符测量的。你想知道整个单词是否适合,所以将字符串分成单词(str.split(" "))并循环遍历单词。其次,先测试函数。它真的那么低效吗?你有注意到任何延迟吗? - Aleadam
我理解你的观点,所以我创建了measure3。然而,我不知道为什么textview即使只有一个没有空格的巨大字符串,也会出现省略号,因此在单词超过宽度的情况下必须使用measure2,但是measure3使用了str.split(" "),这确实提高了效率。我意识到我在这个应用程序上所做的操作不会出现滞后,因为它只读取短到中等长度的字符串,但是假设我在另一个需要读取书籍章节或其他大文件的应用程序中使用此功能,是否有更有效的代码?还是这种方法是唯一的方法? - Derek
@Derek 最后两点,我们就收工吧:(1)如果你一次只动画一个字符,计算速度会比动画快得多,因此字符串的长度并不重要。此外,当到达屏幕底部时,可以停止计算。为什么要浪费时间去计算更多呢?(2)如果你跟踪每行开头单词的数量,那么你可以使用该数字来开始下一次迭代,而不是从头开始(你可能已经这样做了,我没有再看整个代码,只是根据你的回复)。祝你好运! - Aleadam
显示剩余2条评论

2
您可以通过以下代码获取TextView的总行数,并获取每个字符的字符串。然后,您可以根据需要为每行设置样式。
我将第一行设置为粗体。
private void setLayoutListner( final TextView textView ) {
        textView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                textView.getViewTreeObserver().removeGlobalOnLayoutListener(this);

                final Layout layout = textView.getLayout();

                // Loop over all the lines and do whatever you need with
                // the width of the line
                for (int i = 0; i < layout.getLineCount(); i++) {
                    int end = layout.getLineEnd(0);
                    SpannableString content = new SpannableString( textView.getText().toString() );
                    content.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 0, end, 0);
                    content.setSpan(new StyleSpan(android.graphics.Typeface.NORMAL), end, content.length(), 0);
                    textView.setText( content );
                }
            }
        });
    }

尝试这种方式。您可以通过这种方式应用多个样式。

0

我曾经遇到过同样的问题,我通过两个步骤计算每行字符数:

步骤1:计算行数

 val widthOfTvComment = widthOfScreen - marginLeft - marginRight
 var bounds  = Rect()
 var paint = Paint()
 paint.textSize = textSize
 paint.getTextBounds(comment,0,comment.length,bounds)

val lines = ( bounds.width()/widthOfTvComment)

步骤2:计算每行的字符数

val charactersPerLine = comment.length / lines

1
我相信这只是一个近似值! - Xenolion

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接