多行"ReplacementSpan"绘图问题 [英] Multiline `ReplacementSpan` drawing issue
问题描述
只要文本不是太长,我的自定义替换跨度就可以工作,但是一旦文本长于一行,跨度图就会完全破裂.我的理解是,在这种情况下draw()
被调用两次,导致跨度绘制两次.没有办法将第二个抽奖电话与第一个抽奖电话区分开,让您控制要绘制的内容和位置. start
和end
由于报告错误的值而变得无用.
My custom replacement span works as long as text is not too long but as soon as text is longer than one line, span drawing completely breaks apart. My understanding is that draw()
gets called twice in this case causing span to draw twice. There is no way to differentiate that second draw call from first one, giving you control over what to draw and where. start
and end
become useless as they report wrong values.
ReplacementSpan
是否应该甚至适用于多行文本?对于解决此问题的任何帮助,我们将不胜感激.
Is ReplacementSpan
supposed to even work for multiline text? I would appreciate any help to resolve this issue.
当我将所选文本更改为我的CustomReplacementSpan
时,会发生以下情况:
This is what happens when I change selected text to my CustomReplacementSpan
:
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>
推荐答案
显然,ReplacementSpan
无法做到这一点.这是来自文章
Flowing onto a new line is, evidently, something that ReplacementSpan
cannot do. Here is a quote from an article Drawing a rounded corner background on text by Florina Muntenescu who blogs about spans and the like. (Emphasis in the following quote is mine.)
我们需要与文本一起绘制一个可绘制对象.我们可以实现自定义的ReplacementSpan来自己绘制背景和文本. 但是ReplacementSpans无法流入下一行,因此我们将无法支持多行背景.它们看起来更像Chip(材料设计组件),其中每个元素都必须放在一行上.
We need to draw a drawable together with the text. We can implement a custom ReplacementSpan to draw the background and the text ourselves. However ReplacementSpans cannot flow into the next line, therefore we will not be able to support a multi-line background. They would rather look like Chip, the Material Design component, where every element must fit on a single line.
这是您遇到的问题.本文继续探讨您可能要研究的可能解决方案.例如,有可能使用本文概述的某些技术来定义多个ReplacementSpans
,取决于换行符,就像使用背景可绘制对象所做的那样.
This is the issue that you are having. The article goes on about a possible solution that you may want to look into. For example, it may be possible to use some of the techniques outlined in the article to define multiple ReplacementSpans
dependent upon line breaks like is done with the background drawables.
还有其他跨度类型可能更适合您的目的. 此处是其中的列表.
There are other span types that may be more congenial to your purposes. Here is list of them.
这篇关于多行"ReplacementSpan"绘图问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!