使用Span实现各种酷炫效果

前一段时间一直在做富文本展示和文本处理,主要用到了Html.fromHtml()实现加载网页,但实现整段文本的某些特殊如个别文字的点击,改背景色、前景色等效果,就用到了我们今天要用到的Span这个类。

关于加载网页或个别文字点击效果,可以阅读我之前写的一篇文章——用TextView实现富文本展示,点击断句和语音播报

您也关注:

那接下来就先看下效果图


这里写图片描述

今天会简单介绍几个Span的基本用法,也会分享一些比较酷炫的使用方法:

  • 设置字体颜色
  • 改变字体背景色
  • 给文本添加下划线
  • 给文本加边框
  • 彩虹色文字
  • 彩虹色字体渐变动画
  • 打字效果展示文本

1、设置字体颜色

这里写图片描述
/**
 * 设置不同颜色文字
 */
private void setForegroundColor() {
    SpannableString spannableString = new SpannableString(
                "我爱北京天安门,天安门上太阳升 我爱北京天安门,天安门上太阳升");

    spannableString.setSpan(new ForegroundColorSpan(Color.RED), 0, 16, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    tv_diff_color.setText(spannableString);
}

2、改变字体背景色

[图片上传失败...(image-98d6e5-1512636199700)]

/**
 * 设置背景色
 */
private void setBackgroundColor() {
    SpannableString spannableString = new SpannableString(
                "我爱北京天安门,天安门上太阳升 我爱北京天安门,天安门上太阳升");

    spannableString.setSpan(new BackgroundColorSpan(Color.RED), 0, 16, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

    tv_bg_color.setText(spannableString);
}

3、给文本添加下划线

这里写图片描述
/**
 * 设置超链接
 */
private void setLink() {
    SpannableString spannableString = new SpannableString(
                "我爱北京天安门,天安门上太阳升 我爱北京天安门,天安门上太阳升");
    //设置下划线
    spannableString.setSpan(new UnderlineSpan(), 0, 16, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    tv_link.setText(spannableString);
}

4、给文本加边框

这里写图片描述

这种效果就不再是简单的直接使用系统提供的Span类就可以了,需要我们自定义:

public class FrameSpan extends ReplacementSpan {
    private final Paint mPaint;
    private int mWidth;
    public FrameSpan() {
        mPaint = new Paint();
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setColor(Color.RED);
        mPaint.setAntiAlias(true);
    }
    @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);
        canvas.drawText(text, start, end, x, y, paint);
    }
}

类似于自定义View,最重要的是在初始化画笔、获取绘制区域大小、在draw中绘制矩形边框

这里就不再重复累赘了。

然后和之前类似,使用它:

/**
 * 给文字加边框
 */
private void addBox() {
    SpannableString spannableString = new SpannableString(
                "我爱北京天安门,天安门上太阳升 我爱北京天安门,天安门上太阳升");
    spannableString.setSpan(new FrameSpan(), 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
    tv_biankuang.setText(spannableString);
}

5、彩虹色文字

这里写图片描述

自定义Span:

public class RainbowSpan extends CharacterStyle implements UpdateAppearance {
    private int[] colors;

    public RainbowSpan(Context context) {
        colors = context.getResources().getIntArray(R.array.splash_bg);
    }

    @Override
    public void updateDrawState(TextPaint paint) {
        paint.setStyle(Paint.Style.FILL);
        Shader shader = new LinearGradient(0, 0, 0, paint.getTextSize() * colors.length, colors, null,
                Shader.TileMode.MIRROR);
        Matrix matrix = new Matrix();
        matrix.setRotate(90);
        shader.setLocalMatrix(matrix);
        paint.setShader(shader);
    }
}

其中CharSequence是一组可读的Char序列,提供了操作Char序列的接口,是所有Span类的根父类。

使用Shader进行着色渲染,LinearGradient是线性渐变,Gradient是基于Shader类,所以我们通过Paint的setShader方法来设置这个渐变.

LinearGradient参数含义:

  • X0: 渐变起初点坐标x位置
  • y0: 渐变起初点坐标y位置
  • x1: 渐变终点坐标x位置
  • y1: 渐变终点坐标y位置
  • color0: 渐变开始颜色
  • color1: 渐变结束颜色
  • tile: 平铺方式

然后使用

/**
 * 设置彩色字体
 */
private void setColofulText() {
    SpannableString spannableString = new SpannableString(
                "我爱北京天安门,天安门上太阳升 我爱北京天安门,天安门上太阳升");
    spannableString.setSpan(new RainbowSpan(this), 0, 15, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
    tv_color.setText(spannableString);
}

6、彩虹色文字动起来

这里写图片描述

和上一步相比,不同的是,需实时更新、重绘彩色,所以,自定义的Span类相比于上一个RainbowSpan来说,主要有以下不同:

  • 声明变量—— 横向渐变的百分比translateXPercentage,并设置对应get、set方法;
  • 在updateDrawState中使用matrix.postTranslate进行变化;
  • postTranslate是指在setRotate后平移。
public class AnimatedColorSpan extends CharacterStyle implements UpdateAppearance {

    private final int[] colors;
    private Shader shader = null;
    private Matrix matrix = new Matrix();
    private float translateXPercentage = 0;

    public AnimatedColorSpan(Context context) {
        colors = context.getResources().getIntArray(R.array.splash_bg);
    }

    public void setTranslateXPercentage(float percentage) {
        translateXPercentage = percentage;
    }

    public float getTranslateXPercentage() {
        return translateXPercentage;
    }

    @Override
    public void updateDrawState(TextPaint paint) {
        paint.setStyle(Paint.Style.FILL);
        float width = paint.getTextSize() * colors.length;
        if (shader == null) {
            shader = new LinearGradient(0, 0, 0, width, colors, null,
                    Shader.TileMode.MIRROR);
        }
        matrix.reset();
        matrix.setRotate(90);
        matrix.postTranslate(width * translateXPercentage, 0);
        shader.setLocalMatrix(matrix);
        paint.setShader(shader);
    }

}

然后使用:

/**
 * 设置彩色动画
 */
private void setColofulAnimText() {
    final SpannableString spannableString = new SpannableString(
                "我爱北京天安门,天安门上太阳升 我爱北京天安门,天安门上太阳升");
    AnimatedColorSpan span = new AnimatedColorSpan(this);

    spannableString.setSpan(span, 0, 15, 0);

    // 设置动画
    ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(
                span, ANIMATED_COLOR_SPAN_FLOAT_PROPERTY, 0, 100);
    objectAnimator.setEvaluator(new FloatEvaluator());
    objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            tv_color_anim.setText(spannableString);
        }
    });
    objectAnimator.setInterpolator(new LinearInterpolator());
    objectAnimator.setDuration(DateUtils.MINUTE_IN_MILLIS * 2);
    objectAnimator.setRepeatCount(ValueAnimator.INFINITE);
    objectAnimator.start();
}

