前一段时间一直在做富文本展示和文本处理,主要用到了Html.fromHtml()实现加载网页,但实现整段文本的某些特殊如个别文字的点击,改背景色、前景色等效果,就用到了我们今天要用到的Span这个类。
关于加载网页或个别文字点击效果,可以阅读我之前写的一篇文章——用TextView实现富文本展示,点击断句和语音播报。
您也关注:
那接下来就先看下效果图
今天会简单介绍几个Span的基本用法,也会分享一些比较酷炫的使用方法:
- /**
- * 设置不同颜色文字
- */
- private void setForegroundColor() {
- SpannableString spannableString = new SpannableString(
- "我爱北京天安门,天安门上太阳升 我爱北京天安门,天安门上太阳升");
- spannableString.setSpan(new ForegroundColorSpan(Color.RED), 0, 16, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- tv_diff_color.setText(spannableString);
- }
- /**
- * 设置背景色
- */
- private void setBackgroundColor() {
- SpannableString spannableString = new SpannableString(
- "我爱北京天安门,天安门上太阳升 我爱北京天安门,天安门上太阳升");
- spannableString.setSpan(new BackgroundColorSpan(Color.RED), 0, 16, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- tv_bg_color.setText(spannableString);
- }
- /**
- * 设置超链接
- */
- private void setLink() {
- SpannableString spannableString = new SpannableString(
- "我爱北京天安门,天安门上太阳升 我爱北京天安门,天安门上太阳升");
- //设置下划线
- spannableString.setSpan(new UnderlineSpan(), 0, 16, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- tv_link.setText(spannableString);
- }
这种效果就不再是简单的直接使用系统提供的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);
- }
自定义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参数含义:
然后使用
- /**
- * 设置彩色字体
- */
- private void setColofulText() {
- SpannableString spannableString = new SpannableString(
- "我爱北京天安门,天安门上太阳升 我爱北京天安门,天安门上太阳升");
- spannableString.setSpan(new RainbowSpan(this), 0, 15, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
- tv_color.setText(spannableString);
- }
和上一步相比,不同的是,需实时更新、重绘彩色,所以,自定义的Span类相比于上一个RainbowSpan来说,主要有以下不同:
- 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变化的百分比。
先看看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的几种效果,大致就介绍到这,有错误的地方和不足的地方,希望大家提出,我们一起进步^_^。
来源: https://juejin.im/post/5a28fdbb6fb9a0452936a758