测量一个ViewPager

我有一个自定义ViewGroup有一个孩子ViewPager 。 ViewPager由一个PagerAdapter提供,它提供一个LinearLayoutViewPager ,它的高度和宽度都是WRAP_CONTENT LayoutParams

视图显示正确,但在child.measure()上调用child.measure()方法时,它不返回LinearLayout的实际尺寸,但似乎填充了所有剩余的空间。

任何想法,为什么发生这种情况,以及如何修改它?

Solutions Collecting From Web of "测量一个ViewPager"

我对接受的答案并不满意(也没有在评论中ViewPager的所有意见解决scheme),所以我把一个ViewPager放在了一起,这个ViewPager从第一个可用的孩子开始。 这是通过做第二次测量,这样可以窃取第一个孩子的身高。

一个更好的解决scheme是在android.support.v4.view包中创build一个新的类,它实现了一个更好的onMeasure版本(可以访问像populate()这样populate()包可见方法)

不过,以下的解决scheme适合我。

 public class HeightWrappingViewPager extends ViewPager { public HeightWrappingViewPager(Context context) { super(context); } public HeightWrappingViewPager(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); boolean wrapHeight = MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST; if(wrapHeight) { /** * The first super.onMeasure call made the pager take up all the * available height. Since we really wanted to wrap it, we need * to remeasure it. Luckily, after that call the first child is * now available. So, we take the height from it. */ int width = getMeasuredWidth(), height = getMeasuredHeight(); // Use the previously measured width but simplify the calculations widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY); /* If the pager actually has any children, take the first child's * height and call that our own */ if(getChildCount() > 0) { View firstChild = getChildAt(0); /* The child was previously measured with exactly the full height. * Allow it to wrap this time around. */ firstChild.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST)); height = firstChild.getMeasuredHeight(); } heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); super.onMeasure(widthMeasureSpec, heightMeasureSpec); } } } 

查看兼容jar中的ViewPager类的内部:

 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // For simple implementation, or 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)); ... } 

看起来,ViewPager实现不会测量子视图,只是将ViewPager设置为基于父类传递的标准视图。当传递wrap_content时,由于视图分页器并不实际测量它的内容,整个可用区域。

我的build议是根据你的子视图的大小在你的ViewPager上设置一个静态大小。 如果这是不可能的(例如,子视图可能会有所不同),则需要select最大尺寸并在某些视图中处理额外的空间,或者扩展ViewPager并提供一个测量子项的onMeasure。 你会碰到的一个问题是,查看传呼机被devise成宽度不同,因为不同的视图显示,所以你可能会被迫select一个大小,并留在它

如果您在PageAdapter的instantiateItem中设置了标签(position):

 @Override public Object instantiateItem(ViewGroup collection, int page) { LayoutInflater inflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE); View view = (View) inflater.inflate(R.layout.page_item , null); view.setTag(page); 

然后可以使用OnPageChangeListener检索视图(适配器的页面),测量它并调整ViewPager的大小:

 private ViewPager pager; @Override protected void onCreate(Bundle savedInstanceState) { pager = findViewById(R.id.viewpager); pager.setOnPageChangeListener(new SimpleOnPageChangeListener() { @Override public void onPageSelected(int position) { resizePager(position); } }); public void resizePager(int position) { View view = pager.findViewWithTag(position); if (view == null) return; view.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); int width = view.getMeasuredWidth(); int height = view.getMeasuredHeight(); //The layout params must match the parent of the ViewPager RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(width , height); pager.setLayoutParams(params); } } 

根据上面的例子,我发现测量子视图的高度并不总是返回准确的结果。 解决方法是测量任何静态视图的高度(在xml中定义),然后添加在底部dynamic创build的片段的高度。 在我的情况下,静态元素是PagerTitleStrip,我也必须覆盖,以便在横向模式下使用match_parent作为宽度。

所以这里是我从Delyan拿的代码:

 public class WrappingViewPager extends ViewPager { public WrappingViewPager(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // super has to be called in the beginning so the child views can be // initialized. super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (getChildCount() <= 0) return; // Check if the selected layout_height mode is set to wrap_content // (represented by the AT_MOST constraint). boolean wrapHeight = MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST; int width = getMeasuredWidth(); View firstChild = getChildAt(0); // Initially set the height to that of the first child - the // PagerTitleStrip (since we always know that it won't be 0). int height = firstChild.getMeasuredHeight(); if (wrapHeight) { // Keep the current measured width. widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY); } int fragmentHeight = 0; fragmentHeight = measureFragment(((Fragment) getAdapter().instantiateItem(this, getCurrentItem())).getView()); // Just add the height of the fragment: heightMeasureSpec = MeasureSpec.makeMeasureSpec(height + fragmentHeight, MeasureSpec.EXACTLY); // super has to be called again so the new specs are treated as // exact measurements. super.onMeasure(widthMeasureSpec, heightMeasureSpec); } public int measureFragment(View view) { if (view == null) return 0; view.measure(0, 0); return view.getMeasuredHeight(); }} 

和自定义PagerTitleStrip:

 public class MatchingPagerTitleStrip extends android.support.v4.view.PagerTitleStrip { public MatchingPagerTitleStrip(Context arg0, AttributeSet arg1) { super(arg0, arg1); } @Override protected void onMeasure(int arg0, int arg1) { int size = MeasureSpec.getSize(arg0); int newWidthSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY); super.onMeasure(newWidthSpec, arg1); }} 

干杯!

通过参考上面的解决scheme,增加了一些更多的语句来获得视图寻呼机子的最大高度。

请参阅下面的代码。

 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // super has to be called in the beginning so the child views can be // initialized. super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (getChildCount() <= 0) return; // Check if the selected layout_height mode is set to wrap_content // (represented by the AT_MOST constraint). boolean wrapHeight = MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST; int width = getMeasuredWidth(); int childCount = getChildCount(); int height = getChildAt(0).getMeasuredHeight(); int fragmentHeight = 0; for (int index = 0; index < childCount; index++) { View firstChild = getChildAt(index); // Initially set the height to that of the first child - the // PagerTitleStrip (since we always know that it won't be 0). height = firstChild.getMeasuredHeight() > height ? firstChild.getMeasuredHeight() : height; int fHeight = measureFragment(((Fragment) getAdapter().instantiateItem(this, index)).getView()); fragmentHeight = fHeight > fragmentHeight ? fHeight : fragmentHeight; } if (wrapHeight) { // Keep the current measured width. widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY); } // Just add the height of the fragment: heightMeasureSpec = MeasureSpec.makeMeasureSpec(height + fragmentHeight, MeasureSpec.EXACTLY); // super has to be called again so the new specs are treated as // exact measurements. super.onMeasure(widthMeasureSpec, heightMeasureSpec); } 

更好的变化

 height = firstChild.getMeasuredHeight(); 

 height = firstChild.getMeasuredHeight() + getPaddingTop() + getPaddingBottom();