准备知识:
1,xml布局文件:在LayoutInflater里的parser解析,就和普通的标签文件解析一样,通过反射实例化view并获得attrs.
2,LayoutParams:LayoutInflater.inflate时如果root!=null会去根据xml中解析出来的属性生成的LayoutParams,如果root==null则会出现xml中设置的LayoutParams参数失效。这个时候在addView时如果传入的LayoutParams为空则会generateDefaultLayoutParams生成一个默认的LayoutParams(丢失了xml中width/height等设置的属性值)
3,getMeasureWidth() :在执行完onMeasure(里面执行了setMeasuredDimension)后得到的measureWidth,
4,getWidth() :在执行完父容器的onLayout后(执行了自己的layout方法)得到的自己的width
5,计算自己的measureSize是通过onMeasure方法(里面执行了setMeasuredDimension)
6,其中计算子View的measureSpec(包含32位,前2位是mode,后30位是尺寸大小)方法主要是通过下面方法 下面传参分别:spec是当前ViewGroup的measureSpec,padding是ViewGroup的对应方向padding,childDimension一般是ViewGroup的子View在xml中设置的宽、高值。
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
1,原因分析(源码里找原因)
上面已经说了设置尺寸是在onMeasure里,直接看ViewPager里该方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
getDefaultSize(0, heightMeasureSpec));
...
}
里面是直接通过getDefaultSize获取尺寸的,就压根没有用到子View的measureSpec(一般通过getChildMeasureSpec获取),再看看getDefaultSize传参,一个size传0,measureSpec传的是ViewPager自己的measureSpec,这个还记得上面的方法getChildMeasureSpec,这里的ViewPager一般也是被它的父容器(一般measureSpec里的mode是AT_MOST/EXACTLY)这样计算来的,通过getChildMeasureSpec里面的逻辑知道此处ViewPager(xml中的height是wrap_content)从父容器那里获取的measureSpec的mode一般是AT_MOST,heightsize是当前ViewPager父容器的高度。
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
所以通过getDefaultSize返回的高度result = specSize(父容器高度)。
2,解决方法
方法一、直接在xml中给ViewPager设置layout_witdh=100dp(具体的某个值)
因为上面说了用到的是ViewPager的measureSpec,根据getChildMeasureSpec里面的逻辑,可以直接让ViewPager的measureSpec尺寸变成具体的值,mode变成EXACTLY。这样大小就固定是设置的具体值了。
方法二、重写ViewPager的onMeasure,在里面修改它的measureSpec
直接上代码
public class MyViewPager extends ViewPager {
public MyViewPager(@NonNull Context context) {
super(context);
}
public MyViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int childSize = getChildCount();
int maxHeight = 0;
for (int i = 0; i < childSize; i++) {
View child = getChildAt(i);
ViewGroup.LayoutParams params = child.getLayoutParams();
child.measure(widthMeasureSpec, getChildMeasureSpec(heightMeasureSpec , getPaddingTop()+getPaddingBottom() , params.height));
if (child.getMeasuredHeight() > maxHeight) {
maxHeight = child.getMeasuredHeight();
}
}
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (maxHeight > 0 && heightMode == MeasureSpec.AT_MOST) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight , heightMode);
}
super.onMeasure(widthMeasureSpec , heightMeasureSpec);
}
注意:如果用上面还是无效的,大概率是因为getChildMeasureSpec的第三个传参有问题,需要确认你自己PagerAdapter的instantiateItem方法里是否如下
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
View view = LayoutInflater.from(container.getContext()).inflate(R.layout.page,container,false);
TextView textView = view.findViewById(R.id.content);
textView.setText(String.valueOf(position));
container.addView(view);
return view;
}
好了好了,上面都是通过改变ViewPager的measureSpec实现解决方案的。
建议:源码还是要多看多实践才能变得更加深刻。
|