测量ViewPager

时间:2012-02-16 15:01:37

标签: android android-viewpager

我有一个包含子ViewPager的自定义ViewGroup。 ViewPager由PagerAdapter提供,LinearLayoutViewPager LayoutParams提供WRAP_CONTENT child.measure()的身高和宽度。

视图显示正确,但在ViewPager上调用{{1}}方法时,它不返回LinearLayout的实际尺寸,但似乎填满了所有剩余空间。

为什么会发生这种情况以及如何修改它?

6 个答案:

答案 0 :(得分:55)

我对接受的答案(以及评论中的pre-inflate-all-views解决方案)不是很满意,所以我整理了一个ViewPager,它从第一个可用的孩子身上获得了它的高度。它通过执行第二次测量传递来实现此目的,允许您窃取第一个孩子的身高。

更好的解决方案是在android.support.v4.view包中创建一个新类,该类实现更好的onMeasure版本(可以访问包括populate()等包的可见方法

但目前,下面的解决方案很适合我。

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);
        }
    }
}

答案 1 :(得分:11)

查看兼容性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时,因为视图寻呼机实际上没有测量它内容占据了整个可用区域。

我的建议是根据子视图的大小在ViewPager上设置静态大小。如果这是不可能的(例如,子视图可能不同),您需要选择最大大小并在某些视图中处理额外空间或扩展ViewPager并提供测量子视图的onMeasure。您将遇到的一个问题是视图寻呼机的设计宽度不同,因为显示了不同的视图,因此您可能会被迫选择一个大小并保持不变

答案 2 :(得分:3)

如果你在PageAdapter的instantiateItem中设置了标签(位置):

@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);
    }
}

答案 3 :(得分:0)

按照上面的例子,我发现测量子视图的高度并不总能返回准确的结果。解决方案是测量任何静态视图的高度(在xml中定义),然后添加在底部动态创建的片段的高度。 在我的例子中,静态元素是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);
}}

干杯!

答案 4 :(得分:0)

通过参考上述解决方案,增加了一些声明,以获得查看寻呼机子视图的最大高度。

请参阅以下代码。

@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);
}

答案 5 :(得分:0)

更好的改变

height = firstChild.getMeasuredHeight();

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