发布于 

Android图文混排初窥

背景

在使用textview时,常常会有文本混排的需求(一段文本中具有多个样式),如标题开头的标签,聊天消息中的表情等,考虑到不同尺寸屏幕的适配问题,简单粗暴地使用多个textView并不能满足要求。

解决方案

Android官方对文本/图文混排提供了支持:

  1. 在TextView的XML布局文件中添加Compound Drawable属性;
  2. 在对TextView设置字符串时,可以设置Html类型的字符串。Html.fromHtml()方法可以对Html的字符串进行处理,从而使得Html类型的内容满足TextView的要求。在给TextView设置Html类型的内容时,还可以传入一个ImageGetter,从而对Html类型内容中的图片进行处理;
  3. 对TextView设置内容的时候,可以传入CharSequence类型,而一些CharSequence类型可以利用CharacterStyle进行修饰,从而展现出丰富多彩的内容。CharacterStyle拥有很多子类(BackgroundColorSpan,ClickableSpan,ImageSpan,TypefaceSpan等),可以产生出各种各样的效果。

这里主要就最复杂也最强大的第三种方式展开讨论。

CharSequence & Span

在讲述CharSeqeuence之前,有必要先明确一点,TextView能利用上述第三种方式实现文本混排的前提是String实现了CharSequence接口,因此支持利用CharSequence的特性进行修饰定制。

Span的命名乍看之下可能有些令人费解,跨度/区间——很难和文本联系起来,更不用说猜想它的作用了:(支持丰富样式的文本)区间。

使用Span方式实现TextView图文混排的整体流程是:

  1. 创建一个SpannableString或者SpannableStringBuilder对象;
  2. 利用setSpan(Object what, int start, int end, int flags)方法,将SpannableString或者SpannableStringBuilder对象的某些位置的内容替换为具体类型的Span;
  3. 利用TextView的setText(CharSequence text)方法将SpannableString或者SpannableStringBuilder对象进行展示。

其中SpannableString和SpannableStringBuilder这两个类实现了Spannable接口,实现了接口里面定义的方法。

setSpan中中what通常指各种类型的span(ImageSpan、URLSpan、ClickableSpan等),不展开赘述,字面意义上看是支持image,url,clickable的span样式。

span除了从支持的特性划分,还可以从影响到的范围来划分:

如果一个Span影响字符级的文本格式,则继承CharacterStyle;

如果一个Span影响段落层次的文本格式,则实现ParagraphStyle;

如果一个Span修改字符级别的文本外观,则实现UpdateAppearance;

如果一个Span修改字符级文本度量|大小,则实现UpdateLayout。

段落级Span样式:ParagraphStyle

主要实现有以下几种:

  • LeadingMarginSpan:用来处理像word中项目符号一样的接口;
  • AlignmentSpan:用来处理整个段落对其的接口;
  • LineBackgroundSpan:用来处理一行的背景的接口;
  • LineHeightSpan:用来处理一行高度的接口;
  • TabStopSpan:用来将字符串中的”\t”替换成相应的空行

先讲解ParagraphStyle实现中较为简单的LeadingMarginSpan:

该实现主要有以下两个接口方法,可用来控制文本段落偏移,并在偏移的一侧实现一些效果绘制(如列表项开头的小圆点 )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* Returns the amount by which to adjust the leading margin. Positive values
* move away from the leading edge of the paragraph, negative values move
* towards it.
*
* @param first true if the request is for the first line of a paragraph,
* false for subsequent lines
* @return the offset for the margin.
*/
public int getLeadingMargin(boolean first);

/**
* Renders the leading margin. This is called before the margin has been
* adjusted by the value returned by {@link #getLeadingMargin(boolean)}.
*
* @param c the canvas
* @param p the paint. The this should be left unchanged on exit.
* @param x the current position of the margin
* @param dir the base direction of the paragraph; if negative, the margin
* is to the right of the text, otherwise it is to the left.
* @param top the top of the line
* @param baseline the baseline of the line
* @param bottom the bottom of the line
* @param text the text
* @param start the start of the line
* @param end the end of the line
* @param first true if this is the first line of its paragraph
* @param layout the layout containing this line
*/
public void drawLeadingMargin(Canvas c, Paint p,
int x, int dir,
int top, int baseline, int bottom,
CharSequence text, int start, int end,
boolean first, Layout layout);

其中比较重要的一个概念是排版印刷学科中的基线:即字母排列的基准线

字体中,字母向下延伸超过基线的笔画部分,称为降部。

相对的,字体的字母中向上超过主线笔画的部分,也就是比x字高还要高的部分,称为升部。

以下是引自wiki的基线准则

  • 大寫字母位於基线上。最常见的例外是J和Q。
  • 不齊線數字(見阿拉伯數字)位於基线上。
  • 以下不齊線數字有降部:3 4 5 7 9。
  • 以下小寫字母有降部:g j p q y。
  • 有着圓形上下區段的字符,如(0 3 6 8 c C G J o O Q),它們比基線略微有所下沉(overshoot)來造成了一種它們坐落於基線以上的光學幻覺,通過比X字高大寫高度略高來製造它們和flat glyphs如(H x X 1 5 7)同樣高度的錯覺。Peter Karowand的Digital Typefaces中建議,標準的overshoot應當在1.5%左右。

可以将其简单的理解为文字的“重心”位置。

抛砖引玉

准备这次分享所学习到的内容,其实仅仅是帮助我踏入了Android文本特效的大门,因为临近期末,个人时间安排方面的一些原因,还有很多方面没有涉及到,后续有机会再总结。

以下为本次分享依赖的一些参考:

Textview图文基础

精通Span 轻松玩转各种文本特效

FontMetrics简述


本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

本站由 @tsparrot 创建,使用 Stellar 作为主题。