TextView 的新特性,Autosizing 到底是如何实现的? | 源码分析
一、前言
Hi,大家好,我是承香墨影!
前两天聊了一下 Autosizing 的使用,反映还不错。毕竟是这种能解决实际问题的新 Api,确实在需要的时候,用起来会很顺手。
简单回顾一下,Autosizing 是在 Support v26 中新支持的功能,可以根据文本的内容和 TextView 的大小,自动适应齐内部文本的字体大小,来达到完全显示的效果。而这个功能,最低能兼容到 Api Level 14,可以说是一个诚意满满的新 Api。
还不了解 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。
左边是 Android 8.0 的 TextView ,右边是 AppCompatTextViewAutoSizeHelper。
可以看到,整个代码的结构都是一致的,只是部分引用的类不一样而已,但是表达的意思是一致的。
之所以说它们之前大致是一样的,是因为有一些 Api 是 private 的或者被标记为 @hide 了,这样,在外部是无法访问到的。对此 AppCompatTextViewAutoSizeHelper 的做法是用反射的形式去调用它。
例如,实际去修改 TextView 尺寸的方法 autoSizeText() ,看下面它们的区别。
左边是 Android 8.0 的 TextView ,右边是 AppCompatTextViewAutoSizeHelper。
两边都需要获取 mHorizontallyScrolling 的值,TextView 内部当然可以直接调用了,而 AppCompatTextViewAutoSizeHelper 的做法是,使用 invokeAndReturnWithDefault() 方法,通过反射区获取这个值。
所以,我们可以得出结论,两边的实现思路,大体上是没有区别的,只是有一些小细节,会不一样,但是我们不需要太在意这些。
既然,两边的区别不大,之后我们就以 Support v26 中,关于 Autosizing 的源码实现来进行分析。
3.2 触发 Autosizing 的时机
首先这个时机让我自己来设计,也非常好理解。
本质上 Autosizing 就是为了让 TextView 中的文本,能完全显示,在这个过程中会去调整 文本 的字体大小。
那这样,触发它的时机,其实就很容易猜到了:
- 在 文本内容 变动的时候。
- 在 TextView 大小变动的时候。
Support v26 中,之所以能保证兼容,本质上它会自动将 TextView 这样的控件,替换成 AppCompatTextView 来达到兼容的效果,这个过程中,开发者只需要使用 AppCompatActivity 就可以了,其它的不需要开发者来参与。这样,其实我们只需要关注 AppCompatTextView 中的实现逻辑就好了。
前面提到,操作 Autosizing 的具体源码,在 AppCompatTextViewAutoSizeHelper 中。 而 AppCompatTextView 并不直接操作它。
首先 AppCompatTextVIew 会持有 AppCompatTextHelper 这个帮助类,而这个帮助类,又去持有 AppCompatTextViewAutoSizeHelper,最终所有的逻辑都传递到 AppCompatTextViewAutoSizeHelper 中去处理。
所以操作的流程大概是这样的:
而 Autosizing 真实去测量并修改字体大小的逻辑,都在 autoSizeText() 方法中,我们只需要关心它在何时被调用,就能知道具体触发 Autosizing 的时机了。
第一个触发点,它会在 AppCompatTextView 的 onTextChanged() 方法中,直接调用 autoSizeText() 方法。
第二个触发点,会监听 AppCompatTextView 的 onLayout() 方法,在其中调用 AppCompatTextHelper 的 onLayout() 方法。
好了,两个时机都找到了,也验证了我们之前的猜想。
3.3 Autosizing 如何计算大小
前面提到 Autosizing 实际上去修改 TextView 字体的方法,在 AppCompatTextViewAutoSizeHelper 的 autoSizeText() 方法中,这里我们先来看看这个方法的实现。
这一段逻辑,就是 Autosizing 中,很重要的一个逻辑。先来看看它大体上的流程。
- 使用它会使用
isAutoSizeEnabled()方法,判断当前是否开启 Autosizing 。 - 判断
mNeedsAutoSizeText是否为 true,此处判断是主要是看是否存在可变动的尺寸。 - 计算 TextView 本身的显示区域大小,存放在
TEMP_RECTF中。 - 使用
findLargestTextSizeWhichFits()获取到一个合适当前文本长度的最大尺寸值。 - 如果和当前 TextView 的 textSize 不一致,则使用
setTextSizeInternal()将其设置回去。
大体步骤就是这样,接下来我们从细节出发看看它的具体实现。
首先是 isAutoSizeEnable() 方法,它去判断当前是否开启了 Autosizing,其实就是判断 mAutoSizeTextType 属性是否为 none。
而 mNeedsAutoSizeText 这个判断,本质上其实是为了判断 mAutoSizeTextSizesInPx 这个存放尺寸的数组里,是否有值,这个尺寸数组,在后面的 findLargestTextSizeWhichFits() 方法中会用到。
mAutoSizeTextSizesInPx 其实就是一个存放当前 TextView 预估能使用的尺寸数组,是被提前计算出来的,它会在对 Autosizing 受影响的相关的属性做出修改的时候,重新计算。例如:粒度(Granularity)、预设尺寸(PresetSizes)等变动,都会触发重新计算 mAutoSizeTextSizesInPx 的值。
TEMP_RECTF 就没有什么好说的了,无非就是从 TextView 的宽高和 Padding 等属性,计算出一个能用于显示 文本 区域大小。接下来就会去调用 findLargestTextSizeWhichFits() 方法,找到一个当前 文本 内容,最合适的字体大小。
这里逻辑也很清晰,就是使用一个循环,通过 suggestedSizeFitsInSpace() 方法判断取出来的尺寸是否合适。这里为了提高效率,使用了二分算法,去避免全部遍历 mAutoSizeTextSizesInPx 数组,从而提高效率。
接下来就是 suggestedSizeFitsInSpace() 方法,它会根据 TextView 的内容区域和 文本,判断当前给定的尺寸,是否能放的下这些内容。
这里首先使用了一个 TextPaint 对象 mTempTextPaint 来存放 TextView 的一些参数,然后根据 mTempTextPaint 去创建一个使用 StaticLayout 对象,来尝试对文本进行布局。
StaticLayout 是一个为不可编辑的文本布局的类,这意味着一旦布局完成,文本内容就不可以改变。
最终,就能确定,传递进行的字体大小,是否能完全显示在这个区域内。
经过这一通计算,findLargestTextSizeWhichFits() 方法,最终将计算出来的一个合适的字体尺寸,返回回去,再通过 setTextSizeInternal() 设置到 TextView,来达到修改字体大小的目的。
四、源码中能借鉴的功能
现在来看,Autosizing 计算某段文本,在一个 固定的 TextView 中,将展示的单行宽度和行数这个功能,这些算是 Autosizing 中,比较有借鉴意义的功能了。
其它的我暂时没有想到,你觉得还有什么可以借鉴的点呢?在留言中告诉我。
今天在承香墨影公众号的后台,回复『成长』,我会送你一些特别的内容。
我另外还维护了一个技术交流的微信群,有兴趣可以在公众号后台回复:"加群"
推荐阅读:
- 站在Android开发的角度,聊聊Airbnb的Lottie
- 这些工具,让你写博客的时候,只需要专注写作!
- 找了一天找不到 Bug ? 试试 Git 的二分法吧!!!
- 如何更精准的在 Github 上搜索开源库?你需要这些技巧!
- Android 开发,遇上 Emoji 头疼吗?
TextView 的新特性,Autosizing 到底是如何实现的? | 源码分析的更多相关文章
- Java日期时间API系列8-----Jdk8中java.time包中的新的日期时间API类的LocalDate源码分析
目录 0.前言 1.TemporalAccessor源码 2.Temporal源码 3.TemporalAdjuster源码 4.ChronoLocalDate源码 5.LocalDate源码 6.总 ...
- Spring 3.1新特性之二:@Enable*注解的源码,spring源码分析之定时任务Scheduled注解
分析SpringBoot的自动化配置原理的时候,可以观察下这些@Enable*注解的源码,可以发现所有的注解都有一个@Import注解.@Import注解是用来导入配置类的,这也就是说这些自动开启的实 ...
- jQuery 源码分析(十二) 数据操作模块 html特性 详解
jQuery的属性操作模块总共有4个部分,本篇说一下第1个部分:HTML特性部分,html特性部分是对原生方法getAttribute()和setAttribute()的封装,用于修改DOM元素的特性 ...
- Android源码分析(十二)-----Android源码中如何自定义TextView实现滚动效果
一:如何自定义TextView实现滚动效果 继承TextView基类 重写构造方法 修改isFocused()方法,获取焦点. /* * Copyright (C) 2015 The Android ...
- 鸿蒙内核源码分析(CPU篇) | 整个内核就是一个死循环 | 祝新的一年牛气冲天 ! | v32.02
百篇博客系列篇.本篇为: v32.xx 鸿蒙内核源码分析(CPU篇) | 整个内核就是一个死循环 | 51.c.h .o 任务管理相关篇为: v03.xx 鸿蒙内核源码分析(时钟任务篇) | 触发调度 ...
- 鸿蒙内核源码分析(内存规则篇) | 内存管理到底在管什么 | 百篇博客分析OpenHarmony源码 | v16.02
百篇博客系列篇.本篇为: v16.xx 鸿蒙内核源码分析(内存规则篇) | 内存管理到底在管什么 | 51.c.h .o 内存管理相关篇为: v11.xx 鸿蒙内核源码分析(内存分配篇) | 内存有哪 ...
- v77.01 鸿蒙内核源码分析(消息封装篇) | 剖析LiteIpc(上)进程通讯内容 | 新的一年祝大家生龙活虎 虎虎生威
百篇博客分析|本篇为:(消息封装篇) | 剖析LiteIpc进程通讯内容 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析(互斥锁 ...
- 文字太多?控件太小?试试 TextView 的新特性 Autosizeing 吧!
Hi,大家好,我是承香墨影! Android 8.0 已经发布了有一阵子了,如果你有在关注它,你应该会知道它新增了一个对于 TextView 字体大小变动的新特性:Autosizing. 本身这个新特 ...
- ActiveReports 报表控件V12新特性 -- 无需ETL处理,即可实现跨数据源分析数据
ActiveReports是一款专注于 .NET 平台的报表控件,全面满足 HTML5 / WinForms / ASP.NET / ASP.NET MVC / WPF 等平台下报表设计和开发工作需求 ...
随机推荐
- windows远程桌面连接的时候不显示本地盘符
近期远程异地pc机部署项目,远程连上后不显示本地盘符,勾选驱动器也无效,试下例如以下方法 在远程主机的文件地址栏里面键入: \\tsclient\D 后面再加入上对应的盘符,你的盘符的名称是什么盘就加 ...
- Oracle集合操作
在Oracle中提供了三种类型的集合操作: 并(UNION).交(INTERSECT).差(MINUS) UNION:将多个查询的结果组合到一个查询结果之中,并去掉反复值 UNION ALL:将多个查 ...
- Vue深度学习(6)- 组件
使用组件 在Vue中,可以用 Vue.extend() 创建一个组件构造器: var MyComponent = Vue.extend({ template:'..........' //选项 }) ...
- 基于Metronic的Bootstrap开发框架经验总结(18)-- 在代码生成工具Database2Sharp中集成对Bootstrap-table插件的分页及排序支持
在我们开发系统界面,包括Web和Winform的都一样,主要的界面就是列表展示主界面,编辑查看界面,以及一些辅助性的如导入界面,选择界面等,其中列表展示主界面是综合性的数据展示界面,一般往往需要对记录 ...
- 十三、 Spring Boot 启动加载数据 CommandLineRunner
实际应用中,我们会有在项目服务启动的时候就去加载一些数据或做一些事情这样的需求. 为了解决这样的问题,spring Boot 为我们提供了一个方法,通过实现接口 CommandLineRunner 来 ...
- 个人作业2:APP案例分析
产品 产品名 网易云音乐 选择原因 除社交软件和浏览器以外,在手机里存在最久的也是使用次数最多的APP就是它了.不管换多少次手机和电脑,它始终在我的装机必备名单上. 调研与评测 第一次上手体验 第一次 ...
- Zabbix安装之路
这次的教程多半是搬运过来的,但都经过小轩亲自测试与修改了.文章最后将公布原资源地址.此篇算是整合,但又不全是整合. 依旧需求开篇:上头让小轩监控一下服务器的情况,在前几篇也有所提到.于是小轩就到处去找 ...
- HTTP之URL分解
HTTP使用统一资源标识符(Uniform Resource Identifiers, URI)来传输数据和建立连接.URL是一种特殊类型的URI,包含了用于查找某个资源的足够的信息 URL,全称是U ...
- bzoj 4569: [Scoi2016]萌萌哒
Description 一个长度为n的大数,用S1S2S3...Sn表示,其中Si表示数的第i位,S1是数的最高位,告诉你一些限制条件,每个条 件表示为四个数,l1,r1,l2,r2,即两个长度相同的 ...
- 伽罗瓦域(有限域)GFq^12上元素的1→2→4→12塔式扩张(2)------第二次扩张
接上文https://www.cnblogs.com/heshuchao/p/8196307.html 继续探讨塔式扩张的第二部分,即1→2→4→12中2 → 4的元素扩张表示方式与计算公式推导. 3 ...