91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Android布局加載之LayoutInflater示例詳解

發布時間:2020-10-17 13:25:07 來源:腳本之家 閱讀:173 作者:Glumes 欄目:移動開發

前言

Activity 在界面創建時需要將 XML 布局文件中的內容加載進來,正如我們在 ListView 或者 RecyclerView 中需要將 Item 的布局加載進來一樣,都是使用 LayoutInflater 來進行操作的。

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

由于 Android 系統源碼中關于 Content 部分采用的是裝飾模式,Context 的具體功能都是由 ContextImpl 來實現的。通過在 ContextImpl 中找到getSystemService的代碼,一路跟進,得知最后返回的實例是PhoneLayoutInflater。

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

LayoutInflater 只是一個抽象類,而 PhoneLayoutInflater 才是具體的實現類。

inflate 方法加載 View

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

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

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

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

不過有點需要注意的地方,若 root為 null,則在 xml 布局中為 resource設置的屬性會失效,只是單純的加載布局。

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

跟進createViewFromTag方法查看 View 是如何創建出來的。

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

如果設置過 Factory 接口,那么將由 Factory 中的 onCreateView 方法來生成 View 。

關于 LayoutInflater.Factory 的作用,就是用來在加載布局時可以自行去創建 View,搶在系統創建 View 之前去創建。

關于 LayoutInflater.Factory 的使用場景,現在比較多的就是應用的換膚了。

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

// 通過反射生成 View 的參數,分別是 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 的構造器,沒有則調用 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 方法內部,首先從 View 的構造器緩存中查找是否有對應的緩存,若沒有則生成構造器并且放到緩存中去,若有構造器則看能否通過過濾,是否允許該 View 生成。

最后都滿足條件的則是通過 View 的構造器反射生成了 View 。

在生成 View 時采用 Constructor.newInstance調用構造函數,而參數所需要的變量就是mConstructorSignature變量所定義的,分別是 Context 和 AttributeSet。可以看到,在最后生成 View 時也傳入了對應的參數。

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

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

深度遍歷加載布局

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

若布局文件中有多個需要加載的 View ,則通過rInflateChildren方法繼續加載頂層 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 條件不成立,則加載結束了
  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("<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); // 繼續遍歷
    viewGroup.addView(view, params); // 頂層 View 添加 子 View
   }
  }
  if (finishInflate) { // 遍歷解析
   parent.onFinishInflate();
  }
 }

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

最后仍然還是通過createViewFromTag來生成 View ,并以這個生成的 View 為父節點,開始深度遍歷,繼續調用rInflateChildren方法加載布局,并把這個 View 加入到它的父 View 中去。

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

總結

以上就是這篇文章的全部內容了,希望本文的內容對各位Android開發者們能帶來一定的幫助,如果有疑問大家可以留言交流,謝謝大家對億速云的支持。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

乐陵市| 乐山市| 资源县| 珲春市| 类乌齐县| 防城港市| 辽宁省| 甘肃省| 濮阳市| 曲沃县| 神农架林区| 信宜市| 红桥区| 鹤壁市| 车险| 嘉荫县| 孝感市| 蒙自县| 中超| 汤原县| 集贤县| 阜南县| 长春市| 吉安市| 大连市| 行唐县| 盐池县| 塔城市| 南溪县| 进贤县| 衡南县| 隆林| 扶沟县| 比如县| 屯留县| 罗定市| 耿马| 肇州县| 虞城县| 蒲城县| 丰镇市|