I have a TextView
in which every word is a ClickableSpan
. When clicked, the word become bold and the dictionary definition of the word is shown in another TextView. The app works correctly until I make the text in the TextView selectable. When the text is made selectable, the definition is shown on click, but the word is only bold on a double click. Text is selected on double click or long press (but long press does not make the word bold).
My guess is that the problem has something to do with when in the action handling process the draw state is updated, but I am not been able to find a fix. I tried setting the TextView
focusable="false"
but nothing changed. Relevant code is below.
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);
And the WordSpan definition:
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;
}
}
Setting the movement method for the 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);
}
};
}
Edit: I just discovered a new quirk that may help in solving. If I double click but change words between clicks (tap one word & then a different word quickly), on the first tap the definition for that word is shown and on the second tap the FIRST word is bold but the SECOND word is selected (highlighted) and the definition for the FIRST word is still shown.
So for example if I double tap "first" and then "second", when I click "first" the definition for "first" will be shown, and when I touch "second" the word "first" is bold and the word "second" is highlighted but the definition does not change (still showing definition for "first").