因产品的需求,需要在ScrollView中嵌套ListView来达到效果。众所周知,ScrollVIew和ListView都是可滑动的容器,嵌套使用一定会出现一些问题。

​​​​​​


  1. <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. android:layout_width="fill_parent"
  3. android:layout_height="fill_parent" >

  4. <ScrollView
  5. android:id="@+id/scrollview"
  6. android:layout_width="fill_parent"
  7. android:layout_height="fill_parent" >

  8. <ListView
  9. android:id="@+id/listview"
  10. android:layout_width="fill_parent"
  11. android:layout_height="wrap_content" >
  12. </ListView>
  13. </ScrollView>

  14. </FrameLayout>

ScrollView中唯一可存在的子View是一个ListView。但是这么写会出现问题。就是ListView会显示不全,只能显示ListView的部分内容,同时,ListView也是不可滑动的。


然后我发现了ScrollView有一个属性,叫 android:fillViewport="true",如果设置这个属性为true,那么ListView会全屏显示,但是依旧不可滑动。先看看这个属性是什么意思。

Attribute Name

Related Method

Description

​android:fillViewport​

​setFillViewport(boolean)​

Defines whether the scrollview should stretch its content to fill the viewport.

意思大概是,定义scrollview是否把它的内容拉伸去充满整个窗口。

​​​​


  1. <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. android:layout_width="fill_parent"
  3. android:layout_height="fill_parent" >

  4. <ScrollView
  5. android:id="@+id/scrollview"
  6. android:layout_width="fill_parent"
  7. android:layout_height="fill_parent"
  8. <span style="color:#ff0000;">android:fillViewport="true"</span> >

  9. <ListView
  10. android:id="@+id/listview"
  11. android:layout_width="fill_parent"
  12. android:layout_height="wrap_content" >
  13. </ListView>
  14. </ScrollView>

  15. </FrameLayout>

有网友反应上述的属性在他们的测试机上面不起作用。然后搜索了一下stackoverflow.com,国外有一个牛人写了一个方法,用于对Listview重新布局。

​​​​


  1. public class Utility {
  2. public static void setListViewHeightBasedOnChildren(ListView listView) {
  3. ListAdapter listAdapter = listView.getAdapter();
  4. if (listAdapter == null) {
  5. // pre-condition
  6. return;
  7. }

  8. int totalHeight = listView.getPaddingTop() + listView.getPaddingBottom();
  9. for (int i = 0; i < listAdapter.getCount(); i++) {
  10. View listItem = listAdapter.getView(i, null, listView);
  11. if (listItem instanceof ViewGroup) {
  12. listItem.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
  13. }
  14. listItem.measure(0, 0);
  15. totalHeight += listItem.getMeasuredHeight();
  16. }

  17. ViewGroup.LayoutParams params = listView.getLayoutParams();
  18. params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
  19. listView.setLayoutParams(params);
  20. }
  21. }

注意:ListView的item必须是LinearLayout,才能起作用。


好了,解决了ListView显示不全的问题,下一步要解决的就是ListView滑动的问题了。


有大神说可以重写ScrollView,拦截相关的点击事件,已达到listview可以滑动的效果。

​​​​


  1. public class VerticalScrollview extends ScrollView {
  2. public VerticalScrollview(Context context) {
  3. super(context);
  4. }

  5. public VerticalScrollview(Context context, AttributeSet attrs) {
  6. super(context, attrs);
  7. }

  8. public VerticalScrollview(Context context, AttributeSet attrs, int defStyle) {
  9. super(context, attrs, defStyle);
  10. }

  11. @Override
  12. public boolean onInterceptTouchEvent(MotionEvent ev) {
  13. final int action = ev.getAction();
  14. switch (action) {
  15. case MotionEvent.ACTION_DOWN:
  16. Log.i("VerticalScrollview",
  17. "onInterceptTouchEvent: DOWN super false");
  18. super.onTouchEvent(ev);
  19. break;

  20. case MotionEvent.ACTION_MOVE:
  21. return false; // redirect MotionEvents to ourself

  22. case MotionEvent.ACTION_CANCEL:
  23. Log.i("VerticalScrollview",
  24. "onInterceptTouchEvent: CANCEL super false");
  25. super.onTouchEvent(ev);
  26. break;

  27. case MotionEvent.ACTION_UP:
  28. Log.i("VerticalScrollview", "onInterceptTouchEvent: UP super false");
  29. return false;

  30. default:
  31. Log.i("VerticalScrollview", "onInterceptTouchEvent: " + action);
  32. break;
  33. }

  34. return false;
  35. }

  36. @Override
  37. public boolean onTouchEvent(MotionEvent ev) {
  38. super.onTouchEvent(ev);
  39. Log.i("VerticalScrollview", "onTouchEvent. action: " + ev.getAction());
  40. return true;
  41. }
  42. }

