5

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").

Pravin Divraniya
  • 3,642
  • 2
  • 26
  • 45
Matt Robertson
  • 2,283
  • 5
  • 23
  • 51
  • Hey! Am I correct understand question. How to handle double click on TextView with Spannable String with different clickable text? – GensaGames Nov 24 '16 at 09:37
  • It should be a single click instead of a double click. When the user clicks a word, it should bold the word and show the definition. A double click should not do anything. – Matt Robertson Nov 24 '16 at 12:29

1 Answers1

0

Replace createMovementMethod with following. If you error face error plz fix 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);
            }
        };
    }
Qamar
  • 3,993
  • 23
  • 42
  • Thanks for replying, but this does not correct the problem and adds the additional problem that the wrong word is selected on touch. – Matt Robertson Nov 28 '16 at 09:32
  • Also, onDoubleTap() is not triggered by a double tap. It only works when I double tap, but this method is not triggered and so I cannot catch it and redirect like this answer attempts to do. – Matt Robertson Nov 28 '16 at 09:34
  • Yes I know ,required something similar. It was not tested you need to fix it and edit answer itself. thanks – Qamar Nov 28 '16 at 10:08