@flyouting
2014-07-07T00:06:08.000000Z
字数 10657
阅读 6935
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 15px
span = new BulletSpan(15, Color.BLACK);
QuoteSpan影响段落级文本格式。它允许你在段落里添加一个竖线。
/*
public QuoteSpan (int color)
-color: quote vertical line color (optionnal, default is Color.BLUE)
*/
//create a red quote
span = new QuoteSpan(Color.RED);
android.text.style.AlignmentSpan.Standard
AlignmentSpan.Standard影响段落级文本格式。它允许一个段落居中,左对齐,右对齐等。
/*
public Standard(Layout.Alignment align)
-align: alignment to set
*/
//align center a paragraph
span = new AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER);
android.text.style.UnderlineSpan
UnderlineSpan影响字符级文本格式。它可以是字符有下划线
//underline a character
span = new UnderlineSpan();
android.text.style.StrikethroughSpan
StrikethroughSpan影响字符级文本格式。它可是是字符有删除线。
//strikethrough a character
span = new StrikethroughSpan();
android.text.style.SubscriptSpan
SubscriptSpan影响字符级文本格式。它可以是字符有下标字符
//subscript a character
span = new SubscriptSpan();
android.text.style.SuperscriptSpan
SuperscriptSpan影响字符级文本格式,它为字符变成上标
//superscript a character
span = new SuperscriptSpan();
android.text.style.BackgroundColorSpan
BackgroundColorSpan影响字符级文本格式。它允许为一个字符设置背景色。
/*
public BackgroundColorSpan (int color)
-color: background color
*/
//set a green background
span = new BackgroundColorSpan(Color.GREEN);
android.text.style.ForegroundColorSpan
ForegroundColorSpan影响字符级文本格式。它允许设置文本颜色。
/*
public ForegroundColorSpan (int color)
-color: foreground color
*/
//set a red foreground
span = new ForegroundColorSpan(Color.RED);
//replace a character by pic1_small image
span = 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 style
span = new StyleSpan(Typeface.BOLD | Typeface.ITALIC);
android.text.style.TypefaceSpan
TypefaceSpan影响字符级文本格式。它允许设置一个字体。
/*
public TypefaceSpan (String family)
-family: a font family
*/
//set the serif family
span = 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 family
span = 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 24dp
span = 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 bigger
span = new RelativeSizeSpan(2.0f);
ScaleXSpan影响字符级文本格式。它允许扩大字符X轴上大小。
/*
public ScaleXSpan(float proportion)
-proportion: a proportion of actual text scale x
*/
//scale x 3 times bigger
span = new ScaleXSpan(3.0f);
android.text.style.MaskFilterSpan
MaskFilterSpan影响字符级文本格式。它可以为字符设置一个android.graphics.MaskFilter。
BlurMaskFilter不支持硬件加速。
/*
public MaskFilterSpan(MaskFilter filter)
-filter: a filter to apply
*/
//Blur a character
span = new MaskFilterSpan(new BlurMaskFilter(density*2, BlurMaskFilter.Blur.NORMAL));
//Emboss a character
span = 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);
}
@Override
public 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;
}
@Override
public 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 opaque
span.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") {
@Override
public void set(MutableForegroundColorSpan span, Integer value) {
span.setForegroundColor(value);
}
@Override
public 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() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//refresh
mText.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") {
@Override
public void set(FireworksSpanGroup spanGroup, Float value) {
spanGroup.setProgress(value);
}
@Override
public 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() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//refresh the ActionBar title
setTitle(mActionBarTitleSpannableString);
}
});
objectAnimator.start();
这里,我们会看到一种通过自定义Span的方式去绘制,这将为文本定制开启一个有趣的视角。
首先,我们需要创建一个自定义的Span,继承自抽象类ReplacementSpan。
如果你只是想画一个自定义的背景,你可以实现LineBackgroundSpan。
我们需要实现两个方法:
我们来看一个例子,在文字周围绘制一个蓝色矩形
@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;
}
@Override
public 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 Paint
canvas.drawRect(x, top, x + mWidth, bottom, mPaint);
}
写文章的过程中,我意识到Spans真的很强大,像drawables,我想他们还没有被使用的足够多,文本是一个应用中的主要内容,它无处不在,所以不要忘记用Span使它更具活力和新引力。