让listview在scrollview中自由滑动

总有人我listview嵌套scrollview怎么弄。一问就是半天,太耗时,所以写个博客也算是自己总结一下。

目标

  • scrollview嵌套listview,可以自由的定义listview的大小,而不是展示全部listview。

  • 让listview在scrollview中自由滑动。

  • 当listview滑动到顶端或底端的时候,让scrollview开始滑动

直接上图看效果好了:
这里写图片描述

代码也很简单 直接上代码

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/colorPrimary"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="wang.com.scrollholdlistview.MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="@dimen/activity_vertical_margin"
            android:text="Hello World!"
            android:textSize="30sp" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="@dimen/activity_vertical_margin"
            android:text="Hello World!"
            android:textSize="30sp" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="@dimen/activity_vertical_margin"
            android:text="Hello World!"
            android:textSize="30sp" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="@dimen/activity_vertical_margin"
            android:text="Hello World!"
            android:textSize="30sp" />

        <wang.com.scrollholdlistview.MyListView
            android:id="@+id/lv"
            android:layout_width="match_parent"
            android:layout_height="400dp"
            android:background="@color/colorAccent"></wang.com.scrollholdlistview.MyListView>

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="@dimen/activity_vertical_margin"
            android:text="Hello World!"
            android:textSize="30sp" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="@dimen/activity_vertical_margin"
            android:text="Hello World!"
            android:textSize="30sp" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="@dimen/activity_vertical_margin"
            android:text="Hello World!"
            android:textSize="30sp" />

        <wang.com.scrollholdlistview.MyListView
            android:id="@+id/lv1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/colorAccent">

        </wang.com.scrollholdlistview.MyListView>
    </LinearLayout>
</ScrollView>
package wang.com.scrollholdlistview;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.ListView;

/**
 * 创建日期: 16/4/3 下午9:55
 * 作者:wanghao
 * 描述:
 */
public class MyListView extends ListView {


    private static final String TAG = "MyListView";

    public MyListView(Context context) {
        this(context, null);
    }

    public MyListView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }


    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        int defaultsize=measureHight(Integer.MAX_VALUE >> 2, heightMeasureSpec);
        int expandSpec = MeasureSpec.makeMeasureSpec(defaultsize, MeasureSpec.AT_MOST);

        super.onMeasure(widthMeasureSpec, expandSpec);
    }


    private int measureHight(int size, int measureSpec) {
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        if (specMode == MeasureSpec.EXACTLY) {
            Log.i(TAG, "exactly" );

            result = specSize;
        } else {

            result = size;//最小值是200px ,自己设定
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
            Log.i(TAG, "specMode:"+specMode+"--result:"+result );
        }
        return result;
    }


    /**
     * 改listview滑到底端了
     *
     * @return
     */
    public boolean isBottom() {
        int firstVisibleItem = getFirstVisiblePosition();//屏幕上显示的第一条是list中的第几条
        int childcount = getChildCount();//屏幕上显示多少条item
        int totalItemCount = getCount();//一共有多少条
        if ((firstVisibleItem + childcount) >=totalItemCount) {
            return true;
        }
        return false;
    }

    /**
     * 改listview在顶端
     *
     * @return
     */
    public boolean isTop() {
        int firstVisibleItem = getFirstVisiblePosition();
        if (firstVisibleItem ==0) {
            return true;
        }
        return false;
    }

    float down = 0;
    float y;

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                down = event.getRawY();

                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                y = event.getRawY();
                if (isTop()) {
                    if (y - down > 1) {
//                        到顶端,向下滑动 把事件教给父类
                        getParent().requestDisallowInterceptTouchEvent(false);
                    } else {
                        //                        到顶端,向上滑动 把事件拦截 由自己处理
                        getParent().requestDisallowInterceptTouchEvent(true);
                    }
                }

                if (isBottom()) {
                    if (y - down > 1) {
//                        到底端,向下滑动 把事件拦截 由自己处理
                        getParent().requestDisallowInterceptTouchEvent(true);
                    } else {
//                        到底端,向上滑动 把事件教给父类
                        getParent().requestDisallowInterceptTouchEvent(false);
                    }
                }
                break;
            default:
                break;
        }

        return super.dispatchTouchEvent(event);
    }
}

上面代码直接用就可以啦。
那么下面说下好多人遇到过的疑惑

  • 为什么这么写就能避免嵌套问题呢
    下面代码是网上流传最多的
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

int expandSpec = MeasureSpec.makeMeasureSpec(
            Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
    super.onMeasure(widthMeasureSpec, expandSpec);
}

