標籤:部落格 whether ade and mat 運行 print ... 初學者
歡迎轉載,轉載請標明出處:
http://blog.csdn.net/johnny901114/article/details/61913656
本文出自:【餘志強的部落格】
在實際開發中我們常常需要inflate要給布局然後添加到某個版面配置容器裡面去, 要把xml布局檔案轉成一個View對象 需要使用LayoutInflater.inflate方法. 在開發中常常使用如下幾種方式:
inflater.inflate(layoutId, null);inflater.inflate(layoutId, root,false);inflater.inflate(layoutId, root,true);
特別是初學者搞不靈清這種方式有什麼區別, 下面就一一來看看他們到底有什麼不同.
inflater.inflate(layoutId, null)
現在我有一個MainActivity, 我想往這個布局裡add一個布局, 該布局如下所示:
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="20dp" android:background="#50000000" android:orientation="vertical"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="inflate01" android:textAllCaps="false" android:textSize="20sp" /></LinearLayout>
該布局非常簡單, 外層是一個LinearLayout, 背景顏色為#50000000, 大小都是wrap_content, 距左20dp.
然後在MainActivity執行添加操作:
LayoutInflater inflater = LayoutInflater.from(this);View view = inflater.inflate(R.layout.inflater01, null);root.addView(view);
添加View後, 發現並不是我們想要的, 如所示:
從可以看出, 被添加的View, 它的寬度並不是wrap_content而是佔滿了螢幕, 還有設定的margin_left也沒有效果. 也就是受我們設定的這些都失效了.
那隻有看看源碼一探究竟了.
inflater.inflate(R.layout.inflater01, null) 方法裡調用了inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot), 在該方法又調用了 inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) 這個方法是最核心的方法:
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) { synchronized (mConstructorArgs) { final Context inflaterContext = mContext; final AttributeSet attrs = Xml.asAttributeSet(parser); Context lastContext = (Context) mConstructorArgs[0]; mConstructorArgs[0] = inflaterContext; View result = root; try { // Look for the root node. int type; while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { // Empty } final String name = parser.getName(); if (TAG_MERGE.equals(name)) { if (root == null || !attachToRoot) { throw new InflateException("<merge /> can be used only with a valid " + "ViewGroup root and attachToRoot=true"); } rInflate(parser, root, inflaterContext, attrs, false); } else { // Temp is the root view that was found in the xml //把xml裡的相關屬性轉成View對象 final View temp = createViewFromTag(root, name, inflaterContext, attrs); ViewGroup.LayoutParams params = null; //如果參數root不為空白 if (root != null) { // Create layout params that match root, if supplied //根據在XML檔案中的設定,產生LayoutParamsView的LayoutParameter用來適應root布局 //root布局的根節點是哪個容器(如LinearLayout)那這個params就是哪個容器布局的params(LinearLayout.LayoutParams) params = root.generateLayoutParams(attrs); //如果不添加到跟布局, 則設定剛剛產生的params if (!attachToRoot) { // Set the layout params for temp if we are not // attaching. (If we are, we use addView, below) temp.setLayoutParams(params); } } // Inflate all children under temp against its context. //初始化View所有的子控制項 rInflateChildren(parser, temp, attrs, true); // We are supposed to attach all the views we found (int temp) // to root. Do that now. if (root != null && attachToRoot) { //把View添加到root布局中 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) { //igore code... } catch (Exception e) { //igore code... } finally { //igore code... } return result; }}
上面的代碼比較好理解, 主要的地方都加了注釋.
從上面的代碼來看 inflater.inflate(layoutId, null) 方法, 因為root我們傳了null,所以 attachToRoot=false.
根據源碼 inflater.inflate(layoutId, null) 方法執行主要流程如下:
1, 把xml裡的相關屬性轉成View對象
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
2, 最後把 View temp返回了.
所以這個View是沒有 LayoutParams 對象的.
不信的話可以列印一下就知道了:
View view = inflater.inflate(R.layout.inflater01, null);ViewGroup.LayoutParams layoutParams = view.getLayoutParams();if (layoutParams == null) { Log.e("MainActivity", "layoutParams is null");} else { Log.e("MainActivity", "layoutParams width:height" + layoutParams.width + ":" + layoutParams.height);}
毫無疑問輸出null.
一個View沒有 LayoutParams 是沒辦法展示到螢幕上的.
現在只有看看 root.addView(view) 方法了:
public void addView(View child) { addView(child, -1);}public void addView(View child, int index) { if (child == null) { throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup"); } LayoutParams params = child.getLayoutParams(); if (params == null) { params = generateDefaultLayoutParams(); if (params == null) { throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null"); } } addView(child, index, params);}
從上面可以看出, 如果 child 的 LayoutParams為null, root會產生一個預設的 LayoutParams
所以看看 generateDefaultLayoutParams() 方法是什麼樣的? 因為我們這個root是LinearLayout, 所以要到 LinearLayout 的 generateDefaultLayoutParams() 裡去看,而不是ViewGroup :
@Overrideprotected LayoutParams generateDefaultLayoutParams() { if (mOrientation == HORIZONTAL) { return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); } else if (mOrientation == VERTICAL) { return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); } return null;}
我們的root是LinearLayout方向為 VERTICAL 所以 LayoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); 這就解釋了我們是上面啟動並執行效果了: 明明設定的寬和高都是 WRAP_CONTENT, 最後變成了寬度為 MATCH_PARENT, 高度為WRAP_CONTENT.
viewGroup.addView(view, LayoutParams); 相當於如下兩行代碼 :view.setLayoutParams(LayoutParams);viewGroup.addView(view)
有人可能會問 下面三個屬性失效了,那 android:background="#50000000"又起作用呢?
android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginLeft="20dp"
因為layout_width,layout_height,layout_marginLeft,layout_marginLeft都屬於LayoutParams, 但是background屬性不屬於此範圍, 同時所有的標籤屬性都會存在AttributeSet對象裡.
inflater.inflate(layoutId, root,false);
在MainActivity的添加View的方法改成如下形式 :
/** * inflater.inflate(layoutId, root, false); */private void add02() { View view = inflater.inflate(R.layout.inflater01, root, false); ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); if (layoutParams == null) { Log.e("MainActivity", "layoutParams is null"); } else { Log.e("MainActivity", "layoutParams width:height" + layoutParams.width + ":" + layoutParams.height); } root.addView(view);}
布局檔案還是那個布局檔案, 不同的只是把inflate方法的參數該了下:
inflater.inflate(R.layout.inflater01, root, false);
效果如所示 :
這個才是我們要的效果, 寬高和左邊距都是我們要的效果.
根據源碼 inflater.inflate(R.layout.inflater01, root, false);方法執行流程流程如下:
1, 把xml裡的相關屬性轉成View對象
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
2, 根據在XML檔案中的設定,產生LayoutParamsView的LayoutParameter用來適應root布局, 並且設定給View
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);}
2, 最後把 View temp 返回了.
所以, 最終呈現出來的是符合我們的預期的.
inflater.inflate(layoutId, root,true);
inflater.inflate(layoutId, root,true); 和 inflater.inflate(layoutId, root,false); 唯一的區別就是 inflater.inflate(layoutId, root,true); 會自動把View添加到root裡, 不用我們來add進去.
/** * inflater.inflate(layoutId, root, true); */private void add03() { View view = inflater.inflate(R.layout.inflater01, root, true); ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); if (layoutParams == null) { Log.e("MainActivity", "layoutParams is null"); } else { Log.e("MainActivity", "layoutParams width:height" + layoutParams.width + ":" + layoutParams.height); }}
Android LayoutInflater.inflate的使用及源碼分析