@flyouting
2014-07-06T16:06:08.000000Z
字数 10657
阅读 7321
Android Span ImageSpan
在这片文章中,我会表述利用Spans可以做什么,以及更高层次的使用。
主要规则:
类图如下:




是不是有点复杂,推荐一款可视化的类的观察工具有助于直观的了解类结构 class visualizer
当你给一个TextView设置文本时,它使用基类--Layout来管理;文本的渲染。
这个Layout类包含一个boolean型的mSpannedText:当文本是一个Spanned(SpannableString implements Spanned)实例时是True,这个类只执行ParagraphStyle Spans。
draw方法调另外另个方法:
对于每一行文本,如果当前行没有一个LineBackgroundSpan,LineBackgroundSpan#drawBackground会被调起。
对于每一行文本,在必要的时候它会计算LeadingMarginSpan 和 LeadingMarginSpan2,调用LeadingMarginSpan#drawLeadingMargin,这也是AlignmentSpan用于确定文本对齐的地方。最后,如果当前行完成spanned,类Layout会调用TextLine#draw。(每行都会创建个TextLine对象)
android.text.TextLine文档解释:代表一行文本样式,用于测量视觉秩序和渲染。
TextLine 类包含三个Spans的集合:
TextLine#handleRun方法很有意思,它是所有Spans用于渲染文本的地方,相对Spans的类型,它会调用:
如果你想知道更多关于什么是字体度量,看看以下模式:

android.text.style.BulletSpan
BulletSpan影响段落级文本格式。它允许在段落开始处添加一个点。
/*public BulletSpan (int gapWidth, int color)-gapWidth: gap in px between bullet and text-color: bullet color (optionnal, default is transparent)*///create a black BulletSpan with a gap of 15pxspan = new BulletSpan(15, Color.BLACK);

QuoteSpan影响段落级文本格式。它允许你在段落里添加一个竖线。
/*public QuoteSpan (int color)-color: quote vertical line color (optionnal, default is Color.BLUE)*///create a red quotespan = new QuoteSpan(Color.RED);

android.text.style.AlignmentSpan.Standard
AlignmentSpan.Standard影响段落级文本格式。它允许一个段落居中,左对齐,右对齐等。
/*public Standard(Layout.Alignment align)-align: alignment to set*///align center a paragraphspan = new AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER);

android.text.style.UnderlineSpan
UnderlineSpan影响字符级文本格式。它可以是字符有下划线
//underline a characterspan = new UnderlineSpan();

android.text.style.StrikethroughSpan
StrikethroughSpan影响字符级文本格式。它可是是字符有删除线。
//strikethrough a characterspan = new StrikethroughSpan();

android.text.style.SubscriptSpan
SubscriptSpan影响字符级文本格式。它可以是字符有下标字符
//subscript a characterspan = new SubscriptSpan();

android.text.style.SuperscriptSpan
SuperscriptSpan影响字符级文本格式,它为字符变成上标
//superscript a characterspan = new SuperscriptSpan();

android.text.style.BackgroundColorSpan
BackgroundColorSpan影响字符级文本格式。它允许为一个字符设置背景色。
/*public BackgroundColorSpan (int color)-color: background color*///set a green backgroundspan = new BackgroundColorSpan(Color.GREEN);

android.text.style.ForegroundColorSpan
ForegroundColorSpan影响字符级文本格式。它允许设置文本颜色。
/*public ForegroundColorSpan (int color)-color: foreground color*///set a red foregroundspan = new ForegroundColorSpan(Color.RED);

//replace a character by pic1_small imagespan = new ImageSpan(this, R.drawable.pic1_small);

android.text.style.StyleSpan
StyleSpan影响字符级文本格式。它允许为字符设置风格(粗体、斜体、正常)。
/*public StyleSpan (int style)-style: int describing the style (android.graphics.Typeface)*///set a bold+italic stylespan = new StyleSpan(Typeface.BOLD | Typeface.ITALIC);

android.text.style.TypefaceSpan
TypefaceSpan影响字符级文本格式。它允许设置一个字体。
/*public TypefaceSpan (String family)-family: a font family*///set the serif familyspan = new TypefaceSpan("serif");