这样写呢有个弊端,就是 在scrollview里面展示了 listview的全部内容。有100条就显示了100条。
Integer.MAX_VALUE >> 2这个呢就是一个值。让你的listview最大值是多大。你可以写成65535,或者其他数值。为什么用 MeasureSpec.AT_MOST呢?
看下super里面

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // Sets up mListPadding
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

       ...
        if (heightMode == MeasureSpec.AT_MOST) {
            // TODO: after first layout we should maybe start at the first visible position, not 0
            heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
        }

        setMeasuredDimension(widthSize, heightSize);

        mWidthMeasureSpec = widthMeasureSpec;
    }

再看下measureHeightOfChildren()的内容

final int measureHeightOfChildren(int widthMeasureSpec, int startPosition, int endPosition,
            int maxHeight, int disallowPartialChildPosition) {
        final ListAdapter adapter = mAdapter;
        if (adapter == null) {
            return mListPadding.top + mListPadding.bottom;
        }

        // Include the padding of the list
        int returnedHeight = mListPadding.top + mListPadding.bottom;
        final int dividerHeight = ((mDividerHeight > 0) && mDivider != null) ? mDividerHeight : 0;
        // The previous height value that was less than maxHeight and contained
        // no partial children
        int prevHeightWithoutPartialChild = 0;
        int i;
        View child;

        // mItemCount - 1 since endPosition parameter is inclusive
        endPosition = (endPosition == NO_POSITION) ? adapter.getCount() - 1 : endPosition;//获取有多少个item
        final AbsListView.RecycleBin recycleBin = mRecycler;
        final boolean recyle = recycleOnMeasure();
        final boolean[] isScrap = mIsScrap;
//遍历所有item并且 获取到 所有item的整体高度
        for (i = startPosition; i <= endPosition; ++i) {
            child = obtainView(i, isScrap);

            measureScrapChild(child, i, widthMeasureSpec, maxHeight);

            if (i > 0) {
                // Count the divider for all but one child
                returnedHeight += dividerHeight;
            }

            // Recycle the view before we possibly return from the method
            if (recyle && recycleBin.shouldRecycleViewType(
                    ((LayoutParams) child.getLayoutParams()).viewType)) {
                recycleBin.addScrapView(child, -1);
            }

            returnedHeight += child.getMeasuredHeight();
//如果item的高度大于咱们赋值的高度也就是Integer.MAX_VALUE >> 2 
            if (returnedHeight >= maxHeight) {
                // We went over, figure out which height to return.  If returnedHeight > maxHeight,
                // then the i'th position did not fit completely.
                return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1)
                            && (i > disallowPartialChildPosition) // We've past the min pos
                            && (prevHeightWithoutPartialChild > 0) // We have a prev height
                            && (returnedHeight != maxHeight) // i'th child did not fit completely
                        ? prevHeightWithoutPartialChild 如果是大于就用自己item整体的高度作为listview的高度
                        : maxHeight; 如果是等于那么久用 我们赋值的高度
            }
//如果item的高度 不大于等于 默认值 那么久显示他自己的高度了 
            if ((disallowPartialChildPosition >= 0) && (i >= disallowPartialChildPosition)) {
                prevHeightWithoutPartialChild = returnedHeight;
            }
        }

        // At this point, we went through the range of children, and they each
        // completely fit, so return the returnedHeight
        return returnedHeight;
    }

所以我们赋值的Integer.MAX_VALUE >> 2只是一个参考值,你想写多少就是多少。
但是那样写有个弊端就是在xml里面写的高度 就没用了。所以我们就这么写了

  @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        int defaultsize=measureHight(Integer.MAX_VALUE >> 2, heightMeasureSpec);
        int expandSpec = MeasureSpec.makeMeasureSpec(defaultsize, MeasureSpec.AT_MOST);

        super.onMeasure(widthMeasureSpec, expandSpec);
    }


    private int measureHight(int size, int measureSpec) {
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        if (specMode == MeasureSpec.EXACTLY) {
            Log.i(TAG, "exactly" );

            result = specSize;
        } else {

            result = size;//最小值是200px ,自己设定
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
            Log.i(TAG, "specMode:"+specMode+"--result:"+result );
        }
        return result;
    }

这样的话就是如果 xml里面是wrap_content的话 就显示所有item的高度。如果是设置了 高度值 比如是400dp。那么listview显示的高度就是400dp。
为什么那么些 请戳这尼自定义view,viewgroup的onMeasure 方法

那么问题又来了。
如果显示400dp的高度。listview就没法全部显示了。
所以就不能让scrollview拦截事件,要让该事件传递到listview,并且处理。 一行代码就搞定

  getParent().requestDisallowInterceptTouchEvent(true);
  true:就是拦截此事件
  false:不拦截此事件,让父类处理

剩下的就只是当listview滑动到顶端和底端 listview就拦截该事件了。代码里已经体现的很简单了。
尊重原创:http://blog.csdn.net/wanghao200906/article/details/51084975
代码在这里

最近太忙了。发个福利安慰一下自己
这里写图片描述

  • 7
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值