如题,如下图:
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大小的限制值:上限)
网友评论