scrollView中可以自由滚动的listview

直接在scrollView中写listview等可滚动控件会出现子控件高度计算的问题,为了解决这个问题,找到的方案是重写listview中的onmeasure方法:

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

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

但是这样造成的问题是listview会显示出所有的内容,如果listview的数量有100条,那么就很酸爽了.其实说到底还是listview不能滚动,只要让listview可以滚动,问题可以完美解决.

    @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;
        final AbsListView.RecycleBin recycleBin = mRecycler;
        final boolean recyle = recycleOnMeasure();
        final boolean[] isScrap = mIsScrap;

        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();

            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
                        : maxHeight;
            }

            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;
    }

如此看来,就知道,我们可以在listview的onmeasure方法中判断一下,如果listview是写死的高度,那么就将这个死的高度作为listview的最大高度值传给super.measure();否则,传入INTEGER.MAX_VALUE >> 2,这样一来,如果xml文件里面的listview的高度是wrapconten,那么就会显示所有的listview,如果写死,那么listview就是写死的高度,接下来的问题是,如果listview的高度是死的,怎么让listview自由滚动,很简单,android系统提供给我们一个方法,可以拦截或者不拦截touch事件.

getParent().requestDisallowInterceptTouchEvent(true);
true:touch事件由子控件处理
false:touch事件由父类处理

最后附上完整代码:

package com.bbd.picturesel.widgets;

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

/**
 * Created by penny on 2016/4/25.
 */
public class ScrollListView extends ListView {

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

    public ScrollListView(Context context, AttributeSet attributeSet) {
        this(context, attributeSet, 0);
    }

    public ScrollListView(Context context, AttributeSet attributeSet, int defStyleAttr) {
        super(context, attributeSet, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int maxsize = measureHeight(Integer.MAX_VALUE >> 2, heightMeasureSpec);
        int expandSpec = MeasureSpec.makeMeasureSpec(maxsize, MeasureSpec.AT_MOST);
        super.onMeasure(widthMeasureSpec, expandSpec);
    }


    /**
     * @param defaultMaxSize    listview的最大高度默认值
     * @param heightMeasureSpec
     * @return 计算之后的listview的最大高度
     */
    private int measureHeight(int defaultMaxSize, int heightMeasureSpec) {
        int result = 0;
        int measureMode = MeasureSpec.getMode(heightMeasureSpec);
        int measureSize = MeasureSpec.getSize(heightMeasureSpec);
        if (measureMode == MeasureSpec.EXACTLY) {
            result = measureSize;
        } else {
            result = defaultMaxSize;
            if (measureMode == MeasureSpec.AT_MOST) {
                result = Math.min(defaultMaxSize, measureSize);
            }

        }
        return result;
    }

    /**
     * 判断listview是否处于最底部
     *
     * @return
     */
    private boolean isBottom() {
        boolean isBottom = false;
        int firstItemId = getFirstVisiblePosition();
        int currentScreenCount = getChildCount();
        int total = getCount();
        if (firstItemId + currentScreenCount >= total) {
            isBottom = true;
        }
        return isBottom;
    }

    /**
     * 判断listview是否处于最顶部
     *
     * @return
     */
    private boolean isTop() {
        int firstVisiblePosition = getFirstVisiblePosition();
        if (firstVisiblePosition == 0) {
            return true;
        } else
            return false;
    }

    private float y = 0;
    private float touchDown = 0;

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                touchDown = ev.getRawY();
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                y = ev.getRawY();
                if (isTop() && isBottom()) {//listview已全部显示,不自由滚动
                    getParent().requestDisallowInterceptTouchEvent(false);
                } else if (isTop()) {//listview目前在最上部
                    if (y - touchDown > 1) {
                        getParent().requestDisallowInterceptTouchEvent(false);
                    } else {
                        getParent().requestDisallowInterceptTouchEvent(true);
                    }
                } else if (isBottom()) {//listview目前在最下部
                    if (y - touchDown > 1) {//由listview处理touch事件
                        getParent().requestDisallowInterceptTouchEvent(true);
                    } else {//由父控件处理touch事件
                        getParent().requestDisallowInterceptTouchEvent(false);
                    }
                }
                break;
            default:
                break;
        }


        return super.dispatchTouchEvent(ev);
    }
}

感谢原作者:http://blog.csdn.net/wanghao200906/article/details/51084975

原文地址:https://www.cnblogs.com/yiludugufei/p/5434100.html