Обработка щелчка для ClickableSpan с выбираемым текстом требует двойного щелчка
У меня есть TextView
в котором каждое слово является ClickableSpan
, При нажатии слово становится жирным, и словарное определение слова отображается в другом TextView. Приложение работает правильно, пока я не сделаю текст в TextView доступным для выбора. Когда текст становится доступным для выбора, определение показывается по щелчку, но слово выделяется только жирным шрифтом при двойном щелчке. Текст выделяется двойным щелчком или длительным нажатием (но длительное нажатие не делает слово жирным).
Я предполагаю, что проблема как-то связана с тем, что в процессе обработки действий состояние рисования обновляется, но я не смог найти исправления. Я пытался установить TextView
focusable="false"
но ничего не изменилось. Соответствующий код ниже.
curSpan = new WordSpan(index) {
@Override
public void onClick(View view) {
handleWordClick(index,this); // handles code to display definition
setMarking(true);
view.invalidate();
tvText.invalidate();
}
};
spannableStringBuilder.setSpan(curSpan, totalLength, totalLength + strWord, Spanned.SPAN_COMPOSING);
И определение WordSpan:
class WordSpan extends ClickableSpan
{
int id;
private boolean marking = false;
public WordSpan(int id) {
this.id = id;
}
@Override
public void updateDrawState(TextPaint ds) {
ds.setColor(Color.BLACK);
ds.setUnderlineText(false);
if (marking) {
ds.setTypeface(Typeface.create(myFont,Typeface.BOLD));
}
}
@Override
public void onClick(View v) {}
public void setMarking(boolean m) {
marking = m;
}
}
Установка метода движения для TextView:
private MovementMethod createMovementMethod ( Context context ) {
final GestureDetector detector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onSingleTapUp ( MotionEvent e ) {
return true;
}
@Override
public boolean onSingleTapConfirmed ( MotionEvent e ) {
return true;
}
});
return new ScrollingMovementMethod() {
@Override
public boolean canSelectArbitrarily () {
return true;
}
@Override
public void initialize(TextView widget, Spannable text) {
Selection.setSelection(text, text.length());
}
@Override
public void onTakeFocus(TextView view, Spannable text, int dir) {
if ((dir & (View.FOCUS_FORWARD | View.FOCUS_DOWN)) != 0) {
if (view.getLayout() == null) {
// This shouldn't be null, but do something sensible if it is.
Selection.setSelection(text, text.length());
}
} else {
Selection.setSelection(text, text.length());
}
}
@Override
public boolean onTouchEvent ( TextView widget, Spannable buffer, MotionEvent event ) {
// check if event is a single tab
boolean isClickEvent = detector.onTouchEvent(event);
// detect span that was clicked
if (isClickEvent) {
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);
WordSpan[] link = buffer.getSpans(off, off, WordSpan.class);
if (link.length != 0) {
// execute click only for first clickable span
// can be a for each loop to execute every one
if (event.getAction() == MotionEvent.ACTION_UP) {
link[0].onClick(widget);
} else if (event.getAction() == MotionEvent.ACTION_DOWN) {
Selection.setSelection(buffer,
buffer.getSpanStart(link[0]),
buffer.getSpanEnd(link[0]));
}
return true;
}
}
// let scroll movement handle the touch
return super.onTouchEvent(widget, buffer, event);
}
};
}
Изменить: я только что обнаружил новую причуду, которая может помочь в решении. Если я дважды щелкаю, но меняю слова между щелчками (быстро нажимаю одно слово, а затем другое слово), при первом касании отображается определение этого слова, а при втором касании слово ПЕРВЫЙ выделяется жирным шрифтом, но выбирается ВТОРОЕ слово (подсвечивается).) и определение для ПЕРВОГО слова все еще показано.
Так, например, если дважды нажать "первый", а затем "второй", при нажатии "первый" будет показано определение "первый", а при касании "второй" слово "первый" выделено жирным шрифтом, а слово "первый" "второе" подсвечивается, но определение не меняется (по-прежнему отображается определение "первый").
1 ответ
Замещать createMovementMethod
со следующим. Если вы столкнулись с ошибкой, пожалуйста, исправьте ее и отредактируйте этот ответ.
private MovementMethod createMovementMethod (final Context context ) {
return new ScrollingMovementMethod() {
public MotionEvent event;
public Spannable buffer;
public TextView widget;
final GestureDetector detector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onSingleTapUp ( MotionEvent e ) {
return true;
}
@Override
public boolean onSingleTapConfirmed ( MotionEvent e ) {
triggerClick();
return true;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
triggerClick();
return true;
}
private boolean triggerClick() {
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);
WordSpan[] link = buffer.getSpans(off, off, WordSpan.class);
if (link.length != 0) {
// execute click only for first clickable span
// can be a for each loop to execute every one
if (event.getAction() == MotionEvent.ACTION_UP) {
link[0].onClick(widget);
} else if (event.getAction() == MotionEvent.ACTION_DOWN) {
Selection.setSelection(buffer,
buffer.getSpanStart(link[0]),
buffer.getSpanEnd(link[0]));
}
return true;
}
return true;
}
});
@Override
public boolean canSelectArbitrarily () {
return true;
}
@Override
public void initialize(TextView widget, Spannable text) {
Selection.setSelection(text, text.length());
}
@Override
public void onTakeFocus(TextView view, Spannable text, int dir) {
if ((dir & (View.FOCUS_FORWARD | View.FOCUS_DOWN)) != 0) {
if (view.getLayout() == null) {
// This shouldn't be null, but do something sensible if it is.
Selection.setSelection(text, text.length());
}
} else {
Selection.setSelection(text, text.length());
}
}
@Override
public boolean onTouchEvent (TextView widget, Spannable buffer, MotionEvent event ) {
// check if event is a single tab
boolean isClickEvent = detector.onTouchEvent(event);
//record this for GestureDetector
this.widget = widget;
this.buffer = buffer;
this.event = event;
// detect span that was clicked
if (isClickEvent) {
//ignore click here
return true;
}
// let scroll movement handle the touch
return super.onTouchEvent(widget, buffer, event);
}
};
}