在android开发中相信大家对ListView、GridView等组建都很熟悉,在使用它们的时候需要自己配置相关的Adapter,并且配置现骨干的xml文件作为ListView等组建的子View,这些xml文件在Adapter的getView方法中调用。例如:
public View getView(int position,View convertView,ViewGroup parent) { if(convertView==null) { convertView = App.getLayoutInflater().inflate(R.layout.item,null); } return convertView; }item.xml文件如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="400dp" android:background="#008000" > <ImageView android:src="@drawable/ic_launcher" android:layout_width="match_parent" android:layout_height="match_parent" > </ImageView> </RelativeLayout>
用上面的方法会发现无论根View也就是RelativeLayout的layout_width和layout_height设置多大,运行效果始终都是一样的;也就是说此时你想通过RelativeLayout来改变里面子ImageView的大小是行不通的,通常用的解决方式就是里面在添加一个View把ImageView包裹起来,通过设置该View的大小来改变ImageView的大小(注意不一定是ImageView,也可能里面包含了若干个view):
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="400dp" android:background="#008000" > <RelativeLayout android:layout_width="900dp" android:layout_height="200dp" android:background="@android:color/black"> <ImageView android:src="@drawable/ic_launcher" android:layout_width="match_parent" android:layout_height="match_parent" > </ImageView> </RelativeLayout> </RelativeLayout>虽然找到了解决方法,但是知其然还要知其所以然,为什么好这样呢?在分析之前要弄清一个概念性的问题:
layout_width不是width!layout_height不是height!也就是说这两个属性设置的并不是View的宽和高,layout是布局的意思,也即是说这两个属性是View在布局中的宽和高!既然是布局,肯定都有个放置该View的地方,也就是说有一个媒介来放置View,并且在该媒介上划出一个layout_width和layout_heigth的大小来放置该View。如果没有该媒介View布局在哪儿呢?所以说为了上面的问题的根本原因就是因为你没有为xml文件设置一个布局媒介(该媒介也是个View,也即是rootView),所以为了保障你的item.xml中根View的layout_width和layout_heigth能起作用,需要设置一个这样的媒介。代码inflate(int,ViewGroup root)中这个root就是这样的一个媒介,但是通常传递的都是null,所以item.xml文件的根View是没有媒介作为布局依据的,所以不起作用;既然问题的原因找到了那么就可以用一个笨的方法来解决这个问题:为inflate提供一个root,在代码里面我简单的做了一下处理,验证了自己的想法:
public View getView(int position,ViewGroup parent) { if(convertView==null) { convertView = App.getLayoutInflater().inflate(R.layout.item,null); //手动设置一个root,此时在设置layout_width或者layout_height就会起作用了 convertView = App.getLayoutInflater().inflate(R.layout.item,(ViewGroup)convertView); } return convertView; }之所以是笨方法,因为它把item做了两次的xml解析,因为inflate调用了两次。当然此时的item是:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="400dp" android:background="#008000" > <ImageView android:src="@drawable/ic_launcher" android:layout_width="match_parent" android:layout_height="match_parent" > </ImageView> </RelativeLayout>
既然跟inflate第二个参数root有关,那么看看root都干了些什么,追踪源代码最终解析xml的方法代码如下:
//切记,此时root为null,attachToRoot在源码中为root!=null,所以此处为false public View inflate(XmlPullParser parser,ViewGroup root,boolean attachToRoot) { synchronized (mConstructorArgs) { ...... View result = root; //根View,为null try { .... final String name = parser.getName(); //节点名,如果是自定义View的话 就是全限定名 //该if语句暂不用看 if (TAG_MERGE.equals(name)) { // 处理<merge />标签 ... } else { // Temp is the root view that was found in the xml //这个是xml文件对应的那个根View,在item.xml文件中就是RelativeLayout View temp = createViewFromTag(name,attrs); ViewGroup.LayoutParams params = null; //因为root==null,if条件不成立 if (root != null) { // Create layout params that match root,if supplied //根据AttributeSet属性获得一个LayoutParams实例,记住调用者为root。 params = root.generateLayoutParams(attrs); if (!attachToRoot) { //重新设置temp的LayoutParams // 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 //遍历temp下所有的子节点,也就是xml文件跟文件中的所有字View rInflate(parser,temp,attrs); //把xml文件的根节点以及根节点中的子节点的view都添加到root中,当然此时root等于null的情况下此处是不执行的 if (root != null && attachToRoot) { root.addView(temp,params); } //如果根节点为null,就直接返回xml中根节点以及根节点的子节点组成的View if (root == null || !attachToRoot) { result = temp; } } } ... return result; } }
通过分析上面的代码我们可以发现:当root为null的时候,inflate直接返回的是xml文件生成的View,此时返回的View根View就是xml文件的根节点。此时该View没有存在任何的布局中,所以根节点的layout_height和layout_width没有依附的媒介,导致设置这两个属性是无效的。但是如果root不为null的话,inflate返回的View是这么一个View,首先把xml解析成一个View,然后把该View通过root.addView添加到root里面去,然后直接返回root。也就是说此时xml文件中的根节点的layout_width和layout_height就是在root中的布局的宽和高,所以此时这两个属性是有效果的,同时到现在你应该能够理解getView第三个参数parent所代表的含义了,所以上述手动添加root的代码也可以改成这样:
public View getView(int position,ViewGroup parent) { if(convertView==null) { //parent为单独的xml文件,用item.xml测试也可以达到效果 <pre name="code" class="java"> parent= App.getLayoutInflater().inflate(R.layout.parent,parent); } return convertView; }不过写到这里我有个疑问的地方,getView方法的调用是在AbsListView里面的obtainView方法里面调用的,obtainView在GridView的父类AbsListView里面定义的,并且传递的第三个参数为this:
final View child = mAdapter.getView(position,scrapView,this);既然这样为什么不直接在getView方法里面把parent直接作为参数传进去呢?不需要parent=inflate(layoutId,null)了,但是使用的效果是程序运行会抛出异常:java.lang.UnsupportedOperationException: addView(View,LayoutParams) is not supported in AdapterView,
查看源码可以知道GridView的父类AdapterView又把View里的addView及其重载方法都给重写了一遍,重写的只是简单的抛出一个异常让抛出了一个异常:
public void addView(View child,LayoutParams params) { throw new UnsupportedOperationException("addView(View,LayoutParams) " + "is not supported in AdapterView"); }
所以这就知道为什么parent不能直接传给inflate了,通过上面的分析,infate最终会在root不为null的情况下执行如下代码:
//把xml文件的根节点以及根节点中的子节点的view都添加到root中 if (root != null && attachToRoot) { //此处正是抛出异常的地方 root.addView(temp,params); }所以会抛出异常也不会见怪了!所以说在这里我比较怀疑,既然在AbsListView里面把adapter.getView的参数传递this,但是为什么又屏蔽了addView方法,这点是我现在比较疑惑的地方,水平有限暂不分析了!希望有高手看到此处告知一二,感激不尽!
注意,在Activity中我们经常调用setContentView(int layoutResourceId)来设置Activity的View,,为什么这些xml文件根节点的layout_width和layout_height有效果呢?且看下面的分析,分析源码发现setContentView实际上也是调用了inflate方法来对xml文件进行解析:
//Activity中有一个window引用,在Activity的setContentView中会调用window.setContentView,该引用指向的对象是PhoneWindow,此段代码就是PhoneWindow类中的代码 @Override public void setContentView(int layoutResID) { if (mContentParent == null) { //该方法中会对mContentParent进行初始化,保证它不会null installDecor(); } else { mContentParent.removeAllViews(); } //此处代码熟悉了吧,正式像上面的Adapter中一样,只不过此时root不为null!!!! mLayoutInflater.inflate(layoutResID,mContentParent); final Callback cb = getCallback(); if (cb != null) { cb.onContentChanged(); } }可以发现该方法中有一个mContentParent,首先判断mContentParent是否为null,如果为null的话会在installDecor()方法中对它进行初始化,也就是说mContentParent始终可以得到初始化,紧接着会调用inflate(layoutResId,mContentParent)方法,可以看到此时inflate中第二个代表root的参数为mContentparent,且不为null。然后执行该方法对该xml文件进行解析,最终还会执行上面的inlfate(XmlPullParser,root,attachToRoot)方法中去,根据上面的分析,所以可得出结论在Activity中xml的根节点设置layout_width或者layout_height是有效果的!