【Android开发经验】LayoutInflater——80%的Android程序员对它并不了解甚至错误使用

这个标题起的有点夸张哈,但是LayoutInflater这个类的一些用法,在Android开发者使用的过程中,确实存在着一些很普遍的误区,最起码我研究的这么多小项目的源代码,基本上都在错误的使用这个类。今天,看到了一篇文章讲LayoutInflater的用法,瞬间感觉自己对这个类确实不够了解,于是简单的看了下LayoutInflater类的源代码,对这个类有了新的认识。

首先,LayoutInflater这个类是用来干嘛的呢?

我们最常用的便是LayoutInflater的inflate方法,这个方法重载了四种调用方式,分别为:

1. public View inflate(int resource, ViewGroup root)

2. public View inflate(int resource, ViewGroup root, boolean attachToRoot)

3.public View inflate(XmlPullParser parser, ViewGroup root)

4.public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)

这四种使用方式中,我们最常用的是第一种方式,inflate方法的主要作用就是将xml转换成一个View对象,用于动态的创建布局。虽然重载了四个方法,但是这四种方法最终调用的,还是第四种方式。第四种方式也很好理解,内部实现原理就是利用Pull解析器,对Xml文件进行解析,然后返回View对象。

我们以我们经常使用的第一种形式为例,你在重写BaseAdapter的getView方法的时候是否这样做过

  1. public View getView(int position, View convertView, ViewGroup parent) { 
  2.     if (convertView == null) { 
  3.         convertView = inflate(R.layout.item_row, null); 
  4.     } 
  5.     return convertView; 
public View getView(int position, View convertView, ViewGroup parent) {
    if (convertView == null) {
        convertView = inflate(R.layout.item_row, null);
    }
    return convertView;
}
inflate方法有三个参数,分别是

1.resource布局的资源id

2.root填充的根视图

3.attachToRoot是否将载入的视图绑定到根视图中

在这个例子中,我们将root参数设为空,功能确实实现了,但是这里还隐藏着一个隐患,这种方式并不是inflate正确的使用姿势,下面我们通过一个Demo,来说一下这样使用造成的弊端。

首先,我们建立一个这样的项目


这里三个界面,一个主界面,两个测试界面,布局文件中,主界面只负责界面跳转,两个测试界面都是一个简单的Listview,item布局显示效果如下



对应的布局文件如下

  1. <?xml version="1.0" encoding="utf-8"?> 
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
  3.     android:layout_width="match_parent" 
  4.     android:layout_height="60dp" 
  5.     android:background="@android:color/holo_orange_light" 
  6.     android:gravity="center" 
  7.     android:orientation="vertical" > 
  8.  
  9.     <TextView 
  10.         android:id="@+id/tv" 
  11.         android:layout_width="wrap_content" 
  12.         android:layout_height="wrap_content" 
  13.         android:text="11" 
  14.         android:textColor="@android:color/black" 
  15.         android:textSize="22sp" /> 
  16.  
  17. </LinearLayout> 
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="60dp"
    android:background="@android:color/holo_orange_light"
    android:gravity="center"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="11"
        android:textColor="@android:color/black"
        android:textSize="22sp" />

</LinearLayout>

OneActivity的代码如下

  1. public class OneActivity extends Activity { 
  2.  
  3.     private ListView list1; 
  4.  
  5.     @Override 
  6.     protected void onCreate(Bundle savedInstanceState) { 
  7.         super.onCreate(savedInstanceState); 
  8.         setContentView(R.layout.activity_one); 
  9.         list1 = (ListView) findViewById(R.id.list1); 
  10.         list1.setAdapter(new MyAdapter(this)); 
  11.     } 
  12.  
  13.     private class MyAdapter extends BaseAdapter { 
  14.  
  15.         private LayoutInflater inflater; 
  16.  
  17.         MyAdapter(Context context) { 
  18.             inflater = LayoutInflater.from(context); 
  19.         } 
  20.  
  21.         @Override 
  22.         public int getCount() { 
  23.             return 20
  24.         } 
  25.  
  26.         @Override 
  27.         public Object getItem(int position) { 
  28.             return position; 
  29.         } 
  30.  
  31.         @Override 
  32.         public long getItemId(int position) { 
  33.             return position; 
  34.         } 
  35.  
  36.         @Override 
  37.         public View getView(int position, View convertView, ViewGroup parent) { 
  38.  
  39.             if (convertView == null) { 
  40.                 convertView = inflater.inflate(R.layout.item_list, null); 
  41.             } 
  42.             TextView tv = (TextView) convertView.findViewById(R.id.tv); 
  43.             tv.setText(position+""); 
  44.             return convertView; 
  45.         } 
  46.  
  47.     } 
  48.  
public class OneActivity extends Activity {

	private ListView list1;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_one);
		list1 = (ListView) findViewById(R.id.list1);
		list1.setAdapter(new MyAdapter(this));
	}

	private class MyAdapter extends BaseAdapter {

		private LayoutInflater inflater;

		MyAdapter(Context context) {
			inflater = LayoutInflater.from(context);
		}

		@Override
		public int getCount() {
			return 20;
		}

		@Override
		public Object getItem(int position) {
			return position;
		}

		@Override
		public long getItemId(int position) {
			return position;
		}

		@Override
		public View getView(int position, View convertView, ViewGroup parent) {

			if (convertView == null) {
				convertView = inflater.inflate(R.layout.item_list, null);
			}
			TextView tv = (TextView) convertView.findViewById(R.id.tv);
			tv.setText(position+"");
			return convertView;
		}

	}

}

