Android:如何确定TextView中触摸事件位置的字符索引?

11

我有一个带有OnTouchListenerTextView。我的目标是在获取MotionEvent时,获得用户所指向的字符索引。有没有办法获取TextView的底层字体度量?

3个回答

26

你尝试过像这样的方法吗:

Layout layout = this.getLayout();
if (layout != null)
{
    int line = layout.getLineForVertical(y);
    int offset = layout.getOffsetForHorizontal(line, x);

    // At this point, "offset" should be what you want - the character index
}

希望这可以帮助到你...


1
我在Google IO办公时间问了Google工程师,他们得出的是同样的结论。 - James Moore
我很高兴找到了这个答案,谢谢Tony!它完美地运行了!现在,即使TextView包含图像,我也没有处理触摸事件的任何问题。 :-) - einschnaehkeee
谢谢你救了我的命 :) - Mingfei
https://stackoverflow.com/questions/62528990/multiple-line-textview-getlineforvertical-returnin-wrong-line-number-in-android - ATES
如果有人知道字符索引并想要字符位置,则可以参考以下链接:https://dev59.com/8GMl5IYBdhLWcg3woYEv#56740416 - Ultimo_m

5
我不知道有没有简单直接的方法来实现这一点,但是你应该能够通过调用TextView的TextView.getPaint()方法使用Paint对象来组合一些东西。
一旦你有了paint对象,你就可以通过调用Paint.getFontMetrics()来访问底层的FontMetrices,并且可以访问其他函数,如Paint.measureText()、Paint.getTextBounds()和Paint.getTextWidths(),以访问显示文本的实际大小。

谢谢,我最终使用了这个。很遗憾没有内置简单的“pointToCharacterIndex”类型的方法,但是由于Paint的帮助,自己编写也不是什么难事。 - Epaga
@Tony Blues的回答有你所需要的pointToCharacterIndex这种东西。 - James Moore

4
尽管它通常有效,但我在Tony Blues的答案中遇到了一些问题。
首先,即使x坐标远超过行末字符,getOffsetForHorizontal 仍会返回一个偏移量。
其次,返回的字符偏移有时属于下一个字符,而不是指针正下方的字符。显然该方法返回最近光标位置的偏移量。根据靠近程度,此光标可能位于左侧或右侧的字符上。
我的解决方案改用getPrimaryHorizontal 来确定特定偏移量的光标位置,并使用二进制搜索来查找指针x坐标下方的偏移量。
public static int getCharacterOffset(TextView textView, int x, int y) {
    x += textView.getScrollX() - textView.getTotalPaddingLeft();
    y += textView.getScrollY() - textView.getTotalPaddingTop();

    final Layout layout = textView.getLayout();

    final int lineCount = layout.getLineCount();
    if (lineCount == 0 || y < layout.getLineTop(0) || y >= layout.getLineBottom(lineCount - 1))
        return -1;

    final int line = layout.getLineForVertical(y);
    if (x < layout.getLineLeft(line) || x >= layout.getLineRight(line))
        return -1;

    int start = layout.getLineStart(line);
    int end = layout.getLineEnd(line);

    while (end > start + 1) {
        int middle = start + (end - start) / 2;

        if (x >= layout.getPrimaryHorizontal(middle)) {
            start = middle;
        }
        else {
            end = middle;
        }
    }

    return start;
}

编辑:这个更新版本在处理不自然的换行时表现更佳,当一个长单词无法符合一行并被拆分时。

注意事项:在连字号的文本中,点击行尾的连字号将返回其旁边字符的索引。此方法也不适用于RTL文本。


1
@Jenix 我刚刚进行了一个快速测试,似乎 TextView.getOffsetForPosition 经常返回右侧下一个字符的偏移量,而不是指针正下方的字符。尝试将文本视图的字体大小增加到相当大的程度,以便自己查看。 - devconsole
嘿,我又来了! :) 虽然我最喜欢你的方法,但它有一个严重的问题(至少对我来说)。每当自动换行作用于文本时,此代码无法捕获除最后一行以外每行的最后一个字符。如果我在文本中手动放置换行符号,则可以正常工作。你知道为什么会发生这种情况吗? - Jenix
@Jenix 我在 Android 9 模拟器中再次测试了一下,但无法复现此问题。 - devconsole
我创建了一个新的空Android项目,并放置了一个与上面相同的TextView。使用您的代码,当我触摸每行中的最后一个字符f、m、s、y时,我得到-1。我进行了许多类似这样的测试,在阅读了您的新评论后再次进行了测试,但结果仍然相同。在三星Galaxy Note 2、S7 Edge甚至Genymotion模拟器上都得到了确认。 - Jenix
1
@Jenix 在我更新答案之前,请测试此版本:https://gist.github.com/devconsole/64040ec74bde98f59f2d70aa6bfaf4e1 - devconsole
显示剩余6条评论

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