[关闭]
@flyouting 2014-07-06T16:06:08.000000Z 字数 10657 阅读 6635

Span,一个强大的概念

Android Span ImageSpan


在这片文章中,我会表述利用Spans可以做什么,以及更高层次的使用。

你可以下载安装示例apk,或者下载源码

框架层

主要规则:

类图如下:
此处输入图片的描述

此处输入图片的描述

此处输入图片的描述

此处输入图片的描述

是不是有点复杂,推荐一款可视化的类的观察工具有助于直观的了解类结构 class visualizer

如何工作

Layout

当你给一个TextView设置文本时,它使用基类--Layout来管理;文本的渲染。

这个Layout类包含一个boolean型的mSpannedText:当文本是一个Spanned(SpannableString implements Spanned)实例时是True,这个类只执行ParagraphStyle Spans。

draw方法调另外另个方法:

对于每一行文本,如果当前行没有一个LineBackgroundSpanLineBackgroundSpan#drawBackground会被调起。

对于每一行文本,在必要的时候它会计算LeadingMarginSpanLeadingMarginSpan2,调用LeadingMarginSpan#drawLeadingMargin,这也是AlignmentSpan用于确定文本对齐的地方。最后,如果当前行完成spanned,类Layout会调用TextLine#draw。(每行都会创建个TextLine对象)

TextLine

android.text.TextLine文档解释:代表一行文本样式,用于测量视觉秩序和渲染。

TextLine 类包含三个Spans的集合:

TextLine#handleRun方法很有意思,它是所有Spans用于渲染文本的地方,相对Spans的类型,它会调用:

FontMetrics

如果你想知道更多关于什么是字体度量,看看以下模式:
此处输入图片的描述

实际应用

BulletSpan

android.text.style.BulletSpan
BulletSpan影响段落级文本格式。它允许在段落开始处添加一个点。

  1. /*
  2. public BulletSpan (int gapWidth, int color)
  3. -gapWidth: gap in px between bullet and text
  4. -color: bullet color (optionnal, default is transparent)
  5. */
  6. //create a black BulletSpan with a gap of 15px
  7. span = new BulletSpan(15, Color.BLACK);

此处输入图片的描述

QuoteSpan

android.text.style.QuoteSpan

QuoteSpan影响段落级文本格式。它允许你在段落里添加一个竖线。

  1. /*
  2. public QuoteSpan (int color)
  3. -color: quote vertical line color (optionnal, default is Color.BLUE)
  4. */
  5. //create a red quote
  6. span = new QuoteSpan(Color.RED);

此处输入图片的描述

AlignmentSpan.Standard

android.text.style.AlignmentSpan.Standard

AlignmentSpan.Standard影响段落级文本格式。它允许一个段落居中,左对齐,右对齐等。

  1. /*
  2. public Standard(Layout.Alignment align)
  3. -align: alignment to set
  4. */
  5. //align center a paragraph
  6. span = new AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER);

此处输入图片的描述

UnderlineSpan

android.text.style.UnderlineSpan

UnderlineSpan影响字符级文本格式。它可以是字符有下划线

  1. //underline a character
  2. span = new UnderlineSpan();

此处输入图片的描述

StrikethroughSpan

android.text.style.StrikethroughSpan

StrikethroughSpan影响字符级文本格式。它可是是字符有删除线。

  1. //strikethrough a character
  2. span = new StrikethroughSpan();

此处输入图片的描述

SubscriptSpan

android.text.style.SubscriptSpan
SubscriptSpan影响字符级文本格式。它可以是字符有下标字符

  1. //subscript a character
  2. span = new SubscriptSpan();

此处输入图片的描述

SuperscriptSpan

android.text.style.SuperscriptSpan
SuperscriptSpan影响字符级文本格式,它为字符变成上标

  1. //superscript a character
  2. span = new SuperscriptSpan();

此处输入图片的描述

BackgroundColorSpan

android.text.style.BackgroundColorSpan

BackgroundColorSpan影响字符级文本格式。它允许为一个字符设置背景色。

  1. /*
  2. public BackgroundColorSpan (int color)
  3. -color: background color
  4. */
  5. //set a green background
  6. span = new BackgroundColorSpan(Color.GREEN);

