问题描述:
在开发中,经常遇到ViewPager中设置了高度wrap_content时,子布局的高度有可能会失效,或者match_parent。
解决方案:
1.直接修改wrap_content为一个固定的高度。但是这个做法将高度写死了,会产生一些不同屏幕分辨率的适配问题。
2.重写VIewPage中的onMeasure方法。查看了网上的大部分博客,一般都是采用了以下方法。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int height = 0;
//下面遍历所有child的高度
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
child.measure(widthMeasureSpec,
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
int h = child.getMeasuredHeight();
if (h > height) //采用最大的view的高度。
height = h;
}
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
深入分析:
在一个视频中,偶然看到了关于这个的深入分析。
为什么会失效呢:查看ViewPage的源代码中onMuasure方法。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// For simple implementation, our internal size is always 0.
// We depend on the container to specify the layout size of
// our view. We can't really know what it is since we will be
// adding and removing different arbitrary views and do not
// want the layout to change as this happens.
setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
getDefaultSize(0, heightMeasureSpec));
final int measuredWidth = getMeasuredWidth();
final int maxGutterSize = measuredWidth / 10;
mGutterSize = Math.min(maxGutterSize, mDefaultGutterSize);
// Children are just made to fill our space.
int childWidthSize = measuredWidth - getPaddingLeft() - getPaddingRight();
int childHeightSize = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
/*
* Make sure all children have been properly measured. Decor views first.
* Right now we cheat and make this less complicated by assuming decor
* views won't intersect. We will pin to edges based on gravity.
*/
int size = getChildCount();
for (int i = 0; i < size; ++i) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp != null && lp.isDecor) {
final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
int widthMode = MeasureSpec.AT_MOST;
int heightMode = MeasureSpec.AT_MOST;
boolean consumeVertical = vgrav == Gravity.TOP || vgrav == Gravity.BOTTOM;
boolean consumeHorizontal = hgrav == Gravity.LEFT || hgrav == Gravity.RIGHT;
if (consumeVertical) {
widthMode = MeasureSpec.EXACTLY;
} else if (consumeHorizontal) {
heightMode = MeasureSpec.EXACTLY;
}
int widthSize = childWidthSize;
int heightSize = childHeightSize;
if (lp.width != LayoutParams.WRAP_CONTENT) {
widthMode = MeasureSpec.EXACTLY;
if (lp.width != LayoutParams.MATCH_PARENT) {
widthSize = lp.width;
}
}
if (lp.height != LayoutParams.WRAP_CONTENT) {
heightMode = MeasureSpec.EXACTLY;
if (lp.height != LayoutParams.MATCH_PARENT) {
heightSize = lp.height;
}
}
final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode);
final int heightSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode);
child.measure(widthSpec, heightSpec);
if (consumeVertical) {
childHeightSize -= child.getMeasuredHeight();
} else if (consumeHorizontal) {
childWidthSize -= child.getMeasuredWidth();
}
}
}
}
mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY);
mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightSize, MeasureSpec.EXACTLY);
// Make sure we have created all fragments that we need to have shown.
mInLayout = true;
populate();
mInLayout = false;
// Page views next.
size = getChildCount();
for (int i = 0; i < size; ++i) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
if (DEBUG) {
Log.v(TAG, "Measuring #" + i + " " + child + ": " + mChildWidthMeasureSpec);
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp == null || !lp.isDecor) {
final int widthSpec = MeasureSpec.makeMeasureSpec(
(int) (childWidthSize * lp.widthFactor), MeasureSpec.EXACTLY);
child.measure(widthSpec, mChildHeightMeasureSpec);
}
}
}
}
该方法中首先就调用了
setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
getDefaultSize(0, heightMeasureSpec));
这是先对自身进行测量,而没有先去测量子View的高度,这时候传入的高度并不是子View的高度。而修改后的方法,就是先去测量子View的高度,然后在调用 super.onMeasure方法,并且把子View的高度传入进去做参数。
而在后续中,发现这个修改并不是一直有效的,也会失效。问题就出在
child.measure(widthMeasureSpec,
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
这里调用的measure 方法,并且在MeasureSpec.makeMeasureSpec中传入了参数0,和一个 MeasureSpec.UNSPECIFIED(可以解释为我也不知道,由系统决定)参数。
这里我们用measure方法是不可取的,应该使用LayoutParams(获取XML的布局参数),首先去获取子View的Params,然后再从将值传入到measure中。因此修改为
ViewGroup.LayoutParams params=child.getLayoutParams();
child.measure(widthMeasureSpec,getChildMeasureSpec(heightMeasureSpec,0,params.height))
但是发现这样修改后依旧失效。这里就涉及到XML文件解析的时候的反射问题了,因此我们修改下VIewpage适配器中的的反射的方法
将View view= LayoutInflater.from(mContext).inflate(R.layou.item,null);修改为
View view= LayoutInflater.from(mContext).inflate(R.layou.item,viewGroup,false);。
附上代码
public class mPageAdapter extends PagerAdapter {
@Override
public int getCount() {
return 0;
}
@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
return false;
}
@Override
public Object instantiateItem(ViewGroup viewGroup,int position){
//....
//修改前
// View view= LayoutInflater.from(mContext).inflate(R.layou.item,null);
//修改后
View view= LayoutInflater.from(mContext).inflate(R.layou.item,viewGroup,false);
//...
}
}
public class mViewPage extends ViewPager {
public mViewPage(@NonNull Context context) {
super(context);
}
public mViewPage(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int height = 0;
//下面遍历所有child的高度
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
//修改后
ViewGroup.LayoutParams params=child.getLayoutParams();
child.measure(widthMeasureSpec,getChildMeasureSpec(heightMeasureSpec,0,params.height));
// 修改前
// child.measure(widthMeasureSpec,
// MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
int h = child.getMeasuredHeight();
if (h > height) //采用最大的view的高度。
height = h;
}
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
总结:
现在的ViewPage一般都是被用来搭配使用Fragment 或者Banner使用,但是ViewPage开发的最初目的不是用于这两个,因此在绘制的过程中先绘制了自身,而我们在ViewGroup的绘制中,一般是自下而上的绘制,先获取子View的LayoutParams信息。
新手第一次写总结类的博客,有不对的地方请大家指正。