TwoActivity的代码如下

  1. public class TwoActivity extends Activity { 
  2.     private ListView list2; 
  3.  
  4.     @Override 
  5.     protected void onCreate(Bundle savedInstanceState) { 
  6.         super.onCreate(savedInstanceState); 
  7.         setContentView(R.layout.activity_two); 
  8.         list2 = (ListView) findViewById(R.id.list2); 
  9.         list2.setAdapter(new MyAdapter(this)); 
  10.     } 
  11.  
  12.     private class MyAdapter extends BaseAdapter { 
  13.  
  14.         private LayoutInflater inflater; 
  15.  
  16.         MyAdapter(Context context) { 
  17.             inflater = LayoutInflater.from(context); 
  18.         } 
  19.  
  20.         @Override 
  21.         public int getCount() { 
  22.             return 20
  23.         } 
  24.  
  25.         @Override 
  26.         public Object getItem(int position) { 
  27.             return position; 
  28.         } 
  29.  
  30.         @Override 
  31.         public long getItemId(int position) { 
  32.             return position; 
  33.         } 
  34.  
  35.         @Override 
  36.         public View getView(int position, View convertView, ViewGroup parent) { 
  37.  
  38.             if (convertView == null) { 
  39.                 convertView = inflater.inflate(R.layout.item_list, parent,false); 
  40.             } 
  41.             TextView tv = (TextView) convertView.findViewById(R.id.tv); 
  42.             tv.setText(position + ""); 
  43.             return convertView; 
  44.         } 
  45.  
  46.     } 
  47.  
public class TwoActivity extends Activity {
	private ListView list2;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_two);
		list2 = (ListView) findViewById(R.id.list2);
		list2.setAdapter(new MyAdapter(this));
	}

	private class MyAdapter extends BaseAdapter {

		private LayoutInflater inflater;

		MyAdapter(Context context) {
			inflater = LayoutInflater.from(context);
		}

		@Override
		public int getCount() {
			return 20;
		}

		@Override
		public Object getItem(int position) {
			return position;
		}

		@Override
		public long getItemId(int position) {
			return position;
		}

		@Override
		public View getView(int position, View convertView, ViewGroup parent) {

			if (convertView == null) {
				convertView = inflater.inflate(R.layout.item_list, parent,false);
			}
			TextView tv = (TextView) convertView.findViewById(R.id.tv);
			tv.setText(position + "");
			return convertView;
		}

	}

}

两个文件最关键的区别就一句话,