此处输入图片的描述

ForegroundColorSpan

android.text.style.ForegroundColorSpan

ForegroundColorSpan影响字符级文本格式。它允许设置文本颜色。

  1. /*
  2. public ForegroundColorSpan (int color)
  3. -color: foreground color
  4. */
  5. //set a red foreground
  6. span = new ForegroundColorSpan(Color.RED);

此处输入图片的描述

ImageSpan

android.text.style.ImageSpan

  1. //replace a character by pic1_small image
  2. span = new ImageSpan(this, R.drawable.pic1_small);

此处输入图片的描述

StyleSpan

android.text.style.StyleSpan
StyleSpan影响字符级文本格式。它允许为字符设置风格(粗体、斜体、正常)。

  1. /*
  2. public StyleSpan (int style)
  3. -style: int describing the style (android.graphics.Typeface)
  4. */
  5. //set a bold+italic style
  6. span = new StyleSpan(Typeface.BOLD | Typeface.ITALIC);

此处输入图片的描述

TypefaceSpan

android.text.style.TypefaceSpan
TypefaceSpan影响字符级文本格式。它允许设置一个字体。

  1. /*
  2. public TypefaceSpan (String family)
  3. -family: a font family
  4. */
  5. //set the serif family
  6. span = new TypefaceSpan("serif");

此处输入图片的描述

TextAppearanceSpan

android.text.style.TextAppearanceSpan

TextAppearanceSpan影响字符级文本格式。它可以为文本设置样式

  1. /*
  2. public TextAppearanceSpan(Context context, int appearance, int colorList)
  3. -context: a valid context
  4. -appearance: text appearance resource (ex: android.R.style.TextAppearance_Small)
  5. -colorList: a text color resource (ex: android.R.styleable.Theme_textColorPrimary)
  6. public TextAppearanceSpan(String family, int style, int size, ColorStateList color, ColorStateList linkColor)
  7. -family: a font family
  8. -style: int describing the style (android.graphics.Typeface)
  9. -size: text size
  10. -color: a text color
  11. -linkColor: a link text color
  12. */
  13. //set the serif family
  14. span = new TextAppearanceSpan(this/*a context*/, R.style.SpecialTextAppearance);
  1. <style name="SpecialTextAppearance" parent="@android:style/TextAppearance">
  2. <item name="android:textColor">@color/color1</item>
  3. <item name="android:textColorHighlight">@color/color2</item>
  4. <item name="android:textColorHint">@color/color3</item>
  5. <item name="android:textColorLink">@color/color4</item>
  6. <item name="android:textSize">28sp</item>
  7. <item name="android:textStyle">italic</item>
  8. </style>

此处输入图片的描述

AbsoluteSizeSpan

android.text.style.AbsoluteSizeSpan

AbsoluteSizeSpan影响字符级文本格式。它允许设置一个文本字符绝对大小。

  1. /*
  2. public AbsoluteSizeSpan(int size, boolean dip)
  3. -size: a size
  4. -dip: false, size is in px; true, size is in dip (optionnal, default false)
  5. */
  6. //set text size to 24dp
  7. span = new AbsoluteSizeSpan(24, true);

此处输入图片的描述

RelativeSizeSpan

android.text.style.RelativeSizeSpan

RelativeSizeSpan影响字符级文本格式。它允许设置一个文本字符相对大小。

  1. /*
  2. public RelativeSizeSpan(float proportion)
  3. -proportion: a proportion of the actual text size
  4. */
  5. //set text size 2 times bigger
  6. span = new RelativeSizeSpan(2.0f);

此处输入图片的描述

ScaleXSpan

android.text.style.ScaleXSpan

ScaleXSpan影响字符级文本格式。它允许扩大字符X轴上大小。

  1. /*
  2. public ScaleXSpan(float proportion)
  3. -proportion: a proportion of actual text scale x
  4. */
  5. //scale x 3 times bigger
  6. span = new ScaleXSpan(3.0f);

此处输入图片的描述

