1

I want to set ClickableSpans for each character in an EditText, but their positions do not match the character at the end of each line.

Each letter has a ClickableSpan set to it using BreakIterator. Each Clickable opens an AlertDialog showing the clicked letter. It works fine if the letter is not at the end of the line. A red cross shows the click position:

'S' clicked in middle of line works fine:

BUT, clicking letters at the end of a line activates the previous letter's ClickableSpan:

'Y' was clicked but 'X' ClickableSpan activated:

And clicking in the area just before the first letter of the next line will activate the previous line's last letter:

Area just before 'Z' clicked, and 'X' of previous line activated:

How can I get the ClickableSpans to position correctly at the end of lines?

Java code:

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_main);
    EditText et = findViewById(R.id.editText);
    et.setText("ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abc");

    String text = et.getText().toString();
    et.setMovementMethod(LinkMovementMethod.getInstance());
    Spannable spannable = et.getText();

    BreakIterator iterator = BreakIterator.getCharacterInstance();
    iterator.setText(text);
    int iteratorStart = iterator.first();
    for (int iteratorEnd = iterator.next(); iteratorEnd != BreakIterator.DONE;
         iteratorStart = iteratorEnd, iteratorEnd = iterator.next()) {
        String letter = text.substring(iteratorStart, iteratorEnd);
        ClickableSpan cs = generateClickableSpan(letter);
        spannable.setSpan(cs, iteratorStart, iteratorEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    }
}


private ClickableSpan generateClickableSpan(final String letter) {
    return new ClickableSpan() {
        final String currentLetter;

        {
            currentLetter = letter;
        }

        @Override
        public void onClick(View widget) {
            AlertDialog.Builder adBuilder = new AlertDialog.Builder(MainActivity.this);
            AlertDialog ad = adBuilder.create();
            ad.setMessage(letter);
            ad.show();
            TextView tv = ad.findViewById(android.R.id.message);
            tv.setTextSize(24);
            ad.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
        }

        public void updateDrawState(TextPaint ds) {
            ds.setUnderlineText(false);
        }
    };
}

EditText xml:

<EditText
            android:id="@+id/editText"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="8dp"
            android:background="#00000000"
            android:inputType="none"
            android:textIsSelectable="true"
            android:layout_gravity="top"
            android:textSize="42sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

I've tried the solutions and suggestions in the following answers including making a custom LinkMovementMethod class but nothing has worked:

ClickableSpan in the EditText to the end of text calls click() to the end of line

ClickableSpan strange behavior:onClick() called when clicking empty space

Thanks!!

1 Answers1

0

I solve it by using the second link, because the buffer may get two links with the same offset.

public static class MySpanMovementMethod extends LinkMovementMethod {

        @Override
        public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
            int action = event.getAction();

            if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
                int x = (int) event.getX();
                int y = (int) event.getY();

                x -= widget.getTotalPaddingLeft();
                y -= widget.getTotalPaddingTop();

                x += widget.getScrollX();
                y += widget.getScrollY();

                Layout layout = widget.getLayout();
                int line = layout.getLineForVertical(y);
                int off = layout.getOffsetForHorizontal(line, x);
                int end = layout.getLineEnd(line);

                Log.i(TAG, "tts span " + line + "=" + off + "=" + end);

                if (off >= widget.getText().length()) {
                    // Return true so click won't be triggered in the leftover empty space
                    return true;
                }
                TtsClickSpan[] links = buffer.getSpans(off, off, TtsClickSpan.class);

                if (links != null && links.length > 1) {

                    if (action == MotionEvent.ACTION_UP) {
                        links[1].onClick(widget);
                    }
                    return true;
                }

            }

            return super.onTouchEvent(widget, buffer, event);
        }
    }
hyand
  • 1
  • 3