zoukankan      html  css  js  c++  java
  • Android初始化的时候获取加载的布局的宽高(续)--RelativeLayout的陷阱

    接着上次的问题,已经介绍过,在初始化或者说OnCreate方法中获取加载的布局的宽高,最后说到,调用view.measure(0, 0);然后在调用getMeasuredWidth和getMeasuredHeight就可以获得测量的宽高。可以参考:Android如何在初始化的时候获取加载的布局的宽高
    今天在写类似的效果时,给ListView加载一个头部视图,通过listView$addHeadView添加到ListView的头部。为了描述起来简单起见,这个头部视图的布局我做了简化为下面:headview.xml

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
         >
        <ImageView
            android:id="@+id/arrow"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@drawable/information_top" />
    </RelativeLayout>

    然后在初始化的时候想获取该布局的宽高,就这样做了:

    //布局加载器
    	private LayoutInflater inflater;
    	//头部的View
    	private View headView;
    	//头部View的宽
    	private int headViewWidth;
    	//头部View的高
    	private int headViewHeight;
    inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    		headView = inflater.inflate(R.layout.headview, null);
    		headView.measure(0, 0);
    headViewWidth = headView.getMeasuredWidth();
    		headViewHeight = headView.getMeasuredHeight();
    		Log.i(TAG, "headViewWidth-->" + headViewWidth + "  headViewHeight-->" + headViewHeight);
    		addHeaderView(headView);

    结果。。。。。

    程序崩溃了!尼玛,不是说好的headView.measure(0, 0);调用完就能获取到布局的宽高了嘛,怎么这句好报错空指针异常了?

    然后把布局修改成线性布局

    <?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="match_parent"
         >
        <ImageView
            android:id="@+id/arrow"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@drawable/information_top" />
    </LinearLayout>

    竟然就好了。。。


    也获取到了测量的宽高:


    接下来又开始了对问题的研究。为了简化问题,还是按照上次的此路。在上次的基础上添加布局viewgroup_relativelayout.xml,其实就是将外层的LinearLayout修改成RelativeLayout了,其他地方未变。加载该布局,然后测量宽高。

    <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="match_parent"  
         >  
        <View  
            android:layout_width="50dp"  
            android:layout_height="50dp"  
             />  
    </RelativeLayout>

    看结果:


    View,textview和线性布局的ViewGroup和上次的结果一样。但是将相对布局的ViewGroup直接挂掉。。刚开始还是特别的不爽的,为什么经常遇到问题,不过一想,解决问题的过程就是水平提高的过程,所以打起精神进行研究。嘿嘿~

    通过比较发现RelativeLayout出错了,所以很容易将空指针异常的地方定位在RelativeLayout的onMeasure函数中。

    但是这个函数比较复杂了。两百多行代码,里面牵扯了很多的域变量和函数,读起来挺累的。因此在网上搜了好久,在eoe上面看到有人说了下面的方法:

    如果使用Inflater的情况下会出现以上错误,原因是用Inflater渲染组件的时候并没有给其指定父控件,所以渲染器不会去解析width 和 height属性,就会导致空指针异常。

    解决方法:

    view.setLayoutParams(newLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));指定宽度和高度,然后调用measure方法就OK。

    其实这个说法前面就研究过,确实是这样子的,但是LinearLayout为什么没出问题RelativeLayout就出问题了呢,所以还是没有说明白原因在哪里。

    我尝试了一下确实搞定了,原因尚不清楚,那就肯定需要查源代码了。在这个指导下给我提供了方向就是很可能在onMeasure方法里面调用了该相对布局的参数mLayoutParams,由于本来为null,那么调用的话就会报告空指针异常!

    接下来就在源码的onMeasure函数里面将所有出现mLayoutParams的地方打上断点。然后断点调试

    RelativeLayou类(我查看的是4.2的源码)

    //493行
    if (mLayoutParams.width >= 0) {
                    width = Math.max(width, mLayoutParams.width);
                }
    //523行
    if (mLayoutParams.height >= 0) {
                    height = Math.max(height, mLayoutParams.height);
                }

    果然mLayoutParams变量不存在为null。

    到现在为止问题就清楚了。由于调用headView.measure(0, 0);的时候是通过inflate(R.layout.headview, null);方式加载的布局,因此设置的外层RelativeLayout布局的LayoutParams是null的,恰巧相对布局没有检查是否为null就直接调用了mLayoutParams.width或者mLayoutParams.height,因此就报空指针错误了。这可能是一个bug吧我认为。

    既然知道了问题是出在了这里了,那么也很容易解决了。

    至少有以下的解决方法:

    1,加载布局的时候通过inflater.inflate(R.layout.viewgroup_relativelayout,(ViewGroup) findViewById(R.id.mainLayout),false);方式加载。这样的话会设置他的布局参数。

    2 手动设置布局参数添加上

    lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
    setLayoutParams(lp);


    现在再来看上一篇博客里面讲的这个方法:

    private void measureView(View child) {
    		ViewGroup.LayoutParams lp = child.getLayoutParams();
    		if(lp == null){
    			lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
    //			child.setLayoutParams(lp);
    		}
    		//headerView的宽度信息
    		int childMeasureWidth = ViewGroup.getChildMeasureSpec(0, 0, lp.width);
    		int childMeasureHeight;
    		if(lp.height > 0){
    			childMeasureHeight = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
    		} else {
    			childMeasureHeight = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);//未指定
    		}
    		//将宽和高设置给child
    		child.measure(childMeasureWidth, childMeasureHeight);
    	}

    看起来貌似就是为了避免外层布局是RelativeLayout的情况对LayoutParams做了判断!但是直接调用上面这个方法仍然不行,因为new出来的LayoutParams没有设置进去,所以还需要调用child.setLayoutParams(lp);然后就搞定了。经过测试ok!



    回到博客开始在ListView添加头View所遇到的问题也就很容易解决了。

    1,将相对布局换成线性布局。

    2,仍然使用相对布局,使用上面说的两种方法设置外层的相对布局的布局参数。比如使用下面的方法,在家在布局的时候带上父布局,结果搞定!好开心

    inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    		headView = inflater.inflate(R.layout.headview, this,false);		
    		headView.measure(0, 0);
    		//换成方法measureView (headView)也可以
    		headViewWidth = headView.getMeasuredWidth();
    		headViewHeight = headView.getMeasuredHeight();
    		Log.i(TAG, "headViewWidth-->" + headViewWidth + "  headViewHeight-->" + headViewHeight);
    		addHeaderView(headView);

    将headView.measure(0, 0);换成measureView (headView);也可以的。

    但是加载布局的时候不带上父布局,调用的是headView =inflater.inflate(R.layout.headview, null);即使用下面代码:

    inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    		headView = inflater.inflate(R.layout.headview, null);
    		//headView.measure(0, 0);
    		measureView(headView);
    		headViewWidth = headView.getMeasuredWidth();
    		headViewHeight = headView.getMeasuredHeight();
    		Log.i(TAG, "headViewWidth-->" + headViewWidth + "  headViewHeight-->" + headViewHeight);
    		addHeaderView(headView);

    程序竟然又崩溃了。看调试结果:


    不过不用担心,这时的错误不是空指针了,而是类转换异常,相信通过错误很容易修改了

    lp= new AbsListView.LayoutParams(AbsListView.LayoutParams.FILL_PARENT,AbsListView.LayoutParams.WRAP_CONTENT);

    然后在运行就好了,大功告成。最后的忠告就是需要自己需要测量布局宽高的时候慎用RelativeLayout,如同解决ScrollViewListView冲突的时候一种解决方法,这里我们只讨论其中一个冲突就是:导致ListView的高度显示不完整,出现只显示部分内容的情况。解决方法就是就是测量ListView的总高度,然后设置一下,关键代码如下。试想,如果这时候如果各个itemRelativeLayout的话,肯定会出错。

    private void setListViewHeight(ListView listView) {   
            ListAdapter listAdapter = listView.getAdapter();    
            if (listAdapter == null) {   
                return;   
            }   
            int totalHeight = 0;   
            for (int i = 0; i < listAdapter.getCount(); i++) {   
                View listItem = listAdapter.getView(i, null, listView);   
                listItem.measure(0, 0);   
                totalHeight += listItem.getMeasuredHeight();   
            }   
            ViewGroup.LayoutParams params = listView.getLayoutParams();   
            params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));   
            listView.setLayoutParams(params);  
        }  

    最后,这些东西可能有些人写程序的时候都从没遇到过,当然也不影响写出漂亮的APP。但是我认为不管是什么问题,都能锻炼解决问题的能力,都是有助于学习。

    文字表达能力太差,表述能力是硬伤啊!囧…刚开始写博客,感谢大家围观。



    测试加载各种类型布局的宽高参数的Demo和ListView添加头视图为相对布局的Demo

    点此下载代码








  • 相关阅读:
    BestCoder6 1002 Goffi and Squary Partition(hdu 4982) 解题报告
    codeforces 31C Schedule 解题报告
    codeforces 462C Appleman and Toastman 解题报告
    codeforces 460C. Present 解题报告
    BestCoder3 1002 BestCoder Sequence(hdu 4908) 解题报告
    BestCoder3 1001 Task schedule(hdu 4907) 解题报告
    poj 1195 Mobile phones 解题报告
    二维树状数组 探索进行中
    codeforces 460B Little Dima and Equation 解题报告
    通过Sql语句控制SQLite数据库增删改查
  • 原文地址:https://www.cnblogs.com/qhyuan1992/p/5385340.html
Copyright © 2011-2022 走看看