文章目录
- Android ViewPager不支持wrap_content的原因
- 问题
- 源码分析
- 解决
Android ViewPager不支持wrap_content的原因
问题
<androidx.viewpager.widget.ViewPager
android:id="@+id/wrap_view_pager"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#666666" />
将 ViewPager 的高度设置为 wrap_content,但是实现显示的效果却是填充了整个父容器。
源码分析
// ViewPager#onMeasure()
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
getDefaultSize(0, heightMeasureSpec));
}
// View#getDefaultSize()
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;
}
源码分析:
- 前提:View的MeasureSpec是由MeasureMode和MeasureSize构成,子View的MeasureSpec是由父View的MeasureSpec和子View的LayoutParams计算来的。
- 在 ViewPager 中,onMeasure() 直接调用 setMeasuredDimension() 设置宽高,并没有根据子View来计算测量。
- 接着调用 getDefaultSize() 方法,第一个参数传入0,第二个参数传入ViewPager的measureSpec,根据上图父View的MeasureMode和子View的LayoutParams对照关系,由此可得:
- ViewPager的MeasureMode为EXACTLY时,specSize为具体值或父View的剩余空间。
- ViewPager的MeasureMode为AT_MOST时,specSize为父View的剩余空间。
因此当ViewPager设置为wrap_content时,ViewPager为父容器的高度。
解决
重写 onMeasure() 方法,先主动测量子View的大小,将测量后的结果设置给ViewPager。
class WrapContentViewPager @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : ViewPager(context, attrs) {
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
for (i in 0 until childCount) {
val child = getChildAt(i)
measureChild(
child,
widthMeasureSpec,
MeasureSpec.makeMeasureSpec(child.layoutParams.height, MeasureSpec.UNSPECIFIED)
)
}
val heightSize = MeasureSpec.getSize(heightMeasureSpec)
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
var height = 0
when (heightMode) {
MeasureSpec.EXACTLY -> {
height = heightSize
}
else -> {
for (i in 0 until childCount) {
val child = getChildAt(i)
height = Math.max(height, child.measuredHeight)
}
}
}
super.onMeasure(
widthMeasureSpec,
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
)
}
}