我的spannable怎么没有显示?

背景

我想在TextView上使用一个简单的SpannableString ,基于我发现的一个UnderDotSpan类( 这里 )。

原来的UnderDotSpan只是在文本下面放置一个特定大小和颜色的点(不重叠)。 我正在尝试的是先正常使用它,然后使用自定义的drawable而不是点。

问题

与正常的跨度使用相反,这只是不显示任何东西。 甚至没有文字。

这是如何完成一个正常的跨度:

 val text = "1" val timeSpannable = SpannableString(text) timeSpannable.setSpan(ForegroundColorSpan(0xff00ff00.toInt()), 0, text.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) textView.setText(timeSpannable); 

它将在TextView中显示一个绿色的“1”。

但是,当我尝试下一个spannable,它(整个TextView的内容:文本和点)根本不显示:

 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) // this also didn't work: textView.setText(spannable) 

奇怪的是,在我使用的一个项目中,它在RecyclerView中工作正常,而在另一个项目中,它却没有。

这里是UnderDotSpan的代码:

 class UnderDotSpan(private val mDotSize: Float, private val mDotColor: Int, private val mTextColor: Int) : ReplacementSpan() { companion object { @JvmStatic private val DEFAULT_DOT_SIZE_IN_DP = 4 } 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?) = Math.round(paint.measureText(text, start, end)) 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.drawCircle(x + textSize / 2, bottom + mDotSize, mDotSize / 2, paint) paint.color = mTextColor canvas.drawText(text, start, end, x, y.toFloat(), paint) } } 

请注意,TextView没有任何特殊的属性,但我仍然会显示它:

 <android.support.constraint.ConstraintLayout 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" tools:context="com.example.user.myapplication.MainActivity"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent"/> </android.support.constraint.ConstraintLayout> 

我试过了

我尝试从其他跨度类扩展,并尝试以其他方式将文本设置为TextView。

我也尝试了基于UnderDotSpan类的其他span类。 例:

 class UnderDrawableSpan(val drawable: Drawable, val drawableWidth: Int = drawable.intrinsicWidth, val drawableHeight: Int = drawable.intrinsicHeight, val margin: Int = 0) : ReplacementSpan() { override fun getSize(paint: Paint, text: CharSequence, start: Int, end: Int, fm: Paint.FontMetricsInt?): Int = Math.round(paint.measureText(text, start, end)) 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) canvas.drawText(text, start, end, x, y.toFloat(), paint) canvas.save() canvas.translate(x + textSize / 2f - drawableWidth / 2f, y.toFloat() + margin) if (drawableWidth != 0 && drawableHeight != 0) drawable.setBounds(0, 0, drawableWidth, drawableHeight) drawable.draw(canvas) canvas.restore() } } 

在debugging时,我发现draw函数甚至没有被调用,而getSize被调用(并返回一个> 0的值)。

这个问题

为什么不能在TextView上显示跨度?

我用它的方式有什么问题?

我该如何解决这个问题,并使用这个跨度?

如何在其他更复杂的情况下工作?

Solutions Collecting From Web of "我的spannable怎么没有显示?"

基本的问题是ReplacementSpan没有设置高度。 正如ReplacementSpan的来源所述:

如果跨度覆盖整个文本,并且高度未设置,则绘制(Canvas,CharSequence,int,int,float,int,int,int,Paint)}将不会被调用。

这是Archit Sureja发布的重复。 在我原来的文章中,我更新了getSize() ReplacementSpan的高度,但现在我实现了LineHeightSpan.WithDensity接口来完成相同的操作。 (感谢vovahost 在这里获取这些信息。)

但是,还有一些问题需要处理。

您提供的项目引发的问题是,点不适合它所在的TextView 。 你所看到的是点的截断。 如果点的大小超过文本的宽度或高度,该怎么办?

首先,关于高度,如果没有足够的填充已经存在以容纳点,则界面LineHeightSpan.WithDensitychooseHeight()方法将调整TextView的底部填充。 为了做到这一点,我改变了UnderDotSpan的构造UnderDotSpan来传入TextView以便可以操纵填充。

最后一个问题是,如果它比文本更宽,那么在开始和结束时该点被截断。 clipToPadding="false"在这里不起作用,因为点被截断不是因为它被剪裁到填充,而是因为它被剪裁到我们所说的文本宽度在getSize() 。 为了解决这个问题,我修改了getSize()方法来检测点是否比文本测量更宽,并增加返回的值以匹配点的宽度。 一个名为mStartShim的新值是必须应用于文本的graphics和点以使事物适合的量。

最后一个问题是,点的中心是文本底部下面的点的半径,而不是直径,所以绘制点的代码在draw()更改为:

 canvas.drawCircle(x + textSize / 2 + mStartShim, bottom + mDotSize / 2, mDotSize / 2, paint) 

结果如下:

在这里输入图像说明