MaskFilterSpan

android.text.style.MaskFilterSpan

MaskFilterSpan影响字符级文本格式。它可以为字符设置一个android.graphics.MaskFilter
BlurMaskFilter不支持硬件加速。

  1. /*
  2. public MaskFilterSpan(MaskFilter filter)
  3. -filter: a filter to apply
  4. */
  5. //Blur a character
  6. span = new MaskFilterSpan(new BlurMaskFilter(density*2, BlurMaskFilter.Blur.NORMAL));
  7. //Emboss a character
  8. span = new MaskFilterSpan(new EmbossMaskFilter(new float[] { 1, 1, 1 }, 0.4f, 6, 3.5f));

BlurMaskFilter此处输入图片的描述

EmbossMaskFilter 蓝色前景色加粗体
此处输入图片的描述

提高Span的应用层次

动态前景色
此处输入图片的描述
ForegroundColorSpan是只读的。这意味着你不能改变实例化后的前景颜色。因此,要做的第一件事就是实现MutableForegroundColorSpan代码。

  1. public class MutableForegroundColorSpan extends ForegroundColorSpan {
  2. private int mAlpha = 255;
  3. private int mForegroundColor;
  4. public MutableForegroundColorSpan(int alpha, int color) {
  5. super(color);
  6. mAlpha = alpha;
  7. mForegroundColor = color;
  8. }
  9. public MutableForegroundColorSpan(Parcel src) {
  10. super(src);
  11. mForegroundColor = src.readInt();
  12. mAlpha = src.readInt();
  13. }
  14. public void writeToParcel(Parcel dest, int flags) {
  15. super.writeToParcel(dest, flags);
  16. dest.writeInt(mForegroundColor);
  17. dest.writeFloat(mAlpha);
  18. }
  19. @Override
  20. public void updateDrawState(TextPaint ds) {
  21. ds.setColor(getForegroundColor());
  22. }
  23. /**
  24. * @param alpha from 0 to 255
  25. */
  26. public void setAlpha(int alpha) {
  27. mAlpha = alpha;
  28. }
  29. public void setForegroundColor(int foregroundColor) {
  30. mForegroundColor = foregroundColor;
  31. }
  32. public float getAlpha() {
  33. return mAlpha;
  34. }
  35. @Override
  36. public int getForegroundColor() {
  37. return Color.argb(mAlpha, Color.red(mForegroundColor), Color.green(mForegroundColor), Color.blue(mForegroundColor));
  38. }
  39. }

现在你可以在同一实例中改透明值,前景色了,但是当你设置了这些属性,它不刷新视图:你必须手动重设SpannableString。

  1. MutableForegroundColorSpan span = new MutableForegroundColorSpan(255, Color.BLACK);
  2. spannableString.setSpan(span, 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
  3. textView.setText(spannableString);
  4. //here the text is black and fully opaque
  5. span.setAlpha(100);
  6. span.setForegroundColor(Color.RED);
  7. //here the text hasn't changed.
  8. textView.setText(spannableString);
  9. //finally, the text is red and translucent

现在我们想动画展示前景色,我们使用一个自定义的android.util.Property.

  1. private static final Property<MutableForegroundColorSpan, Integer> MUTABLE_FOREGROUND_COLOR_SPAN_FC_PROPERTY =
  2. new Property<MutableForegroundColorSpan, Integer>(Integer.class, "MUTABLE_FOREGROUND_COLOR_SPAN_FC_PROPERTY") {
  3. @Override
  4. public void set(MutableForegroundColorSpan span, Integer value) {
  5. span.setForegroundColor(value);
  6. }
  7. @Override
  8. public Integer get(MutableForegroundColorSpan span) {
  9. return span.getForegroundColor();
  10. }
  11. };

最后我们创建一个 ObjectAnimator,设置自定义的property,在onAnimationUpdate方法里刷新视图。

  1. MutableForegroundColorSpan span = new MutableForegroundColorSpan(255, Color.BLACK);
  2. mSpannableString.setSpan(span, 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
  3. ObjectAnimator objectAnimator = ObjectAnimator.ofInt(span, MUTABLE_FOREGROUND_COLOR_SPAN_FC_PROPERTY, Color.BLACK, Color.RED);
  4. objectAnimator.setEvaluator(new ArgbEvaluator());
  5. objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
  6. @Override
  7. public void onAnimationUpdate(ValueAnimator animation) {
  8. //refresh
  9. mText.setText(mSpannableString);
  10. }
  11. });
  12. objectAnimator.start();

