欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android布局加載之LayoutInflater示例詳解

 更新時(shí)間:2017年03月27日 10:39:59   作者:Glumes  
這篇文章主要介紹了Android布局加載之LayoutInflater的相關(guān)資料,文中介紹的非常詳細(xì),對(duì)大家具有一定的參考借鑒價(jià)值,需要的朋友們下面來(lái)一起看看吧。

前言

Activity 在界面創(chuàng)建時(shí)需要將 XML 布局文件中的內(nèi)容加載進(jìn)來(lái),正如我們?cè)?ListView 或者 RecyclerView 中需要將 Item 的布局加載進(jìn)來(lái)一樣,都是使用 LayoutInflater 來(lái)進(jìn)行操作的。

LayoutInflater 實(shí)例的獲取有多種方式,但最終是通過(guò)(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)來(lái)得到的,也就是說(shuō)加載布局的 LayoutInflater 是來(lái)自于系統(tǒng)服務(wù)的。

由于 Android 系統(tǒng)源碼中關(guān)于 Content 部分采用的是裝飾模式,Context 的具體功能都是由 ContextImpl 來(lái)實(shí)現(xiàn)的。通過(guò)在 ContextImpl 中找到getSystemService的代碼,一路跟進(jìn),得知最后返回的實(shí)例是PhoneLayoutInflater。

  registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
    new CachedServiceFetcher<LayoutInflater>() {
   @Override
   public LayoutInflater createService(ContextImpl ctx) {
    return new PhoneLayoutInflater(ctx.getOuterContext());
   }});

LayoutInflater 只是一個(gè)抽象類,而 PhoneLayoutInflater 才是具體的實(shí)現(xiàn)類。

inflate 方法加載 View

使用 LayoutInflater 時(shí)常用方法就是inflate方法了,將一個(gè)布局文件 ID 傳入并最后解析成一個(gè) View 。

LayoutInflater 加載布局的 inflate 方法也有多種重載形式:

View inflate(@LayoutRes int resource, @Nullable ViewGroup root)
View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)

而這兩者的差別就在于是否要將 resource 布局文件加載到 root布局中去。

不過(guò)有點(diǎn)需要注意的地方,若 root為 null,則在 xml 布局中為 resource設(shè)置的屬性會(huì)失效,只是單純的加載布局。

     // temp 是 xml 布局中的頂層 View
     final View temp = createViewFromTag(root, name, inflaterContext, attrs);
     ViewGroup.LayoutParams params = null;
     if (root != null) { // root 
      // root 不為 null 才會(huì)生成 layoutParams
      params = root.generateLayoutParams(attrs);
      if (!attachToRoot) {
       // 如果不添加到 root 中,則直接把布局參數(shù)設(shè)置給 temp
       temp.setLayoutParams(params);
      }
     }
     // 加載子 View 
     rInflateChildren(parser, temp, attrs, true);
     if (root != null && attachToRoot) {
      root.addView(temp, params);//添加到布局中,則布局參數(shù)用到 addView 中去
     }
     if (root == null || !attachToRoot) {
      result = temp;
     }

跟進(jìn)createViewFromTag方法查看 View 是如何創(chuàng)建出來(lái)的。

   View view; // 最后要返回的 View
   if (mFactory2 != null) {
    view = mFactory2.onCreateView(parent, name, context, attrs); // 是否設(shè)置了 Factory2 
   } else if (mFactory != null) {
    view = mFactory.onCreateView(name, context, attrs); // 是否設(shè)置了 Factory
   } else {
    view = null;
   }
   if (view == null && mPrivateFactory != null) { // 是否設(shè)置了 PrivateFactory
    view = mPrivateFactory.onCreateView(parent, name, context, attrs);
   }
   if (view == null) { // 如果的 Factory 都沒有設(shè)置過(guò),最后在生成 View
    final Object lastContext = mConstructorArgs[0];
    mConstructorArgs[0] = context;
    try {
     if (-1 == name.indexOf('.')) { // 系統(tǒng)控件 
      view = onCreateView(parent, name, attrs);
     } else { // 非系統(tǒng)控件,自定義的 View 
      view = createView(name, null, attrs);
     }
    } finally {
     mConstructorArgs[0] = lastContext;
    }
   }

如果設(shè)置過(guò) Factory 接口,那么將由 Factory 中的 onCreateView 方法來(lái)生成 View 。

關(guān)于 LayoutInflater.Factory 的作用,就是用來(lái)在加載布局時(shí)可以自行去創(chuàng)建 View,搶在系統(tǒng)創(chuàng)建 View 之前去創(chuàng)建。

關(guān)于 LayoutInflater.Factory 的使用場(chǎng)景,現(xiàn)在比較多的就是應(yīng)用的換膚了。

