一.ListView的高度設(shè)置為wrap_content
當(dāng)ScrollView嵌套ListView時(shí),ListView的高度設(shè)置為wrap_content時(shí)會(huì)產(chǎn)生,一般情況下ListView只顯示的第一個(gè)Item。
代碼如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#aaa">
<ListView
android:id="@+id/lv_test1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#ccc" />
</ScrollView>
</LinearLayout>
正常情況下,高度設(shè)置為“wrap_content”的ListView在測(cè)量自己的高度會(huì)使用MeasureSpec.AT_MOST這個(gè)模式高度來返回可包含住其內(nèi)容的高度。
而實(shí)際上當(dāng)ListView被ScrollView嵌套時(shí),ListView使用的測(cè)量模式是ScrollView傳入的MeasureSpec.UNSPECIFIED。
// ScrollView的measureChildWithMargins()代碼:
@Override
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
ViewGroup.LayoutParams lp = child.getLayoutParams();
int childWidthMeasureSpec;
int childHeightMeasureSpec;
childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft
+ mPaddingRight, lp.width);
childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
MeasureSpec.getSize(parentHeightMeasureSpec), MeasureSpec.UNSPECIFIED);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
而在ListView的onMeasure方法中:使用MeasureSpec.UNSPECIFIED模式測(cè)量時(shí)只能加載一部分高度。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Sets up mListPadding
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
...
...
if (heightMode == MeasureSpec.UNSPECIFIED) {
heightSize = mListPadding.top + mListPadding.bottom + childHeight +
getVerticalFadingEdgeLength() * 2;
}
setMeasuredDimension(widthSize, heightSize);
mWidthMeasureSpec = widthMeasureSpec;
}
直接運(yùn)行結(jié)果如下

解決方法:覆寫ListView的onMeasure方法:
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.widget.ListView;
/**
* Created by cxm on 2016/9/15.
*/
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);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public MyListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int newHeightMeasureSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE>>2, // 設(shè)計(jì)一個(gè)較大的值和AT_MOST模式
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, newHeightMeasureSpec);//再調(diào)用原方法測(cè)量
}
}
上述解決方法有一個(gè)缺陷就是,當(dāng)?shù)谝淮芜M(jìn)入界面動(dòng)態(tài)加載listview的i數(shù)據(jù)后,ScrollView會(huì)跳滑到listview的第一個(gè)子項(xiàng)。
解決方法:先設(shè)置ListView失去焦點(diǎn),這并不影響item的點(diǎn)擊事件發(fā)生
結(jié)果如圖

二.ListView高度固定
當(dāng)對(duì)listview的高度設(shè)置為固定值(例200dp)時(shí),listview的高度是可以直接顯示出來的。但嵌套在一起后ScrollView中的ListView就沒法上下滑動(dòng)了,事件先被ScrollView響應(yīng)了。
布局代碼如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:text="Test2"
android:layout_width="match_parent"
android:layout_height="300px" />
<ListView
android:id="@+id/lv_test2"
android:layout_width="match_parent"
android:layout_height="500dp"
android:background="#ccc" />
<TextView
android:text="Test2"
android:layout_width="match_parent"
android:layout_height="300px" />
<TextView
android:text="Test2"
android:layout_width="match_parent"
android:layout_height="300px" />
<TextView
android:text="Test2"
android:layout_width="match_parent"
android:layout_height="300px" />
</LinearLayout>
</ScrollView>
</LinearLayout>

解決方法:當(dāng)ListView自身接收到的滑動(dòng)事件時(shí),使ScrollView取消攔截。ListView區(qū)域內(nèi)的滑動(dòng)事件由自己處理,ListView區(qū)域外滑動(dòng)事件由外層ScrollView處理??梢韵到y(tǒng)自帶的API來實(shí)現(xiàn):requestDisallowInterceptTouchEvent這一方法。
自定義ListView來重寫ListView的dispatchTouchEvent函數(shù):
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.ListView;
/**
* Created by cxm on 2016/9/15.
*/
public class ScollListView extends ListView {
public ScollListView(Context context) {
super(context);
}
public ScollListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ScollListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public ScollListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
getParent().requestDisallowInterceptTouchEvent(false);
break;
}
return super.dispatchTouchEvent(ev);
}
}
如圖

demo地址:https://github.com/ShenChaohui/ScrollViewAndListView.git