android.text.style.TextAppearanceSpan
TextAppearanceSpan影响字符级文本格式。它可以为文本设置样式
/*public TextAppearanceSpan(Context context, int appearance, int colorList)-context: a valid context-appearance: text appearance resource (ex: android.R.style.TextAppearance_Small)-colorList: a text color resource (ex: android.R.styleable.Theme_textColorPrimary)public TextAppearanceSpan(String family, int style, int size, ColorStateList color, ColorStateList linkColor)-family: a font family-style: int describing the style (android.graphics.Typeface)-size: text size-color: a text color-linkColor: a link text color*///set the serif familyspan = new TextAppearanceSpan(this/*a context*/, R.style.SpecialTextAppearance);
<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>

android.text.style.AbsoluteSizeSpan
AbsoluteSizeSpan影响字符级文本格式。它允许设置一个文本字符绝对大小。
/*public AbsoluteSizeSpan(int size, boolean dip)-size: a size-dip: false, size is in px; true, size is in dip (optionnal, default false)*///set text size to 24dpspan = new AbsoluteSizeSpan(24, true);

android.text.style.RelativeSizeSpan
RelativeSizeSpan影响字符级文本格式。它允许设置一个文本字符相对大小。
/*public RelativeSizeSpan(float proportion)-proportion: a proportion of the actual text size*///set text size 2 times biggerspan = new RelativeSizeSpan(2.0f);

ScaleXSpan影响字符级文本格式。它允许扩大字符X轴上大小。
/*public ScaleXSpan(float proportion)-proportion: a proportion of actual text scale x*///scale x 3 times biggerspan = new ScaleXSpan(3.0f);

android.text.style.MaskFilterSpan
MaskFilterSpan影响字符级文本格式。它可以为字符设置一个android.graphics.MaskFilter。
BlurMaskFilter不支持硬件加速。
/*public MaskFilterSpan(MaskFilter filter)-filter: a filter to apply*///Blur a characterspan = new MaskFilterSpan(new BlurMaskFilter(density*2, BlurMaskFilter.Blur.NORMAL));//Emboss a characterspan = new MaskFilterSpan(new EmbossMaskFilter(new float[] { 1, 1, 1 }, 0.4f, 6, 3.5f));
BlurMaskFilter
EmbossMaskFilter 蓝色前景色加粗体

