[Android] 聊一下 AppCompatTextView 與 TextView

拋出

1.compat庫是如何將TextView替換爲AppCompatTextView的? 2.爲何要進行替換? 3.根據替換相關原理,咱們能夠作哪些事情?android

Answer1:bash

第二問:

先從第二問開始吧,AppCompatTextView繼承自TextView,是對TextView的一種擴展,由於在5.0中首次推出了MaterialDesign這種設計風格,可是衆所周知,5.0推出不可能全部的設備所有都更新到最新版本,爲了在早期版本上實現新的功能(這些新功能好比從源碼註釋中解讀到backgroundTint屬性,根據文本內容自適應大小等),即爲了新特性一樣能夠兼容老版本,framework在建立TextView實例的時候,自動幫咱們進行了替換。其它的AppCompatXXX與XXX的關係也是如此。佈局

第一問:

而後第一問,是如何完成替換的,咱們這裏只拿最直觀的流程舉例,且儘量的簡化源碼過程,在討論這個問題以前,先了解幾個預備知識:ui

  • View是這麼被解析建立出來的: 1.LayoutInflater:將佈局XML文件實例化爲其對應的View對象,咱們在Activity中經過setCountentView傳入一個Layout的資源文件id,最終該方法調用到PhoneWindow的setContentView方法,這個方法裏面有調用到mLayoutInflater.inflate(layoutResID,mContentParent); 2.inflate方法,該方法的做用是將指定的XML文件填充到View的層次結構中去,最終不管經過什麼途徑調用到inflate方法,都會走到三個參數的重載方法這裏:
return inflate(parser,root,attachToRoot);
複製代碼

parser你能夠認爲持有將Layout.XML解析後的數據,後兩個參數的意義以下:spa

  • root 爲null,attachToRoot無心義,inflate返回的是當前XML對應的根佈局。
  • root不爲null且attachToRoot爲true,則整個XML對應的佈局就設置了根佈局是root
  • root不爲null且attachToRoot爲false,則會將root的LayoutParams設置給當前的XML佈局。 知道了LayoutInflate.inflate作了什麼,再往下,inflate中會調用到createViewFromTag,從方法名就能知道,繼續往下走,咱們離答案愈來愈近了。 createViewFromTag作的事情很是有意思:
View view;
if(mFactory2 !=null){
   view =mFactory2.onCreateView(parent,name,context,attrs);
}else if(mFactory !=null){
    view =mFactory.onCreateView(name,context,attrs);
}else{
  view =null;
}
if(view == null && mPrivateFacotry !=null){
  view =mPrivateFacotry.onCreateVoew(parent,name,context,attrs);
}
if(view ==null){
  final Object lastContext =mConstructorArgs[0];
  mConstructorArgs[0]=context;
try{
  if(-1 == name.indexOf('.')){     //①
view =onCreateView(parent,name,attrs);
  }else{
view =createVoew(name,null,attrs);
  }
    }finally{
mConstructorArgs[0]=lastContext;
    }
}
return view;
複製代碼

先看①這個if-else,條件是name中有沒有字符「.」,若是有咱們會執行onCreateView,若是沒有會執行createView。name啥時候有點?自定義控件的時候。當是系統控件的時候,createView會有一個填充了第二個參數的調用:createVoew(name,"android.view.",attrs);補上了View控件的全路徑名,而自定義控件則不須要,由於傳入的name就是一個全路徑名。 爲何要全路徑名?由於View控件對象的建立是經過反射來實現的:.net

clazz =mContext.getClassLoader().loadClass(
prefix!= null ? (prefix+name):name).asSubclass(View.class);
...
constructor=clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
sConstructorMap.put(name,constructor);
...
args[1]=attrs;
final View view =constructor.newInstance(args);
複製代碼

下面對這幾布作一個總結: 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的目的。 可是咱們平常中根本就沒有任何地方接觸到了Factory2,那麼它是否是就直接是null呢?到這裏又是一番源碼調來調去,爲了便於理解,只須要知道,這個Factory2,在最開始AppCompatActivity(爲了兼容低版本,咱們如今Activity默認都繼承自它)中的onCreate方法中就已經經過層層調用被設置好了。 既然如今Factory2不爲空,那麼就應該去走它的onCreateView方法了,這裏又是層層調用,最終來到了AppCompatViewInflater的onCreateView方法: 設計

image
若是建立的是非兼容控件(系統控件那麼多,實現兼容的只是經常使用的一些控件),那麼就會是143行,在146行中經過反射建立View對象。 囉裏囉嗦了一大堆,仍是沒回答第一個問題: compat庫是如何將TextView替換爲 AppCompatTextView的?

我的對這個的理解:在將XML文件解析成包含ViewTree信息以後,開始利用這些信息去建立每個View節點,在建立View對象的時候,若是發現這個節點是屬於支持兼容的控件好比TextView,那麼就會去調用到new AppCompatTextView()來建立一個兼容View對象,也就是在建立的時候,就已經實現了替換。code

第三問: 根據替換原理,咱們能夠作哪些事情? 整個替換從上面的源碼中就能夠看到,可以被替換的關鍵是Factory2存在,那麼我以爲,其實問題問的是Factory2能夠用來作什麼? 答案在這裏cdn

參考連接: 每日一問 AppCompatTextView 與 TextView對象

相關文章
相關標籤/搜索