因产品的需求,需要在ScrollView中嵌套ListView来达到效果。众所周知,ScrollVIew和ListView都是可滑动的容器,嵌套使用一定会出现一些问题。
- <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent" >
-
- <ScrollView
- android:id="@+id/scrollview"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent" >
-
- <ListView
- android:id="@+id/listview"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content" >
- </ListView>
- </ScrollView>
-
- </FrameLayout>
ScrollView中唯一可存在的子View是一个ListView。但是这么写会出现问题。就是ListView会显示不全,只能显示ListView的部分内容,同时,ListView也是不可滑动的。
然后我发现了ScrollView有一个属性,叫 android:fillViewport="true",如果设置这个属性为true,那么ListView会全屏显示,但是依旧不可滑动。先看看这个属性是什么意思。
XML Attributes |
Attribute Name |
Related Method |
Description |
android:fillViewport |
setFillViewport(boolean) |
Defines whether the scrollview should stretch its content to fill the viewport. |
意思大概是,定义scrollview是否把它的内容拉伸去充满整个窗口。
- <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent" >
-
- <ScrollView
- android:id="@+id/scrollview"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- <span style="color:#ff0000;">android:fillViewport="true"</span> >
-
- <ListView
- android:id="@+id/listview"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content" >
- </ListView>
- </ScrollView>
-
- </FrameLayout>
有网友反应上述的属性在他们的测试机上面不起作用。然后搜索了一下stackoverflow.com,国外有一个牛人写了一个方法,用于对Listview重新布局。
- public class Utility {
- public static void setListViewHeightBasedOnChildren(ListView listView) {
- ListAdapter listAdapter = listView.getAdapter();
- if (listAdapter == null) {
-
- return;
- }
-
- int totalHeight = listView.getPaddingTop() + listView.getPaddingBottom();
- for (int i = 0; i < listAdapter.getCount(); i++) {
- View listItem = listAdapter.getView(i, null, listView);
- if (listItem instanceof ViewGroup) {
- listItem.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
- }
- listItem.measure(0, 0);
- totalHeight += listItem.getMeasuredHeight();
- }
-
- ViewGroup.LayoutParams params = listView.getLayoutParams();
- params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
- listView.setLayoutParams(params);
- }
- }
注意:ListView的item必须是LinearLayout,才能起作用。
好了,解决了ListView显示不全的问题,下一步要解决的就是ListView滑动的问题了。
有大神说可以重写ScrollView,拦截相关的点击事件,已达到listview可以滑动的效果。
- public class VerticalScrollview extends ScrollView {
- public VerticalScrollview(Context context) {
- super(context);
- }
-
- public VerticalScrollview(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public VerticalScrollview(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- final int action = ev.getAction();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- Log.i("VerticalScrollview",
- "onInterceptTouchEvent: DOWN super false");
- super.onTouchEvent(ev);
- break;
-
- case MotionEvent.ACTION_MOVE:
- return false;
-
- case MotionEvent.ACTION_CANCEL:
- Log.i("VerticalScrollview",
- "onInterceptTouchEvent: CANCEL super false");
- super.onTouchEvent(ev);
- break;
-
- case MotionEvent.ACTION_UP:
- Log.i("VerticalScrollview", "onInterceptTouchEvent: UP super false");
- return false;
-
- default:
- Log.i("VerticalScrollview", "onInterceptTouchEvent: " + action);
- break;
- }
-
- return false;
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- super.onTouchEvent(ev);
- Log.i("VerticalScrollview", "onTouchEvent. action: " + ev.getAction());
- return true;
- }
- }
以上代码测试成功,在ScrollView中的ListView可以滑动。
另外,还可以对ListView设置onTouchListener,在onTouch中去控制外层ScrollView是否拦截触摸事件。
- mListView.setOnTouchListener(new OnTouchListener() {
-
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- int action = event.getAction();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
-
- v.getParent().requestDisallowInterceptTouchEvent(true);
- break;
-
- case MotionEvent.ACTION_UP:
-
- v.getParent().requestDisallowInterceptTouchEvent(false);
- break;
- }
-
-
- v.onTouchEvent(event);
- return true;
- }
- });
以上的所有代码确实解决了ListView滑动的问题。但是,ScrollView的滑动问题没有解决。如果ScrollView中除了ListView还有其他View,那么其他View可能显示不出来。
可以重写ListView去解决这个问题,这个是我所知道的最好的解决方案。
- public class NestedListView extends ListView implements OnTouchListener,
- OnScrollListener {
-
- private int listViewTouchAction;
- private static final int MAXIMUM_LIST_ITEMS_VIEWABLE = 99;
-
- public NestedListView(Context context, AttributeSet attrs) {
- super(context, attrs);
- listViewTouchAction = -1;
- setOnScrollListener(this);
- setOnTouchListener(this);
- }
-
- @Override
- public void onScroll(AbsListView view, int firstVisibleItem,
- int visibleItemCount, int totalItemCount) {
- if (getAdapter() != null
- && getAdapter().getCount() > MAXIMUM_LIST_ITEMS_VIEWABLE) {
- if (listViewTouchAction == MotionEvent.ACTION_MOVE) {
- scrollBy(0, -1);
- }
- }
- }
-
- @Override
- public void onScrollStateChanged(AbsListView view, int scrollState) {
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
- int newHeight = 0;
- final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
- int heightSize = MeasureSpec.getSize(heightMeasureSpec);
- if (heightMode != MeasureSpec.EXACTLY) {
- ListAdapter listAdapter = getAdapter();
- if (listAdapter != null && !listAdapter.isEmpty()) {
- int listPosition = 0;
- for (listPosition = 0; listPosition < listAdapter.getCount()
- && listPosition < MAXIMUM_LIST_ITEMS_VIEWABLE; listPosition++) {
- View listItem = listAdapter.getView(listPosition, null,
- this);
-
-
- if (listItem instanceof ViewGroup) {
- listItem.setLayoutParams(new LayoutParams(
- LayoutParams.WRAP_CONTENT,
- LayoutParams.WRAP_CONTENT));
- }
- listItem.measure(widthMeasureSpec, heightMeasureSpec);
- newHeight += listItem.getMeasuredHeight();
- }
- newHeight += getDividerHeight() * listPosition;
- }
- if ((heightMode == MeasureSpec.AT_MOST) && (newHeight > heightSize)) {
- if (newHeight > heightSize) {
- newHeight = heightSize;
- }
- }
- } else {
- newHeight = getMeasuredHeight();
- }
- setMeasuredDimension(getMeasuredWidth(), newHeight);
- }
-
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- if (getAdapter() != null
- && getAdapter().getCount() > MAXIMUM_LIST_ITEMS_VIEWABLE) {
- if (listViewTouchAction == MotionEvent.ACTION_MOVE) {
- scrollBy(0, 1);
- }
- }
- return false;
- }
- }
参考资料
http://stackoverflow.com/questions/6210895/listview-inside-scrollview-is-not-scrolling-on-android
http://stackoverflow.com/questions/3495890/how-can-i-put-a-listview-into-a-scrollview-without-it-collapsing
http://developer.android.com/reference/android/widget/ScrollView.html