大家可能注意到了,设置动画时用到了动画属性变化器:

/**
 * 彩色动画 属性变化器
 */
private static final Property<AnimatedColorSpan, Float> ANIMATED_COLOR_SPAN_FLOAT_PROPERTY
            = new Property<AnimatedColorSpan, Float>(Float.class, "ANIMATED_COLOR_SPAN_FLOAT_PROPERTY") {
    @Override
    public void set(AnimatedColorSpan span, Float value) {
        span.setTranslateXPercentage(value);
    }

    @Override
    public Float get(AnimatedColorSpan span) {
        return span.getTranslateXPercentage();
    }
};

在变化器的对应方法中设置Span变化的百分比。

7、文本实现打字效果

先看看Span的写法:

这里写图片描述
public class MutableForegroundColorSpan extends CharacterStyle implements UpdateAppearance {

    private int mColor = Color.BLACK;
    private int mAlpha = 0 ;

    @Override
    public void updateDrawState(TextPaint tp) {
        tp.setColor(mColor);
        tp.setAlpha(mAlpha);
    }

    public int getColor() {
        return mColor;
    }

    public void setColor(int color) {
        this.mColor = color;
    }

    public void setAlpha(int alpha) {
        mAlpha = alpha;
    }
}
public class TypeWriterSpanGroup {
    private final float mAlpha;
    private final ArrayList<MutableForegroundColorSpan> mSpans;
    public TypeWriterSpanGroup(float alpha) {
        mAlpha = alpha;
        mSpans = new ArrayList<MutableForegroundColorSpan>();
    }
    public void addSpan(MutableForegroundColorSpan span) {
        span.setAlpha((int) (mAlpha * 255));
        mSpans.add(span);
    }
    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;
    }
}

