ClickableSpan handling of selectable text requires double-click

I have a TextView in which every word is a ClickableSpan . When clicked, the word becomes bold , and the dictionary phrase is shown in another TextView. The application works correctly until I make the text in the TextView available. When the text is made selected, the definition is displayed by clicking, but the word is highlighted in bold with a double click. The text is selected by double-clicking or long pressing (but long pressing does not make the word bold).

I suppose the problem is when the drawing state is updated during the processing of actions, but I could not find a fix. I tried to set the TextView focusable="false" , but nothing changed. The corresponding 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 definition of 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; } } 

Setting the move method for 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 fad that can help with the solution. If I double-clicked, but changed the words between clicks (tap one word and then another word quickly), the first press will show the definition for that word, and the second touch the word FIRST is shown in bold, but SECOND word is selected (highlighted) and the definition for the FIRST word is still shown.

So, for example, if I double-click “first” and then “second”, when I press “first” the definition for “first” will be shown, and when I touch “second” the word “first” is shown in bold and the word “ second "is highlighted but the definition does not change (the definition for" First "is still shown).

+5
source share
1 answer

Replace createMovementMethod following. If you make a mistake plz face error, correct it and edit this answer.

 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); } }; } 
0
source

Source: https://habr.com/ru/post/1260050/


All Articles