简体   繁体   English

处理具有可选文本的ClickableSpan的单击需要双击

[英]Handling click for a ClickableSpan with selectable text requires double click

I have a TextView in which every word is a ClickableSpan . 我有一个TextView ,其中每个词都是一个ClickableSpan When clicked, the word become bold and the dictionary definition of the word is shown in another TextView . 单击时,单词变为粗体,并且该单词的字典定义显示在另一个TextView中 The app works correctly until I make the text in the TextView selectable. 该应用程序可以正常工作,直到我使TextView中的文本变为可选状态为止。 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. 我尝试设置TextView focusable="false"但未进行任何更改。 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: 和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 movement method for the TextView: 设置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. 如果我双击但在两次单击之间更改了单词(快速点击一个单词,然后快速更改另一个单词),则在第一个点击上显示该单词的定义,在第二个点击上,第一个单词为粗体,但第二个单词被选中(突出显示) ),并且仍然显示FIRST单词的定义。

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"). 因此,例如,如果我先双击“ first”,然后双击“ second”,则当我单击“ first”时,将显示“ first”的定义,而当我触摸“ second”时,单词“ first”将加粗,而单词“ first”高亮显示第二个”,但定义不变(仍显示“第一个”的定义)。

Replace createMovementMethod with following. 将以下内容替换为createMovementMethod 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);
            }
        };
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM