本文为转载&地址: https://github.com/itgoyo/AndroidSource-Analysis
简介: Android源码分析,让你更清楚的理解每一个组件的功能与用法。
TextView源码解析
1.简介
TextView作为Android系统上显示和排版文字以及提供对文字的增删改查、图文混排等功能的控件,内部是相对比较复杂的。这么一个复杂的控件自然需要依赖于一些其他的辅助类,例如:Layout以及Layout的相关子类、Span相关的类、MovementMethod接口、TransformationMethod接口等。这篇文章主要介绍TextView的结构和内部处理文字的流程以及TextView相关的辅助类在TextView处理文字过程中的作用。
2.TextView的内部结构和辅助类
TextView内部除了继承自View的相关属性和measure、layout、draw步骤,还包括:
- Layout: TextView的文字排版、折行策略以及文本绘制都是在Layout里面完成的,TextView的自身测量也受Layout的影响。Layout是TextView执行setText方法后,由TextView内部创建的实例,并不能由外部提供。可以用getLayout()方法获取。
- TransformationMethod: 用来处理最终的显示结果的类,例如显示密码的时候把密码转换成圆点。这个类并不直接影响TextView内部储存的Text,只影响显示的结果。
- MovementMethod: 用来处理TextView内部事件响应的类,可以针对TextView内文本的某一个区域做软键盘输入或者触摸事件的响应。
- Drawables: TextView的静态内部类,用来处理和储存TextView的CompoundDrawables,包括TextView的上下左右的Drawable以及错误提示的Drawable。
- Spans: Spans并不是特定的某一个类或者实现了某一个接口的类。它可以是任意类型,Spans实际上做的事情是在TextView的内部的text的某一个区域做标记。其中有部分Spans可以影响TextView的绘制和测量,如ImageSpan、BackgroundColorSpan、AbsoluteSizeSpan。还有可以响应点击事件的ClickableSpan。
- Editor: TextView作为可编辑文本控件的时候(EditText),使用Editor来处理文本的区域选择处理和判断、拼写检查、弹出文本菜单等。
- InputConnection: EditText的文本输入部分是在TextView中完成的。而InputConnection是软键盘和TextView之间的桥梁,所有的软键盘的输入文字、修改文字和删除文字都是通过InputConnection传递给TextView的。
3.TextView的onTouchEvent处理
TextView内部能处理触摸事件的,包括自身的触摸处理、Editor的onTouchEvent、MovementMethod的onTouchEvent。Editor的onTouchEvent主要处理出于编辑状态下的触摸事件,比如点击选中、长按等。MovementMethod则主要负责文本内部有Span的时候的相关处理,比较常见的就是LinkMovementMethod处理ClickableSpan的点击事件。我们来看一下TextView内部对这些触摸事件的处理和优先级的分配:
|
|
4.TextView的创建Layout的过程
TextView内部并不仅仅只有一个用来显示文本内容的Layout,在设置了hint的时候,还需要有一个mHintLayout来处理hint的内容。如果设置了Ellipsize类型为Marquee时,还会有一个mSavedMarqueeModeLayout专门用来显示marquee效果。这些Layout都是通过内部的makeNewLayout方法来创建的:
|
|
TextView的布局创建过程涉及到一个boring的概念,boring是指布局所用的文本里面不包含任何Span,所有的文本方向都是从左到右的布局,并且仅需一行就能显示完全的布局。这种情况下,TextView会使用BoringLayout类来创建相关的布局,以节省不必要的文本测量以及文本折行、Span宽度、文本方向等的计算。下面我们来看一下makeNewLayout中使用频率比较高的makeSingleLayout的代码:
|
|
5.TextView的文字处理和绘制
TextView主要的文字排版和渲染并不是在TextView里面完成的,而是由Layout类来处理文字排版工作。在单纯地使用TextView来展示静态文本的时候,这件事情则是由Layout的子类StaticLayout来完成的。
StaticLayout接收到字符串后,首先做的事情是根据字符串里面的换行符对字符串进行拆分。
|
|
拆分后的段落(Paragraph)被分配给辅助类MeasuredText进行测量得到每个字符的宽度以及每个段落的FontMetric。并通过LineBreaker进行折行的判断
|
|
//把相关属性传给JNI层的LineBreaker
nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart,
firstWidth, firstWidthLineCount, restWidth,
variableTabStops, TAB_INCREMENT, b.mBreakStrategy, b.mHyphenationFrequency);
|
|
计算完每一行的测量相关信息、Span宽高以及折行位置,就可以开始按照最终的行数一行一行地保存下来,以供后面绘制和获取对应文本信息的时候使用。
|
|
这样StaticLayout的排版过程就完成了。文本的绘制则是交给父类Layout来做的,Layout的绘制分为两大部分,drawBackground和drawText。drawBackground做的事情是如果文本内有LineBackgroundSpan则绘制所有的LineBackgroundSpan,然后判断是否有高亮背景(文本选中的背景),如果有则绘制高亮背景。
|
|
drawText用来逐行绘制Layout的文本、影响显示效果的Span、以及Emoji表情等。当有Emoji或者Span的时候,实际绘制工作交给TextLine类来完成。
|
|
我们下面再来看看TextLine是如何绘制有特殊情况的文本的
|
|
这样就完成了文本的绘制工作,简单地总结就是:分析整体文本—>拆分为段落—>计算整体段落的文本包括Span的测量信息—>对文本进行折行—>根据最终行数把文本测量信息保存—>绘制文本的行背景—>判断并获取文本种的Span和Emoji图像—>绘制最终的文本和图像。当然我们省略了一部分内容,比如段落文本方向,单行的文本排版方向的计算,实际的处理要更为复杂。
接下来我们来看一下在测量过程中出现的FontMetrics,这是一个Paint的静态内部类。主要用来储存文字排版的Y轴相关信息。内部仅包含ascent、descent、top、bottom、leading五个数值。如下图:
除了leading以外,其他的数值都是相对于每一行的baseline的,也就是说其他的数值需要加上对应行的baseline才能得到最终真实的坐标。
6.TextView接收软键盘输入
Android上的标准文本编辑控件是EditText,而EditText对软键盘输入的处理,却是在TextView内部实现的。Android为所有的View预留了一个接收软键盘输入的接口类,叫InputConnection。软键盘以InputConnection为桥梁把文字输入、文字修改、文字删除等传递给View。任意View只要重写onCheckIsTextEditor()并返回true,然后重写onCreateInputConnection(EditorInfo outAttrs)返回一个InputConnection的实例,便可以接收软键盘的输入。TextView的软键盘输入接收,是通过EditableInputConnection类来实现的。
|
|
我们再来看一下EditableInputConnection里面的几个主要的方法:
首先是commitText方法,这个方法接收输入法输入的字符并提交给TextView。
|
|
getEditable方法,这个方法并不是InputConnection接口的一部分,而是EditableInputConnection的父类BaseInputConnection的方法,用来获取一个可编辑对象,EditableInputConnection里面的所有修改都针对这个可编辑对象来做。
|
|
deleteSurroundingText方法,这个方法用来删除光标前后的内容:
|
|
commitCompletion和commitCorrection方法,即是用来补全单词和修正错别字的方法,这两个方法内部都是调用TextView对应的方法来实现的。
|
|
8.总结
一个展示文本+文本编辑器功能的控件需要做的事情很多,要对文本进行排版、处理不同的段落风格、处理段落内的不同emoji和span、进行折行计算,然后还需要做文本编辑、文本选择等。而TextView把这些事情明确分工给不同的类。这样不仅仅把复杂问题拆分成了一个个简单的小功能,同时也大大增加了可扩展性。
发现文章,代码有误、对内容有疑问,请在Issue上面提单,技术讨论可以给我Email,欢迎关注左边微信公众号或加QQ群获取更多信息。