前段时间看过一篇 实现类似新浪微博帖子显示(2)——话题、@好友、表情解析工具类类似这种富文本的显示我们一般首先就会想到SpannableString
,原作者也是这样实现的,就想着整理一下 相关知识.
从名称来看一个是 builder
, 一个是 string
,很好理解.关于 SpannableString,SpannableStringBuilder及String
的区别可以看这里
SpannableString与SpannableStringBuilder,SpannableStringBuilder
可以通过append()
方法来动态改变其内部的值,与String
不同的是前两者都可以通过span
来添加额外的信息.
方法声明如下:
void setSpan (Object what, int start, int end, int flags);
参数说明:
flags: 包含四种情况,用四个常量控制
Spanned.SPAN_INCLUSIVE_EXCLUSIVE
从起始下标到结束下标,包括起始下标不包含结束坐标Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
从起始下标到结束下标,但都不包括起始下标和结束下标Spanned.SPAN_INCLUSIVE_INCLUSIVE
从起始下标到终了下标,同时包括起始下标和结束下标Spanned.SPAN_EXCLUSIVE_INCLUSIVE
从起始下标到终了下标,包括结束下标不包含起始坐标关于Android 平台上的Spans
架构,可以查看Spans, a Powerful Concept.
, Spans
主要分为四个层次
ForegroundColorSpan,BackgroundColorSpan
.BulletSpan
,继承自LeadingMarginSpan
ClickableSpan
AbsoluteSizeSpan
具体其继承结构可通过查看其Hierarchy
扩展阅读:
上面的中文译文: Spans,一个强大的概念
Android Span 架构介绍:介绍了多种Spans
的用法.
当你给一个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
如同自定义View
一样.可以继承已有的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
是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
上移即可
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();
}
}
扩展阅读
Android中测量Text的宽度和高度
用TextPaint来绘制文字
Android自定义View使用canvas绘制文字实现居中、自动换行