思路是这样的,每打印一个文字,都是一个对应的MutableForegroundColorSpan,要想实现连续的打印每个字,我们需要创建一个集合来存放所有得Span。

循环集合中所有的Span,除了最近一个打印的字以外,其他的字设置为不透明,第一个跟随动画进行渐变。

看下动画的使用:

/**
 * 打字效果
 */
private void addTyping() {

    String content = "我爱北京天安门,天安门上太阳升 我爱北京天安门,天安门上太阳升";

        final SpannableString spannableString = new SpannableString(content);
    // 添加Span
    final TypeWriterSpanGroup group = new TypeWriterSpanGroup(0);
    for (int index = 0; index <= content.length() - 1; index++) {
        MutableForegroundColorSpan span = new MutableForegroundColorSpan();
        group.addSpan(span);
        spannableString.setSpan(span, index, index + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    }
    // 添加动画
    ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(group, TYPE_WRITER_GROUP_ALPHA_PROPERTY, 0.0f, 1.0f);
    objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            //refresh
            tv_dazi.setText(spannableString);
        }
    });
    objectAnimator.setDuration(5000);
    objectAnimator.setRepeatCount(ValueAnimator.INFINITE);
    objectAnimator.start();
}

还有变化器:

/**
 * 打字动画  属性变化器
 */
private static final Property<TypeWriterSpanGroup, Float> TYPE_WRITER_GROUP_ALPHA_PROPERTY =
            new Property<TypeWriterSpanGroup, Float>(Float.class, "TYPE_WRITER_GROUP_ALPHA_PROPERTY") {
    @Override
    public void set(TypeWriterSpanGroup spanGroup, Float value) {
        spanGroup.setAlpha(value);
    }

    @Override
    public Float get(TypeWriterSpanGroup spanGroup) {
        return spanGroup.getAlpha();
    }
};

关于使用Span实现TextView的几种效果,大致就介绍到这,有错误的地方和不足的地方,希望大家提出,我们一起进步_

更多精彩内容,请关注我的微信公众号——Android机动车

Android机动车.jpg

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,864评论 6 494
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,175评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,401评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,170评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,276评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,364评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,401评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,179评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,604评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,902评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,070评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,751评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,380评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,077评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,312评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,924评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,957评论 2 351

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,074评论 4 62
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,644评论 18 139
  • 昨天下了夜班带儿子去检查眼睛了,这个星期他回来说,黑板上的字都看不清楚了,想着他是不是近视了呢?结果一去检查,都俩...
    月儿的2016阅读 376评论 1 1
  • 今天上午在天津妇联社会服务中心召开了一次重要会议,来自妇联,民政,高校,社会组织的社会工作和心理学专家学者和一线社...
    zhx阅读 679评论 0 1
  • 一个人在途中,独自上路 生于世间城堡中浮沉 当我阅遍了冷冷清风送飘雪 令行人鞋踏破路湿透 当我看尽那远远青山吹飞絮...
    与自己赛跑的人阅读 430评论 0 0