Android——SpannableString实现顶部对齐(上标,下标)

本文探讨了在Android中使用SpannableString实现上标和下标的精确位置调整,包括通过修改baselineShift和Y坐标实现顶部对齐的方法。文章提供了详细的代码示例,展示了如何根据需求调整上标和下标的位置,以达到最佳视觉效果。

上面两张图,大家应该很熟悉这就是上标和下标的效果,实现代码如下:

上标:

代码1:

SpannableString ss = new SpannableString("RM123.456");
SuperscriptSpan superScriptTextSpan = new SuperscriptSpan();
ss.setSpan(superScriptTextSpan, 0, 2, SpannableString.SPAN_INCLUSIVE_EXCLUSIVE);

AbsoluteSizeSpan sizeSpan =new AbsoluteSizeSpan(18,true);
 ss.setSpan(sizeSpan, 0, 2, SpannableString.SPAN_INCLUSIVE_EXCLUSIVE);

tvImageSpan.setText(ss);

效果如图:

代码2:

SpannableString ss = new SpannableString("RM123.456");

AbsoluteSizeSpan sizeSpan =new AbsoluteSizeSpan(18,true);
 ss.setSpan(sizeSpan, 0, 2, SpannableString.SPAN_INCLUSIVE_EXCLUSIVE);

SuperscriptSpan superScriptTextSpan = new SuperscriptSpan();
ss.setSpan(superScriptTextSpan, 0, 2, SpannableString.SPAN_INCLUSIVE_EXCLUSIVE);

tvImageSpan.setText(ss);

效果如图:

请大家仔细观察上面两段代码和效果图,可以发现细微差别:
1、代码中设置字体大小和社这上标效果的代码顺序不同;
2、效果图中上标显示的位置不同,先设置上标效果,结果显示靠上,先设字体显示跟其他文字基本顶部对齐,基本达到我们想要的效果。

下标:

代码1:

SpannableString ss = new SpannableString("RM123.456");

AbsoluteSizeSpan sizeSpan =new AbsoluteSizeSpan(18,true);
ss.setSpan(sizeSpan, 0, 2, SpannableString.SPAN_INCLUSIVE_EXCLUSIVE);

SubscriptSpan superScriptTextSpan = new SubscriptSpan();
ss.setSpan(superScriptTextSpan, 0, 2, SpannableString.SPAN_INCLUSIVE_EXCLUSIVE);
tvImageSpan.setText(ss);

效果如图:

代码2:

SpannableString ss = new SpannableString("RM123.456");

SubscriptSpan superScriptTextSpan = new SubscriptSpan();
ss.setSpan(superScriptTextSpan, 0, 2, SpannableString.SPAN_INCLUSIVE_EXCLUSIVE);

AbsoluteSizeSpan sizeSpan =new AbsoluteSizeSpan(18,true);
ss.setSpan(sizeSpan, 0, 2, SpannableString.SPAN_INCLUSIVE_EXCLUSIVE);

tvImageSpan.setText(ss);

效果如图:

同样,请大家仔细观察上面两段代码和效果图,可以发现:
1、代码中设置字体大小和社这上标效果的代码顺序不同;
2、效果图中上标显示的位置不同,先设字体显示跟其他文字底部对齐,先设置下标效果,结果显示截断。

注意:
这里一定要注意设置字体大小,否则,上标和下标将会超出TextView的显示范围被截断。
如下图:

原因也比较简单了,直接看源码:
SuperscriptSpan

public class SuperscriptSpan extends MetricAffectingSpan implements ParcelableSpan {
    public SuperscriptSpan() {
    }
    
    public SuperscriptSpan(Parcel src) {
    }
    
    public int getSpanTypeId() {
        return getSpanTypeIdInternal();
    }

    /** @hide */
    public int getSpanTypeIdInternal() {
        return TextUtils.SUPERSCRIPT_SPAN;
    }
    
    public int describeContents() {
        return 0;
    }

    public void writeToParcel(Parcel dest, int flags) {
        writeToParcelInternal(dest, flags);
    }

    /** @hide */
    public void writeToParcelInternal(Parcel dest, int flags) {
    }

    @Override
    public void updateDrawState(TextPaint tp) {
        tp.baselineShift += (int) (tp.ascent() / 2);
    }

    @Override
    public void updateMeasureState(TextPaint tp) {
        tp.baselineShift += (int) (tp.ascent() / 2);
    }
}

SubscriptSpan

public class SubscriptSpan extends MetricAffectingSpan implements ParcelableSpan {
    public SubscriptSpan() {
    }
    
