一、前言
Hi,大家好,我是承香墨影!
前两天聊了一下 Autosizing 的使用,反映还不错。毕竟是这种能解决实际问题的新 Api,确实在需要的时候,用起来会很顺手。
简单回顾一下,Autosizing 是在 Support v26 中新支持的功能,可以根据文本的内容和 TextView 的大小,自动适应齐内部文本的字体大小,来达到完全显示的效果。而这个功能,最低能兼容到 Api Level 14,可以说是一个诚意满满的新 Api。
![160fa489d9ee5939?w=386&h=160&f=gif&s=567]()
还不了解 Autosizing 的朋友,可以看看之前的文章《文字太多?控件太小?试试 TextView 的新特性 Autosizeing 吧!》,里面有使用它的详细介绍。
我想,在没有 Autosizing 的时候,应该已经有人以这样的思路在实现功能了。那么,今天就来从源码的角度分析一下,Autosizing 的原理如何,看看它是如何工作的。
二、带着问题看源码
分析源码也是讲究方式方法的,我主推的一个思路,就是带着问题看源码。
很多大型项目,其实本身都是很复杂的,并且涵盖的功能点也非常的多,如果想要一次就把它完整的阅读屡清楚,还是很吃力的。
所以我建议在阅读开源项目之前,你先阅读文档,尝试使用一下它,看看它能做什么,再自己思考一下,如果你是作者,你会如何去实现这些功能的,最后带着这些问题去阅读源码,以问题为出发点,看看那些大牛写的优秀的开源库,到底有什么值得我们借鉴的地方。
总归一句话:阅读源码是为了更好的编写源码!
当我看到到 Autosizing 这个新特性的时候,我有一些好奇的地方在于:
- Android 8.0 的 TextView 和 Support 包中,Autosizing 的实现,有什么区别?
- Autosizing 是会在什么时机,去触发根据文本的内容,计算出一个适合的字体大小。
- Autosizing 是如何计算合适的字体大小的。
- 脱离 Autosizing,源码中的功能,有什么能借鉴的使用场景。
大概就是这些问题吧,接下来我们看看 Autosizing 是如何实现的。
三、Autosizing 源码
3.1 实现的区别
对于 Android 8.0 中和 Support v26 中,具体对于 Autosizing 的实现,有什么区别这一点,大致阅读一下两边的源码,你会发现大致上没区别。
它们之间,和 Autosizing 相关的源码所在的源码文件也不一样:
- Android 8.0 主要在 TextView 中。
- Support v26 主要在 AppCompatTextViewAutoSizeHelper。
随手比对一下它们的 setAutoSizeTextTypeWithDefaults() 方法,这个方法用来标记是否对 TextView 开启 Autosizing。
![setAutoSizeDefaultDiff]()
左边是 Android 8.0 的 TextView ,右边是 AppCompatTextViewAutoSizeHelper。
可以看到,整个代码的结构都是一致的,只是部分引用的类不一样而已,但是表达的意思是一致的。
之所以说它们之前大致是一样的,是因为有一些 Api 是 private 的或者被标记为 @hide 了,这样,在外部是无法访问到的。对此 AppCompatTextViewAutoSizeHelper 的做法是用反射的形式去调用它。
例如,实际去修改 TextView 尺寸的方法 autoSizeText() ,看下面它们的区别。
![autoSizeDiff]()
左边是 Android 8.0 的 TextView ,右边是 AppCompatTextViewAutoSizeHelper。
两边都需要获取 mHorizontallyScrolling 的值,TextView 内部当然可以直接调用了,而 AppCompatTextViewAutoSizeHelper 的做法是,使用 invokeAndReturnWithDefault() 方法,通过反射区获取这个值。
![invokeMethod]()
所以,我们可以得出结论,两边的实现思路,大体上是没有区别的,只是有一些小细节,会不一样,但是我们不需要太在意这些。
既然,两边的区别不大,之后我们就以 Support v26 中,关于 Autosizing 的源码实现来进行分析。
3.2 触发 Autosizing 的时机
首先这个时机让我自己来设计,也非常好理解。
本质上 Autosizing 就是为了让 TextView 中的文本,能完全显示,在这个过程中会去调整 文本 的字体大小。
那这样,触发它的时机,其实就很容易猜到了:
- 在 文本内容 变动的时候。
- 在 TextView 大小变动的时候。
Support v26 中,之所以能保证兼容,本质上它会自动将 TextView 这样的控件,替换成 AppCompatTextView 来达到兼容的效果,这个过程中,开发者只需要使用 AppCompatActivity 就可以了,其它的不需要开发者来参与。这样,其实我们只需要关注 AppCompatTextView 中的实现逻辑就好了。
前面提到,操作 Autosizing 的具体源码,在 AppCompatTextViewAutoSizeHelper 中。 而 AppCompatTextView 并不直接操作它。
首先 AppCompatTextVIew 会持有 AppCompatTextHelper 这个帮助类,而这个帮助类,又去持有 AppCompatTextViewAutoSizeHelper,最终所有的逻辑都传递到 AppCompatTextViewAutoSizeHelper 中去处理。
所以操作的流程大概是这样的:
![stream]()
而 Autosizing 真实去测量并修改字体大小的逻辑,都在 autoSizeText() 方法中,我们只需要关心它在何时被调用,就能知道具体触发 Autosizing 的时机了。
第一个触发点,它会在 AppCompatTextView 的 onTextChanged() 方法中,直接调用 autoSizeText() 方法。
![AppCompatAutoTextSize]()
第二个触发点,会监听 AppCompatTextView 的 onLayout() 方法,在其中调用 AppCompatTextHelper 的 onLayout() 方法。
![helper_onLayout]()
好了,两个时机都找到了,也验证了我们之前的猜想。
3.3 Autosizing 如何计算大小
前面提到 Autosizing 实际上去修改 TextView 字体的方法,在 AppCompatTextViewAutoSizeHelper 的 autoSizeText() 方法中,这里我们先来看看这个方法的实现。
![autoSizeText]()
这一段逻辑,就是 Autosizing 中,很重要的一个逻辑。先来看看它大体上的流程。
- 使用它会使用
isAutoSizeEnabled() 方法,判断当前是否开启 Autosizing 。
- 判断
mNeedsAutoSizeText 是否为 true,此处判断是主要是看是否存在可变动的尺寸。
- 计算 TextView 本身的显示区域大小,存放在
TEMP_RECTF 中。
- 使用
findLargestTextSizeWhichFits() 获取到一个合适当前文本长度的最大尺寸值。
- 如果和当前 TextView 的 textSize 不一致,则使用
setTextSizeInternal() 将其设置回去。
大体步骤就是这样,接下来我们从细节出发看看它的具体实现。
首先是 isAutoSizeEnable() 方法,它去判断当前是否开启了 Autosizing,其实就是判断 mAutoSizeTextType 属性是否为 none。
![isAutoSizeEnable]()
而 mNeedsAutoSizeText 这个判断,本质上其实是为了判断 mAutoSizeTextSizesInPx 这个存放尺寸的数组里,是否有值,这个尺寸数组,在后面的 findLargestTextSizeWhichFits() 方法中会用到。
![px-array]()
mAutoSizeTextSizesInPx 其实就是一个存放当前 TextView 预估能使用的尺寸数组,是被提前计算出来的,它会在对 Autosizing 受影响的相关的属性做出修改的时候,重新计算。例如:粒度(Granularity)、预设尺寸(PresetSizes)等变动,都会触发重新计算 mAutoSizeTextSizesInPx 的值。
TEMP_RECTF 就没有什么好说的了,无非就是从 TextView 的宽高和 Padding 等属性,计算出一个能用于显示 文本 区域大小。接下来就会去调用 findLargestTextSizeWhichFits() 方法,找到一个当前 文本 内容,最合适的字体大小。
![findLargestMethod]()
这里逻辑也很清晰,就是使用一个循环,通过 suggestedSizeFitsInSpace() 方法判断取出来的尺寸是否合适。这里为了提高效率,使用了二分算法,去避免全部遍历 mAutoSizeTextSizesInPx 数组,从而提高效率。
接下来就是 suggestedSizeFitsInSpace() 方法,它会根据 TextView 的内容区域和 文本,判断当前给定的尺寸,是否能放的下这些内容。
![suggestedSizeMethod]()
这里首先使用了一个 TextPaint 对象 mTempTextPaint 来存放 TextView 的一些参数,然后根据 mTempTextPaint 去创建一个使用 StaticLayout 对象,来尝试对文本进行布局。
StaticLayout 是一个为不可编辑的文本布局的类,这意味着一旦布局完成,文本内容就不可以改变。
最终,就能确定,传递进行的字体大小,是否能完全显示在这个区域内。
经过这一通计算,findLargestTextSizeWhichFits() 方法,最终将计算出来的一个合适的字体尺寸,返回回去,再通过 setTextSizeInternal() 设置到 TextView,来达到修改字体大小的目的。
本文转自承香墨影博客园博客,原文链接:http://www.cnblogs.com/plokmju/p/8290394.html,如需转载请自行联系原作者