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

来源:互联网 发布:ipad1越狱后安装软件 编辑:程序博客网 时间:2024/06/10 10:23

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

首先、我们要熟知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;    }
看了这些,相信你也能解决这些小问题了。


0 0