更多问答 >>
-
每日一问 匿名内部类访问的外部类局部变量为什么要用final 修饰,jdk8为啥不需要了? 2/3
2019-08-08 23:51 -
每日一问 事件分发机制大家应该都熟记于心,默认事件分发是逆序的,有哪些方法可以修改分发顺序?
2019-08-11 21:18 -
2019-08-12 09:58
-
每日一问 Android 有哪些 位运算 的例子很值得借鉴?
2019-08-13 21:56 -
2019-08-18 21:42
-
每日一问 大家应该都有泛型在编译期会被擦除的概念,那么为什么我们在运行时还能读取到呢? 3/3
2019-08-04 19:44 -
2019-08-01 21:44
-
每日一问 很多时候我们说"Android16.6ms刷新一次屏幕" 正确吗?
2019-07-30 21:53 -
每日一问 听说过Handler中的IdleHandler吗?
2019-07-25 21:09
compat库是如何将TextView替换为AppCompatTextVew的?
TextView在运行时被替换成AppCompatTextVew的前提是:该Activity必须继承自AppCompatActivity。
它是怎么替换的呢?我们给Activity设置布局一般会使用setContentView方法,打开AppCompatActivity,可以看到它已经重写了这个方法:调用的是getDelegate方法返回的对象的setContentView方法。
getDelegate方法返回的是一个AppCompatDelegate对象,这个AppCompatDelegate是一个抽象类,由AppCompatDelegateImpl去实现,也就是说:getDelegate方法最终返回的是AppCompatDelegateImpl的实例。那现在来看看它的setContentView方法是怎么实现的:
乍一看好像没什么特别的,就是把我们传进去的布局ID,给inflate出来并且把它添加到contentParent上而已。
回到AppCompatActivity那边,看看它的onCreate方法:
点进去:
可以看到AppCompatDelegateImpl在这个方法中会给LayoutInflater设置一个Factory2,并且传的是this,说明它是实现了Factory2的。
我们知道,当LayoutInflater在inflate布局的时候,会优先调用Factory2的onCreateView方法那现在来看看AppCompatDelegate的onCreateView方法:可以看到它把createView的工作交给了AppCompatViewInflater,来看看它是怎么实现的:
emmm,在这个方法中,会把常用的View(ViewGroup除外)替换成对应的AppCompat开头的View,除了TextView,还有ImageView、EditText、SeekBar等等。
为什么要替换成AppCompat开头的一系列View呢?
我们来观察一下各个AppCompat开头的组件,可以发现他们的共同点:都实现了一个叫TintableBackgroundView的接口。
看看它里面有哪些方法:那现在可以大概猜到,替换成AppCompat系列的,就是为了能够让旧版本(5.0以下)能够兼容一个叫BackgroundTint的东西,中文翻译为 背景着色。
这个东西有什么作用呢?看看这张图就懂了:根据替换相关原理,还可以做哪些事情?
最最广为人知的,就是主题替换了。
还可以通过 “替换”,来做出动态控制View属性的效果,这也跟主题替换差不多,但相比于一般的图片,颜色,背景替换,我们还可以做出更加惊喜的效果。镜头回到 AppCompatActivity的setContentView,这个函数其实调用的是 AppCompatDelegateImpl的 setContentView,然后这个函数有这么一行 常见的代码
但是我们不能放过他,继续深入,看到一行代码,注释说 界面时从 xml来的,有点可疑
继续看createViewFromTag,之前 AppCompatDelegateImpl 又把自己 赋值给mFactory2,以下代码会执行他的onCreateView,
镜头再给到 AppCompatDelegateImpl的createView ,最后这个函数执行了 AppCompatViewInflater 的 createView函数,有这么一段代码
大家差不多,看出来,这个些代码把 xml解析出的View,如果符合条件,替换相应的 AppCompat开头的视图
2。我觉得是谷歌开发者不想让 旧项目 把 控件一个个 加个AppCompat开头 ,为了省功夫。
3.我们参考这样的机制可以 替换我们一些自己写的 旧控件,比如 一个远程依赖升级了,但是包名变了,我们使用了这个旧依赖的视图,我们就是全局替换,省功夫
我们要自定义替换 要复写 delegate.createView() 即可
第二问:
先从第二问开始吧,AppCompatTextView继承自TextView,是对TextView的一种扩展,因为在5.0中首次推出了MaterialDesign这种设计风格,但是众所周知的,5.0推出不可能所有的设备全都一下子更新到最新版本,为了在早期版本上实现新的功能(这些新功能比如从源码注释中解读到比如backgroundTint属性,根据文本内容自适应大小等),即为了新特性同样可以兼容老版本,framework在创建TextView实例的时候,自动帮我们进行了替换。其它的AppCompatXXX与XXX的关系也是如此。
第一问:
然后第一问,如何完成替换的,我们这里只拿最直观的流程举例,且尽可能的简化源码过程,在讨论这个问题之前,先了解几个预备知识:
View是怎么被解析创建出来的:
1.LayoutInflater:将布局XML文件实例化为其对应的View对象,我们在Activity中通过setContentView传入一个Layout的资源文件id,最终该方法最终会调用到PhoneWindow的setContentView方法,这个方法里面有调用到
2.inflate方法,该方法的作用是将指定的XML文件填充到View的层次结构中去,最终无论通过什么途径调用到inflate方法,都会走到三个参数的重载方法这里:
parser你可以认为持有将Layout.XML解析后的数据。后两个参数的意义如下:
知道了LayoutInflate.inflate做了什么,再往下,inflate中会调用到createViewFromTag,从方法名就能知道,继续往下走,我们离答案越来越近了。
createViewFromTag做的事情非常有意思:
先看到787行这个if-else,条件是name中有没有"."字符,如果有我们会执行onCreateView,如果没有会执行createView。name啥时候有点?自定义控件的时候。当是系统控件的时候,createView会有一个填充了第二个参数的调用:createView(name, "android.view.", attrs);补上了View控件的全路径名,而自定义控件则不需要,因为传入的name就是一个全路径名。
为什么要全路径名?因为View控件对象的创建是通过反射来实现的:
下面对这几步做一个总结:
XML中保存了ViewTree的结构和View的相关标签信息(包括View的类型和一些属性值),然后这些信息会在后面通过反射的方式(如果没有Factory2和Factory的话)创建实例对象,如果创建的是ViewGroup,则会对它的子View遍历重复创建步骤,创建完View对象后,会add到对应的ViewGroup中。其中相关方法的调用流程是:inflate->rInflate->createViewFromTag->createView。
好像还是没有看到替换?
还是上一张图,我们只解释了后半部分,没有解释前半部分,那么什么是Factory?
继续往下看:
createViewFromTag中会先判断有没有Factory或者Factory2的对象,如果有,则调用Factory的onCreateView方法。这两个类都是接口,其中Factory2是Factory的子接口,都只有唯一一个onCreateView方法。不同之处在于Factory2的onCreateView方法传入了parentView。
该方法的作用就是你可以借助它来改造XML中已经存在了的Tag的值。所以Factory2可以达到改造parentView的目的。但是我们在日常中根本就没有任何地方接触到了Factory(2)呀,那么它是不是就直接是null呢?到这里又是一番源码调来调去,为了便于理解,只需要知道,这个东西(Factory2),在最开始AppCompatActivity(为了兼容低版本,我们现在Activity默认都是继承自它)中的onCreate方法中就已经通过层层调用被设置好了。
既然现在Factory2不为空,那么就应该去走它的onCreateView方法了,这里又是层层调用,最终来到了AppCompatViewInflater 的 createView 方法:
答案就在这里:
如果创建的是非兼容控件(系统控件那么多,实现兼容的只是常用的一些控件),那么就会是143行,在146中通过反射创建View对象。
啰里啰唆扯了一大堆,还是没回答第一个问题:compat库是如何将TextView替换为AppCompatTextVew的?
第三问:
根据替换相关原理,我们可以做哪些事情?
整个替换从图一所示的源码中可以看到,能够被替换的关键是Factory(2)存在,那么我觉得,其实问题问的是Factory(2)可以用来做什么吧?
那么这个时候,就适合去问站长大人了:https://blog.csdn.net/lmj623565791/article/details/51503977
嘿嘿,之前不懂,看源码查找资料又 get 一波。