1 簡(jiǎn)介
之前已經(jīng)講過(guò)TextView的基礎(chǔ)知識(shí),和段落級(jí)別的Span割捅,現(xiàn)在在這進(jìn)一步進(jìn)行講解,這篇文字主要講解如何給TextView設(shè)置字符級(jí)別的Span。如果一個(gè)Span想要影響段落層次的文本格式咖楣,則需要繼承CharacterStyle。
2 CharacterStyle
CharacterStyle是個(gè)抽象類芦昔,字符級(jí)別的Span都需要繼承這個(gè)類诱贿,這個(gè)類里面有一個(gè)抽象方法:
public abstract void updateDrawState(TextPaint tp)
通過(guò)改變TextPaint的屬性就可以得到不同的展現(xiàn)形式。在這個(gè)抽象類里面還有一個(gè)靜態(tài)方法:
public static CharacterStyle wrap(CharacterStyle cs)
一個(gè)CharacterStyle類型的Span只能給一個(gè)Spaned片段使用,如果想這個(gè)Span給多個(gè)片段使用可以使用wrap方法珠十。wrap方法的具體代碼如下:
public static CharacterStyle wrap(CharacterStyle cs) {
if (cs instanceof MetricAffectingSpan) {
return new MetricAffectingSpan.Passthrough((MetricAffectingSpan) cs);
} else {
return new Passthrough(cs);
}
}
再看Passthrough的代碼
private static class Passthrough extends CharacterStyle {
private CharacterStyle mStyle;
/**
* Creates a new Passthrough of the specfied CharacterStyle.
*/
public Passthrough(CharacterStyle cs) {
mStyle = cs;
}
/**
* Passes updateDrawState through to the underlying CharacterStyle.
*/
@Override
public void updateDrawState(TextPaint tp) {
mStyle.updateDrawState(tp);
}
/**
* Returns the CharacterStyle underlying this one, or the one
* underlying it if it too is a Passthrough.
*/
@Override
public CharacterStyle getUnderlying() {
return mStyle.getUnderlying();
}
}
不難發(fā)現(xiàn)其實(shí)就是復(fù)制了一個(gè)CharacterStyle料扰。
3 UpdateAppearance
如果一個(gè)Span修改字符級(jí)別的文本外觀,則實(shí)現(xiàn)UpdateAppearance焙蹭。
上面的Span都實(shí)現(xiàn)了UpdateAppearance接口晒杈,上面的諸多Span都是通過(guò)updateDrawState(TextPaint ds)方法來(lái)實(shí)現(xiàn)相應(yīng)的效果。
- BackgroundColorSpan:ds.bgColor = mColor孔厉;
- ForegroundColorSpan:ds.setColor(mColor)拯钻;
- StrikethroughSpan:ds.setStrikeThruText(true);
- UnderlineSpan:ds.setUnderlineText(true)撰豺;
- MaskFilterSpan:ds.setMaskFilter(mFilter)粪般;
BackgroundColorSpan和ForegroundColorSpan
UnderlineSpan和StrikethroughSpan:
MaskFilterSpan:
可以看一下ClickableSpan的源代碼
public abstract class ClickableSpan extends CharacterStyle implements UpdateAppearance {
/**
* Performs the click action associated with this span.
*/
public abstract void onClick(View widget);
/**
* Makes the text underlined and in the link color.
*/
@Override
public void updateDrawState(TextPaint ds) {
ds.setColor(ds.linkColor);
ds.setUnderlineText(true);
}
}
點(diǎn)擊后通過(guò)updateDrawState(TextPaint ds)方法改變字體外觀,onClick(View widget)則交給子類實(shí)現(xiàn)相應(yīng)的邏輯污桦。
MaskFilterSpan中ds.setMaskFilter(mFilter)可以給字體設(shè)置模糊和浮雕效果亩歹。
span = new MaskFilterSpan(new BlurMaskFilter(density*2, BlurMaskFilter.Blur.NORMAL));
span = new MaskFilterSpan(new EmbossMaskFilter(new float[] { 1, 1, 1 }, 0.4f, 6, 3.5f));
4 UpdateLayout
如果一個(gè)Span修改字符級(jí)文本度量|大小,則實(shí)現(xiàn)UpdateLayout凡橱。在Android源碼中捆憎,只有MetricAffectingSpan實(shí)現(xiàn)了UpdateLayout接口。
接下來(lái)看一下MetricAffectingSpan的源碼梭纹。
public abstract class MetricAffectingSpan
extends CharacterStyle
implements UpdateLayout {
public abstract void updateMeasureState(TextPaint p);
/**
* Returns "this" for most MetricAffectingSpans, but for
* MetricAffectingSpans that were generated by {@link #wrap},
* returns the underlying MetricAffectingSpan.
*/
@Override
public MetricAffectingSpan getUnderlying() {
return this;
}
/**
* A Passthrough MetricAffectingSpan is one that
* passes {@link #updateDrawState} and {@link #updateMeasureState}
* calls through to the specified MetricAffectingSpan
* while still being a distinct object,
* and is therefore able to be attached to the same Spannable
* to which the specified MetricAffectingSpan is already attached.
*/
/* package */ static class Passthrough extends MetricAffectingSpan {
private MetricAffectingSpan mStyle;
/**
* Creates a new Passthrough of the specfied MetricAffectingSpan.
*/
public Passthrough(MetricAffectingSpan cs) {
mStyle = cs;
}
/**
* Passes updateDrawState through to the underlying MetricAffectingSpan.
*/
@Override
public void updateDrawState(TextPaint tp) {
mStyle.updateDrawState(tp);
}
/**
* Passes updateMeasureState through to the underlying MetricAffectingSpan.
*/
@Override
public void updateMeasureState(TextPaint tp) {
mStyle.updateMeasureState(tp);
}
/**
* Returns the MetricAffectingSpan underlying this one, or the one
* underlying it if it too is a Passthrough.
*/
@Override
public MetricAffectingSpan getUnderlying() {
return mStyle.getUnderlying();
}
}
}
可以看見(jiàn)MetricAffectingSpan同樣繼承了CharacterStyle躲惰,因此同樣繼承了抽象方法updateDrawState(TextPaint tp),這個(gè)方法可以交給子類實(shí)現(xiàn)变抽,從而實(shí)現(xiàn)字體外觀的改變础拨。在MetricAffectingSpan類中定義了一個(gè)抽象方法updateMeasureState(TextPaint p),繼承MetricAffectingSpan類的子類可以實(shí)現(xiàn)這個(gè)抽象方法绍载,從而實(shí)現(xiàn)對(duì)字體大小的改變诡宗。在MetricAffectingSpan中同樣也提供了一個(gè)Passthrough的類,從而完成CharacterStyle中定義的wrap方法击儡。
接下來(lái)分別對(duì)MetricAffectingSpan的實(shí)現(xiàn)類進(jìn)行講述塔沃。
4.1 SubscriptSpan和SuperscriptSpan
SubscriptSpan和SuperscriptSpan實(shí)現(xiàn)字體的上下標(biāo)展示,效果如下面的圖片所示:
其實(shí)這兩個(gè)Span的實(shí)現(xiàn)特別簡(jiǎn)單阳谍,通過(guò)查看這兩個(gè)類的實(shí)現(xiàn)蛀柴,能夠幫助我們對(duì)Android的字體有著更深入的理解。
SuperscriptSpan:
@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:
@Override
public void updateDrawState(TextPaint tp) {
tp.baselineShift -= (int) (tp.ascent() / 2);
}
@Override
public void updateMeasureState(TextPaint tp) {
tp.baselineShift -= (int) (tp.ascent() / 2);
}
4.2 AbsoluteSizeSpan和RelativeSizeSpan
AbsoluteSizeSpan和RelativeSizeSpan用來(lái)改變相應(yīng)字符的字體大小矫夯。
/**
* size: 大小
* dip: false鸽疾,size單位為px,true训貌,size單位為dip(默認(rèn)為false)制肮。
*/
//設(shè)置文字大小為24dp
span = new AbsoluteSizeSpan(24, true);
//設(shè)置文字大小為大2倍
span = new RelativeSizeSpan(2.0f);
AbsoluteSizeSpan:
@Override
public void updateDrawState(TextPaint ds) {
if (mDip) {
ds.setTextSize(mSize * ds.density);
} else {
ds.setTextSize(mSize);
}
}
@Override
public void updateMeasureState(TextPaint ds) {
if (mDip) {
ds.setTextSize(mSize * ds.density);
} else {
ds.setTextSize(mSize);
}
}
RelativeSizeSpan:
@Override
public void updateDrawState(TextPaint ds) {
ds.setTextSize(ds.getTextSize() * mProportion);
}
@Override
public void updateMeasureState(TextPaint ds) {
ds.setTextSize(ds.getTextSize() * mProportion);
}
4.3 ScaleXSpan
ScaleXSpan影響字符集的文本格式冒窍。它可以在x軸方向上縮放字符集。
//設(shè)置水平方向上放大3倍
span = new ScaleXSpan(3.0f);
源碼:
@Override
public void updateDrawState(TextPaint ds) {
ds.setTextScaleX(ds.getTextScaleX() * mProportion);
}
@Override
public void updateMeasureState(TextPaint ds) {
ds.setTextScaleX(ds.getTextScaleX() * mProportion);
}
4.4 StyleSpan豺鼻、TypefaceSpan和TextAppearanceSpan
StyleSpan综液、TypefaceSpan和TextAppearanceSpan都可以字體的樣式進(jìn)行改變,StyleSpan可以對(duì)字體設(shè)置bold或者italic的字符樣式儒飒,TypefaceSpan可以對(duì)字體設(shè)置其他的樣式意乓,TextAppearanceSpan通過(guò)xml文件從而對(duì)字體進(jìn)行設(shè)置。
//設(shè)置bold+italic的字符樣式
span = new StyleSpan(Typeface.BOLD | Typeface.ITALIC);
//設(shè)置serif family
span = new TypefaceSpan("serif");
span = new TextAppearanceSpan(this, R.style.SpecialTextAppearance);
<-- style.xml -->
<style name="SpecialTextAppearance" parent="@android:style/TextAppearance">
<item name="android:textColor">@color/color1</item>
<item name="android:textColorHighlight">@color/color2</item>
<item name="android:textColorHint">@color/color3</item>
<item name="android:textColorLink">@color/color4</item>
<item name="android:textSize">28sp</item>
<item name="android:textStyle">italic</item>
</style>
StyleSpan:
@Override
public void updateDrawState(TextPaint ds) {
apply(ds, mStyle);
}
@Override
public void updateMeasureState(TextPaint paint) {
apply(paint, mStyle);
}
private static void apply(Paint paint, int style) {
int oldStyle;
Typeface old = paint.getTypeface();
if (old == null) {
oldStyle = 0;
} else {
oldStyle = old.getStyle();
}
int want = oldStyle | style;
Typeface tf;
if (old == null) {
tf = Typeface.defaultFromStyle(want);
} else {
tf = Typeface.create(old, want);
}
int fake = want & ~tf.getStyle();
if ((fake & Typeface.BOLD) != 0) {
paint.setFakeBoldText(true);
}
if ((fake & Typeface.ITALIC) != 0) {
paint.setTextSkewX(-0.25f);
}
paint.setTypeface(tf);
}
TypefaceSpan:
@Override
public void updateDrawState(TextPaint ds) {
apply(ds, mFamily);
}
@Override
public void updateMeasureState(TextPaint paint) {
apply(paint, mFamily);
}
private static void apply(Paint paint, String family) {
int oldStyle;
Typeface old = paint.getTypeface();
if (old == null) {
oldStyle = 0;
} else {
oldStyle = old.getStyle();
}
Typeface tf = Typeface.create(family, oldStyle);
int fake = oldStyle & ~tf.getStyle();
if ((fake & Typeface.BOLD) != 0) {
paint.setFakeBoldText(true);
}
if ((fake & Typeface.ITALIC) != 0) {
paint.setTextSkewX(-0.25f);
}
paint.setTypeface(tf);
}
TextAppearanceSpan:
@Override
public void updateDrawState(TextPaint ds) {
updateMeasureState(ds);
if (mTextColor != null) {
ds.setColor(mTextColor.getColorForState(ds.drawableState, 0));
}
if (mTextColorLink != null) {
ds.linkColor = mTextColorLink.getColorForState(ds.drawableState, 0);
}
}
@Override
public void updateMeasureState(TextPaint ds) {
if (mTypeface != null || mStyle != 0) {
Typeface tf = ds.getTypeface();
int style = 0;
if (tf != null) {
style = tf.getStyle();
}
style |= mStyle;
if (mTypeface != null) {
tf = Typeface.create(mTypeface, style);
} else if (tf == null) {
tf = Typeface.defaultFromStyle(style);
} else {
tf = Typeface.create(tf, style);
}
int fake = style & ~tf.getStyle();
if ((fake & Typeface.BOLD) != 0) {
ds.setFakeBoldText(true);
}
if ((fake & Typeface.ITALIC) != 0) {
ds.setTextSkewX(-0.25f);
}
ds.setTypeface(tf);
}
if (mTextSize > 0) {
ds.setTextSize(mTextSize);
}
}
4.5 LocaleSpan
LocaleSpan用來(lái)對(duì)字體設(shè)置不同的地區(qū)约素,由于不同地區(qū)的字體會(huì)導(dǎo)致字體大小的變化届良,因此LocaleSpan也需要繼承MetricAffectingSpan。
源碼:
@Override
public void updateDrawState(TextPaint ds) {
apply(ds, mLocale);
}
@Override
public void updateMeasureState(TextPaint paint) {
apply(paint, mLocale);
}
private static void apply(Paint paint, Locale locale) {
paint.setTextLocale(locale);
}
5 ReplacementSpan
ReplacementSpan繼承了MetricAffectingSpan圣猎,但是ReplacementSpan比較復(fù)雜因此在這單獨(dú)講解士葫。在ReplacementSpan里新增加了兩個(gè)抽象方法,ReplacementSpan源碼如下:
public abstract class ReplacementSpan extends MetricAffectingSpan {
public abstract int getSize(Paint paint, CharSequence text,
int start, int end,
Paint.FontMetricsInt fm);
public abstract void draw(Canvas canvas, CharSequence text,
int start, int end, float x,
int top, int y, int bottom, Paint paint);
/**
* This method does nothing, since ReplacementSpans are measured
* explicitly instead of affecting Paint properties.
*/
public void updateMeasureState(TextPaint p) { }
/**
* This method does nothing, since ReplacementSpans are drawn
* explicitly instead of affecting Paint properties.
*/
public void updateDrawState(TextPaint ds) { }
}
抽象方法getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm)返回所占的寬度送悔。其實(shí)根據(jù)getSize方法的參數(shù)我們能夠計(jì)算原本那些字符所占用的寬度慢显,計(jì)算方法如下:
@Override
public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
//return text with relative to the Paint
mWidth = (int) paint.measureText(text, start, end);
return mWidth;
}
通過(guò)這個(gè)寬度我們可以給文字制作相應(yīng)的效果。
抽象方法draw欠啤,可以讓我們?cè)诤线m的區(qū)域繪制相應(yīng)的圖形荚藻,start和end分別為span作用的起始和結(jié)束字符的index,x為起始橫坐標(biāo)洁段,y為baseline對(duì)應(yīng)的坐標(biāo)应狱,top為起始高度,bottom為結(jié)束高度祠丝。
在Android提供的源碼里面提供了一個(gè)抽象類DynamicDrawableSpan來(lái)繼承ReplacementSpan疾呻,而DynamicDrawableSpan又有一個(gè)子類ImageSpan。
5.1 DynamicDrawableSpan
DynamicDrawableSpan是一個(gè)抽象類写半,DynamicDrawableSpan可以做到使用Drawable替代相對(duì)應(yīng)的字符序列岸蜗,展現(xiàn)效果如下所示:
下面我們來(lái)分析一下DynamicDrawableSpan的源碼。
public abstract class DynamicDrawableSpan extends ReplacementSpan {
private static final String TAG = "DynamicDrawableSpan";
/**
* A constant indicating that the bottom of this span should be aligned
* with the bottom of the surrounding text, i.e., at the same level as the
* lowest descender in the text.
*/
public static final int ALIGN_BOTTOM = 0;
/**
* A constant indicating that the bottom of this span should be aligned
* with the baseline of the surrounding text.
*/
public static final int ALIGN_BASELINE = 1;
protected final int mVerticalAlignment;
public DynamicDrawableSpan() {
mVerticalAlignment = ALIGN_BOTTOM;
}
/**
* @param verticalAlignment one of {@link #ALIGN_BOTTOM} or {@link #ALIGN_BASELINE}.
*/
protected DynamicDrawableSpan(int verticalAlignment) {
mVerticalAlignment = verticalAlignment;
}
/**
* Returns the vertical alignment of this span, one of {@link #ALIGN_BOTTOM} or
* {@link #ALIGN_BASELINE}.
*/
public int getVerticalAlignment() {
return mVerticalAlignment;
}
/**
* Your subclass must implement this method to provide the bitmap
* to be drawn. The dimensions of the bitmap must be the same
* from each call to the next.
*/
public abstract Drawable getDrawable();
@Override
public int getSize(Paint paint, CharSequence text,
int start, int end,
Paint.FontMetricsInt fm) {
Drawable d = getCachedDrawable();
Rect rect = d.getBounds();
if (fm != null) {
fm.ascent = -rect.bottom;
fm.descent = 0;
fm.top = fm.ascent;
fm.bottom = 0;
}
return rect.right;
}
@Override
public void draw(Canvas canvas, CharSequence text,
int start, int end, float x,
int top, int y, int bottom, Paint paint) {
Drawable b = getCachedDrawable();
canvas.save();
int transY = bottom - b.getBounds().bottom;
if (mVerticalAlignment == ALIGN_BASELINE) {
transY -= paint.getFontMetricsInt().descent;
}
canvas.translate(x, transY);
b.draw(canvas);
canvas.restore();
}
private Drawable getCachedDrawable() {
WeakReference<Drawable> wr = mDrawableRef;
Drawable d = null;
if (wr != null)
d = wr.get();
if (d == null) {
d = getDrawable();
mDrawableRef = new WeakReference<Drawable>(d);
}
return d;
}
private WeakReference<Drawable> mDrawableRef;
}
- 抽象方法getDrawable()告訴子類需要提供一個(gè)Drawable用來(lái)繪制叠蝇;
- getSize方法中璃岳,通過(guò)設(shè)置FontMetricsInt,從而使得替代字符序列的baseline和圖片的尾部對(duì)齊悔捶,而替代字符序列的垂直高度就為圖片的高度铃慷;
- draw方法中,需要繪制圖片的其實(shí)x坐標(biāo)很明確就是x炎功,y坐標(biāo)可以通過(guò)多種方式獲取枚冗,在baseline對(duì)齊的情況下可以等于top缓溅,也可以等于y-b.getBounds().bottom蛇损,還可以等于bottom-b.getBounds().bottom-descent,各種方法都可以。
在Android系統(tǒng)中淤齐,提供了一個(gè)ImageSpan繼承了DynamicDrawableSpan股囊,實(shí)現(xiàn)了通過(guò)多種方式生成Drawable。