簡介
在我們開發(fā)項目過程中跷跪,我們在座絕大部分的人一定都遇到過ScrollVIew嵌套ListView顯示不全馋嗜,只顯示一個item高度的問題齐板,當(dāng)然吵瞻,你如果說你沒有遇到過也是沒有問題的葛菇。這種現(xiàn)象是很常見的,網(wǎng)上一搜基本解決方法一般有這樣幾種橡羞,
1.1 動態(tài)設(shè)置listview眯停,去測量每個item高度,通過for循環(huán)疊加計算listview的總高度
1.2 使用LinearLayout代替ListView
1.3 自定義MyListView直接集成系統(tǒng)的ListView卿泽,重寫onMeasure()方法
等等...-
實現(xiàn)
接下來帶大家具體分析下這幾種方式的具體使用莺债,前兩種使我們這節(jié)課的一個小插曲,待會看下其實現(xiàn)方式即可签夭,今天我們重點帶大家看下第三種實現(xiàn)方式齐邦,它為什么要這樣去寫方式一: 動態(tài)設(shè)置listview,去測量每個item高度第租,通過for循環(huán)疊加計算listview的總高度
我們大家都知道措拇,在布局文件中如果直接將item高度寫死,是可以解決這個問題的慎宾,但是我們也都知道丐吓,listview高度隨著數(shù)據(jù)是可變化,實際高度還需要實際去測量趟据,那么既然這樣券犁,我們就可以手動的去計算ListView的高度了,代碼如下汹碱,直接作為工具類拷貝到項目中即可使用
/**
* Describe: 動態(tài)設(shè)置listview粘衬,去測量每個item高度,通過for循環(huán)疊加計算listview的總高度
* <p>
* Author: Jack-Chen
* <p>
* Time 16/9/27 下午4:28
*/
public class ListViewUtil {
public static void adaptiveHight(Context context,ListView listView,float dividerHeight) {
try {
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();
if (dividerHeight != -1) {
totalHeight += UIHelper.dip2px(context, dividerHeight) * (listAdapter.getCount() - 1);
}
params.height = totalHeight;
listView.setLayoutParams(params);
}catch (Exception ex){
ex.printStackTrace();
}
}
public static int getItemsHight(ListView listView) {
ListAdapter listAdapter = listView.getAdapter();
if (listAdapter == null) {
return 0;
}
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();
}
return totalHeight;
}
public static int getItemHight(ListView listView) {
ListAdapter listAdapter = listView.getAdapter();
if (listAdapter == null) {
return 0;
}
int itemHeight = 0;
if(listAdapter.getCount()>0) {
View listItem = listAdapter.getView(0, null, listView);
listItem.measure(0, 0);
itemHeight= listItem.getMeasuredHeight();
}
return itemHeight;
}
}
方式二: 使用LinearLayout代替ListView
既然listview不能適應(yīng)ScrollView咳促,那么我們完全可以找一個可以適應(yīng)ScrollView的控件來代替ListView色难,此時LinearLayout是最好的選擇,但如果我們還想繼續(xù)使用已經(jīng)定義好的adapter等缀,那么我們只需要定義一個類去繼承LinearLayout枷莉,最后為其適配BaseAdapter即可
具體代碼如下:
2.2.1: 自定義LinearLayoutForListView 繼承LinearLayout
/**
* Describe: 使用LinearLayout代替ListView
* <p>
* Author: Jack-Chen
* <p>
* Time 16/5/27 下午3:45
*/
public class LinearLayoutForListView extends LinearLayout {
private BaseAdapter adapter;
private OnClickListener onClickListener = null;
/**
* 綁定布局
*/
public void bindLinearLayout() {
int count = adapter.getCount();
this.removeAllViews();
for (int i = 0; i < count; i++) {
View v = adapter.getView(i, null, null);
v.setOnClickListener(this.onClickListener);
addView(v, i);
}
Log.v("countTAG", "" + count);
}
public LinearLayoutForListView(Context context) {
super(context);
}
2.2.2: 將自己之前的ListView布局文件替換為這個包下的布局文件
<com.jackchen.LinearLayoutForListView
android:id="@+id/act_solution_3_mylinearlayout"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
2.2.3 : 然后去替換Activity或Fragment中之前ListView的控件為LinearLayoutForListView,最后為其setAdapter適配數(shù)據(jù)即可
方式三:自定義MyListView直接繼承系統(tǒng)的ListView尺迂,重寫onMeasure()方法
/**
* Describe: 自定義MyListView直接繼承系統(tǒng)的ListView
* <p>
* Author: Jack-Chen
* <p>
* Time 16/8/27 下午2:40
*/
public class MyListView extends ListView {
public MyListView(Context context) {
super(context);
}
public MyListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
* 解決ScrollView嵌套ListView顯示不全問題
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//heightMeasureSpec 參數(shù)1是32位的值 右移2位變成30位的值笤妙, MeasureSpec.AT_MOST是模式,ListView源碼中應(yīng)該要執(zhí)行MeasureSpec.AT_MOST這個if
// if (heightMode == MeasureSpec.AT_MOST) {
// // TODO: after first layout we should maybe start at the first visible position, not 0
// heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
// }
//而不能讓執(zhí)行這個if噪裕,因為這個if里邊剛好是listview的1個item的高度
// if (heightMode == MeasureSpec.UNSPECIFIED) {
// heightSize = mListPadding.top + mListPadding.bottom + childHeight +
// getVerticalFadingEdgeLength() * 2;
// }
heightMeasureSpec =
MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE>>2 , MeasureSpec.AT_MOST) ;
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
這個方法我相信絕大部分的人都是采用這用方式的蹲盘,因為這種方式相對來說比較簡單,直接拷貝過去用即可膳音,但是為什么要這樣去寫召衔,重寫onMeasure()方法后,里邊的參數(shù)為什么是右移2位祭陷,然后模式給他設(shè)置為MeasureSpec.AT_MOST呢苍凛,接下來我給大家來分析下趣席,為什么這樣去寫,大神可以跳過哈
繼承ListView后醇蝴,大家可以直接點擊super.onMeasure(widthMeasureSpec, heightMeasureSpec);進(jìn)入ListView的源碼宣肚,可以看到
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Sets up mListPadding
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//獲取寬高的模式
final int widthMode = MeasureSpec.getMode(widthMeasureSpec); //獲取前兩位
final int heightMode = MeasureSpec.getMode(heightMeasureSpec); //獲取前兩位
//獲取寬高的值
int widthSize = MeasureSpec.getSize(widthMeasureSpec); //獲取后面30位
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int childWidth = 0;
int childHeight = 0;
int childState = 0;
mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED
|| heightMode == MeasureSpec.UNSPECIFIED)) {
final View child = obtainView(0, mIsScrap);
// Lay out child directly against the parent measure spec so that
// we can obtain exected minimum width and height.
measureScrapChild(child, 0, widthMeasureSpec, heightSize);
childWidth = child.getMeasuredWidth();
childHeight = child.getMeasuredHeight();
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (recycleOnMeasure() && mRecycler.shouldRecycleViewType(
((LayoutParams) child.getLayoutParams()).viewType)) {
mRecycler.addScrapView(child, 0);
}
}
if (widthMode == MeasureSpec.UNSPECIFIED) {
widthSize = mListPadding.left + mListPadding.right + childWidth +
getVerticalScrollbarWidth();
} else {
widthSize |= (childState & MEASURED_STATE_MASK);
}
if (heightMode == MeasureSpec.UNSPECIFIED) {
heightSize = mListPadding.top + mListPadding.bottom + childHeight +
getVerticalFadingEdgeLength() * 2;
}
if (heightMode == MeasureSpec.AT_MOST) {
// TODO: after first layout we should maybe start at the first visible position, not 0
heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
}
setMeasuredDimension(widthSize, heightSize);
mWidthMeasureSpec = widthMeasureSpec;
}
在這里要給大家說一下,widthMeasureSpec和heightMeasureSpec分別都包含了2個信息
Integer.MAX_VALUE是一個32位的值悠栓,右移兩位會將Integer.MAX_VALUE變?yōu)橐粋€30位的值霉涨,最后兩位就是MeasureSpec.AT_MOST
那么由這個可知:
final int widthMode = MeasureSpec.getMode(widthMeasureSpec); //獲取前兩位
final int heightMode = MeasureSpec.getMode(heightMeasureSpec); //獲取前兩位
//獲取寬高的值
int widthSize = MeasureSpec.getSize(widthMeasureSpec); //獲取后面30位
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
heightMode 就是MeasureSpec.AT_MOST
heightSize 就是Integer.MAX_VALUE>>2
因為高度heightMode傳遞的是MeasureSpec.AT_MOST,所以就只會進(jìn)到if (heightMode == MeasureSpec.AT_MOST)中惭适,而不會進(jìn)到heightMode == MeasureSpec.UNSPECIFIED中
為什么使用Integer.MAX_VALUE>>2:
這個Integer.MAX_VALUE是表示32位的一個值笙瑟,然后右移兩位,表示有30為的值癞志,表示大小逮走,后邊的MeasureSpec.AT_MOST是表示model,模式
為什么使用MeasureSpec.AT_MOST:
大家可以看到源碼是重寫onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法今阳,其他的我們可以不去看他师溅,直接看里邊有2個if判斷高度的,寬度的不需要看盾舌,其中第一個if是判斷高度的模式 heightMode == MeasureSpec.UNSPECIFIED墓臭,里邊代碼表示距離上邊+距離下邊+子高度剛好表示一個item的高度,那么現(xiàn)在我們再來回過頭想下妖谴,之前ScrollView嵌套ListView只顯示一條窿锉,我們猜想它應(yīng)該是走的這個if判斷里邊,我們要做的就是不要讓它執(zhí)行這個if膝舅,而是要讓它執(zhí)行下邊的if (heightMode == MeasureSpec.AT_MOST)判斷嗡载,而這里的判斷可以直接點擊進(jìn)去heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);看下
而且它傳遞的也是上邊獲取的高度heightSize,到這里我們就知道為什么第二個參數(shù)是
MeasureSpec.AT_MOST仍稀,如果我們不重寫onMeasure()方法洼滚,其實它里邊的高度heightMeasureSpec默認(rèn)是執(zhí)行heightMode == MeasureSpec.UNSPECIFIED,所以高度才會顯示不全
如果你覺得有幫助技潘,可以關(guān)注我遥巴,我會持續(xù)更新簡書博客,會將自己項目中遇到的問題享幽、遇到的bug铲掐、以及解決方法都會分享出來,也許可能像我這樣的文章或者解決方法網(wǎng)上一搜都有值桩,不過也沒有關(guān)系摆霉,自己覺得還是寫出來會比較踏實,因為這些都是自己用過的、思考過的一些東西携栋,還是覺得蠻有用的