在getView方法中,OneActivity是

convertView = inflater.inflate(R.layout.item_list, null);

在getView方法中,TwoActivity是

convertView = inflater.inflate(R.layout.item_list, parent,false);

我们先看一下显示效果,再说两者的区别

OneActivity效果



TwoActivity的显示效果



我们可以很明显的看出来,使用第一种方式,根布局的高度设置60dp没有起作用,系统还是按照包裹内容的方式加载的,为什么会产生这种效果呢?我们从需要inflate方法的源代码中找一下答案。

首先,方式一的源代码实现

  1. public View inflate(XmlPullParser parser, ViewGroup root) { 
  2.         return inflate(parser, root, root != null); 
  3.     } 
public View inflate(XmlPullParser parser, ViewGroup root) {
        return inflate(parser, root, root != null);
    }

当我们使用方式一,并且第二个参数传入null的时候,默认调用的是下面的方法,并且attachToRoot是false

  1. public View inflate(int resource, ViewGroup root, boolean attachToRoot) { 
  2.         if (DEBUG) System.out.println("INFLATING from resource: " + resource); 
  3.         XmlResourceParser parser = getContext().getResources().getLayout(resource); 
  4.         try
  5.             return inflate(parser, root, attachToRoot); 
  6.         } finally
  7.             parser.close(); 
  8.         } 
  9.     } 
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();
        }
    }

在这一个方法中,pull解析器将资源id转化成XmlResourceParser对象,又传给了第四种方式,所以我们需要重点看的还是第四种方式是如何实现的

  1. public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) { 
  2.         synchronized (mConstructorArgs) { 
  3.             Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate"); 
  4.  
  5.             final AttributeSet attrs = Xml.asAttributeSet(parser); 
  6.             Context lastContext = (Context)mConstructorArgs[0]; 
  7.             mConstructorArgs[0] = mContext; 
  8.             View result = root; 
  9.  
  10.             try
  11.                 // Look for the root node. 
  12.                 int type; 
  13.                 while ((type = parser.next()) != XmlPullParser.START_TAG && 
  14.                         type != XmlPullParser.END_DOCUMENT) { 
  15.                     // Empty 
  16.                 } 
  17.  
  18.                 if (type != XmlPullParser.START_TAG) { 
  19.                     throw new InflateException(parser.getPositionDescription() 
  20.                             + ": No start tag found!"); 
  21.                 } 
  22.  
  23.                 final String name = parser.getName(); 
  24.                  
  25.                 if (DEBUG) { 
  26.                     System.out.println("**************************"); 
  27.                     System.out.println("Creating root view: " 
  28.                             + name); 
  29.                     System.out.println("**************************"); 
  30.                 } 
  31.  
  32.                 if (TAG_MERGE.equals(name)) { 
  33.                     if (root == null || !attachToRoot) { 
  34.                         throw new InflateException("<merge /> can be used only with a valid " 
  35.                                 + "ViewGroup root and attachToRoot=true"); 
  36.                     } 
  37.  
  38.                     rInflate(parser, root, attrs, false); 
  39.                 } else
  40.                     // Temp is the root view that was found in the xml 
  41.                     View temp; 
  42.                     if (TAG_1995.equals(name)) { 
  43.                         temp = new BlinkLayout(mContext, attrs); 
  44.                     } else
  45.                         temp = createViewFromTag(root, name, attrs); 
  46.                     } 
  47.  
  48.                     ViewGroup.LayoutParams params = null
  49.  
  50.                     if (root != null) { 
  51.                         if (DEBUG) { 
  52.                             System.out.println("Creating params from root: "
  53.                                     root); 
  54.                         } 
  55.                         // Create layout params that match root, if supplied 
  56.                         params = root.generateLayoutParams(attrs); 
  57.                         if (!attachToRoot) { 
  58.                             // Set the layout params for temp if we are not 
  59.                             // attaching. (If we are, we use addView, below) 
  60.                             temp.setLayoutParams(params); 
  61.                         } 
  62.                     } 
  63.  
  64.                     if (DEBUG) { 
  65.                         System.out.println("-----> start inflating children"); 
  66.                     } 
  67.                     // Inflate all children under temp 
  68.                     rInflate(parser, temp, attrs, true); 
  69.                     if (DEBUG) { 
  70.                         System.out.println("-----> done inflating children"); 
  71.                     } 
  72.  
  73.                     // We are supposed to attach all the views we found (int temp) 
  74.                     // to root. Do that now. 
  75.                     if (root != null && attachToRoot) { 
  76.                         root.addView(temp, params); 
  77.                     } 
  78.  
  79.                     // Decide whether to return the root that was passed in or the 
  80.                     // top view found in xml. 
  81.                     if (root == null || !attachToRoot) { 
  82.                         result = temp; 
  83.                     } 
  84.                 } 
  85.  
  86.             } catch (XmlPullParserException e) { 
  87.                 InflateException ex = new InflateException(e.getMessage()); 
  88.                 ex.initCause(e); 
  89.                 throw ex; 
  90.             } catch (IOException e) { 
  91.                 InflateException ex = new InflateException( 
  92.                         parser.getPositionDescription() 
  93.                         + ": " + e.getMessage()); 
  94.                 ex.initCause(e); 
  95.                 throw ex; 
  96.             } finally
  97.                 // Don't retain static reference on context. 
  98.                 mConstructorArgs[0] = lastContext; 
  99.                 mConstructorArgs[1] = null
  100.             } 
  101.  
  102.             Trace.traceEnd(Trace.TRACE_TAG_VIEW); 
  103.  
  104.             return result; 
  105.         } 
  106.     } 
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

            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;
            }

            Trace.traceEnd(Trace.TRACE_TAG_VIEW);

            return result;
        }
    }

