基本问题在于未设置
ReplacementSpan
的高度。如
ReplacementSpan
的源代码 所述:
如果跨度覆盖整个文本,并且未设置高度,则不会为该跨度调用 draw(Canvas, CharSequence, int, int, float, int, int, int, Paint)}。
这是对 Archit Sureja 发布的内容的重复。在我的原始帖子中,我在
getSize()
中更新了
ReplacementSpan
的高度,但现在我实现了
LineHeightSpan.WithDensity
接口来完成相同的工作。(感谢
vovahost 这里 提供的信息。)
然而,您提出了其他需要解决的问题。
您提供的项目存在的问题是点号不适合必须驻留的
TextView
中。您看到的是截断点。如果点的大小超过文本的宽度或高度怎么办?
首先,关于高度,接口
LineHeightSpan.WithDensity
的
chooseHeight()
方法通过将点的大小添加到字体的有效高度中来调整
TextView
字体底部的定义。为此,点的高度被添加到字体的底部:
fontMetricsInt.bottom = fm.bottom + mDotSize.toInt();
(这是与上一个答案不同的更改,它使用了TextView
的padding。自从这个更改后,UnderDotSpan
类不再需要TextView
。虽然我添加了TextView
,但它并不是真正所需的。)
最后一个问题是,如果圆点比文本更宽,则在开头和结尾时会被截断。在这里,clipToPadding="false"
不能解决问题,因为圆点被截断不是因为它被剪裁到填充区域,而是被剪裁到我们在getSize()
中设置的文本宽度。为了解决这个问题,我修改了getSize()
方法,检测圆点是否比文本测量值更宽,并增加返回值以匹配圆点的宽度。一个名为mStartShim
的新值是必须应用于文本和圆点绘制以使其符合要求的量。
最后一个问题是,圆点的中心点是下方文本的圆点半径而不是直径,因此draw()
中绘制圆点的代码被更改为:
canvas.drawCircle(x + textSize / 2, bottom.toFloat(), mDotSize / 2, paint)
(我还改变了代码,使用Canvas
翻译而不是添加偏移量。效果是相同的。)
以下是结果:
![enter image description here](https://istack.dev59.com/snG54.webp)
activity_main.xml
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/darker_gray">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:background="@android:color/white"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
MainActivity.java
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val text = "1"
val spannable = SpannableString(text)
spannable.setSpan(UnderDotSpan(this@MainActivity, 0xFF039BE5.toInt(), textView.currentTextColor),
0, text.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
textView.setText(spannable, TextView.BufferType.SPANNABLE)
}
}
UnderDotSpan.kt
class UnderDotSpan(private val mDotSize: Float, private val mDotColor: Int, private val mTextColor: Int) : ReplacementSpan(), LineHeightSpan.WithDensity {
companion object {
@JvmStatic
private val DEFAULT_DOT_SIZE_IN_DP = 16
}
var mStartShim = 0;
constructor(context: Context, dotColor: Int, textColor: Int)
: this(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_DOT_SIZE_IN_DP.toFloat(),
context.resources.displayMetrics), dotColor, textColor)
override fun getSize(paint: Paint, text: CharSequence, start: Int, end: Int, fm: Paint.FontMetricsInt?): Int {
val baseTextWidth = paint.measureText(text, start, end)
mStartShim = if (baseTextWidth < mDotSize) ((mDotSize - baseTextWidth) / 2).toInt() else 0
return Math.round(baseTextWidth + mStartShim * 2)
}
override fun draw(canvas: Canvas, text: CharSequence, start: Int, end: Int, x: Float, top: Int,
y: Int, bottom: Int, paint: Paint) {
if (TextUtils.isEmpty(text)) {
return
}
val textSize = paint.measureText(text, start, end)
paint.color = mDotColor
canvas.save()
canvas.translate(mStartShim.toFloat(), -mDotSize / 2)
canvas.drawCircle(x + textSize / 2, bottom.toFloat(), mDotSize / 2, paint)
paint.color = mTextColor
canvas.translate(0f, mDotSize / 2)
canvas.drawText(text, start, end, x, y.toFloat(), paint)
canvas.restore()
}
override fun chooseHeight(charSequence: CharSequence, i: Int, i1: Int, i2: Int, i3: Int,
fontMetricsInt: Paint.FontMetricsInt, textPaint: TextPaint) {
val fm = textPaint.fontMetricsInt
fontMetricsInt.top = fm.top
fontMetricsInt.ascent = fm.ascent
fontMetricsInt.descent = fm.descent
fontMetricsInt.bottom = fm.bottom + mDotSize.toInt();
fontMetricsInt.leading = fm.leading
}
override fun chooseHeight(charSequence: CharSequence, i: Int, i1: Int, i2: Int, i3: Int,
fontMetricsInt: Paint.FontMetricsInt) {
}
}
如果需要在文本下方放置一个小的可绘制对象,可以使用以下基于UnderDotSpan
的类:
UnderDrawableSpan.java
public class UnderDrawableSpan extends ReplacementSpan implements LineHeightSpan.WithDensity {
final private Drawable mDrawable;
final private int mDrawableWidth;
final private int mDrawableHeight;
final private int mMargin;
private int mStartShim = 0;
UnderDrawableSpan(Context context, Drawable drawable, int drawableWidth, int drawableHeight,
int margin) {
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
mDrawable = drawable;
mDrawableWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
(float) drawableWidth, metrics);
mDrawableHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
(float) drawableHeight, metrics);
mMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
(float) margin, metrics);
}
@Override
public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y,
int bottom, @NonNull Paint paint) {
if (TextUtils.isEmpty(text)) {
return;
}
float textWidth = paint.measureText(text, start, end);
float offset = mStartShim + x + (textWidth - mDrawableWidth) / 2;
mDrawable.setBounds(0, 0, mDrawableWidth, mDrawableHeight);
canvas.save();
canvas.translate(offset, bottom - mDrawableHeight);
mDrawable.draw(canvas);
canvas.restore();
canvas.save();
canvas.translate(mStartShim, 0);
canvas.drawText(text, start, end, x, y, paint);
canvas.restore();
}
@Override
public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
float baseTextWidth = paint.measureText(text, start, end);
mStartShim = (baseTextWidth < mDrawableWidth) ? (int) (mDrawableWidth - baseTextWidth) / 2 : 0;
return Math.round(baseTextWidth + mStartShim * 2);
}
@Override
public void chooseHeight(CharSequence charSequence, int i, int i1, int i2, int i3,
Paint.FontMetricsInt fontMetricsInt, TextPaint textPaint) {
Paint.FontMetricsInt fm = textPaint.getFontMetricsInt();
fontMetricsInt.top = fm.top;
fontMetricsInt.ascent = fm.ascent;
fontMetricsInt.descent = fm.descent;
fontMetricsInt.bottom = fm.bottom + mDrawableHeight + mMargin;
fontMetricsInt.leading = fm.leading;
}
@Override
public void chooseHeight(CharSequence charSequence, int i, int i1, int i2, int i3,
Paint.FontMetricsInt fontMetricsInt) {
}
}
使用以下的可绘制 XML 和
UnderDrawableSpan
结合使用会产生如下结果: (可绘制对象的宽度和高度设置为
12dp
。文本的字体大小为
24sp
。)
![enter image description here](https://istack.dev59.com/dfGkY.webp)
gradient_drawable.xml
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<size
android:width="4dp"
android:height="4dp" />
<gradient
android:type="radial"
android:gradientRadius="60%p"
android:endColor="#e96507"
android:startColor="#ece6e1" />
</shape>
最近我有机会重新审视这个问题和答案。我发布了一个更加灵活的UnderDrawableSpan代码版本。在GitHub上有一个演示项目。
UnderDrawableSpan.kt(更新版)
class UnderDrawableSpan(
context: Context, drawable: Drawable, drawableWidth: Int, drawableHeight: Int, margin: Int
) : ReplacementSpan(), LineHeightSpan.WithDensity {
private val mDrawable: Drawable
private var mDrawableWidth: Int
private var mDrawableHeight: Int
private var mMargin: Int
private var mTextOffset = 0f
private var mDrawableOffset = 0f
private var mBaseDescent = 0f
init {
val metrics: DisplayMetrics = context.resources.displayMetrics
mDrawable = drawable
mDrawableWidth = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, drawableWidth.toFloat(), metrics
).toInt()
mDrawableHeight = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, drawableHeight.toFloat(), metrics
).toInt()
mMargin = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, margin.toFloat(), metrics
).toInt()
}
override fun draw(
canvas: Canvas, text: CharSequence, start: Int, end: Int, x: Float, top: Int, y: Int,
bottom: Int, paint: Paint
) {
canvas.drawText(text, start, end, x + mTextOffset, y.toFloat(), paint)
mDrawable.setBounds(0, 0, mDrawableWidth, mDrawableHeight)
canvas.save()
canvas.translate(x + mDrawableOffset + mMargin, y + mBaseDescent + mMargin)
mDrawable.draw(canvas)
canvas.restore()
}
override fun getSize(
paint: Paint, text: CharSequence?, start: Int, end: Int, fm: Paint.FontMetricsInt?
): Int {
val textWidth = paint.measureText(text, start, end)
val additionalWidthNeeded = mDrawableWidth + mMargin * 2 - textWidth
return if (additionalWidthNeeded >= 0) {
mTextOffset = additionalWidthNeeded / 2
textWidth + additionalWidthNeeded
} else {
mDrawableOffset = -additionalWidthNeeded / 2
textWidth
}.toInt()
}
override fun chooseHeight(
text: CharSequence?, start: Int, end: Int, spanstartv: Int, lineHeight: Int,
fm: Paint.FontMetricsInt, paint: TextPaint
) {
val tpMetric = paint.fontMetrics
mBaseDescent = tpMetric.descent
val spaceAvailable = fm.descent - mBaseDescent
val spaceNeeded = mDrawableHeight + mMargin * 2
if (spaceAvailable < spaceNeeded) {
fm.descent += (spaceNeeded - spaceAvailable).toInt()
fm.bottom = fm.descent + (tpMetric.bottom - tpMetric.descent).toInt()
}
}
override fun chooseHeight(
charSequence: CharSequence?, i: Int, i1: Int, i2: Int, i3: Int, fm: Paint.FontMetricsInt
) = throw IllegalStateException("LineHeightSpan.chooseHeight() called but is not supported.")
}
Html.toHtml
吗? - Zoe stands with Ukraine<p dir="ltr">1</p>
。我希望能够像其他正常的span一样使用该代码。 - android developer