Spans,一个强大的概念

1,747 阅读7分钟
原文链接: rocko.xyz

前言

原文:Spans, a Powerful Concept.

最近,我写了一篇关于NewStand app和app上ActionBar的图标的翻转动效的文章。Cyril Mottier建议我采用一个很优雅的方案,即使用Spans去淡入淡出ActionBar的标题。 此外,我一直想尝试所有可用的Sapn色的类型:ImageSpanBackgroundColorSpan等。他们非常简单易用但是(也)没有任何关于它们的文档和详细信息。 因此,在这篇文章中,我将探索在Spans的框架下什么是可以做的,然后,我将会告诉你怎么去进阶使用Spans。 你可以下载和安装demo程序,查看源码

框架

层次

主要规则:

它为我们提供了下面这些美丽的类图: characterstyle paragraphstyle updateappearance updatelayout 因为它有一点复杂所以我建议你使用像这样的可视化类图,以充分理解它的层次结构。

它是如何工作的?

布局(Layout)

当你给一个TextView设置文本时,它使用基类布局去管理文本的渲染。 布局类包含一个布尔mSpannedText:真,当文本是一个Spanned的实例时(SpannableString实现Spanned)。这个类只处理ParagraphStyle Spans。 draw方法调用了其它两个方法:

文本行(TextLine)

android.text.TextLine的文档这么说:代表一行样式的文字,用于测量视觉顺序和为了渲染。 TextLine类包含3个Spans的集合:

  • MetricAffectingSpan set
  • CharacterStyle set
  • ReplacementSpan set

其中有趣的方法:TextLine#handleRun,这也是所有的Spans用来渲染文本的。相对于Span的类型,TextLine调用:

字体规格(Font Metrics)

如果你想知道更多什么是字体规格,那么看下面的图解: fontmetrics

耍起来

BulletSpan

android.text.style.BulletSpan BulletSpan影响段落层次的文本格式。它可以给段落的开始处加上项目符号。

 * gapWidth:项目符号和文本之间的间隙 * color: 项目符号的颜色,默认为透明 */
span = new BulletSpan(15, Color.BLACK);

BulletSpan的效果

QuoteSpan

android.text.style.QuoteSpan QuoteSpan影响段落层次的文本格式。它可以给一个段落加上垂直的引用线。

 * color: 垂直的引用线颜色,默认是蓝色 */
span = new QuoteSpan(Color.RED);

QuoteSpan的效果

AlignmentSpan.Standard

android.text.style.AlignmentSpan.Standard AlignmentSpan.Standard影响段落层次的文本格式。它可以把段落的每一行文本按正常、居中、相反的方式对齐。

 * align: 对齐方式 */
span = new AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER);

AlignmentSpan.Standard的效果

UnderlineSpan

android.text.style.UnderlineSpan UnderlineSpan影响字符级的文本格式。它可以为字符集加上下划线,归功于Paint#setUnderlineText(true))。


span = new UnderlineSpan();

UnderlineSpan效果图

StrikethroughSpan

android.text.style.StrikethroughSpan StrikethroughSpan影响字符级的文本格式。它可以给字符集加上删除线,归功于Paint#setStrikeThruText(true))


span = new StrikethroughSpan();

StrikethroughSpan的效果图

android.text.style.SubscriptSpan SubscriptSpan影响字符级的文本格式,它可以通过减小TextPaint#baselineShift给字符集加下标。


span = new SubscriptSpan();

SubscriptSpan的效果图

SuperscriptSpan

android.text.style.SuperscriptSpan SuperscriptSpan影响字符级的文本格式。它可以通过增加TextPaint#baselineShift 给字符集加上标。


span = new SuperscriptSpan();

SuperscriptSpan效果图

BackgroundColorSpan

android.text.style.BackgroundColorSpan BackgroundColorSpan影响字符级的文本格式。它可以给字符集加上背景颜色。

 * color: 背景颜色 */
span = new BackgroundColorSpan(Color.GREEN);

BackgroundColorSpan的效果图

ForegroundColorSpan

android.text.style.ForegroundColorSpan ForegroundColorSpan影响字符级的文本格式,它可以设置字符集的前景颜色也即文字颜色。

 * color: 前景颜色 */
span = new ForegroundColorSpan(Color.RED);

ForegroundColorSpan的效果图

ImageSpan

android.text.style.ImageSpan ImageSpan影响字符级的文本格式。它可以生成图像字符。这是为数不多的文档齐全的Span所以enjoy it!

 * Context: 上下文 * resourceId: 图像资源id */
span = new ImageSpan(this, R.drawable.pic1_small);

ImageSpan的效果图

StyleSpan

android.text.style.StyleSpan StyleSpan影响字符级的文本格式,它可以给字符集设置样式(blod、italic、normal)。


span = new StyleSpan(Typeface.BOLD | Typeface.ITALIC);

StyleSpan的效果图

TypefaceSpan

android.text.style.TypefaceSpan TypefaceSpan影响字符级的文本格式。它可以给字符设置字体集(monospace、serif等)。


span = new TypefaceSpan("serif");

TypefaceSpan的效果图

TextAppearanceSpan

