前话
今天公司项目遇到了ViewPager+ScrollView的布局方式,水平滑动+垂直滑动,可想而知可能会产生冲突。作为老司机,自信满满的准备好了解决冲突的方案,开撸。
写好运行一波后发现,尼玛啊,ViewPager不见了,说实话以前还真没有写过这种嵌套。没办法,试试将ViewPager固定一下高度呢,看看它存不存在。还真在,这下能看见了。可是不科学啊,我怎么知道页面数据有多少呢,固定值肯定行不通啊。
很自然的,打开浏览器—>谷歌一波。
搜索ViewPager+ScrollView嵌套ViewPager消失的网页很多,大多都一句话:设置ScrollView android:fillViewport=”true”!没想到这么简单,试试看!
果然,可行!网上说,用了这个之后ViewPager会无法滑动,我试了一下没出现啊,结果垂直方向滑不动了,妈的不按套路出牌。心想滑动冲突还不是smallCase。
于是按套路去解决了。至于套路是啥:无非分为外部解决和内部解决。具体方案本文不介绍了,出于:《安卓开发艺术探索》。网上也一大堆,可自行搜索。
ScrollView
经过一番折腾后发现并无卵用,他俩的事件分发完全没错,这就很尴尬了。那不是滑动的问题,会是啥呢?只能是控件本身高度就不够咯,嗯,测量出了问题。
翻翻源码吧,先看ScrollView。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (!mFillViewport) {
return;
}
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (heightMode == MeasureSpec.UNSPECIFIED) {
return;
}
if (getChildCount() > 0) {
final View child = getChildAt(0);
final int widthPadding;
final int heightPadding;
final int targetSdkVersion = getContext()
.getApplicationInfo().targetSdkVersion;
final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (targetSdkVersion >= VERSION_CODES.M) {
widthPadding = mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin;
heightPadding = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin;
} else {
widthPadding = mPaddingLeft + mPaddingRight;
heightPadding = mPaddingTop + mPaddingBottom;
}
final int desiredHeight = getMeasuredHeight() - heightPadding;
if (child.getMeasuredHeight() < desiredHeight) {
final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, widthPadding, lp.width);
final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(desiredHeight, MeasureSpec.EXACTLY);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
关键是:
if (!mFillViewport) {
return;
}
这个mFillViewport看名字就很熟悉,就是刚刚我网上的方案咯,XML里面设置为True了。这里能看出来如果是false,直接返回了,相当于啥也没写,走父类的正常测量方式了。
为true的话,往下走,由于ScrollView只有一个子View,所以测量起来也很简单了。上面的代码用文字解释为:
首先获取儿子大小,如果儿子比自己小,把儿子撑到和自己一样大。如果儿子更大,就任由发展,如下:
难怪我最开始固定设置很大的时候能显示。那也就是说,滑不动是因为撑到一样大而已,并不能显示更多。换句话来说,不是“滑不动”,而是“没有了”。
ViewPager
ViewPager为什么高度不够呢,明明我在里面cang了很多东西啊,好吧,继续看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.
setMeasuredDimension(getDefaultSize(0,widthMeasureSpec),
getDefaultSize(0, heightMeasureSpec));
//省略儿子的测量代码
}
卧槽,看到第一句我就惊呆了,直接按最初大小了,根本没有管Pager里面的控件,不管塞多少大小都是最初的(固定值:父布局分配的)。
好吧,终于找到原因了,我们重写onMeasure帮它测一下儿子的高度,再决定自己的高度。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
View maxHeightView = getChildAt(0);
int size = getChildCount();
//一个一个测量子View
for (int i = 0; i < size; i++) {
View child = getChildAt(i);
if (child != null && child.getVisibility() != GONE) {
child.measure(widthMeasureSpec, heightMeasureSpec);
if (child.getHeight() > maxHeightView.getHeight()) {
maxHeightView = child;
}
}
}
//按viewpager里最大的那个高度算。
if (maxHeightView != null) {
setMeasuredDimension(getMeasuredWidth(), measureHeight(heightMeasureSpec, maxHeightView));
}
}
private int measureHeight(int measureSpec, View view) {
int result;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
//如果是固定值或match_parent
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
result = view.getMeasuredHeight();
//wrap_content
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}
运行,成功!果然百度谷歌不是万能的,有时候学会看源码也是很重要的,熟悉理解View绘制原理的话,解决起来一定不会很耗时。
至于滑动冲突,TMD我压根没遇到,应该是viewpager内部已经做好了处理吧,害我一开始就走错了方向。
参考文献:
http://smilehacker.com/zai-scrollviewzhong-shi-yong-viewpageryi-ji-viewpagershi-yong-wrap_content.html