以上代码测试成功,在ScrollView中的ListView可以滑动。


另外,还可以对ListView设置onTouchListener,在onTouch中去控制外层ScrollView是否拦截触摸事件。

​​​​


  1. mListView.setOnTouchListener(new OnTouchListener() {

  2. @Override
  3. public boolean onTouch(View v, MotionEvent event) {
  4. int action = event.getAction();
  5. switch (action) {
  6. case MotionEvent.ACTION_DOWN:
  7. // Disallow ScrollView to intercept touch events.
  8. v.getParent().requestDisallowInterceptTouchEvent(true);
  9. break;

  10. case MotionEvent.ACTION_UP:
  11. // Allow ScrollView to intercept touch events.
  12. v.getParent().requestDisallowInterceptTouchEvent(false);
  13. break;
  14. }

  15. // Handle ListView touch events.
  16. v.onTouchEvent(event);
  17. return true;
  18. }
  19. });

以上的所有代码确实解决了ListView滑动的问题。但是,ScrollView的滑动问题没有解决。如果ScrollView中除了ListView还有其他View,那么其他View可能显示不出来。


可以重写ListView去解决这个问题,这个是我所知道的最好的解决方案。

​​​​


  1. public class NestedListView extends ListView implements OnTouchListener,
  2. OnScrollListener {

  3. private int listViewTouchAction;
  4. private static final int MAXIMUM_LIST_ITEMS_VIEWABLE = 99;

  5. public NestedListView(Context context, AttributeSet attrs) {
  6. super(context, attrs);
  7. listViewTouchAction = -1;
  8. setOnScrollListener(this);
  9. setOnTouchListener(this);
  10. }

  11. @Override
  12. public void onScroll(AbsListView view, int firstVisibleItem,
  13. int visibleItemCount, int totalItemCount) {
  14. if (getAdapter() != null
  15. && getAdapter().getCount() > MAXIMUM_LIST_ITEMS_VIEWABLE) {
  16. if (listViewTouchAction == MotionEvent.ACTION_MOVE) {
  17. scrollBy(0, -1);
  18. }
  19. }
  20. }

  21. @Override
  22. public void onScrollStateChanged(AbsListView view, int scrollState) {
  23. }

  24. @Override
  25. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  26. super.onMeasure(widthMeasureSpec, heightMeasureSpec);

  27. int newHeight = 0;
  28. final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
  29. int heightSize = MeasureSpec.getSize(heightMeasureSpec);
  30. if (heightMode != MeasureSpec.EXACTLY) {
  31. ListAdapter listAdapter = getAdapter();
  32. if (listAdapter != null && !listAdapter.isEmpty()) {
  33. int listPosition = 0;
  34. for (listPosition = 0; listPosition < listAdapter.getCount()
  35. && listPosition < MAXIMUM_LIST_ITEMS_VIEWABLE; listPosition++) {
  36. View listItem = listAdapter.getView(listPosition, null,
  37. this);
  38. // now it will not throw a NPE if listItem is a ViewGroup
  39. // instance
  40. if (listItem instanceof ViewGroup) {
  41. listItem.setLayoutParams(new LayoutParams(
  42. LayoutParams.WRAP_CONTENT,
  43. LayoutParams.WRAP_CONTENT));
  44. }
  45. listItem.measure(widthMeasureSpec, heightMeasureSpec);
  46. newHeight += listItem.getMeasuredHeight();
  47. }
  48. newHeight += getDividerHeight() * listPosition;
  49. }
  50. if ((heightMode == MeasureSpec.AT_MOST) && (newHeight > heightSize)) {
  51. if (newHeight > heightSize) {
  52. newHeight = heightSize;
  53. }
  54. }
  55. } else {
  56. newHeight = getMeasuredHeight();
  57. }
  58. setMeasuredDimension(getMeasuredWidth(), newHeight);
  59. }

  60. @Override
  61. public boolean onTouch(View v, MotionEvent event) {
  62. if (getAdapter() != null
  63. && getAdapter().getCount() > MAXIMUM_LIST_ITEMS_VIEWABLE) {
  64. if (listViewTouchAction == MotionEvent.ACTION_MOVE) {
  65. scrollBy(0, 1);
  66. }
  67. }
  68. return false;
  69. }
  70. }