Android: как определить индекс символа позиции сенсорного события в TextView?

У меня есть TextView с OnTouchListener. Мне нужен индекс символа, на который указывает пользователь, когда я получаю MotionEvent. Есть ли способ получить базовые метрики шрифта TextView?


person Epaga    schedule 20.02.2010    source источник


Ответы (3)


Я не знаю простого прямого способа сделать это, но вы сможете собрать что-то вместе, используя Рисовать объект TextView с помощью вызова TextView.getPaint()

Когда у вас есть объект рисования, у вас будет доступ к базовым FontMetrices через вызов Paint.getFontMetrics() и доступ к другим функциям, таким как Paint.measureText(), Paint.getTextBounds() и Paint.getTextWidths() для доступа к реальный размер отображаемого текста.

person snctln    schedule 20.02.2010
comment
Спасибо, в итоге я использовал это. Жаль, что нет простого встроенного метода типа pointToCharacterIndex, но это не было ракетостроением, чтобы свернуть мой собственный благодаря Paint. - person Epaga; 22.02.2010
comment
В ответе @Tony Blues есть то, что вы ищете. - person James Moore; 07.07.2011

Вы пробовали что-то вроде этого:

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
}

Надеюсь это поможет...

person Tony Blues    schedule 28.04.2011
comment
Я спросил инженеров Google в рабочее время Google IO, и это то же самое, что они придумали. - person James Moore; 10.05.2011
comment
Я так счастлив, что нашел этот ответ, спасибо, Тони! Он отлично работает! Теперь у меня нет проблем с сенсорными событиями, даже если TextView содержит изображения. :-) - person einschnaehkeee; 29.11.2011
comment
Спасибо за спасение моей жизни :) - person Mingfei; 22.12.2015
comment
stackoverflow.com/questions/62528990/ - person ATES; 23.06.2020
comment
на случай, если кому-то нужна позиция char, когда он знает индекс char - person Ultimo_m; 18.05.2021

Хотя в целом это работает, у меня было несколько проблем с ответом Тони Блюза.

Во-первых, getOffsetForHorizontal возвращает смещение даже если координата x выходит далеко за последний символ строки.

Во-вторых, возвращаемое смещение символа иногда относится к следующему символу, а не к символу непосредственно под указателем. По-видимому, метод возвращает смещение ближайшей позиции курсора. Это может быть слева или справа от персонажа, в зависимости от того, что находится ближе.

В моем решении вместо определения курсора используется 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.

person devconsole    schedule 06.04.2018
comment
Спасибо за информацию. Но мой вопрос: getOffsetForPosition() TextView у вас не работает? Он также использует getLineForVertical() и getOffsetForHorizontal() под капотом, но выполняет некоторые дополнительные вычисления, чтобы быть более точным. Я все равно проголосовал :) - person Jenix; 25.07.2018
comment
@Jenix Я только что провел быстрый тест, и кажется, что TextView.getOffsetForPosition также часто возвращает смещение следующего символа вправо, а не символ непосредственно под указателем. Попробуйте увеличить размер шрифта текстового представления до чего-то довольно большого, чтобы убедиться в этом самостоятельно. - person devconsole; 27.07.2018
comment
Эй, это снова я! :) Хотя мне больше всего нравится ваш способ, у него есть одна серьезная (по крайней мере для меня) проблема. Всякий раз, когда автоматический разрыв строки работает с текстом, этот код не может подобрать каждый последний символ в каждой строке, кроме самого последнего. Если я помещаю символы разрыва строки вручную в те же позиции в тексте, все работает нормально. Вы знаете, почему это происходит? - person Jenix; 24.11.2018
comment
@Jenix Я снова проверил это в эмуляторе Android 9 и не смог воспроизвести проблему. - person devconsole; 28.11.2018
comment
‹TextView android:id=@+id/textView android:textSize=30sp android:layout_width=100dp android:layout_height=wrap_content android:text=abcdefghijklmnopqrstuvwxyz /› - person Jenix; 29.11.2018
comment
Я создал новый пустой проект Android и поместил TextView, такой же, как указано выше. Используя ваш код, я получаю -1, когда касаюсь символов f, m, s, y, которые являются последними в каждой строке. Я провел много подобных тестов и сделал это снова, прочитав ваш новый комментарий, но то же самое. Подтверждено на Samsung Galaxy Note 2, S7 Edge и даже на эмуляторах Genymotion. - person Jenix; 29.11.2018
comment
Ах, я не тестировал на Android 9, но нет смысла, если другие не работают должным образом. - person Jenix; 29.11.2018
comment
@Jenix Извините, я тестировал только естественные разрывы (пробелы во вводном тексте), я думаю, это не имеет ничего общего с версией Android. - person devconsole; 29.11.2018
comment
@Jenix Прежде чем я обновлю свой ответ, проверьте эту версию: gist.github.com/devconsole/64040ec74bde98f59f2d70aa6bfaf4e1 - person devconsole; 29.11.2018
comment
Большой! Превосходно! Работает как шарм! Большое спасибо! - person Jenix; 29.11.2018
comment
Просто любопытно. Считаете ли вы, что ваш код дороже, чем TextView.getOffsetForPosition? - person Jenix; 30.11.2018