繁体   English   中英

大胆的ClickableSpan触摸

[英]Bold ClickableSpan on touch

我有一个TextView,其中每个单词都是ClickableSpan(实际上是ClickableSpan的自定义子类)。 触摸单词时,应以粗体字体显示。 如果我在TextView上设置textIsSelectable(false),它就可以正常工作。 这个词立即加粗。 但是如果文本是可选择的,那么它就不起作用。 但是 - 如果我触摸一个单词,然后关闭屏幕并重新打开,当屏幕显示重新出现时,该单词将以粗体显示。 我已经尝试了所有我能想到的强制重绘(使TextView无效,强制调用Activity的onRestart(),在TextView上调用refreshDrawableState()等)。 我错过了什么?

这是我的ClickableSpan的子类:

public class WordSpan extends ClickableSpan
{
    int id;
    private boolean marking = false;
    TextPaint tp;
    Typeface font;
    int color = Color.BLACK;

    public WordSpan(int id, Typeface font, boolean marked) {
        this.id = id;
        marking = marked;
        this.font = font;
    }

    @Override
    public void updateDrawState(TextPaint ds) {
        ds.setColor(color);
        ds.setUnderlineText(false);

        if (marking)
            ds.setTypeface(Typeface.create(font,Typeface.BOLD));

        tp = ds;
    }

    @Override
    public void onClick(View v) {
        // Empty here -- overriden in activity
    }

    public void setMarking(boolean m) {
        marking = m;
        updateDrawState(tp);
    }

    public void setColor(int col) {
        color = col;
    }
}

这是我的Activity中的WordSpan实例化代码:

... looping through words

curSpan = new WordSpan(index,myFont,index==selectedWordId) {
    @Override
    public void onClick(View view) {
        handleWordClick(index,this);
        setMarking(true);
        tvText.invalidate();
    }
};

... continue loop code

这是我的自定义MovementMethod:

public static 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 false;
        }

        @Override
        public boolean onDown ( MotionEvent e ) {
            return false;
        }

        @Override
        public boolean onDoubleTap ( MotionEvent e ) {
            return false;
        }

        @Override
        public void onShowPress ( MotionEvent e ) {
            return;
        }
    });

    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);
                        return true;
                    }
                    else if (event.getAction() == MotionEvent.ACTION_DOWN) {
                        Selection.setSelection(buffer,
                                               buffer.getSpanStart(link[0]),
                                               buffer.getSpanEnd(link[0]));

                        return false;
                    }
                }
                else {

                }
            }

            // let scroll movement handle the touch
            return super.onTouchEvent(widget, buffer, event);
        }
    };
}

当文本设置为可选时,您的跨度将以某种方式变为不可变( TextView#setTextIsSelectable(true) )。 这是关于理解跨度的一篇很好的文章,它解释了跨度的可变性。 我也认为这篇文章有一些很好的解释

我不确定你的跨度是如何变得不可变的。 也许他们是可变的但只是没有以某种方式显示? 目前还不清楚。 也许某人对此行为有解释。 但是,现在,这是一个修复:

旋转设备或将其关闭并重新打开时,将重新创建或重新应用跨距。 这就是你看到变化的原因。 修复是在单击时不尝试更改跨度,而是使用粗体字体重新应用它。 这样改变就会生效。 你甚至不需要调用invalidate() 跟踪粗体跨度,以便稍后在单击另一个跨度时可以取消对其进行取消。

结果如下:

在此输入图像描述

这是主要的活动。 (请原谅所有的硬编码,但这仅仅是一个样本。)

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private TextView mTextView;
    private WordSpan mBoldedSpan;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Typeface myFont = Typeface.DEFAULT;

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = findViewById(R.id.textView);
        mTextView.setTextIsSelectable(true);

        mTextView.setMovementMethod(createMovementMethod(this));
        SpannableString ss = new SpannableString("Hello world! ");
        int[][] spanStartEnd = new int[][]{{0, 5}, {6, 12}};
        for (int i = 0; i < spanStartEnd.length; i++) {
            WordSpan wordSpan = new WordSpan(i, myFont, false) {
                @Override
                public void onClick(View view) {
//                handleWordClick(index, this); // Not sure what this does.
                    Spannable ss = (Spannable) mTextView.getText();
                    if (mBoldedSpan != null) {
                        reapplySpan(ss, mBoldedSpan, false);
                    }
                    reapplySpan(ss, this, true);
                    mBoldedSpan = this;
                }

                private void reapplySpan(Spannable spannable, WordSpan span, boolean isBold) {
                    int spanStart = spannable.getSpanStart(span);
                    int spanEnd = spannable.getSpanEnd(span);
                    span.setMarking(isBold);
                    spannable.setSpan(span, spanStart, spanEnd, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
                }
            };
            ss.setSpan(wordSpan, spanStartEnd[i][0], spanStartEnd[i][1],
                       Spanned.SPAN_INCLUSIVE_INCLUSIVE);
        }

        mTextView.setText(ss, TextView.BufferType.SPANNABLE);
    }
    // All the other code follows without modification.
}

activity_main.xml中

<android.support.constraint.ConstraintLayout 
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="30sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.04000002"
        tools:text="Hello World!" />

</android.support.constraint.ConstraintLayout>

这是使用StyleSpan版本。 结果是一样的。

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private TextView mTextView;
    private StyleSpan mBoldedSpan;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Typeface myFont = Typeface.DEFAULT;

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = findViewById(R.id.textView);
        mTextView.setTextIsSelectable(true);

        mTextView.setMovementMethod(createMovementMethod(this));
        mBoldedSpan = new StyleSpan(android.graphics.Typeface.BOLD);
        SpannableString ss = new SpannableString("Hello world!");
        int[][] spanStartEnd = new int[][]{{0, 5}, {6, 12}};
        for (int i = 0; i < spanStartEnd.length; i++) {
            WordSpan wordSpan = new WordSpan(i, myFont, false) {
                @Override
                public void onClick(View view) {
//                handleWordClick(index, this); // Not sure what this does.
                    Spannable ss = (Spannable) mTextView.getText();
                    ss.setSpan(mBoldedSpan, ss.getSpanStart(this), ss.getSpanEnd(this),
                               Spanned.SPAN_INCLUSIVE_INCLUSIVE);
                }
            };
            ss.setSpan(wordSpan, spanStartEnd[i][0], spanStartEnd[i][1],
                       Spanned.SPAN_INCLUSIVE_INCLUSIVE);
        }

        mTextView.setText(ss, TextView.BufferType.SPANNABLE);
    }

 // All the other code follows without modification.
}

暂无
暂无

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

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