在 Android 程式裡要動態新增 View,最直接的方法就是使用 LayoutInflater 提供的 inflate method。比較常用的有以下兩個 method:
第一個參數 resource,就是 layout xml file,如 R.layout.your_file。第二個參數 root,指的是這個 inflate 的 view,要成為哪個 view 的 child view。最後一個參數 attachToRoot 是 inflate 的 view 是否要 attach 到 root view,這會影響到回傳的是哪個 view。假如 attachToRoot 為 true,則最後回傳的 view 為 root view (就是第二個參數的 view);反之就是回傳 inflate 的 view。
第二個參數 ViewGroup root 需要特別說明一下,因為這會影響到如何 inflate 新的 view 出來。android 的 view 是有階層 (hierarchy) 的,因此 parent view 的屬性 (attribute) 會影響到 child view。假如你是如以下兩種方式使用 inflate method的話:
因為沒有 root view,系統沒辦法把 inflate view 的屬性正確設定,因此系統會把 inflate view 的屬性設為 null,這會導致 inflate view 的屬性都非你所預期 (稍後會用 LayoutInflater source code 解說)。因此要很注意你 root view 是否要代 null。
接下來直接看 LayoutInflater 的 source code,從 inflate(int resource, ViewGroup root) 開始:
這裡面也只是 invoke 其他 inflate method,只是多了一個參數判斷 root view 是否為 null。其實這個就是 inflate(int resource, ViewGroup root, boolean attachToRoot)。
接下來大概看一下這個最長的 method 在做些什麼事:
第 27 行先宣告一個預定要回傳的 view, 並先把 root view 指給它。
前面判斷完 XML 正確性後,在第 59 行就從 XML 生成 view。
第 67 行宣告一個為 null 的 LayoutParams,並判斷 root view 是否為 null 來決定是否產一個正確的 LayoutParam。這在前面有提到假如 root view 為 null,則 inflate view 就不會依據 root view 去設定 LayoutParams,這也就是你的 inflate view 屬性不如預期的原因。從這邊也可以看到,假如你是使用 inflate(R.layout.your_file, parent, false) 的話,inflate view 還是會有正確的屬性,只是不會 attach 到 root view。
後面幾行就是生成 inflate view 裡的 child view,是否要 attach root view,以及判斷回傳值是 inflate view 還是 root view。
以上就是 inflate method 主要的行為,最後來說點結論吧:
1. 使用inflate(int resource, ViewGroup root) 就是使用 inflate(int resource, ViewGroup root, boolean attachToRoot),只是第三個參數是看 root 是否為 null 來決定。
2. 要注意你是不是真的希望 root view為 null,因為這會影響到 inflate view。
3. inflate(R.layout.your_file, parent, true) 執行完後 inflate view 就會 attach 到 parent view 了,不需 要再 invoke parent.add(inflateView).
實際測試就懶得用了…有興趣的人可以自己試試看,或是參考下面的參考資料喔。
參考資料:
1. layout-inflation-as-intended
2. StackOverflow - making sense of layoutinflater
3. Android - LayoutInflater
inflate(int resource, ViewGroup root) inflate(int resource, ViewGroup root, boolean attachToRoot)
第一個參數 resource,就是 layout xml file,如 R.layout.your_file。第二個參數 root,指的是這個 inflate 的 view,要成為哪個 view 的 child view。最後一個參數 attachToRoot 是 inflate 的 view 是否要 attach 到 root view,這會影響到回傳的是哪個 view。假如 attachToRoot 為 true,則最後回傳的 view 為 root view (就是第二個參數的 view);反之就是回傳 inflate 的 view。
第二個參數 ViewGroup root 需要特別說明一下,因為這會影響到如何 inflate 新的 view 出來。android 的 view 是有階層 (hierarchy) 的,因此 parent view 的屬性 (attribute) 會影響到 child view。假如你是如以下兩種方式使用 inflate method的話:
View view1 = LayoutInflater.from(mContext).inflate(R.layout.your_file, null) View view2 = LayoutInflater.from(mContext).inflate(R.layout.your_file, null, false)
因為沒有 root view,系統沒辦法把 inflate view 的屬性正確設定,因此系統會把 inflate view 的屬性設為 null,這會導致 inflate view 的屬性都非你所預期 (稍後會用 LayoutInflater source code 解說)。因此要很注意你 root view 是否要代 null。
接下來直接看 LayoutInflater 的 source code,從 inflate(int resource, ViewGroup root) 開始:
/**
* Inflate a new view hierarchy from the specified xml resource. Throws
* {@link InflateException} if there is an error.
*
* @param resource ID for an XML layout resource to load (e.g.,
* R.layout.main_page
)
* @param root Optional view to be the parent of the generated hierarchy.
* @return The root View of the inflated hierarchy. If root was supplied,
* this is the root View; otherwise it is the root of the inflated
* XML file.
*/
public View inflate(int resource, ViewGroup root) {
return inflate(resource, root, root != null);
}
這裡面也只是 invoke 其他 inflate method,只是多了一個參數判斷 root view 是否為 null。其實這個就是 inflate(int resource, ViewGroup root, boolean attachToRoot)。
/**
* Inflate a new view hierarchy from the specified xml resource. Throws
* {@link InflateException} if there is an error.
*
* @param resource ID for an XML layout resource to load (e.g.,
* R.layout.main_page
)
* @param root Optional view to be the parent of the generated hierarchy (if
* attachToRoot is true), or else simply an object that
* provides a set of LayoutParams values for root of the returned
* hierarchy (if attachToRoot is false.)
* @param attachToRoot Whether the inflated hierarchy should be attached to
* the root parameter? If false, root is only used to create the
* correct subclass of LayoutParams for the root view in the XML.
* @return The root View of the inflated hierarchy. If root was supplied and
* attachToRoot is true, this is root; otherwise it is the root of
* the inflated XML file.
*/
public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
if (DEBUG) System.out.println("INFLATING from resource: " + resource);
XmlResourceParser parser = getContext().getResources().getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
裡面的行為就是產生一個 XML parser,並再 invoke 其他 inflate method。/** * Inflate a new view hierarchy from the specified XML node. Throws * {@link InflateException} if there is an error. * * Important For performance * reasons, view inflation relies heavily on pre-processing of XML files * that is done at build time. Therefore, it is not currently possible to * use LayoutInflater with an XmlPullParser over a plain XML file at runtime. * * @param parser XML dom node containing the description of the view * hierarchy. * @param root Optional view to be the parent of the generated hierarchy (if * attachToRoot is true), or else simply an object that * provides a set of LayoutParams values for root of the returned * hierarchy (if attachToRoot is false.) * @param attachToRoot Whether the inflated hierarchy should be attached to * the root parameter? If false, root is only used to create the * correct subclass of LayoutParams for the root view in the XML. * @return The root View of the inflated hierarchy. If root was supplied and * attachToRoot is true, this is root; otherwise it is the root of * the inflated XML file. */ public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) { synchronized (mConstructorArgs) { final AttributeSet attrs = Xml.asAttributeSet(parser); Context lastContext = (Context)mConstructorArgs[0]; mConstructorArgs[0] = mContext; View result = root; try { // Look for the root node. int type; while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { // Empty } if (type != XmlPullParser.START_TAG) { throw new InflateException(parser.getPositionDescription() + ": No start tag found!"); } final String name = parser.getName(); if (DEBUG) { System.out.println("**************************"); System.out.println("Creating root view: " + name); System.out.println("**************************"); } if (TAG_MERGE.equals(name)) { if (root == null || !attachToRoot) { throw new InflateException("can be used only with a valid " + "ViewGroup root and attachToRoot=true"); } rInflate(parser, root, attrs, false); } else { // Temp is the root view that was found in the xml View temp; if (TAG_1995.equals(name)) { temp = new BlinkLayout(mContext, attrs); } else { temp = createViewFromTag(root, name, attrs); } ViewGroup.LayoutParams params = null; if (root != null) { if (DEBUG) { System.out.println("Creating params from root: " + root); } // Create layout params that match root, if supplied params = root.generateLayoutParams(attrs); if (!attachToRoot) { // Set the layout params for temp if we are not // attaching. (If we are, we use addView, below) temp.setLayoutParams(params); } } if (DEBUG) { System.out.println("-----> start inflating children"); } // Inflate all children under temp rInflate(parser, temp, attrs, true); if (DEBUG) { System.out.println("-----> done inflating children"); } // We are supposed to attach all the views we found (int temp) // to root. Do that now. if (root != null && attachToRoot) { root.addView(temp, params); } // Decide whether to return the root that was passed in or the // top view found in xml. if (root == null || !attachToRoot) { result = temp; } } } catch (XmlPullParserException e) { InflateException ex = new InflateException(e.getMessage()); ex.initCause(e); throw ex; } catch (IOException e) { InflateException ex = new InflateException( parser.getPositionDescription() + ": " + e.getMessage()); ex.initCause(e); throw ex; } finally { // Don't retain static reference on context. mConstructorArgs[0] = lastContext; mConstructorArgs[1] = null; } return result; } }
接下來大概看一下這個最長的 method 在做些什麼事:
View result = root;
第 27 行先宣告一個預定要回傳的 view, 並先把 root view 指給它。
// Temp is the root view that was found in the xml View temp; if (TAG_1995.equals(name)) { temp = new BlinkLayout(mContext, attrs); } else { temp = createViewFromTag(root, name, attrs); }
前面判斷完 XML 正確性後,在第 59 行就從 XML 生成 view。
ViewGroup.LayoutParams params = null; if (root != null) { if (DEBUG) { System.out.println("Creating params from root: " + root); } // Create layout params that match root, if supplied params = root.generateLayoutParams(attrs); if (!attachToRoot) { // Set the layout params for temp if we are not // attaching. (If we are, we use addView, below) temp.setLayoutParams(params); } }
第 67 行宣告一個為 null 的 LayoutParams,並判斷 root view 是否為 null 來決定是否產一個正確的 LayoutParam。這在前面有提到假如 root view 為 null,則 inflate view 就不會依據 root view 去設定 LayoutParams,這也就是你的 inflate view 屬性不如預期的原因。從這邊也可以看到,假如你是使用 inflate(R.layout.your_file, parent, false) 的話,inflate view 還是會有正確的屬性,只是不會 attach 到 root view。
// Inflate all children under temp rInflate(parser, temp, attrs, true); if (DEBUG) { System.out.println("-----> done inflating children"); } // We are supposed to attach all the views we found (int temp) // to root. Do that now. if (root != null && attachToRoot) { root.addView(temp, params); } // Decide whether to return the root that was passed in or the // top view found in xml. if (root == null || !attachToRoot) { result = temp; }
後面幾行就是生成 inflate view 裡的 child view,是否要 attach root view,以及判斷回傳值是 inflate view 還是 root view。
以上就是 inflate method 主要的行為,最後來說點結論吧:
1. 使用inflate(int resource, ViewGroup root) 就是使用 inflate(int resource, ViewGroup root, boolean attachToRoot),只是第三個參數是看 root 是否為 null 來決定。
2. 要注意你是不是真的希望 root view為 null,因為這會影響到 inflate view。
3. inflate(R.layout.your_file, parent, true) 執行完後 inflate view 就會 attach 到 parent view 了,不需 要再 invoke parent.add(inflateView).
實際測試就懶得用了…有興趣的人可以自己試試看,或是參考下面的參考資料喔。
參考資料:
1. layout-inflation-as-intended
2. StackOverflow - making sense of layoutinflater
3. Android - LayoutInflater
留言
張貼留言