Android LayoutInflater深度解析 给你带来全新的认识

转载请标明出处:, 本文出自:1、题外话

相信大家对LayoutInflate都不陌生,特别在ListView的Adapter的getView方法中基本都会出现,使用inflate方法去加载一个布局,用于ListView的每个Item的布局。Inflate有三个参数,我在初学Android的时候这么理解的:

对于Inflate的三个参数(int resource, ViewGroup root, boolean attachToRoot)

如果inflate(layoutId, null )则layoutId的最外层的控件的宽高是没有效果的

如果inflate(layoutId, root, false ) 则认为和上面效果是一样的

如果inflate(layoutId, root, true ) 则认为这样的话layoutId的最外层控件的宽高才能正常显示

如果你也这么认为,那么你有就必要好好阅读这篇文章,因为这篇文章首先会验证上面的理解是错误的,然后从源码角度去解释,最后会从ViewGroup与View的角度去解释。

2、实践是验证真理的唯一标准

下面我写一个特别常见的例子来验证上面的理解是错误的,一个特别简单的ListView,每个Item中放一个按钮:

Activity的布局文件:

<ListView xmlns:android=""xmlns:tools=""android:id="@+id/id_listview"android:layout_width="fill_parent"android:layout_height="wrap_content" ></ListView>ListView的Item的布局文件:<Button xmlns:android=""xmlns:tools=""android:id="@+id/id_btn"android:layout_width="120dp"android:layout_height="120dp" ></Button>

ListView的适配器:

package com.example.zhy_layoutinflater;import java.util.List;import android.content.Context;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.BaseAdapter;import android.widget.Button;public class MyAdapter extends BaseAdapter{private LayoutInflater mInflater;private List<String> mDatas;public MyAdapter(Context context, List<String> datas){mInflater = LayoutInflater.from(context);mDatas = datas;}@Overridepublic int getCount(){return mDatas.size();}@Overridepublic Object getItem(int position){return mDatas.get(position);}@Overridepublic long getItemId(int position){return position;}@Overridepublic View getView(int position, View convertView, ViewGroup parent){ViewHolder holder = null;if (convertView == null){holder = new ViewHolder();convertView = mInflater.inflate(R.layout.item, null);//convertView = mInflater.inflate(R.layout.item, parent ,false);//convertView = mInflater.inflate(R.layout.item, parent ,true);holder.mBtn = (Button) convertView.findViewById(R.id.id_btn);convertView.setTag(holder);} else{holder = (ViewHolder) convertView.getTag();}holder.mBtn.setText(mDatas.get(position));return convertView;}private final class ViewHolder{Button mBtn;}}主Activity:package com.example.zhy_layoutinflater;import java.util.Arrays;import java.util.List;import android.app.Activity;import android.os.Bundle;import android.widget.ListView;public class MainActivity extends Activity{private ListView mListView;private MyAdapter mAdapter;private List<String> mDatas = Arrays.asList("Hello", "Java", "Android");@Overrideprotected void onCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mListView = (ListView) findViewById(R.id.id_listview);mAdapter = new MyAdapter(this, mDatas);mListView.setAdapter(mAdapter);}}好了,相信大家对这个例子都再熟悉不过了,没啥好说的,我们主要关注getView里面的inflate那行代码:下面我依次把getView里的写成:1、convertView = mInflater.inflate(R.layout.item, null);2、convertView = mInflater.inflate(R.layout.item, parent ,false);3、convertView = mInflater.inflate(R.layout.item, parent ,true);分别看效果图:图1:

图2:

图3:

FATAL EXCEPTION: mainjava.lang.UnsupportedOperationException: addView(View, LayoutParams) is not supported in AdapterView嗯,没错没有图3,第三种写法会报错。

由上面三行代码的变化,产生3个不同的结果,可以看到

inflater(resId, null )的确不能正确处理宽高的值,但是inflater(resId,parent,false)并非和inflater(resId, null )效果一致,它可以看出完美的显示了宽和高。

而inflater(resId,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 xmlView 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 suppliedparams = 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 temprInflate(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;}}第6行:首先声明了View result = root ;//最终返回值为result

第43行执行了:temp = createViewFromTag(root, name, attrs);创建了View

所有的胜利,与征服自己的胜利比起来,都是微不足道

Android LayoutInflater深度解析 给你带来全新的认识

相关文章:

你感兴趣的文章:

标签云: