为什么没有显示我的spannable? [英] How come my spannable isn't shown?

查看:103
本文介绍了为什么没有显示我的spannable?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试根据我发现的UnderDotSpan类在TextView上使用简单的SpannableString(

I'm trying to use a simple SpannableString on a TextView, based on an UnderDotSpan class I've found (here).

原始的UnderDotSpan只是在文本本身下方放置了特定大小和颜色的点(不重叠).我要尝试的是先正常使用它,然后使用自定义的drawable代替点.

The original UnderDotSpan just puts a dot of a specific size and color below the text itself (not overlapping). What I'm trying is to first use it normally, and then use a customized drawable instead of a dot.

与通常的跨度用法相反,该用法没有显示任何内容.甚至没有文字.

As opposed to a normal span usage, this one just doesn't show anything. Not even the text.

这是正常跨度的完成方式:

Here's how it's done for a normal span:

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

它将在TextView中显示绿色的"1".

it will show a green "1" in the TextView.

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

But when I try the next spannable, it (entire TextView content: text and dot) doesn't show up at all:

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中运行良好,而在另一个项目中,则没有.

Weird thing is that in one project that I use, it works fine inside RecyclerView , and in another, it doesn't.

这是UnderDotSpan的代码:

Here's the code of 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没有任何特殊属性,但是无论如何我都会显示它:

Note that the TextView doesn't have any special properties, but I will show it anyway:

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

我尝试过的

我尝试从其他span类扩展,也尝试以其他方式将文本设置为TextView.

What I've tried

I tried to extend from other span classes, and also tried to set the text to the TextView in other ways.

我还尝试了基于UnderDotSpan类制作的其他span类.示例:

I've also tried other span classes I've made, based on the UnderDotSpan class. Example:

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()
    }

}

在调试时,我发现甚至没有调用draw函数,而getSize确实被调用(并返回> 0值).

While debugging, I've found that draw function isn't even called, while getSize do get called (and returns a >0 value).

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

Why can't the span be shown on the TextView ?

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

What's wrong with the way I've used it?

如何修复它并使用此跨度?

How can I fix it, and use this span ?

在其他更复杂的情况下,它怎么可能起作用?

How come it might work in other, more complex cases?

推荐答案

基本问题是未为ReplacementSpan设置高度.如

The basic problem is that the height is not set for the ReplacementSpan. As stated in the the source for ReplacementSpan:

如果跨度覆盖了整个文本,并且未设置高度,则不会为该跨度调用draw(Canvas,CharSequence,int,int,float,int,int,int,Paint)}.

If the span covers the whole text, and the height is not set, draw(Canvas, CharSequence, int, int, float, int, int, int, Paint)} will not be called for the span.

这是Archit Sureja发布的内容的重复.在我的原始帖子中,我更新了getSize()ReplacementSpan的高度,但是现在我实现了LineHeightSpan.WithDensity接口来执行相同的操作. (感谢 vovahost 此处,以获取此信息.)

This is a repeat of what Archit Sureja posted. In my original post, I updated the height of the ReplacementSpan in getSize() but I now implement the LineHeightSpan.WithDensity interface to do the same. (Thanks to vovahost here for this information.)

但是,您还提出了其他一些需要解决的问题.

There are, however, additional issues that you have brought up that need to be addressed.

您提供的项目提出的问题是该点不适合其必须驻留的TextView.您所看到的是圆点的截断.如果点的大小超过文本的宽度或高度,该怎么办?

The issue raised by your supplied project is that the dot does not fit within the TextView in which it must reside. What you are seeing is truncation of the dot. What to do if the size of the dot exceeds either the width of the text or its height?

首先,关于高度,接口LineHeightSpan.WithDensitychooseHeight()方法通过将点的大小添加到字体的有效高度来调整TextView字体的底部.为此,将点的高度添加到字体的底部:

First, regarding the height, the chooseHeight() method of interface LineHeightSpan.WithDensity adjusts what is considered the bottom of the TextView font by adding in the size of the dot to the font's effective height. To do this, the height of the dot is added to the the bottom of the font:

fontMetricsInt.bottom = fm.bottom + mDotSize.toInt(); 

(这是对使用TextView填充的答案的最后一次迭代的更改.由于此更改,UnderDotSpan类不再需要TextView.尽管我已经添加了TextView,实际上并不需要.)