若沒有設(shè)置過(guò) Factory 接口,則是判斷是否為自定義控件或者系統(tǒng)控件,不管是 onCreateView 方法還是 createView 方法,內(nèi)部最終都是調(diào)用到了 createView 方法,通過(guò)它來(lái)生成 View 。

// 通過(guò)反射生成 View 的參數(shù),分別是 Context 和 AttributeSet 類
static final Class<?>[] mConstructorSignature = new Class[] {
   Context.class, AttributeSet.class};
public final View createView(String name, String prefix, AttributeSet attrs)
   throws ClassNotFoundException, InflateException {
  Constructor<? extends View> constructor = sConstructorMap.get(name);
  Class<? extends View> clazz = null;
  if (constructor == null) { // 從緩存中得到 View 的構(gòu)造器,沒有則調(diào)用 getConstructor
    clazz = mContext.getClassLoader().loadClass(
      prefix != null ? (prefix + name) : name).asSubclass(View.class);
    if (mFilter != null && clazz != null) {
     boolean allowed = mFilter.onLoadClass(clazz);
     if (!allowed) {
      failNotAllowed(name, prefix, attrs);
     }
    }
    constructor = clazz.getConstructor(mConstructorSignature);
    constructor.setAccessible(true);
    sConstructorMap.put(name, constructor);
   } else {
    // If we have a filter, apply it to cached constructor
    if (mFilter != null) { // 過(guò)濾,是否允許生成該 View
     // Have we seen this name before?
     Boolean allowedState = mFilterMap.get(name);
     if (allowedState == null) {
      // New class -- remember whether it is allowed
      clazz = mContext.getClassLoader().loadClass(
        prefix != null ? (prefix + name) :     name).asSubclass(View.class);
      boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
      mFilterMap.put(name, allowed);
      if (!allowed) {
       failNotAllowed(name, prefix, attrs);
      }
     } else if (allowedState.equals(Boolean.FALSE)) {
      failNotAllowed(name, prefix, attrs); // 不允許生成該 View
     }
    }
   }
  Object[] args = mConstructorArgs;
  args[1] = attrs;
  final View view = constructor.newInstance(args); // 通過(guò)反射生成 View
  return view;

在 createView 方法內(nèi)部,首先從 View 的構(gòu)造器緩存中查找是否有對(duì)應(yīng)的緩存,若沒有則生成構(gòu)造器并且放到緩存中去,若有構(gòu)造器則看能否通過(guò)過(guò)濾,是否允許該 View 生成。

最后都滿足條件的則是通過(guò) View 的構(gòu)造器反射生成了 View 。

在生成 View 時(shí)采用 Constructor.newInstance調(diào)用構(gòu)造函數(shù),而參數(shù)所需要的變量就是mConstructorSignature變量所定義的,分別是 Context 和 AttributeSet。可以看到,在最后生成 View 時(shí)也傳入了對(duì)應(yīng)的參數(shù)。

采用 Constructor.newInstance的形式反射生成 View ,是為了解耦,只需要有了類名,就可以加載出來(lái)。

由此可見,LayoutInflater 加載布局仍然是需要傳遞 Context的,不光是為了得到 LayoutInflater ,在反射生成 View 時(shí)同樣會(huì)用到。

深度遍歷加載布局

如果需要加載的布局只有一個(gè)控件,那么 LayoutInflater 返回那個(gè) View 工作也就結(jié)束了。

若布局文件中有多個(gè)需要加載的 View ,則通過(guò)rInflateChildren方法繼續(xù)加載頂層 View 下的 View ,最后通過(guò)rInflate方法來(lái)加載。

void rInflate(XmlPullParser parser, View parent, Context context,
   AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
  final int depth = parser.getDepth();
  int type;
  // 若 while 條件不成立,則加載結(jié)束了
  while (((type = parser.next()) != XmlPullParser.END_TAG ||
    parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
   if (type != XmlPullParser.START_TAG) {
    continue;
   }
   final String name = parser.getName(); // 從 XmlPullParser 中得到 name 出來(lái)解析
   if (TAG_REQUEST_FOCUS.equals(name)) { // name 各種情況下的解析
    parseRequestFocus(parser, parent);
   } else if (TAG_TAG.equals(name)) {
    parseViewTag(parser, parent, attrs);
   } else if (TAG_INCLUDE.equals(name)) {
    if (parser.getDepth() == 0) {
     throw new InflateException("<include /> cannot be the root element");
    }
    parseInclude(parser, context, parent, attrs);
   } else if (TAG_MERGE.equals(name)) {
    throw new InflateException("<merge /> must be the root element");
   } else {
    final View view = createViewFromTag(parent, name, context, attrs);
    final ViewGroup viewGroup = (ViewGroup) parent;
    final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
    rInflateChildren(parser, view, attrs, true); // 繼續(xù)遍歷
    viewGroup.addView(view, params); // 頂層 View 添加 子 View
   }
  }
  if (finishInflate) { // 遍歷解析
   parent.onFinishInflate();
  }
 }

rInflate方法首先判斷是否解析結(jié)束了,若沒有,則從 XmlPullParser 中加載出下一個(gè) View 進(jìn)行處理,中間還會(huì)對(duì)不同的類型進(jìn)行處理,比如TAG_REQUEST_FOCUS、TAG_TAG、TAG_INCLUDE、TAG_MERGE等等。

最后仍然還是通過(guò)createViewFromTag來(lái)生成 View ,并以這個(gè)生成的 View 為父節(jié)點(diǎn),開始深度遍歷,繼續(xù)調(diào)用rInflateChildren方法加載布局,并把這個(gè) View 加入到它的父 View 中去。

至于為什么生成 View 的方法名字createViewFromTag從字面上來(lái)看是來(lái)自于 Tag標(biāo)簽,想必是和 XmlPullParser解析布局生成的內(nèi)容有關(guān)。

總結(jié)

以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)各位Android開發(fā)者們能帶來(lái)一定的幫助,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。

相關(guān)文章

  • 淺談Android中多線程切換的幾種方法

    淺談Android中多線程切換的幾種方法

    本篇文章主要介紹了淺談Android中多線程切換的幾種方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-01-01
  • Android四種數(shù)據(jù)存儲(chǔ)的應(yīng)用方式

    Android四種數(shù)據(jù)存儲(chǔ)的應(yīng)用方式

    這篇文章主要介紹了Android四種數(shù)據(jù)存儲(chǔ)的應(yīng)用方式的相關(guān)資料,希望通過(guò)本文能幫助到大家,讓大家理解掌握Android存儲(chǔ)數(shù)據(jù)的方法,需要的朋友可以參考下
    2017-10-10
  • Android 線程之自定義帶消息循環(huán)Looper的實(shí)例

    Android 線程之自定義帶消息循環(huán)Looper的實(shí)例

    這篇文章主要介紹了Android 線程之自定義帶消息循環(huán)Looper的實(shí)例的相關(guān)資料,希望通過(guò)本文能幫助到大家,需要的朋友可以參考下
    2017-10-10
  • OpenGL ES紋理詳解

    OpenGL ES紋理詳解

    這篇文章主要為大家詳細(xì)介紹了OpenGL ES紋理的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-05-05
  • Android自定義照相機(jī)詳解

    Android自定義照相機(jī)詳解

    幾乎每個(gè)APP都會(huì)用的相機(jī)功能,下面小編把內(nèi)容整理分享到腳本之家平臺(tái),供大家參考
    2016-04-04
  • 自定義toast外形,多次點(diǎn)擊不會(huì)總是彈出toast的實(shí)現(xiàn)方法

    自定義toast外形,多次點(diǎn)擊不會(huì)總是彈出toast的實(shí)現(xiàn)方法

    下面小編就為大家?guī)?lái)一篇自定義toast外形,多次點(diǎn)擊不會(huì)總是彈出toast的實(shí)現(xiàn)方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-04-04
  • Android封裝Banner控件方法介紹

    Android封裝Banner控件方法介紹

    android-banner實(shí)現(xiàn)了一般banner循環(huán)輪播的效果,一頁(yè)只顯示一張圖片,也可以一頁(yè)顯示一張圖和相鄰兩個(gè)圖片的一部分,此項(xiàng)目?jī)H僅是banner展示圖片,沒有多余的諸如指示器、頁(yè)面切換動(dòng)畫等效果代碼,詳見效果圖和案例代碼
    2023-03-03
  • Flutter WillPopScope攔截返回事件原理示例詳解

    Flutter WillPopScope攔截返回事件原理示例詳解

    這篇文章主要為大家介紹了Flutter WillPopScope攔截返回事件原理示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-09-09
  • Android自定義View實(shí)現(xiàn)QQ運(yùn)動(dòng)積分轉(zhuǎn)盤抽獎(jiǎng)功能

    Android自定義View實(shí)現(xiàn)QQ運(yùn)動(dòng)積分轉(zhuǎn)盤抽獎(jiǎng)功能

    這篇文章主要為大家詳細(xì)介紹了Android自定義View實(shí)現(xiàn)QQ運(yùn)動(dòng)積分轉(zhuǎn)盤抽獎(jiǎng)功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-10-10
  • Android開發(fā)之廣播機(jī)制淺析

    Android開發(fā)之廣播機(jī)制淺析

    這篇文章主要介紹了Android開發(fā)之廣播機(jī)制淺析,主要包括了發(fā)布、接收及配置廣播的實(shí)例,需要的朋友可以參考下
    2014-08-08

最新評(píng)論