Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Determining which word is clicked in an android textview

Basically, I want to display a passage of text (potentially, quite a long text), and allow the user to click on any word. At that point, I want to determine which word they clicked on. I also want to get the whole sentence in which the word appears (this is pretty trivial, assuming I can determine which position the word is at within the text).

Ideally, I would just listen for a onTouch event, get the X and Y, and say something like textView.wordAt(event.x, event.y) or textView.cursorPositionNearest(event.x, event.y), but it seems it's not so easy :-)

My current best effort involves using a TextView and creating one ClickableSpan per word. It works, but it ain't exactly elegant, and I guess it would start to eat memory if I use it on longer texts.

private final String text = "This is the text";
private TextView textView;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_text_view);

    textView = (TextView) findViewById(R.id.text_view);

    SpannableString ss = new SpannableString(text);
    //  create spans for "this", "is", "the" and "text"
    ss.setSpan(new IndexedClickableSpan(0, 4), 0, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    ss.setSpan(new IndexedClickableSpan(5, 7), 5, 7, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    ss.setSpan(new IndexedClickableSpan(8, 11), 8, 11, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    ss.setSpan(new IndexedClickableSpan(12, 16), 12, 16, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

    textView.setText(ss);
}

private final class IndexedClickableSpan extends ClickableSpan {

    int startIndex, endIndex;

    public IndexedClickableSpan(int startIndex, int endIndex) {
        this.startIndex = startIndex;
        this.endIndex = endIndex;
    }

    @Override
    public void onClick(View widget) {
        String word = TextViewActivity.this.text.substring(startIndex, endIndex);
        Toast.makeText(TextViewActivity.this, "You clicked on " + word, Toast.LENGTH_SHORT).show();
    }
}

If anyone has a better idea, I'd love to hear it.

Thanks in advance, Dave

Not sure how i'm really supposed to answer questions on stackoverflow, but I've managed to rip some code out of the Android 15 API and modify it very slightly to do what i needed. Thanks to Dheeraj for the suggestion.

The new code allows me to get a caret position based on a touch event position, from there I should be able to get the word that was touched, and the sentence it appears in. Code attached:

public int getOffsetForPosition(TextView textView, float x, float y) {
    if (textView.getLayout() == null) {
        return -1;
    }
    final int line = getLineAtCoordinate(textView, y);
    final int offset = getOffsetAtCoordinate(textView, line, x);
    return offset;
}

private int getOffsetAtCoordinate(TextView textView2, int line, float x) {
    x = convertToLocalHorizontalCoordinate(textView2, x);
    return textView2.getLayout().getOffsetForHorizontal(line, x);
}

private float convertToLocalHorizontalCoordinate(TextView textView2, float x) {
    x -= textView2.getTotalPaddingLeft();
    // Clamp the position to inside of the view.
    x = Math.max(0.0f, x);
    x = Math.min(textView2.getWidth() - textView2.getTotalPaddingRight() - 1, x);
    x += textView2.getScrollX();
    return x;
}

private int getLineAtCoordinate(TextView textView2, float y) {
    y -= textView2.getTotalPaddingTop();
    // Clamp the position to inside of the view.
    y = Math.max(0.0f, y);
    y = Math.min(textView2.getHeight() - textView2.getTotalPaddingBottom() - 1, y);
    y += textView2.getScrollY();
    return textView2.getLayout().getLineForVertical((int) y);
}
like image 847
Gabby Moore Avatar asked Jul 22 '12 14:07

Gabby Moore


1 Answers

If you're targeting API level 14+, you could use getOffsetForPosition().

For previous Android version, see if you can copy the code for this method from Android source and extend your own TextView.

like image 137
Dheeraj Vepakomma Avatar answered Nov 03 '22 03:11

Dheeraj Vepakomma