参考博客原链
四个主要接口
if a Span affects character-level text(字符级格式化) formatting, it extends CharacterStyle.
if a Span affects paragraph-level text (段落级格式化)formatting, it implements ParagraphStyle
if a Span modifies the character-level (字符级文本样式)text appearance, it implements UpdateAppearance
if a Span modifies the character-level text metrics|size(字符级文本大小和矩形), it implements UpdateLayout
CharacterStyle
ParagraphStyle
UpdateAppearance/UpdateLayout
工作机制
LAYOUT
当在 TextView中设置文字时,其使用 Layout 管理整个文字的渲染
Layout 类包含了一个 boolean参数 mSpannedText
: true 当text是 Spanned (SpannableString implements Spanned)的子类。该类值处理ParagraphStyle类型的Spans
draw)方法相关内部调用了两个其他方法:
- drawBackground
对每一行的text,如果有对每一行设置设置 LineBackgroundSpan,就会调用LineBackgroundSpan#drawBackground)
- drawText
对每一行的text,计算 LeadingMarginSpan and LeadingMarginSpan2 并且会在需要的时候调用LeadingMarginSpan#drawLeadingMargin) . 同样会在使用AlignmentSpan 决定text的alignment是调用. 最后,如果当前的行是 spanned, Layout 将会调用 TextLine#draw 方法(每一行对应创建一个TextLine)
TEXTLINE
android.text.TextLine 的文档描述:代表一行有样式的text,负责按实际视觉顺序测绘和渲染
TextLine类包含了3个Spans集合:
MetricAffectingSpan set
CharacterStyle set
ReplacementSpan set
核心的方法是 TextLine#handleRun . 在此是所有Spans被用于渲染text的地方,根据 Span的类型,TextLine会调用:
CharacterStyle#updateDrawState 会改变MetricAffectingSpan and CharacterStyle Span中 TextPaint的配置属性
TextLine#handleReplacement 用于ReplacementSpan,通过调用Replacement#getSize) 去获取 replacement width, 更行字体 metrics ,最后调用 Replacement#draw).
FONTMETRICS
关于文字的度量,有如下图:
ascent: 基准线和上边距之间部分
descent: 基准线之下部分
leading: 两行基准线之间的距离
PLAYGROUND
无序列表
//15是中间间距
span = new BulletSpan(15, Color.BLACK);
�
引用Span
//默认只能设置颜色,不能设置gap
span = new QuoteSpan(Color.RED);
�
ALIGN_NORMAL,
ALIGN_OPPOSITE,
ALIGN_CENTER,
/** @hide */
ALIGN_LEFT,
/** @hide */
ALIGN_RIGHT,
span = new AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER);
下和上标注,通过 TextPaint#baselineShift 来调整标注的位置
android.text.style.TextAppearanceSpan
主要是文字的样式配置
<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>
对文字设置绝对的大小
span = new AbsoluteSizeSpan(24, true);
设置相对于其他文字的大小比例
span = new RelativeSizeSpan(2.0f);
运行设置一个android.graphics.MaskFilter 在文字上
//高斯模糊某块文字
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));
FLAG误解
Span对flag的设置分四类
Spanned.SPAN_INCLUSIVE_EXCLUSIVE
Spanned.SPAN_INCLUSIVE_INCLUSIVE
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
Spanned.SPAN_EXCLUSIVE_INCLUSIVE
之前的理解是指对是否包含start,end的index位置,但实际表达的意思是 在设置此flag后,后输入的文字是否会受到影响. 后输入的文字可以指start前,或者end后,一般都是使用Spanned.SPAN_EXCLUSIVE_EXCLUSIVE稳妥
�高阶用法
ANIMATE COLOR
ForegroundColorSpan是 只读的,因此在初始化完成后就不能对其更改。因此继承自实现ForegroundColorSpan去改变属性
public class MutableForegroundColorSpan extends ForegroundColorSpan {
private int mAlpha = 255;
private int mForegroundColor;
public MutableForegroundColorSpan(int alpha,int color) {
super(color);
this.mAlpha = alpha;
this.mForegroundColor = color;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(mAlpha);
dest.writeInt(mForegroundColor);
}
@Override
public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
ds.setColor(getForegroundColor());
}
@Override
public int getForegroundColor() {
return Color.argb(mAlpha,Color.red(mForegroundColor), Color.green(mForegroundColor), Color.blue(mForegroundColor));
}
public void setAlpha(@IntRange(from = 0,to = 255) int alpha){
this.mAlpha = alpha;
}
public void setForegroundColor(int foregroundColor){
this.mForegroundColor = foregroundColor;
}
}
通过利用ObjectAnimator以及自定义属性(Property),由 SpannableString 去操作设置在 TextView的文字,也就是动态改变 MutableForegroundColorSpan 的颜色,再通过 TextView 去强制刷新
- 设置携带Span的 SpannableString 到 TextView
MutableForegroundColorSpan span = new MutableForegroundColorSpan(255, mTextColor);
SpannableString ss = new SpannableString("text");
ss.setSpan(span, 0, "text".length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
mText.setText(ss);
�2. 创建 MutableForegroundColorSpan 自定义属性(值为Int型),通过 set/get 方法来控制其alpha值
此触发的调用链
1.ValueAnimator#start()
2.ValueAnimator#addAnimationCallback
3.AnimationHandler#doAnimationFrame
4.ValueAnimator#doAnimationFrame
5.ValueAnimator#animateBasedOnTime
6.ObjectAnimator#animateValue -> ValueAnimator#animateValue
7.onAnimationUpdate
8.PropertyValueHolder#setAnimatedValue(Object target) #960L
9.mProperty.set(target, getAnimatedValue()) (如果property不为null,为null则用放射的方式去invoke)
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 去控制 MutableForegroundColorSpan 属性的变更
//颜色由Color.BLACK -> Color.RED变化
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) {
//在此会调前,MutableForegroundColorSpan的属性已经由第二步属性了,因此这里需要重新调用onDraw方法绘制
mText.invalidate();
}
});
objectAnimator.setInterpolator(mSmoothInterpolator);
objectAnimator.setDuration(600);
objectAnimator.start();
�TYPE WRITER
�类似挨着输入字的动画
主要思路就是将每个字符看成一个span, 用 progress 来控制span的 alpha值. 比如当progress = 0.5f时,则将前1/2的span设置alpha为255,后 1/2的span设置alpha为0.
public void setAlpha(float progress) {
int size = mSpans.size();//字符个数
float total = 1.0f * size * progress; // 字符进度
if(DEBUG) Log.d(TAG, "progress " + progress + " * 1.0f * size => " + total);
for(int index = 0 ; index < size; index++) {
MutableForegroundColorSpan span = mSpans.get(index);
if(index > total){
span.setAlpha(0);
}else {
span.setAlpha(255);
}
}
}
