Обработка щелчка для ClickableSpan с выбираемым текстом требует двойного щелчка

У меня есть TextView в котором каждое слово является ClickableSpan, При нажатии слово становится жирным, и словарное определение слова отображается в другом TextView. Приложение работает правильно, пока я не сделаю текст в TextView доступным для выбора. Когда текст становится доступным для выбора, определение показывается по щелчку, но слово выделяется только жирным шрифтом при двойном щелчке. Текст выделяется двойным щелчком или длительным нажатием (но длительное нажатие не делает слово жирным).

Я предполагаю, что проблема как-то связана с тем, что в процессе обработки действий состояние рисования обновляется, но я не смог найти исправления. Я пытался установить TextViewfocusable="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);
            }
        };
    }
Другие вопросы по тегам