簡體   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