android string 原理,Android中的SpannableString,Spans以及TextView绘制原理

前言

前段时间看过一篇 实现类似新浪微博帖子显示(2)——话题、@好友、表情解析工具类类似这种富文本的显示我们一般首先就会想到SpannableString,原作者也是这样实现的,就想着整理一下 相关知识.

SpannableString与SpannableStringBuilder

从名称来看一个是 builder, 一个是 string,很好理解.关于 SpannableString,SpannableStringBuilder及String的区别可以看这里

SpannableString与SpannableStringBuilder,SpannableStringBuilder可以通过append()方法来动态改变其内部的值,与String不同的是前两者都可以通过span来添加额外的信息.

SetSpan方法

方法声明如下:

void setSpan (Object what, int start, int end, int flags);

参数说明:

what : 文本格式,可以设置成前景色,背景色,下划线,中划线,模糊等

start : 字符串设置格式的起始下标

end : 字符串设置格式结束下标

flags : 标识

flags: 包含四种情况,用四个常量控制

Spanned.SPAN_INCLUSIVE_EXCLUSIVE 从起始下标到结束下标,包括起始下标不包含结束坐标

Spanned.SPAN_EXCLUSIVE_EXCLUSIVE 从起始下标到结束下标,但都不包括起始下标和结束下标

Spanned.SPAN_INCLUSIVE_INCLUSIVE 从起始下标到终了下标,同时包括起始下标和结束下标

Spanned.SPAN_EXCLUSIVE_INCLUSIVE 从起始下标到终了下标,包括结束下标不包含起始坐标

Spans框架

关于Android 平台上的Spans架构,可以查看Spans, a Powerful Concept., Spans主要分为四个层次

如果一个Span影响字符级的文本格式,则继承CharacterStyle.包括我们常用的ForegroundColorSpan,BackgroundColorSpan.

如果一个Span影响段落层次的文本格式,则实现ParagraphStyle.包括了BulletSpan,继承自LeadingMarginSpan

如果一个Span修改字符级别的文本外观,则实现UpdateAppearance.包括了ClickableSpan

如果一个Span修改字符级文本度量|大小,则实现UpdateLayout.包括了AbsoluteSizeSpan

具体其继承结构可通过查看其Hierarchy

扩展阅读:

工作原理

当你给一个TextView设置文本时,它使用Layout去管理文本的渲染。Layout包含有三个子类.分别是

BoringLayout:负责显示单行文本,isBoring用于判断是否是单行文本.

DynamicLayout:负责渲染Spannable,且内部会设置SpanWatcher,有soan的时候会reflow,进而重新计算布局.

StaticLayout: 单行文本,且非Spannable的时候,不会监听span变化,效率较DynamicLayout高.里面处理了换行.

Layout.draw()会负责文本的绘制,其中drawBackground会递归LineBackgroundSpan并调用lineBackgroundSpan.drawBackground来进行背景的绘制.

drawText则会首先通过TextLine.obtain()生成TextLine,如果是文本,则调用canvas.drawText绘制,如果包含了Spannble,emoji,则交给TextLine绘制.

TextLine#draw()会调用drawRun,进而调用handleRun()进行文本的渲染.

TextLayoutCache为了提高效率,在4.0之后加入.

如果想要提升TextView的渲染效率,可以使用StaticLayout.

扩展阅读:

自定义Span

除了上述四类 Span,我们还可以自定义Span,自定义Span如同自定义View一样.可以继承已有的Span(扩展),也可以通过继承抽象了或者接口完全自定义.

扩展已有的Span

类似demo中扩展ForegroundColorSpan来实现的ActionBar的淡入效果.这里需要借助属性动画的相关知识.

一般需要复写updateDrawState,getSize或者draw方法.

public void updateDrawState(TextPaint ds){}

updateDrawState方法最终会被TextLine#handleRun()方法调用

public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {}

getSize()方法,返回新的更换Span后的size

@Override

public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) {

//draw something

canvas.drawText(text, start, end, x, y, paint);

}

draw使用Canvas绘制一些文本之外的东西.可以是 背景..

完全自定义

通过源码我们发现CharacterStyle,ParagraphStyle.UpdateAppearance和UpdateLayout都是一个空接口.我们要实现自定义Span,则只有继承自其子类,一般是MetricAffectingSpan,ReplacementSpan,LineBackgroundSpan

比如,如果需要自定义背景则可以继承LineBackgroundSpan,就像Demo中的LetterLineBackgroundSpan

学习demo:flavienlaurent/spans,里面列举了各种Span的用法.

FontMetrics

就是所谓的字体规格,如下图所示

0818b9ca8b590ca3270a3433284dd417.png

FontMetrics是Paint的内部类,里面包含了一些关于字体的常量.

其中Baseline(基线),ascent(上坡度),descent(下坡度),leading(行间距),这些常量集合canvas使得我们的绘制工作变得更加的自由

而我们绘制文字的时候一般使用的是TextPaint这个继承自Paint的类.

//获取文本宽度

TextPaint textPaint = new TextPaint();

paint.setTextSize(size);//设置字体大小

paint.setTypeface(Typeface.xx);//设置字体

float width = Layout.getDesiredWidth(str,textPaint);

图文混排居中

比如,我们在使用ImageSpan的时候,并且使用了lineSpacingExtra来设置行间距后,会出现图片下沉,即图片和文字不再一条线上,这时候就可以通过FontMetrics来设置改变.如,评论中插入 图像,将span上移即可

0818b9ca8b590ca3270a3433284dd417.png

解决方法

public class EmojiSpan extends ImageSpan {

public EmojiSpan(Bitmap drawable) {

super(drawable);

}

@Override

public void draw(@NonNull Canvas canvas, CharSequence text,

int start, int end, float x,

int top, int y, int bottom, @NonNull Paint paint) {

// image to draw

Drawable b = getDrawable();

// font metrics of text to be replaced

Paint.FontMetricsInt fm = paint.getFontMetricsInt();

int transY = (y + fm.descent + y + fm.ascent) / 2

- b.getBounds().bottom / 2;

canvas.save();

canvas.translate(x, transY);

b.draw(canvas);

canvas.restore();

}

}

扩展阅读

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值