activity_main.xml中

 <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" /> 

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, textView), 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, private val mTextView: TextView) : ReplacementSpan(), LineHeightSpan.WithDensity { companion object { @JvmStatic private val DEFAULT_DOT_SIZE_IN_DP = 16 } var mStartShim = 0; constructor(context: Context, dotColor: Int, textColor: Int, textView: TextView) : this(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_DOT_SIZE_IN_DP.toFloat(), context.resources.displayMetrics), dotColor, textColor, textView) 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(paint.measureText(text, start, end)) + 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.drawCircle(x + textSize / 2 + mStartShim, bottom + mDotSize / 2, mDotSize / 2, paint) paint.color = mTextColor canvas.drawText(text, start, end, x + mStartShim, y.toFloat(), paint) } 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 fontMetricsInt.leading = fm.leading // Adjust start/end padding if there isn't enough room to fit our dot. if (mTextView.getPaddingBottom() < mDotSize) { mTextView.setPadding(mTextView.paddingLeft, mTextView.paddingTop, mTextView.paddingRight, mDotSize.toInt()) } } override fun chooseHeight(charSequence: CharSequence, i: Int, i1: Int, i2: Int, i3: Int, fontMetricsInt: Paint.FontMetricsInt) { // This method should not be invoked and is here because it is required to be. } } 

你的跨度没有显示,因为没有设置高度,因此不会调用绘制方法。

请参考这个链接

https://developer.android.com/reference/android/text/style/ReplacementSpan.html

GetSize() – 返回跨度的宽度。 扩展类可以通过更新Paint.FontMetricsInt的属性来设置跨度的高度。 如果跨度覆盖整个文本,并且未设置高度,则不会为跨度调用绘制(Canvas,CharSequence,int,int,float,int,int,int,Paint)。

Paint.FontMetricsInt对象的所有variables是0,所以没有高度,所以不调用draw方法。

对于如何Paint.FontMatricsInt工作,你可以参考这个链接。

Android的FontMetrics中顶部,上升,底线,下降,底部和领先的含义

所以我们使用getSize的参数获取paint对象的帮助来设置Paint.FontMetricsInt。

这里是我的代码,我改变了一些有关设置高度的东西。

 class UnderDotSpan(private val mDotSize: Float, private val mDotColor: Int, private val mTextColor: Int) : ReplacementSpan() { companion object { @JvmStatic private val DEFAULT_DOT_SIZE_IN_DP = 16 } 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 asd = paint.getFontMetricsInt() fm?.leading = asd.leading fm?.top = asd.top fm?.bottom = asd.bottom fm?.ascent = asd.ascent fm?.descent = asd.descent return Math.round(measureText(paint, text, start, end)) } 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.drawCircle(x + textSize / 2, (bottom /2).toFloat(), mDotSize / 2, paint) paint.color = mTextColor canvas.drawText(text, start, end, x, y.toFloat(), paint) } private fun measureText(paint: Paint, text: CharSequence, start: Int, end: Int): Float { return paint.measureText(text, start, end) } } 

我得到的最终输出如下所示

在这里输入图像说明

更新的答案

用这个在文本下面画圈

 class UnderDotSpan(private val mDotSize: Float, private val mDotColor: Int, private val mTextColor: Int) : ReplacementSpan() { companion object { @JvmStatic private val DEFAULT_DOT_SIZE_IN_DP = 4 } 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 asd = paint.getFontMetricsInt() fm?.leading = asd.leading + mDotSize.toInt() fm?.top = asd.top fm?.bottom = asd.bottom fm?.ascent = asd.ascent fm?.descent = asd.descent return Math.round(paint.measureText(text, start, end)) } 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.drawCircle(x + textSize / 2, bottom + mDotSize, mDotSize / 2, paint) paint.color = mTextColor canvas.drawText(text, start, end, x, y.toFloat(), paint) } } 

和最后一个IMP

val text = "1\n"而不是val text = "1"

你好,请find下面的链接,这对你有帮助,并解决了你的问题。

https://github.com/Rajagopalr3/CustomizedTextView?utm_source=android-arsenal.com&utm_medium=referral&utm_campaign=6526

上面这个链接对你来说是非常有用的,它有太多的function,很容易做你的工作,希望它适合你。

一旦textview设置文本然后使用:

 textview.setMovementMethod(LinkMovementMethod.getInstance()); 

例:

 tvDescription.setText(hashText); tvDescription.setMovementMethod(LinkMovementMethod.getInstance()); 
 /* * Set text with hashtag and mentions on TextView * */ public void setTextOnTextView(String description, TextView tvDescription) { SpannableString hashText = new SpannableString(description); Pattern pattern = Pattern.compile("@([A-Za-z0-9_-]+)"); Matcher matcher = pattern.matcher(hashText); while (matcher.find()) { final StyleSpan bold = new StyleSpan(android.graphics.Typeface.BOLD); // Span to make text bold hashText.setSpan(bold, matcher.start(), matcher.end(), 0); } Pattern patternHash = Pattern.compile("#([A-Za-z0-9_-]+)"); Matcher matcherHash = patternHash.matcher(hashText); while (matcherHash.find()) { final StyleSpan bold = new StyleSpan(android.graphics.Typeface.BOLD); // Span to make text bold hashText.setSpan(bold, matcherHash.start(), matcherHash.end(), 0); } tvDescription.setText(hashText); tvDescription.setMovementMethod(LinkMovementMethod.getInstance()); }