如何使用自定义跨度在带有占位符的 TextView 格式化文本上设置多个跨度? [英] How to set multiple spans on a TextView's formatted text with placeholder, using customized span?

查看:18
本文介绍了如何使用自定义跨度在带有占位符的 TextView 格式化文本上设置多个跨度?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

背景

我知道如何在静态文本中的部分文本上设置多个跨度,正如我在

计划是%1$d 个月"的文本颜色为#3792e5",并有一个比默认下划线略深的特殊下划线.我使用了一个特殊的自定义标签uu"作为特殊的下划线,在代码中处理.

问题是,无论我做什么,我都找不到如何同时显示文本颜色和下划线.

我的尝试

由于这个问题有一个占位符(并且要格式化的文本周围的文本可能不同),我不得不使用Html.FromHtml":

 String formattedStr = getString(R.string.potential_free_upgrade_1_d_months, 9);Spanned textToShow = Html.fromHtml(formattedStr, null, new TagHandler() {整数开始;@覆盖public void handleTag(final boolean opening, final String tag, Editable output, final XMLReader xmlReader) {开关(标签){案例uu":如果(开场)start = output.length();别的 {int end = output.length();//output.setSpan(new ForegroundColorSpan(0xff3792e5), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);输出.setSpan(new DrawableSpan(ResourcesCompat.getDrawable(getResources(), R.drawable.bit_below_underline, null)),开始, 结束, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);}}}});titleTextView.setText(textToShow);

DrawableSpan.java

public class DrawableSpan extends ReplacementSpan {私有 Drawable mDrawable;私有最终 Rect mPadding;公共 DrawableSpan(Drawable drawable) {极好的();mDrawable = 可绘制;mPadding = new Rect();mDrawable.getPadding(mPadding);}@覆盖public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint) {RectF rect = new RectF(x, top, x + measureText(paint, text, start, end), bottom);mDrawable.setBounds((int) rect.left - mPadding.left, (int) rect.top - mPadding.top, (int) rect.right + mPadding.right, (int) rect.bottom + mPadding.bottom);canvas.drawText(文本,开始,结束,x,y,油漆);mDrawable.draw(画布);}@覆盖public int getSize(@NonNull Paintpaint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {返回 Math.round(paint.measureText(text, start, end));}私人浮点测量文本(油漆,CharSequence 文本,int 开始,int 结束){返回paint.measureText(文本,开始,结束);}}

res/drawable/bit_below_underline.xml

<padding android:bottom="30dp"/><中风机器人:宽度=1dp"android:color="#3792e5"/></形状>

我尝试将 2 个setSpan"一起调用(首先在上面的代码中进行了注释),但没有帮助.

问题

如何在文本部分设置 2 个跨度,正如我上面指定的(带有占位符的部分文本),以便一个是文本颜色,另一个是自定义下划线?

解决方案

在测试您的代码时,似乎 ReplacementSpans 在任何 CharacterStyleSpan 之前绘制.所以尽量使用 MetricAffectingSpan .它会在 ReplacementSpans 之前绘制.

所以使用自定义 MetricAffectingSpan 而不是 ForegroundColorSpan

import android.text.TextPaint;导入 android.text.style.MetricAffectingSpan;公共类 BGColorSpan 扩展 MetricAffectingSpan {私有整数颜色;公共BGColorSpan(整数颜色){this.color = 颜色;}@覆盖public void updateMeasureState(TextPaint textPaint) {textPaint.setColor(颜色);}@覆盖public void updateDrawState(TextPaint textPaint) {textPaint.setColor(颜色);}}

好的.我试图从 android SDK 源代码解释这个问题,在这个 for 循环中有 continue 关键字,它将跳过 CharacterStyleSpan

的呈现

 final float originalX = x;for (int i = start, inext; i 

Background

I know how to set multiple spans on a partial text within a static text, as I've asked here :

final SpannableString text = new SpannableString("Hello stackOverflow");
text.setSpan(new RelativeSizeSpan(1.5f), text.length() - "stackOverflow".length(), text.length(),
            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
text.setSpan(new ForegroundColorSpan(Color.RED), 3, text.length() - 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
tv.setText(text);

This will set the "stackOverflow" to have 2 spans on itself.

I also know how to set a drawable span on a part of the text, as I've asked here.

The problem

Now I need to set 2 spans on a text that's generated from formatting a text with a placeholder, while still having other styles being set as usual.

For example, suppose I have the next text in strings.xml:

<string name="potential_free_upgrade_1_d_months">
    <![CDATA[
    Potential free upgrade: <uu><b><font color=\'#3792e5\'>%1$d months</font></b></uu>]]>
</string>

The plan is that "%1$d months" will have a text color of "#3792e5" and will have a special underline that is a bit more below than the default one. I used a special customized tag "uu" for the special underline, to be handled in code.

Thing is, no matter what I do, I can't find how to have both the text color AND the underline being shown together.

What I've tried

Since this problem has a placeholder (and the text can be different around the text to be formatted), I had to use "Html.FromHtml" :

    String formattedStr = getString(R.string.potential_free_upgrade_1_d_months, 9);
    Spanned textToShow = Html.fromHtml(formattedStr, null, new TagHandler() {
        int start;

        @Override
        public void handleTag(final boolean opening, final String tag, Editable output, final XMLReader xmlReader) {
            switch (tag) {
                case "uu":
                    if (opening)
                        start = output.length();
                    else {
                        int end = output.length();
                        //output.setSpan(new ForegroundColorSpan(0xff3792e5), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                        output.setSpan(
                                new DrawableSpan(ResourcesCompat.getDrawable(getResources(), R.drawable.bit_below_underline, null)),
                                start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    }
            }
        }
    });
    titleTextView.setText(textToShow);

DrawableSpan.java

public class DrawableSpan extends ReplacementSpan {
    private Drawable mDrawable;
    private final Rect mPadding;

    public DrawableSpan(Drawable drawable) {
        super();
        mDrawable = drawable;
        mPadding = new Rect();
        mDrawable.getPadding(mPadding);
    }

    @Override
    public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint) {
        RectF rect = new RectF(x, top, x + measureText(paint, text, start, end), bottom);
        mDrawable.setBounds((int) rect.left - mPadding.left, (int) rect.top - mPadding.top, (int) rect.right + mPadding.right, (int) rect.bottom + mPadding.bottom);
        canvas.drawText(text, start, end, x, y, paint);
        mDrawable.draw(canvas);
    }

    @Override
    public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
        return Math.round(paint.measureText(text, start, end));
    }

    private float measureText(Paint paint, CharSequence text, int start, int end) {
        return paint.measureText(text, start, end);
    }
}

res/drawable/bit_below_underline.xml

<shape
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="line">
    <padding android:bottom="30dp"/>
    <stroke
        android:width="1dp"
        android:color="#3792e5"/>
</shape>

I tried to call the 2 "setSpan" together (first is commented in the code above), but it didn't help.

The question

How can I set 2 spans on the part of the text, as I've specified above (partial text with placeholder) , so that one will be of text-color, and another of an customized-underline ?

解决方案

When tested your code, it seems like ReplacementSpans are drawn before any CharacterStyleSpan . So try to use MetricAffectingSpan .It will draw before ReplacementSpans .

So Use custom MetricAffectingSpan instead of ForegroundColorSpan

import android.text.TextPaint;
import android.text.style.MetricAffectingSpan;

public class BGColorSpan extends MetricAffectingSpan {

    private int color;

    public BGColorSpan(int color) {
        this.color = color;
    }

    @Override
    public void updateMeasureState(TextPaint textPaint) {
        textPaint.setColor(color);
    }

    @Override
    public void updateDrawState(TextPaint textPaint) {
        textPaint.setColor(color);
    }
} 

OK . I am trying to explain the issue from the android SDK Source code, In this for loop there is continue keyword it will skip the rendering of CharacterStyleSpan

 final float originalX = x;
    for (int i = start, inext; i < measureLimit; i = inext) {
        TextPaint wp = mWorkPaint;
        wp.set(mPaint);

        inext = mMetricAffectingSpanSpanSet.getNextTransition(mStart + i, mStart + limit) -
                mStart;
        int mlimit = Math.min(inext, measureLimit);

        ReplacementSpan replacement = null;

        for (int j = 0; j < mMetricAffectingSpanSpanSet.numberOfSpans; j++) {
            // Both intervals [spanStarts..spanEnds] and [mStart + i..mStart + mlimit] are NOT
            // empty by construction. This special case in getSpans() explains the >= & <= tests
            if ((mMetricAffectingSpanSpanSet.spanStarts[j] >= mStart + mlimit) ||
                    (mMetricAffectingSpanSpanSet.spanEnds[j] <= mStart + i)) continue;
            MetricAffectingSpan span = mMetricAffectingSpanSpanSet.spans[j];
            if (span instanceof ReplacementSpan) {
                replacement = (ReplacementSpan)span;
            } else {
                // We might have a replacement that uses the draw
                // state, otherwise measure state would suffice.
                span.updateDrawState(wp);
            }
        }

        if (replacement != null) {
            x += handleReplacement(replacement, wp, i, mlimit, runIsRtl, c, x, top, y,
                    bottom, fmi, needWidth || mlimit < measureLimit);

            // I think this line making your issue
            continue;
        }

        for (int j = i, jnext; j < mlimit; j = jnext) {
            jnext = mCharacterStyleSpanSet.getNextTransition(mStart + j, mStart + inext) -
                    mStart;
            int offset = Math.min(jnext, mlimit);

            wp.set(mPaint);
            for (int k = 0; k < mCharacterStyleSpanSet.numberOfSpans; k++) {
                // Intentionally using >= and <= as explained above
                if ((mCharacterStyleSpanSet.spanStarts[k] >= mStart + offset) ||
                        (mCharacterStyleSpanSet.spanEnds[k] <= mStart + j)) continue;

                CharacterStyle span = mCharacterStyleSpanSet.spans[k];
                span.updateDrawState(wp);
            }

            // Only draw hyphen on last run in line
            if (jnext < mLen) {
                wp.setHyphenEdit(0);
            }
            x += handleText(wp, j, jnext, i, inext, runIsRtl, c, x,
                    top, y, bottom, fmi, needWidth || jnext < measureLimit, offset);
        }
    }       

这篇关于如何使用自定义跨度在带有占位符的 TextView 格式化文本上设置多个跨度?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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