ScrollView里面嵌套Listview,ListView為什么只顯示第一行的高度?

這個(gè)是一個(gè)老問(wèn)題了,這里記錄一下,供自己參考學(xué)習(xí)

首先遇到這個(gè)問(wèn)題,我們肯定會(huì)思考,ListView只顯示了一行,是不是它的測(cè)量出了問(wèn)題?
我們首先看一下ListView的onMeasure方法

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

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

由于我們只關(guān)心它的高度,所以我們來(lái)看看里面具體測(cè)量高度的代碼

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

我們通過(guò)源代碼可以看出,當(dāng)豎直方向的測(cè)量模式為 MeasureSpec.AT_MOST 的時(shí)候,此時(shí)得到的測(cè)量高度heightSize 為measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1),而當(dāng)我們進(jìn)入這個(gè)方法查看的時(shí)候發(fā)現(xiàn),它就是計(jì)算ListView的所有item的高度之和。但是當(dāng)ListView嵌套在ScrollView里面的時(shí)候,顯示高度只有一行,顯然不是走這里的代碼。

這時(shí),我們?cè)倏纯戳硗庖环N測(cè)量模式MeasureSpec.UNSPECIFIED,這種測(cè)量模式只在源代碼中使用。當(dāng)是這種測(cè)量模式的時(shí)候,測(cè)量高度heightSize 為 mListPadding.top + mListPadding.bottom + childHeight +getVerticalFadingEdgeLength() * 2。就是上內(nèi)邊距+下內(nèi)邊距+childHeight +上下邊框高度。再看看childHeight ,往前看,我們又會(huì)看到這樣幾行代碼

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

什么意思?就是當(dāng)widthMode 或heightMode 有一個(gè)測(cè)量模式為MeasureSpec.UNSPECIFIED的時(shí)候,那么就會(huì)只測(cè)量一個(gè)子item的高度,這下知道了,也就是當(dāng)測(cè)量ListView高度的時(shí)候,如果說(shuō)只顯示了一行的高度,那么就是因?yàn)椴呗阅J绞荕easureSpec.UNSPECIFIED

那么問(wèn)題又來(lái)了,為什么當(dāng)ListView嵌套在ScrollView里面的時(shí)候,測(cè)量模式就變成了MeasureSpec.UNSPECIFIED了呢?

那么我們又開始思考了,控件的測(cè)量模式是父容器給的,是不是ScrollView在給ListView測(cè)量模式的時(shí)候除了問(wèn)題?

我們來(lái)看看ScrollView的onMeasure方法

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        if (!mFillViewport) {
            return;
        }

        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        if (heightMode == MeasureSpec.UNSPECIFIED) {
            return;
        }

        if (getChildCount() > 0) {
            final View child = getChildAt(0);
            final int widthPadding;
            final int heightPadding;
            final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
            final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
            if (targetSdkVersion >= VERSION_CODES.M) {
                widthPadding = mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin;
                heightPadding = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin;
            } else {
                widthPadding = mPaddingLeft + mPaddingRight;
                heightPadding = mPaddingTop + mPaddingBottom;
            }

            final int desiredHeight = getMeasuredHeight() - heightPadding;
            if (child.getMeasuredHeight() < desiredHeight) {
                final int childWidthMeasureSpec = getChildMeasureSpec(
                        widthMeasureSpec, widthPadding, lp.width);
                final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                        desiredHeight, MeasureSpec.EXACTLY);
                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            }
        }
    }

我們?cè)谧x代碼的時(shí)候發(fā)現(xiàn),當(dāng)heightMode == MeasureSpec.UNSPECIFIED的時(shí)候就直接return了,所以我們繼續(xù)往上看,進(jìn)入super.onMeasure(widthMeasureSpec, heightMeasureSpec)方法,這時(shí)我們發(fā)現(xiàn)如下代碼

for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (mMeasureAllChildren || child.getVisibility() != GONE) {
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                maxWidth = Math.max(maxWidth,
                        child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                maxHeight = Math.max(maxHeight,
                        child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
                childState = combineMeasuredStates(childState, child.getMeasuredState());
                if (measureMatchParentChildren) {
                    if (lp.width == LayoutParams.MATCH_PARENT ||
                            lp.height == LayoutParams.MATCH_PARENT) {
                        mMatchParentChildren.add(child);
                    }
                }
            }
        }

這是逐個(gè)測(cè)量子View的一個(gè)for循環(huán),我們只需要關(guān)注measureChildWithMargins方法,我們點(diǎn)進(jìn)去繼續(xù)查看(進(jìn)入了ViewGroup),其代碼如下

protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

這時(shí)我們看到了child.measure(childWidthMeasureSpec, childHeightMeasureSpec),感覺(jué)勝利就在眼前,我們稍微往前看看

final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);

然后進(jìn)入getChildMeasureSpec方法查看(源代碼就不貼了,有點(diǎn)多),發(fā)現(xiàn)只有當(dāng)ScrollView自己的測(cè)量模式為MeasureSpec.UNSPECIFIED的時(shí)候,才會(huì)給子View也傳遞MeasureSpec.UNSPECIFIED這個(gè)測(cè)量模式,但是前面我們有說(shuō)了呀,ScrollView自己不可能是這個(gè)測(cè)量模式的,因?yàn)槿绻沁@個(gè)測(cè)量模式,那么在自己的onMeasure方法的開始地方就return了回去。。。。。。

又開始思考,給子View測(cè)量的代碼就在這里,這里不會(huì)給View傳遞MeasureSpec.UNSPECIFIED,所以,measureChildWithMargins應(yīng)該被復(fù)寫了!

經(jīng)過(guò)查看,果然在ScrollView里面發(fā)現(xiàn)了該方法已被重寫,我再貼出代碼

@Override
    protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int usedTotal = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin +
                heightUsed;
        final int childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
                Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - usedTotal),
                MeasureSpec.UNSPECIFIED);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

就這樣了,最后再給出解決辦法,就是自定義ListView控件,重寫onMeasure方法
我給出代碼,各位自己參詳

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 defStyle)
    {
        super(context, attrs, defStyle);
    }

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

    }
}
版權(quán)聲明:個(gè)人原創(chuàng),若轉(zhuǎn)載,請(qǐng)注明出處
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容