一、ViewPager的wrap_content无效的原因
看ViewPager的源码:
@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.
**///1、上来就先测量自己,默认设置最大的宽高**
setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
getDefaultSize(0, heightMeasureSpec));
final int measuredWidth = getMeasuredWidth();
final int maxGutterSize = measuredWidth / 10;
mGutterSize = Math.min(maxGutterSize, mDefaultGutterSize);
**///2、child的宽高,占满父控件**
// 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);
**///3、从Adapter中获取childView**
// Make sure we have created all fragments that we need to have shown.
mInLayout = true;
populate();
mInLayout = false;
**///4、测量非Decor的childView**
// 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);
}
}
}
}
我在源码标记了四步,看四步源码,可以看到wrap_content无效的原因。
二、解决方案
思路就是OnMeasure 方法进行重写,使 ViewPager 的高度为其子视图的最大高度值。对child进行measure的时候要传入正确的measureSpec,子视图可能是wrap_content,也可能是100dp的固定值或者match_parent,其实就是转变成measureSpec的过程。继承ViewPager ,自定义WrapContentHeightViewPager如下:
WrapContentHeightViewPager.java:
/**
* ViewPager wrapContent解决方案
*/
public class WrapContentHeightViewPager extends ViewPager {
/**
* Constructor
*
* @param context the context
*/
public WrapContentHeightViewPager(Context context) {
super(context);
}
/**
* Constructor
*
* @param context the context
* @param attrs the attribute set
*/
public WrapContentHeightViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
// @Override
// protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//
// // find the first child view
// View view = getChildAt(0);
// if (view != null) {
// // measure the first child view with the specified measure spec
// view.measure(widthMeasureSpec, heightMeasureSpec);
// }
//
// setMeasuredDimension(getMeasuredWidth(), measureHeight(heightMeasureSpec, view));
// }
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int height = 0;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
ViewGroup.LayoutParams params = child.getLayoutParams();
int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, getPaddingTop() + getPaddingBottom(), params.height);
child.measure(widthMeasureSpec, childHeightMeasureSpec);
int h = child.getMeasuredHeight();
if (h > height) height = h;
}
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
/**
* Determines the height of this view
*
* @param measureSpec A measureSpec packed into an int
* @param view the base view with already measured height
*
* @return The height of the view, honoring constraints from measureSpec
*/
private int measureHeight(int measureSpec, View view) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
// set the height from the base view if available
if (view != null) {
result = view.getMeasuredHeight();
}
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}
}
三、减少渲染和inflate源码分析
如果用的是
View view=LayoutInflater.from(mContext).inflate(R.layuot.item_layout,container,false);
container不能是null,第三个参数要设置为false,原因我们看源码中的标注:
LayoutInflater.java的inflate源码如下:
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
///省略部分代码
if (TAG_MERGE.equals(name)) {
///省略部分代码
} else {
// Temp is the root view that was found in the xml
**///1、temp就是子布局,root就是子布局的最外层布局,如果设为null,肯定就无效了,获取不到item的xml中的属性**
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// Create layout params that match root, if supplied
**//2、基于root构建一个params**
params = root.generateLayoutParams(attrs);
///3、false的时候进入if的代码
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
///4、如果是false,给根布局设置一个来自xml构建的params
temp.setLayoutParams(params);
}
}
if (DEBUG) {
System.out.println("-----> start inflating children");
}
// Inflate all children under temp against its context.
rInflateChildren(parser, temp, attrs, true);
if (DEBUG) {
System.out.println("-----> done inflating children");
}
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
///5、如果inflate的第三参数传false,在代码中如果也有addView的话,会进入死循环
///6、传true在系统源码中有用到,setContentView()中初始化DecorView的时候会用到。
if (root != null && attachToRoot) {
root.addView(temp, params);
}
// Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch (XmlPullParserException e) {
final InflateException ie = new InflateException(e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(parser.getPositionDescription()
+ ": " + e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
return result;
}
}
**///7、第三个参数不传和传true一样**
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}