动态前景色
ForegroundColorSpan是只读的。这意味着你不能改变实例化后的前景颜色。因此,要做的第一件事就是实现MutableForegroundColorSpan代码。
public class MutableForegroundColorSpan extends ForegroundColorSpan {private int mAlpha = 255;private int mForegroundColor;public MutableForegroundColorSpan(int alpha, int color) {super(color);mAlpha = alpha;mForegroundColor = color;}public MutableForegroundColorSpan(Parcel src) {super(src);mForegroundColor = src.readInt();mAlpha = src.readInt();}public void writeToParcel(Parcel dest, int flags) {super.writeToParcel(dest, flags);dest.writeInt(mForegroundColor);dest.writeFloat(mAlpha);}@Overridepublic void updateDrawState(TextPaint ds) {ds.setColor(getForegroundColor());}/*** @param alpha from 0 to 255*/public void setAlpha(int alpha) {mAlpha = alpha;}public void setForegroundColor(int foregroundColor) {mForegroundColor = foregroundColor;}public float getAlpha() {return mAlpha;}@Overridepublic int getForegroundColor() {return Color.argb(mAlpha, Color.red(mForegroundColor), Color.green(mForegroundColor), Color.blue(mForegroundColor));}}
现在你可以在同一实例中改透明值,前景色了,但是当你设置了这些属性,它不刷新视图:你必须手动重设SpannableString。
MutableForegroundColorSpan span = new MutableForegroundColorSpan(255, Color.BLACK);spannableString.setSpan(span, 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);textView.setText(spannableString);//here the text is black and fully opaquespan.setAlpha(100);span.setForegroundColor(Color.RED);//here the text hasn't changed.textView.setText(spannableString);//finally, the text is red and translucent
现在我们想动画展示前景色,我们使用一个自定义的android.util.Property.
private static final Property<MutableForegroundColorSpan, Integer> MUTABLE_FOREGROUND_COLOR_SPAN_FC_PROPERTY =new Property<MutableForegroundColorSpan, Integer>(Integer.class, "MUTABLE_FOREGROUND_COLOR_SPAN_FC_PROPERTY") {@Overridepublic void set(MutableForegroundColorSpan span, Integer value) {span.setForegroundColor(value);}@Overridepublic Integer get(MutableForegroundColorSpan span) {return span.getForegroundColor();}};
最后我们创建一个 ObjectAnimator,设置自定义的property,在onAnimationUpdate方法里刷新视图。
MutableForegroundColorSpan span = new MutableForegroundColorSpan(255, Color.BLACK);mSpannableString.setSpan(span, 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);ObjectAnimator objectAnimator = ObjectAnimator.ofInt(span, MUTABLE_FOREGROUND_COLOR_SPAN_FC_PROPERTY, Color.BLACK, Color.RED);objectAnimator.setEvaluator(new ArgbEvaluator());objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {//refreshmText.setText(mSpannableString);}});objectAnimator.start();

“烟花”动画是使字母随机淡入。首先,把文本切成多个Span(例如,一个字符一个Span),一个一个淡入。使用前面介绍MutableForegroundColorSpan,我们将创建一个特殊的对象代表一个Span集。并为每个调用setAlpha,并为每个span设置一个随机的透明值。
private static final class FireworksSpanGroup {private final float mAlpha;private final ArrayList<MutableForegroundColorSpan> mSpans;private FireworksSpanGroup(float alpha) {mAlpha = alpha;mSpans = new ArrayList<MutableForegroundColorSpan>();}public void addSpan(MutableForegroundColorSpan span) {span.setAlpha((int) (mAlpha * 255));mSpans.add(span);}public void init() {Collections.shuffle(mSpans);}public void setAlpha(float alpha) {int size = mSpans.size();float total = 1.0f * size * alpha;for(int index = 0 ; index < size; index++) {MutableForegroundColorSpan span = mSpans.get(index);if(total >= 1.0f) {span.setAlpha(255);total -= 1.0f;} else {span.setAlpha((int) (total * 255));total = 0.0f;}}}public float getAlpha() { return mAlpha; }}
创建自定义property
private static final Property<FireworksSpanGroup, Float> FIREWORKS_GROUP_PROGRESS_PROPERTY =new Property<FireworksSpanGroup, Float>(Float.class, "FIREWORKS_GROUP_PROGRESS_PROPERTY") {@Overridepublic void set(FireworksSpanGroup spanGroup, Float value) {spanGroup.setProgress(value);}@Overridepublic Float get(FireworksSpanGroup spanGroup) {return spanGroup.getProgress();}};
最后创建group,设置ObjectAnimator
final FireworksSpanGroup spanGroup = new FireworksSpanGroup();//init the group with multiple spans//spanGroup.addSpan(span);//set spans on the ActionBar spannable title//mActionBarTitleSpannableString.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);spanGroup.init();ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(spanGroup, FIREWORKS_GROUP_PROGRESS_PROPERTY, 0.0f, 1.0f);objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {//refresh the ActionBar titlesetTitle(mActionBarTitleSpannableString);}});objectAnimator.start();
这里,我们会看到一种通过自定义Span的方式去绘制,这将为文本定制开启一个有趣的视角。
首先,我们需要创建一个自定义的Span,继承自抽象类ReplacementSpan。
如果你只是想画一个自定义的背景,你可以实现LineBackgroundSpan。
我们需要实现两个方法:
我们来看一个例子,在文字周围绘制一个蓝色矩形
@Overridepublic int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {//return text with relative to the PaintmWidth = (int) paint.measureText(text, start, end);return mWidth;}@Overridepublic void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) {//draw the frame with custom Paintcanvas.drawRect(x, top, x + mWidth, bottom, mPaint);}



写文章的过程中,我意识到Spans真的很强大,像drawables,我想他们还没有被使用的足够多,文本是一个应用中的主要内容,它无处不在,所以不要忘记用Span使它更具活力和新引力。