ClickableSpan странное поведение: onClick() вызывается при нажатии пустого пространства

У меня есть TextView с ClickableSpan, так как layout_height и layout_width - wrap_content.

Когда текст в TextView не слишком длинный, он работает нормально. Когда текст достаточно длинный, так что он занимает 2 строки, он также отлично работает, но имеет странное поведение.

То есть, когда я щелкнул по второй строке пустого пространства (не заполняя текст еще, но часть TextView), вызывается обратный вызов ClickableSpan onClick().

Я не ожидаю этого, когда я нажимал только на пустое пространство, но не на растянутый текст. Хотя это не сильно влияет на меня, я хочу знать, что стоит.

Я установил ClickableSpan с помощью кода ниже:

TextView tv = (TextView) findViewById(R.id.text);
tv.setText("TEXT TEXT TEXT TEXT");
SpannableStringBuilder ssb = new SpannableStringBuilder();
ssb.append(tv.getText());
ssb.setSpan(new TestClickableSpan(), ssb.length()-5, ssb.length(), 0);
tv.setText(ssb);
tv.setMovementMethod(LinkMovementMethod.getInstance());
tv.setOnTouchListener(new TextViewOnTouchListener());

TextViewOnTouchListener:

class TextViewOnTouchListener implements OnTouchListener{

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            Log.d("TextView", "onTouch");
            return false;
        }       
}

TestClickableSpan:

class TestClickableSpan extends ClickableSpan{

        @Override
        public void onClick(View arg0) {
            Log.d("ClickableSpan", "Confirm OnClick: "+arg0.toString());
        }       
    }

Ответ 1

Мне удалось решить эту проблему, расширив LinkMovementMethod и проверив, произошло ли событие touch смещение равно или больше, чем длина текста:

public class MovementMethod 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);

            if (off >= widget.getText().length()) {
               // Return true so click won't be triggered in the leftover empty space
                return true;
            }
        }

        return super.onTouchEvent(widget, buffer, event);
    }
}

Ответ 2

Ответ dor506 отлично сработал для меня для EditTexts с одной строкой. Однако, когда пользователь набрал несколько строк, решение будет применяться только к последней строке. То есть ClickableSpans в нижней строке будет вести себя должным образом, но во всех других строках будет щелкать через пробел.

Я решил это, изменив:

if (off >= widget.getText().length()) {
    // Return true so click won't be triggered in the leftover empty space
    return true;
}

чтобы:

if (off >= layout.getLineVisibleEnd(line)) {
    // Return true so click won't be triggered in the leftover empty space
    return true;
}

(Извините за публикацию в качестве отдельного ответа - у меня недостаточно репутации, чтобы добавить комментарий, но я подумал, что это может стоить документирования!)

Ответ 3

Ответ dor506 у меня не сработал, поэтому я искал другие решения и нашел этот репозиторий: https://github.com/saket/Better-Link-Movement-Method

Добавив его в свой проект, используйте его так:

textView.setMovementMethod(BetterLinkMovementMethod.newInstance());

Это должно решить проблему.