ScrollView嵌套ListView时可能产生的问题解决

字数 425阅读 181

一.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

推荐阅读更多精彩内容