代码比较长,我们重点关注下面的代码

  1. if (root != null) { 
  2.                         if (DEBUG) { 
  3.                             System.out.println("Creating params from root: "
  4.                                     root); 
  5.                         } 
  6.                         // Create layout params that match root, if supplied 
  7.                         params = root.generateLayoutParams(attrs); 
  8.                         if (!attachToRoot) { 
  9.                             // Set the layout params for temp if we are not 
  10.                             // attaching. (If we are, we use addView, below) 
  11.                             temp.setLayoutParams(params); 
  12.                         } 
  13.                     } 
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);
                        }
                    }

这些代码的意思就是,当我们传进来的root参数不是空的时候,并且attachToRoot是false的时候,也就是上面的TwoActivity的实现方式的时候,会给temp设置一个LayoutParams参数。那么这个temp又是干嘛的呢?

  1. <pre name="code" class="java">// We are supposed to attach all the views we found (int temp) 
  2.                     // to root. Do that now. 
  3.                     if (root != null && attachToRoot) { 
  4.                         root.addView(temp, params); 
  5.                     } 
  6.  
  7.                     // Decide whether to return the root that was passed in or the 
  8.                     // top view found in xml. 
  9.                     if (root == null || !attachToRoot) { 
  10.                         result = temp; 
  11.                     } 
<pre name="code" class="java">// 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;
                    }


 

现在应该明白了吧,当我们传进来的root不是null,并且第三个参数是false的时候,这个temp就被加入到了root中,并且把root当作最终的返回值返回了。而当我们设置root为空的时候,没有设置LayoutParams参数的temp对象,作为返回值返回了。

因此,我们可以得出下面的结论:

1.若我们采用convertView = inflater.inflate(R.layout.item_list, null);方式填充视图,item布局中的根视图的layout_XX属性会被忽略掉,然后设置成默认的包裹内容方式

2.如果我们想保证item的视图中的参数不被改变,我们需要使用convertView = inflater.inflate(R.layout.item_list, parent,false);这种方式进行视图的填充

3.除了使用这种方式,我们还可以设置item布局的根视图为包裹内容,然后设置内部控件的高度等属性,这样就不会修改显示方式了。


最后,给出那篇文章的链接http://blog.jobbole.com/72156/大家可以去看看

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值