ActionBar烟花效果

此处输入图片的描述

“烟花”动画是使字母随机淡入。首先,把文本切成多个Span(例如,一个字符一个Span),一个一个淡入。使用前面介绍MutableForegroundColorSpan,我们将创建一个特殊的对象代表一个Span集。并为每个调用setAlpha,并为每个span设置一个随机的透明值。

  1. private static final class FireworksSpanGroup {
  2. private final float mAlpha;
  3. private final ArrayList<MutableForegroundColorSpan> mSpans;
  4. private FireworksSpanGroup(float alpha) {
  5. mAlpha = alpha;
  6. mSpans = new ArrayList<MutableForegroundColorSpan>();
  7. }
  8. public void addSpan(MutableForegroundColorSpan span) {
  9. span.setAlpha((int) (mAlpha * 255));
  10. mSpans.add(span);
  11. }
  12. public void init() {
  13. Collections.shuffle(mSpans);
  14. }
  15. public void setAlpha(float alpha) {
  16. int size = mSpans.size();
  17. float total = 1.0f * size * alpha;
  18. for(int index = 0 ; index < size; index++) {
  19. MutableForegroundColorSpan span = mSpans.get(index);
  20. if(total >= 1.0f) {
  21. span.setAlpha(255);
  22. total -= 1.0f;
  23. } else {
  24. span.setAlpha((int) (total * 255));
  25. total = 0.0f;
  26. }
  27. }
  28. }
  29. public float getAlpha() { return mAlpha; }
  30. }

创建自定义property

  1. private static final Property<FireworksSpanGroup, Float> FIREWORKS_GROUP_PROGRESS_PROPERTY =
  2. new Property<FireworksSpanGroup, Float>(Float.class, "FIREWORKS_GROUP_PROGRESS_PROPERTY") {
  3. @Override
  4. public void set(FireworksSpanGroup spanGroup, Float value) {
  5. spanGroup.setProgress(value);
  6. }
  7. @Override
  8. public Float get(FireworksSpanGroup spanGroup) {
  9. return spanGroup.getProgress();
  10. }
  11. };

最后创建group,设置ObjectAnimator

  1. final FireworksSpanGroup spanGroup = new FireworksSpanGroup();
  2. //init the group with multiple spans
  3. //spanGroup.addSpan(span);
  4. //set spans on the ActionBar spannable title
  5. //mActionBarTitleSpannableString.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
  6. spanGroup.init();
  7. ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(spanGroup, FIREWORKS_GROUP_PROGRESS_PROPERTY, 0.0f, 1.0f);
  8. objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
  9. @Override
  10. public void onAnimationUpdate(ValueAnimator animation) {
  11. //refresh the ActionBar title
  12. setTitle(mActionBarTitleSpannableString);
  13. }
  14. });
  15. objectAnimator.start();

Draw with your own Span

这里,我们会看到一种通过自定义Span的方式去绘制,这将为文本定制开启一个有趣的视角。

首先,我们需要创建一个自定义的Span,继承自抽象类ReplacementSpan

如果你只是想画一个自定义的背景,你可以实现LineBackgroundSpan
我们需要实现两个方法:

我们来看一个例子,在文字周围绘制一个蓝色矩形

  1. @Override
  2. public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
  3. //return text with relative to the Paint
  4. mWidth = (int) paint.measureText(text, start, end);
  5. return mWidth;
  6. }
  7. @Override
  8. public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) {
  9. //draw the frame with custom Paint
  10. canvas.drawRect(x, top, x + mWidth, bottom, mPaint);
  11. }

此处输入图片的描述

其他例子

总结

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

原文地址
翻译:flyouting

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注