Bold ClickableSpan на ощупь - PullRequest
0 голосов
/ 05 июля 2018

У меня есть TextView, в котором каждое слово является ClickableSpan (на самом деле пользовательский подкласс ClickableSpan). При касании слова оно должно отображаться жирным шрифтом. Если я установлю textIsSelectable (false) в TextView, все будет работать нормально. Слово сразу выделяется жирным шрифтом. Но если текст можно выделить, то он не работает. НО - если я касаюсь слова, а затем выключаю и снова включаю экран, когда на экране снова появляется слово, оно выделено жирным шрифтом. Я перепробовал все, что мог придумать, чтобы вызвать перерисовку (сделать недействительным TextView, принудительно вызвать метод onRestart (), refreshDrawableState () для TextView и т. Д.). Чего мне не хватает?

Вот мой подкласс 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;
    }
}

Вот код создания 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);
        }
    };
}

1 Ответ

0 голосов
/ 11 июля 2018

Ваши диапазоны так или иначе становятся неизменными, когда текст установлен как выбираемый (TextView#setTextIsSelectable(true)). Вот хорошая статья о Understanding Spans , которая объясняет изменчивость промежутков. Я также думаю, что этот пост имеет несколько хороших объяснений

Я не уверен, как ваши промежутки становятся неизменными. Может быть, они изменчивы, но просто не отображаются как-то? Непонятно. Может быть, у кого-то есть объяснение этому поведению. Но сейчас есть исправление:

Когда вы поворачиваете устройство или выключаете и снова включаете его, промежутки восстанавливаются или просто применяются повторно. Вот почему вы видите изменения. Исправление состоит в том, чтобы не пытаться изменить пролеты при нажатии, а повторно применить его с жирным шрифтом. Таким образом, изменения вступят в силу. Вам даже не нужно будет звонить invalidate(). Следите за полужирным промежутком, чтобы его можно было распаковать позже при нажатии на другой промежуток.

Вот результат:

enter image description here

Вот основная деятельность. (Пожалуйста, прости все жесткое кодирование, но это всего лишь пример.)

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.
}
...