一.ListView的高度设置为wrap_content
当ScrollView嵌套ListView时,ListView的高度设置为wrap_content时会产生,一般情况下ListView只显示的第一个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>
正常情况下,高度设置为“wrap_content”的ListView在测量自己的高度会使用MeasureSpec.AT_MOST这个模式高度来返回可包含住其内容的高度。
而实际上当ListView被ScrollView嵌套时,ListView使用的测量模式是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模式测量时只能加载一部分高度。
@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;
}
直接运行结果如下
20180711_111957.gif
解决方法:覆写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, // 设计一个较大的值和AT_MOST模式
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, newHeightMeasureSpec);//再调用原方法测量
}
}
上述解决方法有一个缺陷就是,当第一次进入界面动态加载listview的i数据后,ScrollView会跳滑到listview的第一个子项。
解决方法:先设置ListView失去焦点,这并不影响item的点击事件发生
结果如图
20180711_111602.gif
二.ListView高度固定
当对listview的高度设置为固定值(例200dp)时,listview的高度是可以直接显示出来的。但嵌套在一起后ScrollView中的ListView就没法上下滑动了,事件先被ScrollView响应了。
布局代码如下:
<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>
20180711_112616.gif
解决方法:当ListView自身接收到的滑动事件时,使ScrollView取消拦截。ListView区域内的滑动事件由自己处理,ListView区域外滑动事件由外层ScrollView处理。可以系统自带的API来实现:requestDisallowInterceptTouchEvent这一方法。
自定义ListView来重写ListView的dispatchTouchEvent函数:
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);
}
}
如图
20180711_112219(1).gif
demo地址:https://github.com/ShenChaohui/ScrollViewAndListView.git
网友评论