    public SubscriptSpan(Parcel src) {
    }
    
    public int getSpanTypeId() {
        return getSpanTypeIdInternal();
    }

    /** @hide */
    public int getSpanTypeIdInternal() {
        return TextUtils.SUBSCRIPT_SPAN;
    }
    
    public int describeContents() {
        return 0;
    }

    public void writeToParcel(Parcel dest, int flags) {
        writeToParcelInternal(dest, flags);
    }

    /** @hide */
    public void writeToParcelInternal(Parcel dest, int flags) {
    }

    @Override
    public void updateDrawState(TextPaint tp) {
        tp.baselineShift -= (int) (tp.ascent() / 2);
    }

    @Override
    public void updateMeasureState(TextPaint tp) {
        tp.baselineShift -= (int) (tp.ascent() / 2);
    }
}

从源码中我们可以看出,上标SuperscriptSpan,下标SubscriptSpan本身不可以修改大小,只是修改了baselineShift,即修改baseline的位置,而且简单粗暴,直接取ascent的一半进行位移,所以大多数时候肯定达不到我们想要的效果,但这里也给了我们一个启发,我们可以通过修改baselineShift来调整上标或者下标的位置以打到我们想要的效果。

好了,说了这么多,我们该说说我们今天主要讨论的顶部对齐问题了,其实前面也提到了实现方式,这里我尝试了两种方式达到我们要的顶部对齐方式。

修改baselineShift
public class SuperScriptTextSpan extends MetricAffectingSpan {
    private int fontSizeSp = -1;
    private boolean isSp;

    public SuperScriptTextSpan(int fontSizeSp, boolean isSp) {
        this.fontSizeSp = fontSizeSp;
        this.isSp = isSp;
    }

    @Override
    public void updateDrawState(TextPaint ds) {
        float ascent = ds.ascent();
        Log.d("SuperScriptTextSpan", "ascent-> " + ds.ascent());
        if (fontSizeSp != -1) {
            ds.setTextSize(isSp ? fontSizeSp * ds.density : fontSizeSp);
        }
        float newAscent = ds.ascent();
        Log.d("SuperScriptTextSpan", "newAscent-> " + ds.ascent());

        ds.baselineShift += ascent - newAscent;

		//方法2:
		//Rect bounds = new Rect();
        //ds.getTextBounds("1A", 0, 2, bounds);
        //int shift = bounds.top - bounds.bottom;
        //if (fontSizeSp != -1) {
        //    ds.setTextSize(isSp ? fontSizeSp * ds.density : fontSizeSp);
        //}
        //ds.getTextBounds("1A", 0, 2, bounds);
        //shift += bounds.bottom - bounds.top;
        //ds.baselineShift += shift;
    }

    @Override
    public void updateMeasureState(TextPaint p) {
        updateDrawState(p);
    }
}
修改Y坐标
public class SuperScriptTextSpan extends ReplacementSpan {
    private int fontSizeSp = -1;
    private boolean isSp;

    public SuperScriptTextSpan(int fontSizeSp, boolean isSp) {
        this.fontSizeSp = fontSizeSp;
        this.isSp = isSp;
    }

    @Override
    public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, @Nullable Paint.FontMetricsInt fm) {
        Paint newPaint = getCustomTextPaint(paint);
        return (int) newPaint.measureText(text, start, end);
    }

    @Override
    public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int
            bottom, @NonNull Paint paint) {
        Paint newPaint = getCustomTextPaint(paint);
        Paint.FontMetricsInt fontMetricsInt = newPaint.getFontMetricsInt();
        canvas.drawText(text, start, end, x, y + fontMetricsInt.ascent, newPaint);
    }

    private TextPaint getCustomTextPaint(Paint srcPaint) {
        TextPaint textPaint = new TextPaint(srcPaint);
        if (fontSizeSp != -1) {
            textPaint.setTextSize(isSp ? fontSizeSp * textPaint.density : fontSizeSp);
        }
        return textPaint;
    }
}

MainActivity.java

 SpannableString ss = new SpannableString("RM123.456");

SuperScriptTextSpan superScriptTextSpan = new SuperScriptTextSpan(18,true);
ss.setSpan(superScriptTextSpan, 0, 2, SpannableString.SPAN_INCLUSIVE_EXCLUSIVE);

 tvImageSpan.setText(ss);

这两个方法都非常的简单,而且可以修改字体大小哦。
这些方法中,我觉得最省事就是直接修改Y坐标,快准狠,哈哈,而且效果也是最满意的,跟其他文字完全顶部对齐。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值