1. 程式人生 > >ScrollView巢狀ListView導致item顯示不全的原因

ScrollView巢狀ListView導致item顯示不全的原因

一句話總結:

ScrollView重寫了它的父類FrameLayout的measureChild和measureChildWithMargins方法,使傳入子類的HeightMeasureSpec的模式為UNSPECIFIED,導致listview計算高度時跳過了measureHeightOfChildren方法的執行,只計算了第一個item的高度。

分析:

首先看listview的onMeasure方法中關於高度的賦值:
      1276行:
      int heightSize = MeasureSpec.getSize(heightMeasureSpec);//從heightMeasureSpec中獲取高度
1308行: //如果為UNSPECIFIED則高度為第一個child的高度加上邊距 if (heightMode == MeasureSpec.UNSPECIFIED) { heightSize = mListPadding.top + mListPadding.bottom + childHeight + getVerticalFadingEdgeLength() * 2; } 1313行: //如果為AT_MOST則去執行measureHeightOfChildren方法,計算最大高度
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); }

可以看出listview的高度計算取決於傳入onMeasure方法的MeasureSpec的mode型別。通過了解View的測量流程可以知道,onMeasure方法在measure方法中被呼叫。而measure方法一般被其父view呼叫。此巢狀情況下,listview的父view是scrollview,那麼現在去看一下scrollview中onMeasure的情況:

     super.onMeasure(widthMeasureSpec, heightMeasureSpec);

     if (!mFillViewport) {
                return;
         }
     ......

注意開始的這兩行判斷,mFillViewport預設是false的,在我們沒有人為設定的情況下,onMeasure執行到這裡就return了,下面的程式碼是不會執行的。所以真正執行的應該只有super.onMeasure(widthMeasureSpec, heightMeasureSpec);呼叫父類的onMeasure方法,ScrollView的父類是Framlayout,Framlayout的高度測量呼叫的是ViewGroup提供的getChildMeasureSpec方法。此方法內只有當父view的MeasureSpec的mode為UNSPECIFIED時,子view才有可能被賦予UNSPECIFIED。(通過實際測試,FrameLayout巢狀ListView並不會對ListView產生影響。通過DEBUG也發現ScrollView呼叫父類onMeasure方法時傳入的heightMeasureSpec的mdoe也並非是UNSPECIFIED)。那麼可以確定,還是ScrollView自身對ListView產生了影響,與FrameLayout關係不大。那麼視線就轉到了ScrollView對FrameLayout的方法的重寫上:

@Override
protected void measureChild(View child, int parentWidthMeasureSpec,
       int parentHeightMeasureSpec) {

       ......
       childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
            Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - verticalPadding),
                              MeasureSpec.UNSPECIFIED);

            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
           }

@Override
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,int parentHeightMeasureSpec, int heightUsed) {

      ......
      final int childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
      Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - usedTotal),
                               MeasureSpec.UNSPECIFIED);

       child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
          }

可以看到ScrollView重寫了measureChild和measureChildWithMargins方法,並將childHeightMeasureSpec的mode強制更改為UNSPECIFIED。呼叫的父類的onMeasure方法中執行的measureChildWithMargins方法其實是ScrollView重寫過的。最終導致ListView的heightMode為UNSPECIFIED。