(This is a change from the last iteration of this answer which used the TextView's padding. Since this change, the TextView no longer is needed by the UnderDotSpan class. Although I had added the TextView, it is not really needed.)

最后一个问题是,如果点宽大于文本,则点的开头和结尾将被截断. clipToPadding="false"在这里不起作用,因为点被截断不是因为它被裁剪到填充上,而是因为它被裁剪到了我们所说的getSize()中的文本宽度.为了解决这个问题,我修改了getSize()方法,以检测点何时宽于文本尺寸,并增加返回值以匹配点的宽度.一个叫做mStartShim的新值是为了使文本适合而必须应用于文本和点的数量.

The last issue is that the dot is cutoff at the start and end if it is wider than the text. clipToPadding="false" doesn't work here because the dot is cutoff not because it is being clipped to the padding but because it is being clipped to what we said the text width is in getSize(). To fix this, I modified the getSize() method to detect when the dot is wider than the text measurement and to increase the returned value to match the dot's width. A new value called mStartShim is the amount that must be applied to the drawing of the text and the dot to make things fit.

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

The final issue is that the center of the dot is the radius of the dot below the bottom of the text and not the diameter, so the code to draw the dot was changed in draw() to:

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

(我还更改了代码以进行Canvas转换,而不是添加偏移量.效果是相同的.)

(I also changed the code to do Canvas translation instead of adding the offsets. The effect is the same.)

这是结果:

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),
                0, text.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
        textView.setText(spannable, TextView.BufferType.SPANNABLE)
    }
}

UnderDotSpan.kt

// From the original UnderDotSpan: Also implement the LineHeightSpan.WithDensity interface to
// compute the height of our "dotted" font.

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
    }

    // Additional horizontal space to the start, if needed, to fit the dot
    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)

    // ReplacementSpan override to determine the size (length) of the text.
    override fun getSize(paint: Paint, text: CharSequence, start: Int, end: Int, fm: Paint.FontMetricsInt?): Int {
        val baseTextWidth = paint.measureText(text, start, end)

        // If the width of the text is less than the width of our dot, increase the text width
        // to match the dot's width; otherwise, just return the width of the text.
        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()

        // Draw the circle in the horizontal center and under the text. Add in the
        // offset (mStartShim) if we had to increase the length of the text to accommodate our dot.
        canvas.translate(mStartShim.toFloat(), -mDotSize / 2)

        // Draw a circle, but this could be any other shape or drawable. It just has
        // to fit into the allotted space which is the size of the dot.
        canvas.drawCircle(x + textSize / 2, bottom.toFloat(), mDotSize / 2, paint)
        paint.color = mTextColor

        // Keep the starting shim, but reset the y-translation to write the text.
        canvas.translate(0f, mDotSize / 2)
        canvas.drawText(text, start, end, x, y.toFloat(), paint)
        canvas.restore()
    }

    // LineHeightSpan.WithDensity override to determine the height of the font with the dot.
    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

        // Our "dotted" font now must accommodate the size of the dot, so change the bottom of the
        // font to accommodate the dot.
        fontMetricsInt.bottom = fm.bottom + mDotSize.toInt();
        fontMetricsInt.leading = fm.leading
    }

    // LineHeightSpan.WithDensity override that is needed to satisfy the interface but not called.
    override fun chooseHeight(charSequence: CharSequence, i: Int, i1: Int, i2: Int, i3: Int,
                              fontMetricsInt: Paint.FontMetricsInt) {
    }
}


对于在文本下放置小图形对象的更一般情况,以下类基于UnderDotSpan起作用:


For the more general case of placing a small drawable under the text the following class works and is based upon 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;

    // How much we need to jog the text to line up with a larger-than-text-width drawable.
    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();
    }

    // ReplacementSpan override to determine the size (length) of the text.
    @Override
    public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
        float baseTextWidth = paint.measureText(text, start, end);

        // If the width of the text is less than the width of our drawable, increase the text width
        // to match the drawable's width; otherwise, just return the width of the text.
        mStartShim = (baseTextWidth < mDrawableWidth) ? (int) (mDrawableWidth - baseTextWidth) / 2 : 0;
        return Math.round(baseTextWidth + mStartShim * 2);
    }

    // LineHeightSpan.WithDensity override to determine the height of the font with the dot.
    @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;

        // Our font now must accommodate the size of the drawable, so change the bottom of the
        // font to accommodate the drawable.
        fontMetricsInt.bottom = fm.bottom + mDrawableHeight + mMargin;
        fontMetricsInt.leading = fm.leading;
    }

    // Required but not used.
    @Override
    public void chooseHeight(CharSequence charSequence, int i, int i1, int i2, int i3,
                             Paint.FontMetricsInt fontMetricsInt) {
    }
}

UnderDrawableSpan中使用以下可绘制XML会产生以下结果: (可绘制对象的宽度和高度设置为12dp.文本的字体大小为24sp.)

Use of the following drawable XML with UnderDrawableSpan produces this result:. (Width and height of the drawable is set to 12dp. Font size of the text is 24sp.)

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>

这篇关于为什么没有显示我的spannable?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