Multiline `ReplacementSpan` проблема рисования - PullRequest
0 голосов
/ 19 ноября 2018

Мой пользовательский интервал замены работает до тех пор, пока текст не слишком длинный, но как только текст длиннее одной строки, рисование интервала полностью разрывается. Насколько я понимаю, draw() вызывается дважды в этом случае, вызывая рисование span дважды. Невозможно отличить этот второй вызов от первого, давая вам контроль над тем, что рисовать и где. start и end становятся бесполезными, так как сообщают о неправильных значениях.

Должен ли ReplacementSpan работать даже для многострочного текста? Буду признателен за любую помощь в решении этой проблемы.

enter image description here

Вот что происходит, когда я заменяю выделенный текст на мой CustomReplacementSpan:

enter image description here

CustomReplacementSpan.kt

import android.graphics.Canvas
import android.graphics.Paint
import android.os.Build
import android.text.Layout
import android.text.StaticLayout
import android.text.TextPaint
import android.text.TextUtils
import android.text.style.ReplacementSpan
import androidx.core.graphics.withTranslation

class CustomReplacementSpan(val spanText: String, val color: Int) : ReplacementSpan() {

    override fun getSize(paint: Paint, text: CharSequence?, start: Int, end: Int, fm: Paint.FontMetricsInt?): Int {
        return paint.measureText(spanText).toInt()
    }

    override fun draw(
        canvas: Canvas,
        text: CharSequence?,
        start: Int,
        end: Int,
        x: Float,
        top: Int,
        y: Int,
        bottom: Int,
        paint: Paint
    ) {
        paint.color = color

        canvas.drawMultilineText(
            text = spanText,
            textPaint = paint as TextPaint,
            width = canvas.width,
            x = x,
            y = top.toFloat()
        )
    }


}

fun Canvas.drawMultilineText(
    text: CharSequence,
    textPaint: TextPaint,
    width: Int,
    x: Float,
    y: Float,
    start: Int = 0,
    end: Int = text.length,
    alignment: Layout.Alignment = Layout.Alignment.ALIGN_NORMAL,
    spacingMult: Float = 1f,
    spacingAdd: Float = 0f,
    includePad: Boolean = true,
    ellipsizedWidth: Int = width,
    ellipsize: TextUtils.TruncateAt? = null
) {
    val staticLayout =
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            StaticLayout.Builder.obtain(text, start, end, textPaint, width)
                .setAlignment(alignment)
                .setLineSpacing(spacingAdd, spacingMult)
                .setIncludePad(includePad)
                .setEllipsizedWidth(ellipsizedWidth)
                .setEllipsize(ellipsize)
                .build()
        } else {
            StaticLayout(
                text, start, end, textPaint, width, alignment,
                spacingMult, spacingAdd, includePad, ellipsize, ellipsizedWidth
            )
        }

    staticLayout.draw(this, x, y)
}

private fun StaticLayout.draw(canvas: Canvas, x: Float, y: Float) {
    canvas.withTranslation(x, y) {
        draw(this)
    }
}

MainActivity.kt

import android.os.Bundle
import android.text.Spannable
import android.view.View
import android.widget.EditText
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat


class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    fun applySpan(view: View) {
        val editText = findViewById<EditText>(R.id.edit)
        if (editText.selectionStart < 0 || editText.selectionEnd < 0) {
            return
        }
        val fullText = editText.text
        val text = fullText.subSequence(editText.selectionStart, editText.selectionEnd)
        val span = CustomReplacementSpan(text.toString(), ContextCompat.getColor(this, android.R.color.holo_blue_dark))
        editText.text.setSpan(span, editText.selectionStart, editText.selectionEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/edit"
        style="@style/Widget.AppCompat.EditText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="20sp" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="applySpan"
        android:text="Make it span" />

</LinearLayout>

1 Ответ

0 голосов
/ 02 декабря 2018

Переход на новую строку - это, очевидно, то, что ReplacementSpan не может сделать. Вот цитата из статьи Рисунок фона с закругленными углами на тексте Флорины Мунтенеску, которая ведет блог о пролетах и ​​тому подобном. (Акцент в следующей цитате мой.)

Нам нужно нарисовать нарисованное вместе с текстом. Мы можем реализовать собственный ReplacementSpan, чтобы нарисовать фон и текст сами. Однако ReplacementSpans не может перейти в следующую строку , поэтому мы не сможем поддерживать многострочный фон. Скорее они будут выглядеть как Chip, компонент Material Design, где каждый элемент должен помещаться на одной строке.

Это проблема, с которой вы столкнулись. В статье рассказывается о возможном решении, которое вы можете рассмотреть. Например, может быть возможно использовать некоторые из методов, описанных в статье, чтобы определить несколько ReplacementSpans в зависимости от разрывов строк, как это делается с фоновыми объектами рисования.

Существуют и другие типы диапазонов, которые могут быть более подходящими для ваших целей. Здесь - их список.

...