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,如同解決ScrollView和ListView衝突的時候一種解決方法,這裡我們只討論其中一個衝突就是:導致ListView的高度顯示不完整,出現只顯示部分內容的情況。解決方法就是就是測量ListView的總高度,然後設定一下,關鍵程式碼如下。試想,如果這時候如果各個item是RelativeLayout的話,肯定會出錯。
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