android.text.style.TextAppearanceSpan TextAppearanceSpan影响字符级的文本格式。它可以给字符集设置外观(appearance)。

 * TextAppearanceSpan(Context context, int appearance, int colorList) * 		context: 上下文 *		appearance:appearance资源id(例如:android.R.style.TextAppearance_Small) *		colorList:文本的颜色资源id(例如:android.R.styleable.Theme_textColorPrimary) * * TextAppearanceSpan(String family, int style, int size, ColorStateList color, ColorStateList linkColor) *		family:字体family *		style:描述样式(例如:android.graphics.Typeface) *		size:文字大小 *		color:文字颜色 *		linkColor:连接文本的颜色 */
span = new TextAppearanceSpan(this, R.style.SpecialTextAppearance);

<-- style.xml="" --="">
    <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>

TextAppearanceSpan效果图

AbsoluteSizeSpan

android.text.style.AbsoluteSizeSpan AbsoluteSizeSpan影响字符级的文本格式。它可以设置一个字符集的绝对文字大小。

 * size: 大小 * dip: false,size单位为px,true,size单位为dip(默认为false)。 */
span = new AbsoluteSizeSpan(24, true);

AbsoluteSizeSpan的效果图

RelativeSizeSpan

android.text.style.RelativeSizeSpan RelativeSizeSpan影响字符水平的文本格式。它可以设置字符集的文本大小。


span = new RelativeSizeSpan(2.0f);

RelativeSizeSpan的效果图

ScaleXSpan

android.text.style.ScaleXSpan ScaleXSpan印象字符集的文本格式。它可以在x轴方向上缩放字符集。


span = new ScaleXSpan(3.0f);

ScaleXSpan的效果图

MaskFilterSpan

android.text.style.MaskFilterSpan MaskFilterSpan影响字符集文本格式。它可以给字符集设置android.graphics.MaskFilter警告:BlurMaskFilter不支持硬件加速


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));

MaskFilterSpan的效果图: BlurMaskFilter MaskFilterSpan的效果图: EmbossMaskFilter

Spans进阶

前景色(文字颜色)动画

前景色(文字颜色)动画 ForegroundColorSpan为只读。这意味实例化之后着你不能改变你不能改变前景色。所以,要做的第一件事就是编写一个MutableForegroundColorSpan。 MutableForegroundColorSpan.java

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);
span.setAlpha(100);
span.setForegroundColor(Color.RED);
textView.setText(spannableString);

现在我们要前景色的动画。我们可以自定义android.util.Property

private static final Property MUTABLE_FOREGROUND_COLOR_SPAN_FC_PROPERTY =
new Property(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 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) {
        mText.setText(mSpannableString);
    }
    });
objectAnimator.start();

ActionBar”烟火”

ActionBar “烟火”动画是让文字随机淡入。首先,把文字切断成多个spans(例如,一个character的span),淡入spans后再淡入其它的spans。用前面介绍的MutableForegroundColorSpan,我们将创建一组特殊的span对象。在span组调用对应的setAlpha方法,我们随机设置每个span的透明度。

private static final class FireworksSpanGroup {
        private final float mAlpha;
        private final ArrayList mSpans;
        private FireworksSpanGroup(float alpha) {
            mAlpha = alpha;
            mSpans = new ArrayList();
        }
        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; }
    }
    

我们创建一个自定义属性动画的属性去更改FireworksSpanGroup的透明度

private static final Property FIREWORKS_GROUP_PROGRESS_PROPERTY =
new Property(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();
    }
    };

最后,我们创建span组并使用一个ObjectAnimator给其加上动画。

final FireworksSpanGroup spanGroup = new FireworksSpanGroup();
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){
        setTitle(mActionBarTitleSpannableString);
    }
    });
objectAnimator.start();

使用自定义的span

在本节中,我们将看到使用自定义span来绘制的方式。这是文本定制很好的方式。 首先,我们要创建一个继承ReplacementSpan抽象类的自定义Span。 如果你想画一个自定义的背景,你可以实现LineBackgroundSpan ,这是影响段落级的文本格式。 我们必须实现2个方法:

  • getSize):这个方法返回新的你更换后的size。 text:Span管理的文本 start:文本开始处 end:文本结尾处 fm:字体规格,可以为空
  • draw):可以使用Canvas绘制。 x:绘制文本的x坐标 top:线(line)的顶部(译者注:line的定义参看前面字体规格这一节) y:基线 bottom:线的底部、

让我们看一个例子,画一个包围文本的蓝色矩形。 FrameSpan.java

@Override
public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm){
    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){
    canvas.drawRect(x, top, x + mWidth, bottom, mPaint);
    }

自定义Span的效果

附加

Sample app包含了一些Spans进阶的例子,如下: Progressive blur Typewriter

总结

在编写这篇文章的过程中,我意识到Spans是真的像Drawable那样强大的,我认为它们还没有被充分运用。文本是一个应用程序的主要内容,它无处不在,所以不要忘记,通过Spans让它变得更具活力和吸引力!

  • 打赏 如果觉得我的博客、分享对您有帮助,可以扫一扫随意打赏,您的支持将鼓励我继续创作、分享!

    ⇐支付宝||微信⇒⇐支付宝||微信⇒