美文网首页
ScrollView中嵌套ListView显示不全的问题

ScrollView中嵌套ListView显示不全的问题

作者: 100个大西瓜 | 来源:发表于2021-05-06 11:25 被阅读0次

如题,如下图:


p1.jpg

打印了一下ListView#onMeasure(int ,int )的调用栈,
public class MyListView extends ListView

2021-05-06 10:33:45.882 21482-21482/com.by5388.demo.asdpap.scrollviewwithlistview E/MyListView: onMeasure: 
    java.lang.Exception
        at com.by5388.demo.asdpap.scrollviewwithlistview.MyListView.onMeasure(MyListView.java:40)
        at android.view.View.measure(View.java:24872)
        at android.widget.ScrollView.measureChildWithMargins(ScrollView.java:1556)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
        at android.widget.ScrollView.onMeasure(ScrollView.java:528)
        at android.view.View.measure(View.java:24872)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7071)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
        at android.support.v7.widget.ContentFrameLayout.onMeasure(ContentFrameLayout.java:143)

看前面几行就好了。

在ScrollView中,measureChildWithMargins():根据子view的参数来测量子view的

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

对于嵌套的子View的高度的限制都是没有限制模式:MeasureSpec.UNSPECIFIED

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

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

而对于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;
    }

由于父容器指定的高度限制模式是:MeasureSpec.UNSPECIFIED
而自身测量过程中,匹配到

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

而 childHeight = child.getMeasuredHeight(); 是当前ListView第一个子View的高度,
意味着默认情况下,ScrollView中嵌套的ListView最多只能显示一个Item的高度,
而我们想要的应该是匹配到这个:


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

而其中的

final int measureHeightOfChildren(int widthMeasureSpec, int startPosition, int endPosition,
                int maxHeight, int disallowPartialChildPosition) ;

大致是把每个item的高度加起来,这个不做分析。
所以如何让 heightMode == MeasureSpec.AT_MOST呢?
很简单,只需要扩展ListView 然后重写 onMeasure()方法,把ScrollView传递来的heightMeasureSpec更改成MeasureSpec.AT_MOST即可

package packageName;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.ListView;

import static android.view.View.MeasureSpec.AT_MOST;
import static android.view.View.MeasureSpec.EXACTLY;
import static android.view.View.MeasureSpec.UNSPECIFIED;

/**
 * @author admin  on 2021/5/5.
 */
public class MyListView extends ListView {
    public static final String TAG = MyListView.class.getSimpleName();

    public MyListView(Context context) {
        super(context);
    }

    public MyListView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public MyListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        heightMeasureSpec |= AT_MOST;
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}

这样子就能解决标题所说的显示不全的问题。如下图:


p2.jpg

对于int widthMeasureSpec, int heightMeasureSpec,这2个参数都是组合参数,里面包含了2个值,分别是值和模式,
参考看

final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);

详情看MeasureSpec.getMode(int) 和 MeasureSpec.getSize(int ),
简单来说高2位表示SpecMode(父View对子view大小的限制模式),
低30位表示SpecSize(父View对子view大小的限制值:上限)

相关文章

网友评论

      本文标题:ScrollView中嵌套ListView显示不全的问题

      本文链接:https://www.haomeiwen.com/subject/ucordltx.html