對於Inflate的三個參數(int resource, ViewGroup root, boolean attachToRoot),不少人可能有這樣的誤解:node
是不是真的像這樣呢?今天咱們就從源碼的角度去探究:android
咱們就採用一個ListView,item爲一個按鈕app
Activity的佈局文件:ide
<?xml version="1.0" encoding="utf-8"?> <ListView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/mListView" android:layout_width="match_parent" android:layout_height="wrap_content" > </ListView>
item的佈局文件:佈局
<?xml version="1.0" encoding="utf-8"?> <Button xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/btn_test" android:layout_width="120dp" android:layout_height="120dp" > </Button>
ListView的適配器:測試
/** * Created by quguangel on 2016/12/1. */ public class MyAdapter extends BaseAdapter { private String[] listData; private LayoutInflater mInflater; public MyAdapter(String[] listData,Context mContext){ this.listData = listData; this.mInflater = LayoutInflater.from(mContext); } @Override public int getCount() { return listData.length; } @Override public Object getItem(int i) { return listData[i]; } @Override public long getItemId(int i) { return i; } @Override public View getView(int i, View view, ViewGroup viewGroup) { if(view == null){ view = mInflater.inflate(R.layout.listview_item_layout,null); holder = new ViewHolder(); holder.button = (Button) view.findViewById(R.id.btn_test); view.setTag(holder); }else{ holder = (ViewHolder) view.getTag(); } holder.button.setText(listData[i]); return view; } ViewHolder holder; static class ViewHolder{ public Button button; } }
主Activity:this
package qu.com.handlerthread; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.widget.ListView; public class ListViewActivity extends AppCompatActivity { private static final String[] datas = {"張三","李四","王五"}; private ListView mListView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_list_view); mListView = (ListView) findViewById(R.id.mListView); mListView.setAdapter(new MyAdapter(datas,this)); } }
咱們主要關注getView裏面的inflate那行代碼:下面我依次把getView裏的寫成:
一、convertView = mInflater.inflate(R.layout.item, null);
二、convertView = mInflater.inflate(R.layout.item, parent ,false);
三、convertView = mInflater.inflate(R.layout.item, parent ,true);spa
分別看效果圖:code
效果圖1:convertView = mInflater.inflate(R.layout.listview_item_layout, null);xml
效果圖2:convertView = mInflater.inflate(R.layout.listview_item_layout, parent ,false);
咱們如今只經過運行1,2效果圖就能夠看出,這兩種方式加載的佈局明顯不一樣,這與咱們上面的所說的不同。真的是這樣嗎?等下揭曉。
效果圖3:convertView = mInflater.inflate(R.layout.listview_item_layout, parent ,true);
看到第三種狀況,你是否是感到特別的驚訝,沒錯,就是報錯了。
從這三幅效果圖能夠看出,這三種方式加載的佈局現實後效果徹底不同。
inflate(R.layout.listview_item_layout, null)的確不能正確處理寬高,可是並不是和
inflate(R.layout.listview_item_layout, parent ,false)顯示的效果相同,而它能夠完美的顯示正確的寬高,
對於inflate(R.layout.listview_item_layout, parent ,true)卻報錯了,等下咱們源碼來分析爲啥會報錯。
由此可知:咱們前面所說的3個結論明顯有錯誤。
下面咱們經過源碼來解釋這三種加載方式的差別
這三個方法,最終都會執行下面的代碼:
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("<merge /> 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; } }
1.首先拿到咱們加載XML文件的屬性值
2.聲明View result = root; 最終值返回result。
View temp; if (TAG_1995.equals(name)) { temp = new BlinkLayout(mContext, attrs); } else { temp = createViewFromTag(root, name, attrs); }
在這裏咱們建立了View;
if(root!=null) { params = root.generateLayoutParams(attrs); if (!attachToRoot) { temp.setLayoutParams(params); } }
能夠看到,當root不爲null,attachToRoot爲false時,爲temp設置了LayoutParams.
繼續往下看:
if (root != null && attachToRoot) { root.addView(temp, params); }
能夠看到,當root不爲null,attachToRoot爲true時,將temp按照params添加到root中
if (root == null || !attachToRoot) { result = temp; }
能夠看到,當root爲null,直接將temp賦值給result;最後返回result。
從上面的分析已經能夠看出:
Inflate(resId , null ) 只建立temp ,返回temp
Inflate(resId , parent, false )建立temp,而後執行temp.setLayoutParams(params);返回temp
Inflate(resId , parent, true ) 建立temp,而後執行root.addView(temp, params);最後返回root
由上面已經可以解釋:
Inflate(resId , null )不能正確處理寬和高是由於:layout_width,layout_height是相對了父級設置的,必須與父級的LayoutParams一致。而此temp的getLayoutParams爲null
Inflate(resId , parent,false ) 能夠正確處理,由於temp.setLayoutParams(params);這個params正是root.generateLayoutParams(attrs);獲得的。
Inflate(resId , parent,true )不只可以正確的處理,並且已經把resId這個view加入到了parent,而且返回的是parent,和以上二者返回值有絕對的區別,還記得文章前面的例子上,MyAdapter裏面的getView報的錯誤:
這是由於源碼中調用了root.addView(temp, params);而此時的root是咱們的ListView,ListView爲AdapterView的子類:
直接看AdapterView的源碼:
@Override public void addView(View child) { throw new UnsupportedOperationException("addView(View) is not supported in AdapterView"); }
看到這裏就能解決我以前出現的異常呢。
主Activity的佈局文件:
<?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/mTextView" android:layout_width="120dp" android:layout_height="120dp" android:text="測試" android:textSize="30sp" android:gravity="center" > </TextView>
主Activity:
public class ListViewActivity extends AppCompatActivity { private LayoutInflater mInflater; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_list_view); mInflater = LayoutInflater.from(this); View testView1 = mInflater.inflate(R.layout.activity_list_view,null); View testView2 = mInflater.inflate(R.layout.activity_list_view, (ViewGroup)findViewById(android.R.id.content),false); View testView3 = mInflater.inflate(R.layout.activity_list_view, (ViewGroup)findViewById(android.R.id.content),true); Log.e("le","testView1="+testView1 +"testView1.layoutParams="+testView1.getLayoutParams()); Log.e("le","testView2="+testView1 +"testView2.layoutParams="+testView2.getLayoutParams()); Log.e("le","testView3="+testView1 +"testView3.layoutParams="+testView3.getLayoutParams()); } }
能夠看到咱們的主Activity並無執行setContentView,僅僅執行了LayoutInflater的3個方法。
注:parent咱們用的是Activity的內容區域:即android.R.id.content,是一個FrameLayout,咱們在setContentView(resId)時,其實系統會自動爲了包上一層FrameLayout(id=content)。
按照咱們上面的說法:
view1的layoutParams 應該爲null
view2的layoutParams 應該不爲null,且爲FrameLayout.LayoutParams
view3爲FrameLayout,且將這個button添加到Activity的內容區域了(由於R.id.content表明Actvity內容區域)
下面看一下輸出結果,和Activity的展現:
可見,雖然咱們沒有執行setContentView,可是依然能夠看到繪製的控件,是由於
View testView3 = mInflater.inflate(R.layout.activity_list_view,
(ViewGroup)findViewById(android.R.id.content),true)
這個方法內部已經執行了root.addView(temp , params); 上面已經解析過了。
也能夠看出:和咱們的推測徹底一致,到此已經徹底說明了inflate3個重載的方法的區別。相信你們之後在使用時也能選擇出最好的方式。不過下面準備從ViewGroup和View的角度來講一下,爲啥layoutParams爲null,就不能這確的處理。
若是你們對自定義ViewGroup和自定義View有必定的掌握,確定不會對onMeasure方法陌生:
ViewGroup的onMeasure方法所作的是:
爲childView設置測量模式和測量出來的值。
如何設置呢?就是根據LayoutParams。
若是childView的寬爲:LayoutParams. MATCH_PARENT,則設置模式爲MeasureSpec.EXACTLY,且爲childView計算寬度。
若是childView的寬爲:固定值(即大於0),則設置模式爲MeasureSpec.EXACTLY,且將lp.width直接做爲childView的寬度。
若是childView的寬爲:LayoutParams. WRAP_CONTENT,則設置模式爲:MeasureSpec.AT_MOST
高度與寬度相似。
View的onMeasure方法:
主要作的就是根據ViewGroup傳入的測量模式和測量值,計算本身應該的寬和高:
通常是這樣的流程:
若是寬的模式是AT_MOST:則本身計算寬的值。
若是寬的模式是EXACTLY:則直接使用MeasureSpec.getSize(widthMeasureSpec);
對於最後一塊,若是不清楚,沒關係,之後我會在自定義ViewGroup和自定義View時詳細講解的。
大概就是這樣的流程,真正的繪製過程確定比這個要複雜,就是爲了說明若是View的寬和高若是設置爲準確值,則必定依賴於LayoutParams,因此咱們的inflate(resId,null)纔沒能正確處理寬和高。