美文网首页Android开发
ScrollView嵌套ListView时可能产生的问题解决

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

作者: _执_念__ | 来源:发表于2018-07-11 11:07 被阅读21次

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

    相关文章

      网友评论

        本文标题:ScrollView嵌套ListView时可能产生的问题解决

        本文链接:https://www.haomeiwen.com/subject/mzvcpftx.html