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

這個是一個老問題了缕减,這里記錄一下雷客,供自己參考學習

首先遇到這個問題,我們肯定會思考烛卧,ListView只顯示了一行佛纫,是不是它的測量出了問題?
我們首先看一下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)心它的高度总放,所以我們來看看里面具體測量高度的代碼

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

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

這時,我們再看看另外一種測量模式MeasureSpec.UNSPECIFIED融虽,這種測量模式只在源代碼中使用享完。當是這種測量模式的時候,測量高度heightSize 為 mListPadding.top + mListPadding.bottom + childHeight +getVerticalFadingEdgeLength() * 2有额。就是上內(nèi)邊距+下內(nèi)邊距+childHeight +上下邊框高度般又。再看看childHeight ,往前看巍佑,我們又會看到這樣幾行代碼

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

什么意思茴迁?就是當widthMode 或heightMode 有一個測量模式為MeasureSpec.UNSPECIFIED的時候,那么就會只測量一個子item的高度萤衰,這下知道了堕义,也就是當測量ListView高度的時候,如果說只顯示了一行的高度脆栋,那么就是因為策略模式是MeasureSpec.UNSPECIFIED

那么問題又來了倦卖,為什么當ListView嵌套在ScrollView里面的時候,測量模式就變成了MeasureSpec.UNSPECIFIED了呢筹吐?

那么我們又開始思考了糖耸,控件的測量模式是父容器給的,是不是ScrollView在給ListView測量模式的時候除了問題丘薛?

我們來看看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);
            }
        }
    }

我們在讀代碼的時候發(fā)現(xiàn),當heightMode == MeasureSpec.UNSPECIFIED的時候就直接return了邦危,所以我們繼續(xù)往上看洋侨,進入super.onMeasure(widthMeasureSpec, heightMeasureSpec)方法,這時我們發(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);
                    }
                }
            }
        }

這是逐個測量子View的一個for循環(huán)倦蚪,我們只需要關(guān)注measureChildWithMargins方法希坚,我們點進去繼續(xù)查看(進入了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);
    }

這時我們看到了child.measure(childWidthMeasureSpec, childHeightMeasureSpec)陵且,感覺勝利就在眼前裁僧,我們稍微往前看看

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

然后進入getChildMeasureSpec方法查看(源代碼就不貼了,有點多)慕购,發(fā)現(xiàn)只有當ScrollView自己的測量模式為MeasureSpec.UNSPECIFIED的時候聊疲,才會給子View也傳遞MeasureSpec.UNSPECIFIED這個測量模式,但是前面我們有說了呀沪悲,ScrollView自己不可能是這個測量模式的获洲,因為如果是這個測量模式,那么在自己的onMeasure方法的開始地方就return了回去殿如。贡珊。最爬。。门岔。爱致。

又開始思考,給子View測量的代碼就在這里寒随,這里不會給View傳遞MeasureSpec.UNSPECIFIED糠悯,所以,measureChildWithMargins應(yīng)該被復寫了牢裳!

經(jīng)過查看逢防,果然在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)聲明:個人原創(chuàng)局嘁,若轉(zhuǎn)載,請注明出處
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末晦墙,一起剝皮案震驚了整個濱河市悦昵,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌晌畅,老刑警劉巖但指,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異抗楔,居然都是意外死亡棋凳,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進店門连躏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來剩岳,“玉大人,你說我怎么就攤上這事入热∨淖兀” “怎么了?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵勺良,是天一觀的道長绰播。 經(jīng)常有香客問我,道長郑气,這世上最難降的妖魔是什么幅垮? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上忙芒,老公的妹妹穿的比我還像新娘示弓。我一直安慰自己,他們只是感情好呵萨,可當我...
    茶點故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布奏属。 她就那樣靜靜地躺著,像睡著了一般潮峦。 火紅的嫁衣襯著肌膚如雪囱皿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天忱嘹,我揣著相機與錄音嘱腥,去河邊找鬼。 笑死拘悦,一個胖子當著我的面吹牛齿兔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播础米,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼分苇,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了屁桑?” 一聲冷哼從身側(cè)響起医寿,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蘑斧,沒想到半個月后靖秩,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡竖瘾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年盆偿,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片准浴。...
    茶點故事閱讀 39,739評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖捎稚,靈堂內(nèi)的尸體忽然破棺而出乐横,到底是詐尸還是另有隱情,我是刑警寧澤今野,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布葡公,位于F島的核電站,受9級特大地震影響条霜,放射性物質(zhì)發(fā)生泄漏催什。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一宰睡、第九天 我趴在偏房一處隱蔽的房頂上張望蒲凶。 院中可真熱鬧气筋,春花似錦、人聲如沸旋圆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽灵巧。三九已至搀矫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間刻肄,已是汗流浹背瓤球。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留敏弃,地道東北人卦羡。 一個月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓,卻偏偏與公主長得像权她,于是被迫代替她去往敵國和親虹茶。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,647評論 2 354

推薦閱讀更多精彩內(nèi)容