上标:
代码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坐标,快准狠,哈哈,而且效果也是最满意的,跟其他文字完全顶部对齐。