ScrollView巢狀GridView、ListView的那些事兒
在android開發中,經常有一些UI需要進行固定style的動態佈局,然而由於現在的UI都喜歡把一個介面拉的很長,所以我們很多情況下需要使用ScrollView來巢狀列表控制元件來實現UI。這樣就導致了很多不順心的問題。
問題一:列表控制元件顯示不完全
原因是巢狀情況下,ScrollView不能正確的計算列表控制元件的高度。
有兩種解決方案
方案一
在介面卡賦值完成後程式碼動態計算列表的高度。這裡貼出ListView的計算程式碼,GridView的計算方式類似,不過需要考慮列數,下面程式碼沒有加上列表控制元件padding的計算,如果你設定了這個屬性,需要加上計算程式碼
public void setListViewHeightBasedOnChildren(ListView listView) {
// 獲取ListView對應的Adapter
ListAdapter listAdapter = listView.getAdapter();
if (listAdapter == null) {
return;
}
int totalHeight = 0;
for (int i = 0, len = listAdapter.getCount(); i < len; i++) {
// listAdapter.getCount()返回資料項的數目
View listItem = listAdapter.getView(i, null, listView);
// 計運算元項View 的寬高
listItem.measure(0, 0);
// 統計所有子項的總高度
totalHeight += listItem.getMeasuredHeight();
}
ViewGroup.LayoutParams params = listView.getLayoutParams();
params .height = totalHeight+ (listView.getDividerHeight() * (listAdapter.getCount() - 1));
// listView.getDividerHeight()獲取子項間分隔符佔用的高度
// params.height最後得到整個ListView完整顯示需要的高度
listView.setLayoutParams(params);
}
方案二
重寫列表控制元件的onMeasure方法,這種方案不會出現列表控制元件本身的滾動條,並且viewholder複用機制會失效
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(1 << 16, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
方案一程式碼多,需要多次寫,建議寫成工具類方便呼叫;方案二在資料量大到不能一屏顯示完的情況下會有效能問題,而且快速滑動的時候ScrollView會不停的去計算列表控制元件的高度。賊影響繪製效能。
兩種方案有利有弊,大家自己取捨
問題二:列表控制元件自動獲取焦點,導致ScrollView自動滾動到列表控制元件所在的位置
這個問題其實有很多種解決方案,歸結起來是兩種。
方案一
等待列表控制元件資料全部載入完成後(包括圖片載入)呼叫ScrollView.fullScroll(ScrollView.FOCUS_UP);
方法讓ScrollView滾動到頂部。這個載入完成的時間不好控制,搞得不好會有滾動動畫出現,很尷尬的事情。
方案二
重寫列表控制元件的如下兩個方法,使之固定返回false
@Override
public boolean isFocused() {
return false;
}
@Override
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
return false;
}
兩種方案的優缺點很明顯,喔,第二種方案的缺點我目前沒發現。如果你這麼使用發現了什麼坑,請留言告知
問題三 滑動衝突
這問題就更操蛋了,根據UI的不同,操蛋程度也不同,涉及到view的事件傳遞知識,很難給出所有情況的解決程式碼
解決起來也離不開幾個要點,不過首先你得熟悉view的事件傳遞
你需要根據情況決定重寫列表控制元件與ScrollView的如下幾個方法,根據情況給方法返回不同的bool值來告訴控制元件是否攔截或者傳遞事件,需要哪個控制元件相應哪個方向的滾動事件就攔截哪個方向的事件傳遞,作為一個有追求的開發者,切記不要一通亂攔截
@Override
public boolean onTouchEvent(MotionEvent ev) {
return super.onTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return super.dispatchTouchEvent(ev);
}
給個小彩蛋
getParent().requestDisallowInterceptTouchEvent(boolean b);
這一句程式碼可以在子控制元件裡決定是否讓父容器獲取事件
有什麼建議的可以留言喔
如果我的部落格對您有幫助,請留言鼓勵下或者點個贊吧!
我建了一個QQ群(群號:121606151),用於大家討論交流Android技術問題,有興趣的可以加下,大家一起進步。