您的位置:首页 > 其它

安卓开发之ScrollView嵌套ListView的一些问题和解决

2018-01-15 14:30 513 查看


一、ListView的高度不能完全展开

这种情况是当ScrollView嵌套ListView时,ListView的高度设置为wrap_content时会产生,一般情况下ListView只显示的第一个Item。

正常情况下,高度设置为“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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

而在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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

解决方法一:覆写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);//再调用原方法测量
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

解决方法二:在Activity中动态修改ListView的高度,注意当ListView的子item需要根布局是LinearLayout,或需要为一个View,因为有些布局中没有measure这个方法,比如RelativeLayout。
public void setListViewHeightBasedOnChildren(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);  // 获取item高度
totalHeight += listItem.getMeasuredHeight();
}

ViewGroup.LayoutParams params = listView.getLayoutParams();
// 最后再加上分割线的高度和padding高度,否则显示不完整。
params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1))+listView.getPaddingTop()+listView.getPaddingBottom();
listView.setLayoutParams(params);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

缺陷:上述两种解决方法都有一个缺陷就是,当第一次进入界面动态加载listview的i数据后,ScrollView会跳滑到listview的第一个子项。处理缺陷可以有两种方式:

方式一: 

先设置ListView失去焦点,这并不影响item的点击事件发生
.setFocusable(false);
1
2

方式二: 

重新调整ScrollView的位置
mScrollView.post(new Runnable() {
@Override
public void run() {
mScrollView.scrollTo(0,0);
}
});
1
2
3
4
5
6
7


二、ListView高度固定后无法触发滑动时间

当对listview的高度设置为固定值(例200dp)时,listview的高度是可以直接显示出来的。但嵌套在一起后ScrollView中的ListView就没法上下滑动了,事件先被ScrollView响应了。

解决方法:当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);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

解决方式二:或者也可以给ListView绑定触摸事件的监听:
scrollListView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
scrollListView.getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
scrollListView.getParent().requestDisallowInterceptTouchEvent(false);
break;
}
return false;
}
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17


三、demo演示效果图:




Demo代码地址:Github

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: