真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

Android布局加載之LayoutInflater示例詳解

前言

創(chuàng)新互聯(lián)公司專注于七星網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗(yàn)。 熱誠(chéng)為您提供七星營(yíng)銷型網(wǎng)站建設(shè),七星網(wǎng)站制作、七星網(wǎng)頁設(shè)計(jì)、七星網(wǎng)站官網(wǎng)定制、小程序設(shè)計(jì)服務(wù),打造七星網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供七星網(wǎng)站排名全網(wǎng)營(yíng)銷落地服務(wù)。

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

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

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

  registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
    new CachedServiceFetcher() {
   @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布局中去。

不過有點(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)建出來的。

   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è)置過,最后在生成 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è)置過 Factory 接口,那么將由 Factory 中的 onCreateView 方法來生成 View 。

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

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

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

// 通過反射生成 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) { // 過濾,是否允許生成該 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); // 通過反射生成 View
  return view;

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

最后都滿足條件的則是通過 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 ,是為了解耦,只需要有了類名,就可以加載出來。

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

深度遍歷加載布局

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

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

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 出來解析
   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(" cannot be the root element");
    }
    parseInclude(parser, context, parent, attrs);
   } else if (TAG_MERGE.equals(name)) {
    throw new InflateException(" 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等等。

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

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

總結(jié)

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


名稱欄目:Android布局加載之LayoutInflater示例詳解
文章鏈接:http://weahome.cn/article/igoeci.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部