您好,登錄后才能下訂單哦!
前言
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開發者們能帶來一定的幫助,如果有疑問大家可以留言交流,謝謝大家對億速云的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。