天天看点

深入理解布局容器绘制,解决ListView嵌套listview,或者ScrollView嵌套listview,gridview的高度问题解决方法

当然,在做的时候可能有很多方法,当然这也不是最好的方法。

首先、我们要熟知android界面是怎么绘制出来的。今天就简单的谈一下,我们都知道,布局容器绘制经过三个步骤,就是测量,布局,绘制,对应到代码中就是

onMeasure-----------onLayout-----------onDraw。哪具体是怎么绘制的呢?下面是系统源码。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mOrientation == VERTICAL) {
            measureVertical(widthMeasureSpec, heightMeasureSpec);
        } else {
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
        }
    }
           
以LinearLayout为例,从源码可以看出,系统先判断布局方向,才开始测量。
for (int i = 0; i < count; ++i) {
            final View child = getVirtualChildAt(i);
            if (child == null) {
                mTotalLength += measureNullChild(i);
                continue;
            }

            if (child.getVisibility() == View.GONE) {
               i += getChildrenSkipCount(child, i);
               continue;
            }

            if (hasDividerBeforeChildAt(i)) {
                mTotalLength += mDividerHeight;
            }

            final LayoutParams lp = (LayoutParams) child.getLayoutParams();

            totalWeight += lp.weight;

            final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
            if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
                skippedMeasure = true;
            } else {
                if (useExcessSpace) {
                    lp.height = LayoutParams.WRAP_CONTENT;
                }
                final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
                measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                        heightMeasureSpec, usedHeight);

                final int childHeight = child.getMeasuredHeight();
                if (useExcessSpace) {
                    lp.height = 0;
                    consumedExcessSpace += childHeight;
                }

                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                       lp.bottomMargin + getNextLocationOffset(child));

                if (useLargestChild) {
                    largestChildHeight = Math.max(childHeight, largestChildHeight);
                }
            }


            if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {
               mBaselineChildTop = mTotalLength;
            }

            if (i < baselineChildIndex && lp.weight > 0) {
                throw new RuntimeException("A child of LinearLayout with index "
                        + "less than mBaselineAlignedChildIndex has weight > 0, which "
                        + "won't work.  Either remove the weight, or don't set "
                        + "mBaselineAlignedChildIndex.");
            }

            boolean matchWidthLocally = false;
            if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {
                matchWidth = true;
                matchWidthLocally = true;
            }

            final int margin = lp.leftMargin + lp.rightMargin;
            final int measuredWidth = child.getMeasuredWidth() + margin;
            maxWidth = Math.max(maxWidth, measuredWidth);
            childState = combineMeasuredStates(childState, child.getMeasuredState());

            allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
            if (lp.weight > 0) {
                weightedMaxWidth = Math.max(weightedMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);
            } else {
                alternativeMaxWidth = Math.max(alternativeMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);
            }

            i += getChildrenSkipCount(child, i);
        }

        if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {
            mTotalLength += mDividerHeight;
        }
           
很明显: 1、系统将会拿出嵌套的容器组件的子控件,进行测量然后记录总的高度或宽度,要注意要么测量高度,要么测量宽度取决于布局方向,当然相对布局没有该方法,他是通过相对比较来绘制的。
if (child == null) {
                mTotalLength += measureNullChild(i);
                continue;
            }
           
2、判断非容器类控件是否是GONE。如果是就不进行测量
if (child.getVisibility() == View.GONE) {
               i += getChildrenSkipCount(child, i);
               continue;
            }
           
3、判断是否包含边线,如果有记录线的高度
if (hasDividerBeforeChildAt(i)) {
                mTotalLength += mDividerHeight;
            }
           
4、最后才去测量,控件真正的高度。并作记录
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
           
5、将所得的宽和高,和最大尺寸作对比。 但这些似乎好像和正提没关系。那为什么ListView嵌套listview,或者ScrollView嵌套listview,gridview无法绘制高度呢? 原来,在设计中他只会测量非容器布局的高度,因此当这样嵌套因为都是要动态测量的所以获取不到。就达不到我们想要的效果 第一步中测量非容器布局的源码如下:
View getVirtualChildAt(int index) {
        return getChildAt(index);
    }
           

那我们怎样解决呢?

下面就让我教你怎么做:

当ListView嵌套listview,或者ScrollView嵌套listview,gridview外层的布局测量到内层发现没有非容器布局,因此高度就是自身高度,而测量又是从最内(上)层开始测量的,测量后并不给自身设置布局参数。

因此呢,内层listview是能测量到item高度的,而外层已经限制了宽和高,所以就不能达到效果。为了达到效果我们要让外层容器认为内层是个非容器布局,是一个有固定高度的容器。

具体我们就要必须给内层容器设置布局参数,具体如下:这是封装后的listview,解决办法,没封装,当然就不需要holder了
       
public class LvHeightUtil {
    public static void setListViewHeightBasedOnChildren(ListView listView, BaseHolder holder) {
        ListAdapter listAdapter = listView.getAdapter();
        if (listAdapter == null) {
            return;
        }
        int totalHeight = 0;
        for (int i = 0; i < listAdapter.getCount(); i++) {

//            View listItem = listAdapter.getView(i, holder.mItemRootView, listView);
//            listItem.measure(0, 0);
//            totalHeight += listItem.getMeasuredHeight();
            totalHeight +=150;//如果是lv嵌套lv某种条件下需要固定item高度,不然测不出来。
        }

        ViewGroup.LayoutParams params = listView.getLayoutParams();
        params.height = UIUtil.dip2px(totalHeight)
                + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
        listView.setLayoutParams(params);
    }
    public static void setGridViewHeightBasedOnChildren(GridView gridView ,BaseHolder holder) {
        ListAdapter adapter = gridView.getAdapter();
        if (adapter == null) {
            return;
        }
        int totalHeight = 0;
        for (int i = 0; i < adapter.getCount()/2; i++) {

            View listItem = adapter.getView(i, holder.mItemRootView, gridView);
            listItem.measure(0, 0);
            totalHeight += listItem.getMeasuredHeight();
        }

        ViewGroup.LayoutParams params = gridView.getLayoutParams();
        params.height = totalHeight;
        gridView.setLayoutParams(params);
    }
}
           
listView源码
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);

        int childWidth = 0;
        int childHeight = 0;
        int childState = 0;

        mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
        if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED
                || heightMode == MeasureSpec.UNSPECIFIED)) {
            final View child = obtainView(0, mIsScrap);

            // Lay out child directly against the parent measure spec so that
            // we can obtain exected minimum width and height.
            measureScrapChild(child, 0, widthMeasureSpec, heightSize);

            childWidth = child.getMeasuredWidth();
            childHeight = child.getMeasuredHeight();
            childState = combineMeasuredStates(childState, child.getMeasuredState());

            if (recycleOnMeasure() && mRecycler.shouldRecycleViewType(
                    ((LayoutParams) child.getLayoutParams()).viewType)) {
                mRecycler.addScrapView(child, 0);
            }
        }

        if (widthMode == MeasureSpec.UNSPECIFIED) {
            widthSize = mListPadding.left + mListPadding.right + childWidth +
                    getVerticalScrollbarWidth();
        } else {
            widthSize |= (childState & MEASURED_STATE_MASK);
        }

        if (heightMode == MeasureSpec.UNSPECIFIED) {
            heightSize = mListPadding.top + mListPadding.bottom + childHeight +
                    getVerticalFadingEdgeLength() * 2;
        }

        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;
    }
           
看了这些,相信你也能解决这些小问题了。